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:
- 'app/views/groups/runners/edit.html.haml'
- 'app/views/groups/settings/_advanced.html.haml'
- 'app/views/groups/settings/_lfs.html.haml'
- 'app/views/help/_shortcuts.html.haml'
- 'app/views/help/index.html.haml'
- 'app/views/help/instance_configuration.html.haml'
- 'app/views/help/instance_configuration/_gitlab_ci.html.haml'
......
......@@ -3,12 +3,11 @@ import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import Vue from 'vue';
import { flatten } from 'lodash';
import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
import { refreshCurrentPage, visitUrl } from '~/lib/utils/url_utility';
import findAndFollowLink from '~/lib/utils/navigation_utility';
import { parseBoolean } from '~/lib/utils/common_utils';
import { disableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
import ShortcutsToggle from './shortcuts_toggle.vue';
import { keysFor, TOGGLE_PERFORMANCE_BAR, TOGGLE_CANARY } from './keybindings';
const defaultStopCallback = Mousetrap.prototype.stopCallback;
......@@ -20,15 +19,6 @@ Mousetrap.prototype.stopCallback = function customStopCallback(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
* attached to a `<textarea>` element using `jQuery.data`
......@@ -65,7 +55,8 @@ function getToolbarBtnToShortcutsMap($textarea) {
export default class Shortcuts {
constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
this.enabledHelp = [];
this.helpModalElement = null;
this.helpModalVueInstance = null;
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
......@@ -107,11 +98,33 @@ export default class Shortcuts {
}
onToggleHelp(e) {
if (e.preventDefault) {
if (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) {
......@@ -144,34 +157,6 @@ export default class Shortcuts {
$(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) {
if (!this.filterInput) {
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
gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path
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
if Gitlab.config.sentry.enabled
......
......@@ -23,7 +23,7 @@ RSpec.describe 'Help Pages' do
it 'opens shortcuts help dialog' do
find('.js-trigger-shortcut').click
expect(page).to have_selector('#modal-shortcuts')
expect(page).to have_selector('[data-testid="modal-shortcuts"]')
end
end
end
......
......@@ -27,14 +27,13 @@ RSpec.describe 'User uses shortcuts', :js do
open_modal_shortcut_keys
# modal-shortcuts still in the DOM, but hidden
expect(find('#modal-shortcuts', visible: false)).not_to be_visible
expect(page).not_to have_selector('[data-testid="modal-shortcuts"]')
page.refresh
open_modal_shortcut_keys
# 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
it 're-enables shortcuts' do
......@@ -47,7 +46,7 @@ RSpec.describe 'User uses shortcuts', :js do
close_modal
open_modal_shortcut_keys
expect(find('#modal-shortcuts')).to be_visible
expect(find('[data-testid="modal-shortcuts"]')).to be_visible
end
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