Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/behaviors')
-rw-r--r--app/assets/javascripts/behaviors/autosize.js11
-rw-r--r--app/assets/javascripts/behaviors/index.js22
-rw-r--r--app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js5
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js21
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_math.js3
-rw-r--r--app/assets/javascripts/behaviors/shortcuts.js6
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js99
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js17
8 files changed, 158 insertions, 26 deletions
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index add43b81f6d..d61797b7ae4 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,8 +1,13 @@
import Autosize from 'autosize';
+import { waitForCSSLoaded } from '../helpers/startup_css_helper';
document.addEventListener('DOMContentLoaded', () => {
- const autosizeEls = document.querySelectorAll('.js-autosize');
+ waitForCSSLoaded(() => {
+ const autosizeEls = document.querySelectorAll('.js-autosize');
- Autosize(autosizeEls);
- Autosize.update(autosizeEls);
+ Autosize(autosizeEls);
+ Autosize.update(autosizeEls);
+
+ autosizeEls.forEach(el => el.classList.add('js-autosize-initialized'));
+ });
});
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 8060938c72a..fd12c282b62 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -1,7 +1,7 @@
+import $ from 'jquery';
import './autosize';
import './bind_in_out';
import './markdown/render_gfm';
-import initGFMInput from './markdown/gfm_auto_complete';
import initCopyAsGFM from './markdown/copy_as_gfm';
import initCopyToClipboard from './copy_to_clipboard';
import './details_behavior';
@@ -15,9 +15,27 @@ import initCollapseSidebarOnWindowResize from './collapse_sidebar_on_window_resi
import initSelect2Dropdowns from './select2';
installGlEmojiElement();
-initGFMInput();
+
initCopyAsGFM();
initCopyToClipboard();
+
initPageShortcuts();
initCollapseSidebarOnWindowResize();
initSelect2Dropdowns();
+
+document.addEventListener('DOMContentLoaded', () => {
+ window.requestIdleCallback(
+ () => {
+ // Check if we have to Load GFM Input
+ const $gfmInputs = $('.js-gfm-input:not(.js-gfm-input-initialized)');
+ if ($gfmInputs.length) {
+ import(/* webpackChunkName: 'initGFMInput' */ './markdown/gfm_auto_complete')
+ .then(({ default: initGFMInput }) => {
+ initGFMInput($gfmInputs);
+ })
+ .catch(() => {});
+ }
+ },
+ { timeout: 500 },
+ );
+});
diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
index 6bbd2133344..d712c90242c 100644
--- a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
+++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js
@@ -2,8 +2,8 @@ import $ from 'jquery';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import { parseBoolean } from '~/lib/utils/common_utils';
-export default function initGFMInput() {
- $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
+export default function initGFMInput($els) {
+ $els.each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = parseBoolean(el.dataset.supportsAutocomplete);
@@ -14,6 +14,7 @@ export default function initGFMInput() {
milestones: enableGFM,
mergeRequests: enableGFM,
labels: enableGFM,
+ vulnerabilities: enableGFM,
});
});
}
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index 01627b7206d..5e9d80e1529 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -5,7 +5,6 @@ import renderMermaid from './render_mermaid';
import renderMetrics from './render_metrics';
import highlightCurrentUser from './highlight_current_user';
import initUserPopovers from '../../user_popovers';
-import initMRPopovers from '../../mr_popover';
// Render GitLab flavoured Markdown
//
@@ -17,9 +16,25 @@ $.fn.renderGFM = function renderGFM() {
renderMermaid(this.find('.js-render-mermaid'));
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.js-user-link').get());
- initMRPopovers(this.find('.gfm-merge_request').get());
+
+ const mrPopoverElements = this.find('.gfm-merge_request').get();
+ if (mrPopoverElements.length) {
+ import(/* webpackChunkName: 'MrPopoverBundle' */ '../../mr_popover')
+ .then(({ default: initMRPopovers }) => {
+ initMRPopovers(mrPopoverElements);
+ })
+ .catch(() => {});
+ }
+
renderMetrics(this.find('.js-render-metrics').get());
return this;
};
-$(() => $('body').renderGFM());
+$(() => {
+ window.requestIdleCallback(
+ () => {
+ $('body').renderGFM();
+ },
+ { timeout: 500 },
+ );
+});
diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js
index 03d9955f8fc..30783562da9 100644
--- a/app/assets/javascripts/behaviors/markdown/render_math.js
+++ b/app/assets/javascripts/behaviors/markdown/render_math.js
@@ -1,5 +1,6 @@
import { deprecatedCreateFlash as flash } from '~/flash';
import { s__, sprintf } from '~/locale';
+import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
// Renders math using KaTeX in any element with the
// `js-render-math` class
@@ -111,7 +112,7 @@ class SafeMathRenderer {
// Give the browser time to reflow the svg
waitForReflow(() => {
- const deltaTime = Date.now() - this.startTime;
+ const deltaTime = differenceInMilliseconds(this.startTime);
this.totalMS += deltaTime;
this.renderElement();
diff --git a/app/assets/javascripts/behaviors/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts.js
index 7987a533ae5..7352be0dbd5 100644
--- a/app/assets/javascripts/behaviors/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts.js
@@ -1,5 +1,3 @@
-import Shortcuts from './shortcuts/shortcuts';
-
export default function initPageShortcuts() {
const { page } = document.body.dataset;
const pagesWithCustomShortcuts = [
@@ -29,7 +27,9 @@ export default function initPageShortcuts() {
// the pages above have their own shortcuts sub-classes instantiated elsewhere
// TODO: replace this whitelist with something more automated/maintainable
if (page && !pagesWithCustomShortcuts.includes(page)) {
- return new Shortcuts();
+ import(/* webpackChunkName: 'shortcutsBundle' */ './shortcuts/shortcuts')
+ .then(({ default: Shortcuts }) => new Shortcuts())
+ .catch(() => {});
}
return false;
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 85636f3e5d2..8a8b61a57cd 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import Vue from 'vue';
+import { flatten } from 'lodash';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
import ShortcutsToggle from './shortcuts_toggle.vue';
import axios from '../../lib/utils/axios_utils';
@@ -9,13 +10,13 @@ import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils';
-const defaultStopCallback = Mousetrap.stopCallback;
-Mousetrap.stopCallback = (e, element, combo) => {
+const defaultStopCallback = Mousetrap.prototype.stopCallback;
+Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) {
return false;
}
- return defaultStopCallback(e, element, combo);
+ return defaultStopCallback.call(this, e, element, combo);
};
function initToggleButton() {
@@ -27,6 +28,39 @@ function initToggleButton() {
});
}
+/**
+ * The key used to save and fetch the local Mousetrap instance
+ * attached to a `<textarea>` element using `jQuery.data`
+ */
+const LOCAL_MOUSETRAP_DATA_KEY = 'local-mousetrap-instance';
+
+/**
+ * Gets a mapping of toolbar button => keyboard shortcuts
+ * associated to the given markdown editor `<textarea>` element
+ *
+ * @param {HTMLTextAreaElement} $textarea The jQuery-wrapped `<textarea>`
+ * element to extract keyboard shortcuts from
+ *
+ * @returns A Map with keys that are jQuery-wrapped toolbar buttons
+ * (i.e. `$toolbarBtn`) and values that are arrays of string
+ * keyboard shortcuts (e.g. `['command+k', 'ctrl+k]`).
+ */
+function getToolbarBtnToShortcutsMap($textarea) {
+ const $allToolbarBtns = $textarea.closest('.md-area').find('.js-md');
+ const map = new Map();
+
+ $allToolbarBtns.each(function attachToolbarBtnHandler() {
+ const $toolbarBtn = $(this);
+ const keyboardShortcuts = $toolbarBtn.data('md-shortcuts');
+
+ if (keyboardShortcuts?.length) {
+ map.set($toolbarBtn, keyboardShortcuts);
+ }
+ });
+
+ return map;
+}
+
export default class Shortcuts {
constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
@@ -34,6 +68,7 @@ export default class Shortcuts {
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
+ Mousetrap.bind('/', Shortcuts.focusSearch);
Mousetrap.bind('f', this.focusFilter.bind(this));
Mousetrap.bind('p b', Shortcuts.onTogglePerfBar);
@@ -143,4 +178,62 @@ export default class Shortcuts {
e.preventDefault();
}
}
+
+ /**
+ * Initializes markdown editor shortcuts on the provided `<textarea>` element
+ *
+ * @param {JQuery} $textarea The jQuery-wrapped `<textarea>` element
+ * where markdown shortcuts should be enabled
+ * @param {Function} handler The handler to call when a
+ * keyboard shortcut is pressed inside the markdown `<textarea>`
+ */
+ static initMarkdownEditorShortcuts($textarea, handler) {
+ const toolbarBtnToShortcutsMap = getToolbarBtnToShortcutsMap($textarea);
+
+ const localMousetrap = new Mousetrap($textarea[0]);
+
+ // Save a reference to the local mousetrap instance on the <textarea>
+ // so that it can be retrieved when unbinding shortcut handlers
+ $textarea.data(LOCAL_MOUSETRAP_DATA_KEY, localMousetrap);
+
+ toolbarBtnToShortcutsMap.forEach((keyboardShortcuts, $toolbarBtn) => {
+ localMousetrap.bind(keyboardShortcuts, e => {
+ e.preventDefault();
+
+ handler($toolbarBtn);
+ });
+ });
+
+ // Get an array of all shortcut strings that have been added above
+ const allShortcuts = flatten([...toolbarBtnToShortcutsMap.values()]);
+
+ const originalStopCallback = Mousetrap.prototype.stopCallback;
+ localMousetrap.stopCallback = function newStopCallback(e, element, combo) {
+ if (allShortcuts.includes(combo)) {
+ return false;
+ }
+
+ return originalStopCallback.call(this, e, element, combo);
+ };
+ }
+
+ /**
+ * Removes markdown editor shortcut handlers originally attached
+ * with `initMarkdownEditorShortcuts`.
+ *
+ * Note: it is safe to call this function even if `initMarkdownEditorShortcuts`
+ * has _not_ yet been called on the given `<textarea>`.
+ *
+ * @param {JQuery} $textarea The jQuery-wrapped `<textarea>`
+ * to remove shortcut handlers from
+ */
+ static removeMarkdownEditorShortcuts($textarea) {
+ const localMousetrap = $textarea.data(LOCAL_MOUSETRAP_DATA_KEY);
+
+ if (localMousetrap) {
+ getToolbarBtnToShortcutsMap($textarea).forEach(keyboardShortcuts => {
+ localMousetrap.unbind(keyboardShortcuts);
+ });
+ }
+ }
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
index 8658081c6c2..f0d2ecfd210 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
@@ -5,12 +5,11 @@ export default class ShortcutsFindFile extends ShortcutsNavigation {
constructor(projectFindFile) {
super();
- const oldStopCallback = Mousetrap.stopCallback;
- this.projectFindFile = projectFindFile;
+ const oldStopCallback = Mousetrap.prototype.stopCallback;
- Mousetrap.stopCallback = (e, element, combo) => {
+ Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) {
if (
- element === this.projectFindFile.inputElement[0] &&
+ element === projectFindFile.inputElement[0] &&
(combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')
) {
// when press up/down key in textbox, cursor prevent to move to home/end
@@ -18,12 +17,12 @@ export default class ShortcutsFindFile extends ShortcutsNavigation {
return false;
}
- return oldStopCallback(e, element, combo);
+ return oldStopCallback.call(this, e, element, combo);
};
- Mousetrap.bind('up', this.projectFindFile.selectRowUp);
- Mousetrap.bind('down', this.projectFindFile.selectRowDown);
- Mousetrap.bind('esc', this.projectFindFile.goToTree);
- Mousetrap.bind('enter', this.projectFindFile.goToBlob);
+ Mousetrap.bind('up', projectFindFile.selectRowUp);
+ Mousetrap.bind('down', projectFindFile.selectRowDown);
+ Mousetrap.bind('esc', projectFindFile.goToTree);
+ Mousetrap.bind('enter', projectFindFile.goToBlob);
}
}