Commit d5b98c93 authored by Lukas 'Eipi' Eipert's avatar Lukas 'Eipi' Eipert Committed by Nicolò Maria Mezzopera

Move keyboard shortcut help modal to Vue

Currently the keyboard shortcut modal is rendered via HAML and injected
into the DOM via JavaScript eval. This is rather inefficient and might
even introduce security vulnerabilities. Thus we move the static HAML
file to be a conditionally rendered Vue Component instead.
parent 295400e4
...@@ -109,7 +109,6 @@ linters: ...@@ -109,7 +109,6 @@ linters:
- 'app/views/groups/runners/edit.html.haml' - 'app/views/groups/runners/edit.html.haml'
- 'app/views/groups/settings/_advanced.html.haml' - 'app/views/groups/settings/_advanced.html.haml'
- 'app/views/groups/settings/_lfs.html.haml' - 'app/views/groups/settings/_lfs.html.haml'
- 'app/views/help/_shortcuts.html.haml'
- 'app/views/help/index.html.haml' - 'app/views/help/index.html.haml'
- 'app/views/help/instance_configuration.html.haml' - 'app/views/help/instance_configuration.html.haml'
- 'app/views/help/instance_configuration/_gitlab_ci.html.haml' - 'app/views/help/instance_configuration/_gitlab_ci.html.haml'
......
...@@ -3,12 +3,11 @@ import Cookies from 'js-cookie'; ...@@ -3,12 +3,11 @@ import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import Vue from 'vue'; import Vue from 'vue';
import { flatten } from 'lodash'; import { flatten } from 'lodash';
import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils'; import { refreshCurrentPage, visitUrl } from '~/lib/utils/url_utility';
import axios from '../../lib/utils/axios_utils'; import findAndFollowLink from '~/lib/utils/navigation_utility';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility'; import { parseBoolean } from '~/lib/utils/common_utils';
import findAndFollowLink from '../../lib/utils/navigation_utility';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle'; import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
import ShortcutsToggle from './shortcuts_toggle.vue';
import { keysFor, TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY } from './keybindings'; import { keysFor, TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY } from './keybindings';
const defaultStopCallback = Mousetrap.prototype.stopCallback; const defaultStopCallback = Mousetrap.prototype.stopCallback;
...@@ -20,15 +19,6 @@ Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo ...@@ -20,15 +19,6 @@ Mousetrap.prototype.stopCallback = function customStopCallback(e, element, combo
return defaultStopCallback.call(this, e, element, combo); return defaultStopCallback.call(this, e, element, combo);
}; };
function initToggleButton() {
return new Vue({
el: document.querySelector('.js-toggle-shortcuts'),
render(createElement) {
return createElement(ShortcutsToggle);
},
});
}
/** /**
* The key used to save and fetch the local Mousetrap instance * The key used to save and fetch the local Mousetrap instance
* attached to a `<textarea>` element using `jQuery.data` * attached to a `<textarea>` element using `jQuery.data`
...@@ -65,7 +55,8 @@ function getToolbarBtnToShortcutsMap($textarea) { ...@@ -65,7 +55,8 @@ function getToolbarBtnToShortcutsMap($textarea) {
export default class Shortcuts { export default class Shortcuts {
constructor() { constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this); this.onToggleHelp = this.onToggleHelp.bind(this);
this.enabledHelp = []; this.helpModalElement = null;
this.helpModalVueInstance = null;
Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('s', Shortcuts.focusSearch);
...@@ -107,11 +98,33 @@ export default class Shortcuts { ...@@ -107,11 +98,33 @@ export default class Shortcuts {
} }
onToggleHelp(e) { onToggleHelp(e) {
if (e.preventDefault) { if (e?.preventDefault) {
e.preventDefault(); e.preventDefault();
} }
Shortcuts.toggleHelp(this.enabledHelp); if (this.helpModalElement && this.helpModalVueInstance) {
this.helpModalVueInstance.$destroy();
this.helpModalElement.remove();
this.helpModalElement = null;
this.helpModalVueInstance = null;
} else {
this.helpModalElement = document.createElement('div');
document.body.append(this.helpModalElement);
this.helpModalVueInstance = new Vue({
el: this.helpModalElement,
components: {
ShortcutsHelp: () => import('./shortcuts_help.vue'),
},
render: (createElement) => {
return createElement('shortcuts-help', {
on: {
hidden: this.onToggleHelp,
},
});
},
});
}
} }
static onTogglePerfBar(e) { static onTogglePerfBar(e) {
...@@ -144,34 +157,6 @@ export default class Shortcuts { ...@@ -144,34 +157,6 @@ export default class Shortcuts {
$(document).triggerHandler('markdown-preview:toggle', [e]); $(document).triggerHandler('markdown-preview:toggle', [e]);
} }
static toggleHelp(location) {
const $modal = $('#modal-shortcuts');
if ($modal.length) {
$modal.modal('toggle');
return null;
}
return axios
.get(gon.shortcuts_path, {
responseType: 'text',
})
.then(({ data }) => {
$.globalEval(data, { nonce: getCspNonceValue() });
if (location && location.length > 0) {
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) {
results.push($(location[i]).show());
}
return results;
}
return $('.js-more-help-button').remove();
})
.then(initToggleButton);
}
focusFilter(e) { focusFilter(e) {
if (!this.filterInput) { if (!this.filterInput) {
this.filterInput = $('input[type=search]', '.nav-controls'); this.filterInput = $('input[type=search]', '.nav-controls');
......
This diff is collapsed.
This diff is collapsed.
:plain
$("body").append("#{escape_javascript(render('shortcuts'))}");
$("#modal-shortcuts").modal();
...@@ -13,7 +13,6 @@ module Gitlab ...@@ -13,7 +13,6 @@ module Gitlab
gon.asset_host = ActionController::Base.asset_host gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path gon.webpack_public_path = webpack_public_path
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = Gitlab::Routing.url_helpers.help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if Gitlab.config.sentry.enabled if Gitlab.config.sentry.enabled
......
...@@ -23,7 +23,7 @@ RSpec.describe 'Help Pages' do ...@@ -23,7 +23,7 @@ RSpec.describe 'Help Pages' do
it 'opens shortcuts help dialog' do it 'opens shortcuts help dialog' do
find('.js-trigger-shortcut').click find('.js-trigger-shortcut').click
expect(page).to have_selector('#modal-shortcuts') expect(page).to have_selector('[data-testid="modal-shortcuts"]')
end end
end end
end end
......
...@@ -27,14 +27,13 @@ RSpec.describe 'User uses shortcuts', :js do ...@@ -27,14 +27,13 @@ RSpec.describe 'User uses shortcuts', :js do
open_modal_shortcut_keys open_modal_shortcut_keys
# modal-shortcuts still in the DOM, but hidden expect(page).not_to have_selector('[data-testid="modal-shortcuts"]')
expect(find('#modal-shortcuts', visible: false)).not_to be_visible
page.refresh page.refresh
open_modal_shortcut_keys open_modal_shortcut_keys
# after reload, shortcuts modal doesn't exist at all until we add it # after reload, shortcuts modal doesn't exist at all until we add it
expect(page).not_to have_selector('#modal-shortcuts') expect(page).not_to have_selector('[data-testid="modal-shortcuts"]')
end end
it 're-enables shortcuts' do it 're-enables shortcuts' do
...@@ -47,7 +46,7 @@ RSpec.describe 'User uses shortcuts', :js do ...@@ -47,7 +46,7 @@ RSpec.describe 'User uses shortcuts', :js do
close_modal close_modal
open_modal_shortcut_keys open_modal_shortcut_keys
expect(find('#modal-shortcuts')).to be_visible expect(find('[data-testid="modal-shortcuts"]')).to be_visible
end end
def open_modal_shortcut_keys def open_modal_shortcut_keys
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment