diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /app/assets/javascripts/behaviors | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'app/assets/javascripts/behaviors')
7 files changed, 213 insertions, 507 deletions
diff --git a/app/assets/javascripts/behaviors/date_picker.js b/app/assets/javascripts/behaviors/date_picker.js new file mode 100644 index 00000000000..efd89ec4330 --- /dev/null +++ b/app/assets/javascripts/behaviors/date_picker.js @@ -0,0 +1,33 @@ +import $ from 'jquery'; +import Pikaday from 'pikaday'; +import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility'; + +export default function initDatePickers() { + $('.datepicker').each(function initPikaday() { + const $datePicker = $(this); + const datePickerVal = $datePicker.val(); + + const calendar = new Pikaday({ + field: $datePicker.get(0), + theme: 'gitlab-theme animate-picker', + format: 'yyyy-mm-dd', + container: $datePicker.parent().get(0), + parse: (dateString) => parsePikadayDate(dateString), + toString: (date) => pikadayToString(date), + onSelect(dateText) { + $datePicker.val(calendar.toString(dateText)); + }, + firstDay: gon.first_day_of_week, + }); + + calendar.setDate(parsePikadayDate(datePickerVal)); + + $datePicker.data('pikaday', calendar); + }); + + $('.js-clear-due-date,.js-clear-start-date').on('click', (e) => { + e.preventDefault(); + const calendar = $(e.target).siblings('.datepicker').data('pikaday'); + calendar.setDate(null); + }); +} diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js index 8238f5523f3..12f47255bdf 100644 --- a/app/assets/javascripts/behaviors/markdown/render_math.js +++ b/app/assets/javascripts/behaviors/markdown/render_math.js @@ -114,6 +114,12 @@ class SafeMathRenderer { throwOnError: true, maxSize: 20, maxExpand: 20, + trust: (context) => + // this config option restores the KaTeX pre-v0.11.0 + // behavior of allowing certain commands and protocols + // eslint-disable-next-line @gitlab/require-i18n-strings + ['\\url', '\\href'].includes(context.command) && + ['http', 'https', 'mailto', '_relative'].includes(context.protocol), }); } catch (e) { // Don't show a flash for now because it would override an existing flash message diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index 5b5148a850b..f5b2d266c18 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { once } from 'lodash'; +import { once, countBy } from 'lodash'; import { deprecatedCreateFlash as flash } from '~/flash'; import { darkModeEnabled } from '~/lib/utils/color_utils'; import { __, sprintf } from '~/locale'; @@ -22,6 +22,8 @@ import { __, sprintf } from '~/locale'; const MAX_CHAR_LIMIT = 2000; // Max # of mermaid blocks that can be rendered in a page. const MAX_MERMAID_BLOCK_LIMIT = 50; +// Max # of `&` allowed in Chaining of links syntax +const MAX_CHAINING_OF_LINKS_LIMIT = 30; // Keep a map of mermaid blocks we've already rendered. const elsProcessingMap = new WeakMap(); let renderedMermaidBlocks = 0; @@ -64,6 +66,18 @@ function importMermaidModule() { }); } +function shouldLazyLoadMermaidBlock(source) { + /** + * If source contains `&`, which means that it might + * contain Chaining of links a new syntax in Mermaid. + */ + if (countBy(source)['&'] > MAX_CHAINING_OF_LINKS_LIMIT) { + return true; + } + + return false; +} + function fixElementSource(el) { // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly. const source = el.textContent.replace(/<br\s*\/>/g, '<br>'); @@ -128,7 +142,8 @@ function renderMermaids($els) { if ( (source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT || - renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT + renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT || + shouldLazyLoadMermaidBlock(source) ) { const html = ` <div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert"> diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js index 6abbd7f3243..c63dba05f10 100644 --- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js +++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js @@ -375,7 +375,7 @@ export const MR_PREVIOUS_FILE_IN_DIFF = { export const MR_GO_TO_FILE = { id: 'mergeRequests.goToFile', description: __('Go to file'), - defaultKeys: ['t', 'mod+p'], + defaultKeys: ['mod+p', 't'], customizable: false, }; diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcut.vue b/app/assets/javascripts/behaviors/shortcuts/shortcut.vue new file mode 100644 index 00000000000..e5992779a99 --- /dev/null +++ b/app/assets/javascripts/behaviors/shortcuts/shortcut.vue @@ -0,0 +1,80 @@ +<script> +import { __, s__ } from '~/locale'; + +// Map some keys to their proper representation depending on the system +// See also: https://craig.is/killing/mice#keys +const getKeyMap = () => { + const keyMap = { + up: '↑', + down: '↓', + left: '←', + right: '→', + ctrl: s__('KeyboardKey|Ctrl'), + shift: s__('KeyboardKey|Shift'), + enter: s__('KeyboardKey|Enter'), + esc: s__('KeyboardKey|Esc'), + command: '⌘', + option: window.gl?.client?.isMac ? '⌥' : s__('KeyboardKey|Alt'), + }; + + // Meta and alt are aliases + keyMap.meta = keyMap.command; + keyMap.alt = keyMap.option; + + // Mod is Command on Mac, and Ctrl on Windows/Linux + keyMap.mod = window.gl?.client?.isMac ? keyMap.command : keyMap.ctrl; + + return keyMap; +}; + +export default { + functional: true, + props: { + shortcuts: { + type: Array, + required: true, + }, + }, + + render(createElement, context) { + const keyMap = getKeyMap(); + + const { staticClass } = context.data; + + const shortcuts = context.props.shortcuts.reduce((acc, shortcut, i) => { + if ( + !window.gl?.client?.isMac && + (shortcut.includes('command') || shortcut.includes('meta')) + ) { + return acc; + } + const keys = shortcut.split(/([ +])/); + + if (i !== 0 && acc.length) { + acc.push(` ${__('or')} `); + // If there are multiple alternative shortcuts, + // we keep them on the same line if they are single-key, e.g. `]` or `j` + // but if they consist of multiple keys, we insert a line break, e.g.: + // `shift` + `]` <br> or `shift` + `j` + if (keys.length > 1) { + acc.push(createElement('br')); + } + } + + keys.forEach((key) => { + if (key === '+') { + acc.push(' + '); + } else if (key === ' ') { + acc.push(` ${__('then')} `); + } else { + acc.push(createElement('kbd', {}, [keyMap[key] ?? key])); + } + }); + + return acc; + }, []); + + return createElement('div', { staticClass }, shortcuts); + }, +}; +</script> diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue index 49216cc4aa0..cb7c6f9f6bc 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue @@ -1,525 +1,99 @@ <script> -/* eslint-disable @gitlab/vue-require-i18n-strings */ -import { GlIcon, GlModal } from '@gitlab/ui'; +import { GlModal, GlSearchBoxByType } from '@gitlab/ui'; +import { s__, __ } from '~/locale'; +import { keybindingGroups } from './keybindings'; +import Shortcut from './shortcut.vue'; import ShortcutsToggle from './shortcuts_toggle.vue'; export default { components: { - GlIcon, GlModal, + GlSearchBoxByType, ShortcutsToggle, + Shortcut, + }, + data() { + return { + searchTerm: '', + }; }, computed: { - ctrlCharacter() { - return window.gl.client.isMac ? '⌘' : 'ctrl'; - }, - onDotCom() { - return window.gon.dot_com; + filteredKeybindings() { + if (!this.searchTerm) { + return keybindingGroups; + } + + const search = this.searchTerm.toLocaleLowerCase(); + + const mapped = keybindingGroups.map((group) => { + if (group.name.toLocaleLowerCase().includes(search)) { + return group; + } + return { + ...group, + keybindings: group.keybindings.filter((binding) => + binding.description.toLocaleLowerCase().includes(search), + ), + }; + }); + + return mapped.filter((group) => group.keybindings.length); }, }, + i18n: { + title: __(`Keyboard shortcuts`), + search: s__(`KeyboardShortcuts|Search keyboard shortcuts`), + noMatch: s__(`KeyboardShortcuts|No shortcuts matched your search`), + }, }; </script> <template> <gl-modal modal-id="keyboard-shortcut-modal" size="lg" + :title="$options.i18n.title" data-testid="modal-shortcuts" + body-class="shortcut-help-body gl-p-0!" :visible="true" :hide-footer="true" @hidden="$emit('hidden')" > - <template #modal-title> - <shortcuts-toggle /> - </template> - <div class="row"> - <div class="col-lg-4"> - <table class="shortcut-mappings text-2"> - <tbody> - <tr> - <th></th> - <th>{{ __('Global Shortcuts') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>?</kbd> - </td> - <td>{{ __('Toggle this dialog') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift p</kbd> - </td> - <td>{{ __('Go to your projects') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift g</kbd> - </td> - <td>{{ __('Go to your groups') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift a</kbd> - </td> - <td>{{ __('Go to the activity feed') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift l</kbd> - </td> - <td>{{ __('Go to the milestone list') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift s</kbd> - </td> - <td>{{ __('Go to your snippets') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>s</kbd> - / - <kbd>/</kbd> - </td> - <td>{{ __('Start search') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift i</kbd> - </td> - <td>{{ __('Go to your issues') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift m</kbd> - </td> - <td>{{ __('Go to your merge requests') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>shift t</kbd> - </td> - <td>{{ __('Go to your To-Do list') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>p</kbd> - <kbd>b</kbd> - </td> - <td>{{ __('Toggle the Performance Bar') }}</td> - </tr> - <tr v-if="onDotCom"> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>x</kbd> - </td> - <td>{{ __('Toggle GitLab Next') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Editing') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>{{ ctrlCharacter }} shift p</kbd> - </td> - <td>{{ __('Toggle Markdown preview') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-up" /> - </kbd> - </td> - <td> - {{ __('Edit your most recent comment in a thread (from an empty textarea)') }} - </td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Wiki') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>e</kbd> - </td> - <td>{{ __('Edit wiki page') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Repository Graph') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-left" /> - </kbd> - / - <kbd>h</kbd> - </td> - <td>{{ __('Scroll left') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-right" /> - </kbd> - / - <kbd>l</kbd> - </td> - <td>{{ __('Scroll right') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-up" /> - </kbd> - / - <kbd>k</kbd> - </td> - <td>{{ __('Scroll up') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-down" /> - </kbd> - / - <kbd>j</kbd> - </td> - <td>{{ __('Scroll down') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - shift - <gl-icon name="arrow-up" /> - / k - </kbd> - </td> - <td>{{ __('Scroll to top') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - shift - <gl-icon name="arrow-down" /> - / j - </kbd> - </td> - <td>{{ __('Scroll to bottom') }}</td> - </tr> - </tbody> - </table> - </div> - <div class="col-lg-4"> - <table class="shortcut-mappings text-2"> - <tbody> - <tr> - <th></th> - <th>{{ __('Project') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>p</kbd> - </td> - <td>{{ __("Go to the project's overview page") }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>v</kbd> - </td> - <td>{{ __("Go to the project's activity feed") }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>r</kbd> - </td> - <td>{{ __('Go to releases') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>f</kbd> - </td> - <td>{{ __('Go to files') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>t</kbd> - </td> - <td>{{ __('Go to find file') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>c</kbd> - </td> - <td>{{ __('Go to commits') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>n</kbd> - </td> - <td>{{ __('Go to repository graph') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>d</kbd> - </td> - <td>{{ __('Go to repository charts') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>i</kbd> - </td> - <td>{{ __('Go to issues') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>i</kbd> - </td> - <td>{{ __('New issue') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>b</kbd> - </td> - <td>{{ __('Go to issue boards') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>m</kbd> - </td> - <td>{{ __('Go to merge requests') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>j</kbd> - </td> - <td>{{ __('Go to jobs') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>l</kbd> - </td> - <td>{{ __('Go to metrics') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>e</kbd> - </td> - <td>{{ __('Go to environments') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>k</kbd> - </td> - <td>{{ __('Go to kubernetes') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>s</kbd> - </td> - <td>{{ __('Go to snippets') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>g</kbd> - <kbd>w</kbd> - </td> - <td>{{ __('Go to wiki') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Project Files') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-up" /> - </kbd> - </td> - <td>{{ __('Move selection up') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd> - <gl-icon name="arrow-down" /> - </kbd> - </td> - <td>{{ __('Move selection down') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>enter</kbd> - </td> - <td>{{ __('Open Selection') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>esc</kbd> - </td> - <td>{{ __('Go back (while searching for files)') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>y</kbd> - </td> - <td>{{ __('Go to file permalink (while viewing a file)') }}</td> - </tr> - </tbody> - </table> - </div> - <div class="col-lg-4"> - <table class="shortcut-mappings text-2"> - <tbody> - <tr> - <th></th> - <th>{{ __('Epics, issues, and merge requests') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>r</kbd> - </td> - <td>{{ __('Comment/Reply (quoting selected text)') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>e</kbd> - </td> - <td>{{ __('Edit description') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>l</kbd> - </td> - <td>{{ __('Change label') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Issues and merge requests') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>a</kbd> - </td> - <td>{{ __('Change assignee') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>m</kbd> - </td> - <td>{{ __('Change milestone') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Merge requests') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>]</kbd> - / - <kbd>j</kbd> - </td> - <td>{{ __('Next file in diff') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>[</kbd> - / - <kbd>k</kbd> - </td> - <td>{{ __('Previous file in diff') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>{{ ctrlCharacter }} p</kbd> - </td> - <td>{{ __('Go to file') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>n</kbd> - </td> - <td>{{ __('Next unresolved discussion') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>p</kbd> - </td> - <td>{{ __('Previous unresolved discussion') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>b</kbd> - </td> - <td>{{ __('Copy source branch name') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Merge request commits') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>c</kbd> - </td> - <td>{{ __('Next commit') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>x</kbd> - </td> - <td>{{ __('Previous commit') }}</td> - </tr> - </tbody> - <tbody> - <tr> - <th></th> - <th>{{ __('Web IDE') }}</th> - </tr> - <tr> - <td class="shortcut"> - <kbd>{{ ctrlCharacter }} p</kbd> - </td> - <td>{{ __('Go to file') }}</td> - </tr> - <tr> - <td class="shortcut"> - <kbd>{{ ctrlCharacter }} enter</kbd> - </td> - <td>{{ __('Commit (when editing commit message)') }}</td> - </tr> - </tbody> - </table> - </div> + <div + class="gl-sticky gl-top-0 gl-py-5 gl-px-5 gl-display-flex gl-align-items-center gl-bg-white" + > + <gl-search-box-by-type + v-model.trim="searchTerm" + :aria-label="$options.i18n.search" + class="gl-w-half gl-mr-3" + /> + <shortcuts-toggle class="gl-w-half gl-ml-3" /> + </div> + <div v-if="filteredKeybindings.length === 0" class="gl-px-5"> + {{ $options.i18n.noMatch }} + </div> + <div v-else class="shortcut-help-container gl-mt-8 gl-px-5 gl-pb-5"> + <section + v-for="group in filteredKeybindings" + :key="group.id" + class="shortcut-help-mapping gl-mb-4" + > + <strong class="shortcut-help-mapping-title gl-w-half gl-display-inline-block"> + {{ group.name }} + </strong> + <div + v-for="keybinding in group.keybindings" + :key="keybinding.id" + class="gl-display-flex gl-align-items-center" + > + <shortcut + class="gl-w-40p gl-flex-shrink-0 gl-text-right gl-pr-4" + :shortcuts="keybinding.defaultKeys" + /> + <div class="gl-w-half gl-flex-shrink-0 gl-flex-grow-1"> + {{ keybinding.description }} + </div> + </div> + </section> </div> </gl-modal> </template> diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue index 6cbe443062a..8f1518a1c9c 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue @@ -6,7 +6,7 @@ import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './sho export default { i18n: { - toggleLabel: __('Keyboard shortcuts'), + toggleLabel: __('Toggle shortcuts'), }, components: { GlToggle, @@ -31,14 +31,12 @@ export default { </script> <template> - <div v-if="localStorageUsable" class="d-inline-flex align-items-center js-toggle-shortcuts"> + <div v-if="localStorageUsable" class="js-toggle-shortcuts"> <gl-toggle v-model="shortcutsEnabled" - aria-describedby="shortcutsToggle" :label="$options.i18n.toggleLabel" label-position="left" @change="onChange" /> - <div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div> </div> </template> |