diff options
Diffstat (limited to 'app/assets/javascripts/behaviors')
9 files changed, 148 insertions, 26 deletions
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index d61797b7ae4..3e9d77cdf6b 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -1,5 +1,5 @@ import Autosize from 'autosize'; -import { waitForCSSLoaded } from '../helpers/startup_css_helper'; +import { waitForCSSLoaded } from '~/helpers/startup_css_helper'; document.addEventListener('DOMContentLoaded', () => { waitForCSSLoaded(() => { diff --git a/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js b/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js index d9164f6204a..c4af34b848b 100644 --- a/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js +++ b/app/assets/javascripts/behaviors/collapse_sidebar_on_window_resize.js @@ -8,7 +8,6 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; * @sentrify */ export default () => { - const $sidebarGutterToggle = $('.js-sidebar-toggle'); let bootstrapBreakpoint = bp.getBreakpointSize(); $(window).on('resize.app', () => { @@ -19,8 +18,13 @@ export default () => { const breakpointSizes = ['md', 'sm', 'xs']; if (breakpointSizes.includes(bootstrapBreakpoint)) { - const $gutterIcon = $sidebarGutterToggle.find('i'); - if ($gutterIcon.hasClass('fa-angle-double-right')) { + const $toggleContainer = $('.js-sidebar-toggle-container'); + const isExpanded = $toggleContainer.data('is-expanded'); + const $expandIcon = $('.js-sidebar-expand'); + + if (isExpanded) { + const $sidebarGutterToggle = $expandIcon.closest('.js-sidebar-toggle'); + $sidebarGutterToggle.trigger('click'); } @@ -28,11 +32,12 @@ export default () => { // Sidebar has an icon which corresponds to collapsing the sidebar // only then trigger the click. - if (sidebarGutterVueToggleEl) { - const collapseIcon = sidebarGutterVueToggleEl.querySelector('i.fa-angle-double-right'); - - if (collapseIcon) { - collapseIcon.click(); + if ( + sidebarGutterVueToggleEl && + !sidebarGutterVueToggleEl.classList.contains('js-sidebar-collapsed') + ) { + if (sidebarGutterVueToggleEl) { + sidebarGutterVueToggleEl.click(); } } } diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js index 48bcba7bcca..430a8c38387 100644 --- a/app/assets/javascripts/behaviors/copy_to_clipboard.js +++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js @@ -1,19 +1,24 @@ import $ from 'jquery'; import Clipboard from 'clipboard'; import { sprintf, __ } from '~/locale'; +import { fixTitle, show } from '~/tooltips'; function showTooltip(target, title) { - const $target = $(target); - const originalTitle = $target.data('originalTitle'); + const { originalTitle } = target.dataset; + const hideTooltip = () => { + target.removeEventListener('mouseout', hideTooltip); + setTimeout(() => { + target.setAttribute('title', originalTitle); + fixTitle(target); + }, 100); + }; - if (!$target.data('hideTooltip')) { - $target - .attr('title', title) - .tooltip('_fixTitle') - .tooltip('show') - .attr('title', originalTitle) - .tooltip('_fixTitle'); - } + target.setAttribute('title', title); + + fixTitle(target); + show(target); + + target.addEventListener('mouseout', hideTooltip); } function genericSuccess(e) { diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index bcf732e9522..16373b523b2 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -3,9 +3,7 @@ import isEmojiUnicodeSupported from '../emoji/support'; import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji'; class GlEmoji extends HTMLElement { - constructor() { - super(); - + connectedCallback() { this.initialize(); } initialize() { diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js index fd12c282b62..613309a1c5a 100644 --- a/app/assets/javascripts/behaviors/index.js +++ b/app/assets/javascripts/behaviors/index.js @@ -13,6 +13,9 @@ import './toggler_behavior'; import './preview_markdown'; import initCollapseSidebarOnWindowResize from './collapse_sidebar_on_window_resize'; import initSelect2Dropdowns from './select2'; +import { loadStartupCSS } from './load_startup_css'; + +loadStartupCSS(); installGlEmojiElement(); diff --git a/app/assets/javascripts/behaviors/load_startup_css.js b/app/assets/javascripts/behaviors/load_startup_css.js new file mode 100644 index 00000000000..1d7bf716475 --- /dev/null +++ b/app/assets/javascripts/behaviors/load_startup_css.js @@ -0,0 +1,15 @@ +export const loadStartupCSS = () => { + // We need to fallback to dispatching `load` in case our event listener was added too late + // or the browser environment doesn't load media=print. + // Do this on `window.load` so that the default deferred behavior takes precedence. + // https://gitlab.com/gitlab-org/gitlab/-/issues/239357 + window.addEventListener( + 'load', + () => { + document + .querySelectorAll('link[media=print]') + .forEach(x => x.dispatchEvent(new Event('load'))); + }, + { once: true }, + ); +}; diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js index d712c90242c..83f2ca0bdc2 100644 --- a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js +++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js @@ -14,7 +14,6 @@ export default function initGFMInput($els) { milestones: enableGFM, mergeRequests: enableGFM, labels: enableGFM, - vulnerabilities: enableGFM, }); }); } diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js new file mode 100644 index 00000000000..bbcc40ab9fe --- /dev/null +++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js @@ -0,0 +1,96 @@ +import { flatten } from 'lodash'; +import { s__ } from '~/locale'; +import AccessorUtilities from '~/lib/utils/accessor'; +import { shouldDisableShortcuts } from './shortcuts_toggle'; + +export const LOCAL_STORAGE_KEY = 'gl-keyboard-shortcuts-customizations'; + +let parsedCustomizations = {}; +const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); + +if (localStorageIsSafe) { + try { + parsedCustomizations = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || '{}'); + } catch (e) { + /* do nothing */ + } +} + +/** + * A map of command => keys of all keyboard shortcuts + * that have been customized by the user. + * + * @example + * { "globalShortcuts.togglePerformanceBar": ["p e r f"] } + * + * @type { Object.<string, string[]> } + */ +export const customizations = parsedCustomizations; + +// All available commands +export const TOGGLE_PERFORMANCE_BAR = 'globalShortcuts.togglePerformanceBar'; + +/** 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'], + }, + ], + }, +] + + // 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], + })), + }; + }); + +/** + * 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; + }, + {}, +); + +/** + * Gets keyboard shortcuts associated with a command + * + * @param {string} command The command string. All command + * strings are available as imports from this file. + * + * @returns {string[]} An array of keyboard shortcut strings bound to the command + * + * @example + * import { keysFor, TOGGLE_PERFORMANCE_BAR } from '~/behaviors/shortcuts/keybindings' + * + * Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), handler); + */ +export const keysFor = command => { + if (shouldDisableShortcuts()) { + return []; + } + + return commandToKeys[command]; +}; diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 8a8b61a57cd..a53150f8d61 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -9,6 +9,7 @@ import axios from '../../lib/utils/axios_utils'; import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility'; import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils'; +import { keysFor, TOGGLE_PERFORMANCE_BAR } from './keybindings'; const defaultStopCallback = Mousetrap.prototype.stopCallback; Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo) { @@ -70,7 +71,7 @@ export default class Shortcuts { Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('/', Shortcuts.focusSearch); Mousetrap.bind('f', this.focusFilter.bind(this)); - Mousetrap.bind('p b', Shortcuts.onTogglePerfBar); + Mousetrap.bind(keysFor(TOGGLE_PERFORMANCE_BAR), Shortcuts.onTogglePerfBar); const findFileURL = document.body.dataset.findFile; @@ -117,9 +118,9 @@ export default class Shortcuts { e.preventDefault(); const performanceBarCookieName = 'perf_bar_enabled'; if (parseBoolean(Cookies.get(performanceBarCookieName))) { - Cookies.set(performanceBarCookieName, 'false', { path: '/' }); + Cookies.set(performanceBarCookieName, 'false', { expires: 365, path: '/' }); } else { - Cookies.set(performanceBarCookieName, 'true', { path: '/' }); + Cookies.set(performanceBarCookieName, 'true', { expires: 365, path: '/' }); } refreshCurrentPage(); } |