Commit 103a129e authored by Steve Mokris's avatar Steve Mokris Committed by Enrique Alcántara

Improve strikethrough in Markdown editor

- Adds a toolbar button
- Adds a keyboard shortcut (mod+shift+x)
- Avoids showing the label autocomplete menu

Changelog: added
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81092
parent 739c74e6
...@@ -131,6 +131,13 @@ export const ITALIC_TEXT = { ...@@ -131,6 +131,13 @@ export const ITALIC_TEXT = {
customizable: false, customizable: false,
}; };
export const STRIKETHROUGH_TEXT = {
id: 'editing.strikethroughText',
description: __('Strikethrough text'),
defaultKeys: ['mod+shift+x'],
customizable: false,
};
export const LINK_TEXT = { export const LINK_TEXT = {
id: 'editing.linkText', id: 'editing.linkText',
description: __('Link text'), description: __('Link text'),
...@@ -511,7 +518,14 @@ export const GLOBAL_SHORTCUTS_GROUP = { ...@@ -511,7 +518,14 @@ export const GLOBAL_SHORTCUTS_GROUP = {
export const EDITING_SHORTCUTS_GROUP = { export const EDITING_SHORTCUTS_GROUP = {
id: 'editing', id: 'editing',
name: __('Editing'), name: __('Editing'),
keybindings: [BOLD_TEXT, ITALIC_TEXT, LINK_TEXT, TOGGLE_MARKDOWN_PREVIEW, EDIT_RECENT_COMMENT], keybindings: [
BOLD_TEXT,
ITALIC_TEXT,
STRIKETHROUGH_TEXT,
LINK_TEXT,
TOGGLE_MARKDOWN_PREVIEW,
EDIT_RECENT_COMMENT,
],
}; };
export const WIKI_SHORTCUTS_GROUP = { export const WIKI_SHORTCUTS_GROUP = {
......
...@@ -574,6 +574,10 @@ class GfmAutoComplete { ...@@ -574,6 +574,10 @@ class GfmAutoComplete {
// Do not match if there is no `~` before the cursor // Do not match if there is no `~` before the cursor
return null; return null;
} }
if (subtext.endsWith('~~')) {
// Do not match if there are two consecutive `~` characters (strikethrough) before the cursor
return null;
}
const lastCandidate = subtext.split(flag).pop(); const lastCandidate = subtext.split(flag).pop();
if (labels.find((label) => label.title.startsWith(lastCandidate))) { if (labels.find((label) => label.title.startsWith(lastCandidate))) {
return lastCandidate; return lastCandidate;
......
<script> <script>
import { GlPopover, GlButton, GlTooltipDirective, GlTabs, GlTab } from '@gitlab/ui'; import { GlPopover, GlButton, GlTooltipDirective, GlTabs, GlTab } from '@gitlab/ui';
import $ from 'jquery'; import $ from 'jquery';
import { keysFor, BOLD_TEXT, ITALIC_TEXT, LINK_TEXT } from '~/behaviors/shortcuts/keybindings'; import {
keysFor,
BOLD_TEXT,
ITALIC_TEXT,
STRIKETHROUGH_TEXT,
LINK_TEXT,
} from '~/behaviors/shortcuts/keybindings';
import { getSelectedFragment } from '~/lib/utils/common_utils'; import { getSelectedFragment } from '~/lib/utils/common_utils';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { CopyAsGFM } from '../../../behaviors/markdown/copy_as_gfm'; import { CopyAsGFM } from '../../../behaviors/markdown/copy_as_gfm';
...@@ -149,6 +155,7 @@ export default { ...@@ -149,6 +155,7 @@ export default {
shortcuts: { shortcuts: {
bold: keysFor(BOLD_TEXT), bold: keysFor(BOLD_TEXT),
italic: keysFor(ITALIC_TEXT), italic: keysFor(ITALIC_TEXT),
strikethrough: keysFor(STRIKETHROUGH_TEXT),
link: keysFor(LINK_TEXT), link: keysFor(LINK_TEXT),
}, },
i18n: { i18n: {
...@@ -199,6 +206,16 @@ export default { ...@@ -199,6 +206,16 @@ export default {
:shortcuts="$options.shortcuts.italic" :shortcuts="$options.shortcuts.italic"
icon="italic" icon="italic"
/> />
<toolbar-button
tag="~~"
:button-title="
sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
modifierKey,
})
"
:shortcuts="$options.shortcuts.strikethrough"
icon="strikethrough"
/>
<toolbar-button <toolbar-button
:prepend="true" :prepend="true"
:tag="tag" :tag="tag"
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' }, data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' },
title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) }) title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) })
= markdown_toolbar_button({ icon: "strikethrough",
data: { "md-tag" => "~~", "md-shortcuts": '["mod+shift+x"]' },
title: sprintf(s_("MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)") % { modifier_key: modifier_key }) })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") }) = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") }) = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
......
...@@ -51,6 +51,7 @@ descriptions): ...@@ -51,6 +51,7 @@ descriptions):
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | Toggle Markdown preview when editing text in a text field that has **Write** and **Preview** tabs at the top. | | <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | Toggle Markdown preview when editing text in a text field that has **Write** and **Preview** tabs at the top. |
| <kbd>Command</kbd> + <kbd>b</kbd> | <kbd>Control</kbd> + <kbd>b</kbd> | Bold the selected text (surround it with `**`). | | <kbd>Command</kbd> + <kbd>b</kbd> | <kbd>Control</kbd> + <kbd>b</kbd> | Bold the selected text (surround it with `**`). |
| <kbd>Command</kbd> + <kbd>i</kbd> | <kbd>Control</kbd> + <kbd>i</kbd> | Italicize the selected text (surround it with `_`). | | <kbd>Command</kbd> + <kbd>i</kbd> | <kbd>Control</kbd> + <kbd>i</kbd> | Italicize the selected text (surround it with `_`). |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>s</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>s</kbd> | Strike through the selected text (surround it with `~~`). |
| <kbd>Command</kbd> + <kbd>k</kbd> | <kbd>Control</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). | | <kbd>Command</kbd> + <kbd>k</kbd> | <kbd>Control</kbd> + <kbd>k</kbd> | Add a link (surround the selected text with `[]()`). |
The shortcuts for editing in text fields are always enabled, even if other The shortcuts for editing in text fields are always enabled, even if other
......
...@@ -22383,6 +22383,12 @@ msgstr "" ...@@ -22383,6 +22383,12 @@ msgstr ""
msgid "MarkdownEditor|Add italic text (%{modifier_key}I)" msgid "MarkdownEditor|Add italic text (%{modifier_key}I)"
msgstr "" msgstr ""
msgid "MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)"
msgstr ""
msgid "MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)"
msgstr ""
msgid "Marked For Deletion At - %{deletion_time}" msgid "Marked For Deletion At - %{deletion_time}"
msgstr "" msgstr ""
...@@ -35087,6 +35093,9 @@ msgstr "" ...@@ -35087,6 +35093,9 @@ msgstr ""
msgid "Strikethrough" msgid "Strikethrough"
msgstr "" msgstr ""
msgid "Strikethrough text"
msgstr ""
msgid "Subgroup information" msgid "Subgroup information"
msgstr "" msgstr ""
......
...@@ -111,6 +111,20 @@ RSpec.describe 'GFM autocomplete', :js do ...@@ -111,6 +111,20 @@ RSpec.describe 'GFM autocomplete', :js do
fill_in 'Comment', with: "test\n\n@" fill_in 'Comment', with: "test\n\n@"
expect(find_autocomplete_menu).to be_visible expect(find_autocomplete_menu).to be_visible
end end
it 'does not open label autocomplete menu after strikethrough', :aggregate_failures do
fill_in 'Comment', with: "~~"
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: "~~gone~~"
expect(page).not_to have_css('.atwho-view')
fill_in 'Comment', with: "~"
expect(find_autocomplete_menu).to be_visible
fill_in 'Comment', with: "test\n\n~"
expect(find_autocomplete_menu).to be_visible
end
end end
context 'xss checks' do context 'xss checks' do
......
...@@ -37,6 +37,14 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do ...@@ -37,6 +37,14 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do
expect(markdown_field.value).to eq('_italic_') expect(markdown_field.value).to eq('_italic_')
end end
it 'strikes text when <modifier>+<shift>+x is pressed' do
type_and_select('strikethrough')
markdown_field.send_keys([modifier_key, :shift, 'x'])
expect(markdown_field.value).to eq('~~strikethrough~~')
end
it 'links text when <modifier>+K is pressed' do it 'links text when <modifier>+K is pressed' do
type_and_select('link') type_and_select('link')
......
...@@ -76,7 +76,7 @@ describe('Markdown field component', () => { ...@@ -76,7 +76,7 @@ describe('Markdown field component', () => {
const getPreviewLink = () => subject.findByTestId('preview-tab'); const getPreviewLink = () => subject.findByTestId('preview-tab');
const getWriteLink = () => subject.findByTestId('write-tab'); const getWriteLink = () => subject.findByTestId('write-tab');
const getMarkdownButton = () => subject.find('.js-md'); const getMarkdownButton = () => subject.find('.js-md');
const getAllMarkdownButtons = () => subject.findAll('.js-md'); const getListBulletedButton = () => subject.findAll('.js-md[title="Add a bullet list"]');
const getVideo = () => subject.find('video'); const getVideo = () => subject.find('video');
const getAttachButton = () => subject.find('.button-attach-file'); const getAttachButton = () => subject.find('.button-attach-file');
const clickAttachButton = () => getAttachButton().trigger('click'); const clickAttachButton = () => getAttachButton().trigger('click');
...@@ -185,7 +185,7 @@ describe('Markdown field component', () => { ...@@ -185,7 +185,7 @@ describe('Markdown field component', () => {
it('converts a line', async () => { it('converts a line', async () => {
const textarea = subject.find('textarea').element; const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 0); textarea.setSelectionRange(0, 0);
const markdownButton = getAllMarkdownButtons().wrappers[5]; const markdownButton = getListBulletedButton();
markdownButton.trigger('click'); markdownButton.trigger('click');
await nextTick(); await nextTick();
...@@ -195,7 +195,7 @@ describe('Markdown field component', () => { ...@@ -195,7 +195,7 @@ describe('Markdown field component', () => {
it('converts multiple lines', async () => { it('converts multiple lines', async () => {
const textarea = subject.find('textarea').element; const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 50); textarea.setSelectionRange(0, 50);
const markdownButton = getAllMarkdownButtons().wrappers[5]; const markdownButton = getListBulletedButton();
markdownButton.trigger('click'); markdownButton.trigger('click');
await nextTick(); await nextTick();
......
...@@ -46,6 +46,7 @@ describe('Markdown field header component', () => { ...@@ -46,6 +46,7 @@ describe('Markdown field header component', () => {
const buttons = [ const buttons = [
'Add bold text (⌘B)', 'Add bold text (⌘B)',
'Add italic text (⌘I)', 'Add italic text (⌘I)',
'Add strikethrough text (⌘⇧X)',
'Insert a quote', 'Insert a quote',
'Insert suggestion', 'Insert suggestion',
'Insert code', 'Insert code',
......
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