Resolve "Add "Link" shortcut/icon in markdown editor to make it easier to add references"
This commit is contained in:
parent
57dc233325
commit
ceafbbd317
9 changed files with 145 additions and 21 deletions
|
@ -31,11 +31,17 @@ function blockTagText(text, textArea, blockTag, selected) {
|
|||
}
|
||||
}
|
||||
|
||||
function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
|
||||
function moveCursor({ textArea, tag, wrapped, removedLastNewLine, select }) {
|
||||
var pos;
|
||||
if (!textArea.setSelectionRange) {
|
||||
return;
|
||||
}
|
||||
if (select && select.length > 0) {
|
||||
// calculate the part of the text to be selected
|
||||
const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
|
||||
const endPosition = startPosition + select.length;
|
||||
return textArea.setSelectionRange(startPosition, endPosition);
|
||||
}
|
||||
if (textArea.selectionStart === textArea.selectionEnd) {
|
||||
if (wrapped) {
|
||||
pos = textArea.selectionStart - tag.length;
|
||||
|
@ -51,7 +57,7 @@ function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
|
|||
}
|
||||
}
|
||||
|
||||
export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) {
|
||||
export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) {
|
||||
var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
|
||||
removedLastNewLine = false;
|
||||
removedFirstNewLine = false;
|
||||
|
@ -82,11 +88,16 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
|
|||
|
||||
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
|
||||
|
||||
const textPlaceholder = '{text}';
|
||||
|
||||
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
|
||||
if (blockTag != null && blockTag !== '') {
|
||||
textToInsert = blockTagText(text, textArea, blockTag, selected);
|
||||
} else {
|
||||
textToInsert = selectedSplit.map(function(val) {
|
||||
if (tag.indexOf(textPlaceholder) > -1) {
|
||||
return tag.replace(textPlaceholder, val);
|
||||
}
|
||||
if (val.indexOf(tag) === 0) {
|
||||
return "" + (val.replace(tag, ''));
|
||||
} else {
|
||||
|
@ -94,6 +105,8 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
|
|||
}
|
||||
}).join('\n');
|
||||
}
|
||||
} else if (tag.indexOf(textPlaceholder) > -1) {
|
||||
textToInsert = tag.replace(textPlaceholder, selected);
|
||||
} else {
|
||||
textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
|
||||
}
|
||||
|
@ -107,17 +120,17 @@ export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap
|
|||
}
|
||||
|
||||
insertText(textArea, textToInsert);
|
||||
return moveCursor(textArea, tag, wrap, removedLastNewLine);
|
||||
return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), wrap, removedLastNewLine, select });
|
||||
}
|
||||
|
||||
function updateText(textArea, tag, blockTag, wrap) {
|
||||
function updateText({ textArea, tag, blockTag, wrap, select }) {
|
||||
var $textArea, selected, text;
|
||||
$textArea = $(textArea);
|
||||
textArea = $textArea.get(0);
|
||||
text = $textArea.val();
|
||||
selected = selectedText(text, textArea);
|
||||
$textArea.focus();
|
||||
return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap);
|
||||
return insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select });
|
||||
}
|
||||
|
||||
function replaceRange(s, start, end, substitute) {
|
||||
|
@ -127,7 +140,12 @@ function replaceRange(s, start, end, substitute) {
|
|||
export function addMarkdownListeners(form) {
|
||||
return $('.js-md', form).off('click').on('click', function() {
|
||||
const $this = $(this);
|
||||
return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
|
||||
return updateText({
|
||||
textArea: $this.closest('.md-area').find('textarea'),
|
||||
tag: $this.data('mdTag'),
|
||||
blockTag: $this.data('mdBlock'),
|
||||
wrap: !$this.data('mdPrepend'),
|
||||
select: $this.data('mdSelect') });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,12 @@
|
|||
button-title="Insert code"
|
||||
icon="code"
|
||||
/>
|
||||
<toolbar-button
|
||||
tag="[{text}](url)"
|
||||
tag-select="url"
|
||||
button-title="Add a link"
|
||||
icon="link"
|
||||
/>
|
||||
<toolbar-button
|
||||
:prepend="true"
|
||||
tag="* "
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
tagSelect: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
prepend: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -40,6 +45,7 @@
|
|||
<button
|
||||
v-tooltip
|
||||
:data-md-tag="tag"
|
||||
:data-md-select="tagSelect"
|
||||
:data-md-block="tagBlock"
|
||||
:data-md-prepend="prepend"
|
||||
:title="buttonTitle"
|
||||
|
|
|
@ -18,14 +18,15 @@
|
|||
Preview
|
||||
|
||||
%li.md-header-toolbar.active
|
||||
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: "Add bold text" })
|
||||
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: "Add italic text" })
|
||||
= 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: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
|
||||
= markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
|
||||
= markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
|
||||
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: "Go full screen", data: { container: "body" } }
|
||||
= markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
|
||||
= markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
|
||||
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
|
||||
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
|
||||
= markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
|
||||
= markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
|
||||
= markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
|
||||
= markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
|
||||
%button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
|
||||
= sprite_icon("screen-full")
|
||||
|
||||
.md-write-holder
|
||||
|
|
5
changelogs/unreleased/44627-add-link-md-editor.yml
Normal file
5
changelogs/unreleased/44627-add-link-md-editor.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add link button to markdown editor toolbar
|
||||
merge_request: 18579
|
||||
author: Jan Beckmann
|
||||
type: added
|
|
@ -3651,6 +3651,33 @@ msgstr ""
|
|||
msgid "Markdown enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Add a bullet list"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Add a link"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Add a numbered list"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Add a task list"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Add bold text"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Add italic text"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Go full screen"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Insert a quote"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownToolbar|Insert code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max access level"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('init markdown', () => {
|
|||
textArea.selectionStart = 0;
|
||||
textArea.selectionEnd = 0;
|
||||
|
||||
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
|
||||
|
||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||
});
|
||||
|
@ -32,7 +32,7 @@ describe('init markdown', () => {
|
|||
textArea.value = initialValue;
|
||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||
|
||||
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
|
||||
|
||||
expect(textArea.value).toEqual(`${initialValue}\n* `);
|
||||
});
|
||||
|
@ -43,7 +43,7 @@ describe('init markdown', () => {
|
|||
textArea.value = initialValue;
|
||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||
|
||||
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
|
||||
|
||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||
});
|
||||
|
@ -54,9 +54,70 @@ describe('init markdown', () => {
|
|||
textArea.value = initialValue;
|
||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||
|
||||
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected: '', wrap: false });
|
||||
|
||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with selection', () => {
|
||||
const text = 'initial selected value';
|
||||
const selected = 'selected';
|
||||
beforeEach(() => {
|
||||
textArea.value = text;
|
||||
const selectedIndex = text.indexOf(selected);
|
||||
textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
|
||||
});
|
||||
|
||||
it('applies the tag to the selected value', () => {
|
||||
insertMarkdownText({ textArea, text: textArea.value, tag: '*', blockTag: null, selected, wrap: true });
|
||||
|
||||
expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
|
||||
});
|
||||
|
||||
it('replaces the placeholder in the tag', () => {
|
||||
insertMarkdownText({ textArea, text: textArea.value, tag: '[{text}](url)', blockTag: null, selected, wrap: false });
|
||||
|
||||
expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
|
||||
});
|
||||
|
||||
describe('and text to be selected', () => {
|
||||
const tag = '[{text}](url)';
|
||||
const select = 'url';
|
||||
|
||||
it('selects the text', () => {
|
||||
insertMarkdownText({ textArea,
|
||||
text: textArea.value,
|
||||
tag,
|
||||
blockTag: null,
|
||||
selected,
|
||||
wrap: false,
|
||||
select });
|
||||
|
||||
const expectedText = text.replace(selected, `[${selected}](url)`);
|
||||
expect(textArea.value).toEqual(expectedText);
|
||||
expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
|
||||
expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
|
||||
});
|
||||
|
||||
it('selects the right text when multiple tags are present', () => {
|
||||
const initialValue = `${tag} ${tag} ${selected}`;
|
||||
textArea.value = initialValue;
|
||||
const selectedIndex = initialValue.indexOf(selected);
|
||||
textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
|
||||
insertMarkdownText({ textArea,
|
||||
text: textArea.value,
|
||||
tag,
|
||||
blockTag: null,
|
||||
selected,
|
||||
wrap: false,
|
||||
select });
|
||||
|
||||
const expectedText = initialValue.replace(selected, `[${selected}](url)`);
|
||||
expect(textArea.value).toEqual(expectedText);
|
||||
expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select));
|
||||
expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -153,7 +153,7 @@ describe('Markdown field component', () => {
|
|||
const textarea = vm.$el.querySelector('textarea');
|
||||
|
||||
textarea.setSelectionRange(0, 0);
|
||||
vm.$el.querySelectorAll('.js-md')[4].click();
|
||||
vm.$el.querySelectorAll('.js-md')[5].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
|
@ -168,7 +168,7 @@ describe('Markdown field component', () => {
|
|||
const textarea = vm.$el.querySelector('textarea');
|
||||
|
||||
textarea.setSelectionRange(0, 50);
|
||||
vm.$el.querySelectorAll('.js-md')[4].click();
|
||||
vm.$el.querySelectorAll('.js-md')[5].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('Markdown field header component', () => {
|
|||
});
|
||||
|
||||
it('renders markdown buttons', () => {
|
||||
expect(vm.$el.querySelectorAll('.js-md').length).toBe(7);
|
||||
expect(vm.$el.querySelectorAll('.js-md').length).toBe(8);
|
||||
});
|
||||
|
||||
it('renders `write` link as active when previewMarkdown is false', () => {
|
||||
|
|
Loading…
Reference in a new issue