From f64a639bcfa1fc2bc89ca7db268f594306edfd7c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 16 Mar 2021 18:18:33 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-10-stable-ee --- .../javascripts/behaviors/copy_to_clipboard.js | 38 ++---- .../javascripts/behaviors/markdown/render_math.js | 99 +++++++++------ .../javascripts/behaviors/shortcuts/keybindings.js | 135 ++++++++++----------- .../javascripts/behaviors/toggler_behavior.js | 2 +- 4 files changed, 137 insertions(+), 137 deletions(-) (limited to 'app/assets/javascripts/behaviors') diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js index a31bcc2cb41..de248340738 100644 --- a/app/assets/javascripts/behaviors/copy_to_clipboard.js +++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js @@ -1,31 +1,27 @@ import Clipboard from 'clipboard'; import $ from 'jquery'; import { sprintf, __ } from '~/locale'; -import { fixTitle, show } from '~/tooltips'; +import { fixTitle, add, show, once } from '~/tooltips'; function showTooltip(target, title) { - const { originalTitle } = target.dataset; - const hideTooltip = () => { - target.removeEventListener('mouseout', hideTooltip); - setTimeout(() => { + const { title: originalTitle } = target.dataset; + + once('hidden', (tooltip) => { + if (tooltip.target === target) { target.setAttribute('title', originalTitle); fixTitle(target); - }, 100); - }; + } + }); target.setAttribute('title', title); - fixTitle(target); show(target); - - target.addEventListener('mouseout', hideTooltip); + setTimeout(() => target.blur(), 1000); } function genericSuccess(e) { // Clear the selection and blur the trigger so it loses its border e.clearSelection(); - $(e.trigger).blur(); - showTooltip(e.trigger, __('Copied')); } @@ -88,24 +84,8 @@ export default function initCopyToClipboard() { * @param {HTMLElement} btnElement */ export function clickCopyToClipboardButton(btnElement) { - const $btnElement = $(btnElement); - // Ensure the button has already been tooltip'd. - // If the use hasn't yet interacted (i.e. hovered or clicked) - // with the button, Bootstrap hasn't yet initialized - // the tooltip, and its `data-original-title` will be `undefined`. - // This value is used in the functions above. - $btnElement.tooltip(); - btnElement.dispatchEvent(new MouseEvent('mouseover')); + add([btnElement], { show: true }); btnElement.click(); - - // Manually trigger the necessary events to hide the - // button's tooltip and allow the button to perform its - // tooltip cleanup (updating the title from "Copied" back - // to its original title, "Copy branch name"). - setTimeout(() => { - btnElement.dispatchEvent(new MouseEvent('mouseout')); - $btnElement.tooltip('hide'); - }, 2000); } diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js index 5479866c99a..8238f5523f3 100644 --- a/app/assets/javascripts/behaviors/markdown/render_math.js +++ b/app/assets/javascripts/behaviors/markdown/render_math.js @@ -1,6 +1,6 @@ -import { deprecatedCreateFlash as flash } from '~/flash'; +import { spriteIcon } from '~/lib/utils/common_utils'; import { differenceInMilliseconds } from '~/lib/utils/datetime_utility'; -import { s__, sprintf } from '~/locale'; +import { s__ } from '~/locale'; // Renders math using KaTeX in any element with the // `js-render-math` class @@ -13,30 +13,10 @@ import { s__, sprintf } from '~/locale'; const MAX_MATH_CHARS = 1000; const MAX_RENDER_TIME_MS = 2000; -// These messages might be used with inline errors in the future. Keep them around. For now, we will -// display a single error message using flash(). - -// const CHAR_LIMIT_EXCEEDED_MSG = sprintf( -// s__( -// 'math|The following math is too long. For performance reasons, math blocks are limited to %{maxChars} characters. Try splitting up this block, or include an image instead.', -// ), -// { maxChars: MAX_MATH_CHARS }, -// ); -// const RENDER_TIME_EXCEEDED_MSG = s__( -// "math|The math in this entry is taking too long to render. Any math below this point won't be shown. Consider splitting it among multiple entries.", -// ); - -const RENDER_FLASH_MSG = sprintf( - s__( - 'math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead.', - ), - { maxChars: MAX_MATH_CHARS }, -); - // Wait for the browser to reflow the layout. Reflowing SVG takes time. // This has to wrap the inner function, otherwise IE/Edge throw "invalid calling object". const waitForReflow = (fn) => { - window.requestAnimationFrame(fn); + window.requestIdleCallback(fn); }; /** @@ -67,37 +47,69 @@ class SafeMathRenderer { this.renderElement = this.renderElement.bind(this); this.render = this.render.bind(this); + this.attachEvents = this.attachEvents.bind(this); } - renderElement() { - if (!this.queue.length) { + renderElement(chosenEl) { + if (!this.queue.length && !chosenEl) { return; } - const el = this.queue.shift(); + const el = chosenEl || this.queue.shift(); + const forceRender = Boolean(chosenEl); const text = el.textContent; el.removeAttribute('style'); - - if (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS) { - if (!this.flashShown) { - flash(RENDER_FLASH_MSG); - this.flashShown = true; - } - + if (!forceRender && (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS)) { // Show unrendered math code + const wrapperElement = document.createElement('div'); const codeElement = document.createElement('pre'); + codeElement.className = 'code'; codeElement.textContent = el.textContent; - el.parentNode.replaceChild(codeElement, el); + + const { parentNode } = el; + parentNode.replaceChild(wrapperElement, el); + + const html = ` + + `; + + if (!wrapperElement.classList.contains('lazy-alert-shown')) { + wrapperElement.innerHTML = html; + wrapperElement.append(codeElement); + wrapperElement.classList.add('lazy-alert-shown'); + } // Render the next math this.renderElement(); } else { this.startTime = Date.now(); + /* Get the correct reference to the display container when: + * a.) Happy path: when the math block is present, and + * b.) When we've replace the block with
 for lazy rendering
+       */
+      let displayContainer = el;
+      if (el.tagName === 'PRE') {
+        displayContainer = el.parentElement;
+      }
+
       try {
-        el.innerHTML = this.katex.renderToString(text, {
+        displayContainer.innerHTML = this.katex.renderToString(text, {
           displayMode: el.getAttribute('data-math-style') === 'display',
           throwOnError: true,
           maxSize: 20,
@@ -135,6 +147,22 @@ class SafeMathRenderer {
     // and less prone to timeouts.
     setTimeout(this.renderElement, 400);
   }
+
+  attachEvents() {
+    document.body.addEventListener('click', (event) => {
+      if (!event.target.classList.contains('js-lazy-render-math')) {
+        return;
+      }
+
+      const parent = event.target.closest('.js-lazy-render-math-container');
+
+      const pre = parent.nextElementSibling;
+
+      parent.remove();
+
+      this.renderElement(pre);
+    });
+  }
 }
 
 export default function renderMath($els) {
@@ -146,6 +174,7 @@ export default function renderMath($els) {
     .then(([katex]) => {
       const renderer = new SafeMathRenderer($els.get(), katex);
       renderer.render();
+      renderer.attachEvents();
     })
     .catch(() => {});
 }
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index 0513e807ed6..a8fe00d26e6 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -1,91 +1,80 @@
-import { flatten } from 'lodash';
+import { memoize } from 'lodash';
 import AccessorUtilities from '~/lib/utils/accessor';
 import { s__ } from '~/locale';
-import { shouldDisableShortcuts } from './shortcuts_toggle';
 
-export const LOCAL_STORAGE_KEY = 'gl-keyboard-shortcuts-customizations';
-
-let parsedCustomizations = {};
-const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
+const isCustomizable = (command) =>
+  'customizable' in command ? Boolean(command.customizable) : true;
 
-if (localStorageIsSafe) {
-  try {
-    parsedCustomizations = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || '{}');
-  } catch (e) {
-    /* do nothing */
-  }
-}
+export const LOCAL_STORAGE_KEY = 'gl-keyboard-shortcuts-customizations';
 
 /**
- * A map of command => keys of all keyboard shortcuts
- * that have been customized by the user.
+ * @returns { Object. } A map of command ID => keys of all
+ * keyboard shortcuts that have been customized by the user. These
+ * customizations are fetched from `localStorage`. This function is memoized,
+ * so its return value will not reflect changes made to the `localStorage` data
+ * after it has been called once.
  *
  * @example
  * { "globalShortcuts.togglePerformanceBar": ["p e r f"] }
- *
- * @type { Object. }
  */
-export const customizations = parsedCustomizations;
+export const getCustomizations = memoize(() => {
+  let parsedCustomizations = {};
+  const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
+
+  if (localStorageIsSafe) {
+    try {
+      parsedCustomizations = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || '{}');
+    } catch (e) {
+      /* do nothing */
+    }
+  }
+
+  return parsedCustomizations;
+});
 
 // All available commands
-export const TOGGLE_PERFORMANCE_BAR = 'globalShortcuts.togglePerformanceBar';
-export const TOGGLE_CANARY = 'globalShortcuts.toggleCanary';
+export const TOGGLE_PERFORMANCE_BAR = {
+  id: 'globalShortcuts.togglePerformanceBar',
+  description: s__('KeyboardShortcuts|Toggle the Performance Bar'),
+  // eslint-disable-next-line @gitlab/require-i18n-strings
+  defaultKeys: ['p b'],
+};
 
-/** All keybindings, grouped and ordered with descriptions */
-export const keybindingGroups = [
-  {
-    groupId: 'globalShortcuts',
-    name: s__('KeyboardShortcuts|Global Shortcuts'),
-    keybindings: [
-      {
-        description: s__('KeyboardShortcuts|Toggle the Performance Bar'),
-        command: TOGGLE_PERFORMANCE_BAR,
-        // eslint-disable-next-line @gitlab/require-i18n-strings
-        defaultKeys: ['p b'],
-      },
-      {
-        description: s__('KeyboardShortcuts|Toggle GitLab Next'),
-        command: TOGGLE_CANARY,
-        // eslint-disable-next-line @gitlab/require-i18n-strings
-        defaultKeys: ['g x'],
-      },
-    ],
-  },
-]
+export const TOGGLE_CANARY = {
+  id: 'globalShortcuts.toggleCanary',
+  description: s__('KeyboardShortcuts|Toggle GitLab Next'),
+  // eslint-disable-next-line @gitlab/require-i18n-strings
+  defaultKeys: ['g x'],
+};
 
-  // For each keybinding object, add a `customKeys` property populated with the
-  // user's custom keybindings (if the command has been customized).
-  // `customKeys` will be `undefined` if the command hasn't been customized.
-  .map((group) => {
-    return {
-      ...group,
-      keybindings: group.keybindings.map((binding) => ({
-        ...binding,
-        customKeys: customizations[binding.command],
-      })),
-    };
-  });
+export const WEB_IDE_COMMIT = {
+  id: 'webIDE.commit',
+  description: s__('KeyboardShortcuts|Commit (when editing commit message)'),
+  defaultKeys: ['mod+enter'],
+  customizable: false,
+};
 
-/**
- * A simple map of command => keys. All user customizations are included in this map.
- * This mapping is used to simplify `keysFor` below.
- *
- * @example
- * { "globalShortcuts.togglePerformanceBar": ["p e r f"] }
- */
-const commandToKeys = flatten(keybindingGroups.map((group) => group.keybindings)).reduce(
-  (acc, binding) => {
-    acc[binding.command] = binding.customKeys || binding.defaultKeys;
-    return acc;
-  },
-  {},
-);
+// All keybinding groups
+export const GLOBAL_SHORTCUTS_GROUP = {
+  id: 'globalShortcuts',
+  name: s__('KeyboardShortcuts|Global Shortcuts'),
+  keybindings: [TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY],
+};
+
+export const WEB_IDE_GROUP = {
+  id: 'webIDE',
+  name: s__('KeyboardShortcuts|Web IDE'),
+  keybindings: [WEB_IDE_COMMIT],
+};
+
+/** All keybindings, grouped and ordered with descriptions */
+export const keybindingGroups = [GLOBAL_SHORTCUTS_GROUP, WEB_IDE_GROUP];
 
 /**
  * Gets keyboard shortcuts associated with a command
  *
- * @param {string} command The command string. All command
- * strings are available as imports from this file.
+ * @param {string} command The command object. All command
+ * objects are available as imports from this file.
  *
  * @returns {string[]} An array of keyboard shortcut strings bound to the command
  *
@@ -95,9 +84,11 @@ const commandToKeys = flatten(keybindingGroups.map((group) => group.keybindings)
  * Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), handler);
  */
 export const keysFor = (command) => {
-  if (shouldDisableShortcuts()) {
-    return [];
+  if (!isCustomizable(command)) {
+    // if the command is defined with `customizable: false`,
+    // don't allow this command to be customized.
+    return command.defaultKeys;
   }
 
-  return commandToKeys[command];
+  return getCustomizations()[command.id] || command.defaultKeys;
 };
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 4b63143c4ba..30424fee46a 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -30,7 +30,7 @@ $(() => {
   }
 
   $('body').on('click', '.js-toggle-button', function toggleButton(e) {
-    e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'open');
+    e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'selected');
     toggleContainer($(this).closest('.js-toggle-container'));
 
     const targetTag = e.currentTarget.tagName.toLowerCase();
-- 
cgit v1.2.1