Skip to content
Snippets Groups Projects
Commit 066a99b6 authored by Sam Bigelow's avatar Sam Bigelow
Browse files

Add markdown buttons to file editor

Currently, we have markdown files in many places (e.g. comments, new
issues, etc.). This Merge Request detects if the file being edited is a
markdown file and adds markdown buttons and their functionality to the
single file editor (Not the web IDE)
parent 28cffb9f
No related branches found
No related tags found
No related merge requests found
Showing
with 421 additions and 213 deletions
Loading
Loading
@@ -16,6 +16,7 @@ export default () => {
const filePath = editBlobForm.data('blobFilename');
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
const isMarkdown = editBlobForm.data('is-markdown');
const commitButton = $('.js-commit-button');
const cancelLink = $('.btn.btn-cancel');
 
Loading
Loading
@@ -27,7 +28,13 @@ export default () => {
window.onbeforeunload = null;
});
 
new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
new EditBlob({
assetsPath: `${urlRoot}${assetsPath}`,
filePath,
currentAction,
projectId,
isMarkdown,
});
new NewCommitForm(editBlobForm);
 
// returning here blocks page navigation
Loading
Loading
Loading
Loading
@@ -6,22 +6,31 @@ import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
 
export default class EditBlob {
constructor(assetsPath, aceMode, currentAction, projectId) {
this.configureAceEditor(aceMode, assetsPath);
// The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) {
this.options = options;
this.configureAceEditor();
this.initModePanesAndLinks();
this.initSoftWrap();
this.initFileSelectors(currentAction, projectId);
this.initFileSelectors();
}
 
configureAceEditor(filePath, assetsPath) {
configureAceEditor() {
const { filePath, assetsPath, isMarkdown } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
 
this.editor = ace.edit('editor');
 
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
 
Loading
Loading
@@ -32,7 +41,8 @@ export default class EditBlob {
}
}
 
initFileSelectors(currentAction, projectId) {
initFileSelectors() {
const { currentAction, projectId } = this.options;
this.fileTemplateMediator = new TemplateSelectorMediator({
currentAction,
editor: this.editor,
Loading
Loading
Loading
Loading
@@ -8,6 +8,10 @@ function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
 
function addBlockTags(blockTag, selected) {
return `${blockTag}\n${selected}\n${blockTag}`;
}
function lineBefore(text, textarea) {
var split;
split = text
Loading
Loading
@@ -24,19 +28,45 @@ function lineAfter(text, textarea) {
.split('\n')[0];
}
 
function editorBlockTagText(text, blockTag, selected, editor) {
const lines = text.split('\n');
const selectionRange = editor.getSelectionRange();
const shouldRemoveBlock =
lines[selectionRange.start.row - 1] === blockTag &&
lines[selectionRange.end.row + 1] === blockTag;
if (shouldRemoveBlock) {
if (blockTag !== null) {
// ace is globally defined
// eslint-disable-next-line no-undef
const { Range } = ace.require('ace/range');
const lastLine = lines[selectionRange.end.row + 1];
const rangeWithBlockTags = new Range(
lines[selectionRange.start.row - 1],
0,
selectionRange.end.row + 1,
lastLine.length,
);
editor.getSelection().setSelectionRange(rangeWithBlockTags);
}
return selected;
}
return addBlockTags(blockTag, selected);
}
function blockTagText(text, textArea, blockTag, selected) {
const before = lineBefore(text, textArea);
const after = lineAfter(text, textArea);
if (before === blockTag && after === blockTag) {
const shouldRemoveBlock =
lineBefore(text, textArea) === blockTag && lineAfter(text, textArea) === blockTag;
if (shouldRemoveBlock) {
// To remove the block tag we have to select the line before & after
if (blockTag != null) {
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
}
return selected;
} else {
return blockTag + '\n' + selected + '\n' + blockTag;
}
return addBlockTags(blockTag, selected);
}
 
function moveCursor({
Loading
Loading
@@ -46,33 +76,48 @@ function moveCursor({
positionBetweenTags,
removedLastNewLine,
select,
editor,
editorSelectionStart,
editorSelectionEnd,
}) {
var pos;
if (!textArea.setSelectionRange) {
if (textArea && !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 (positionBetweenTags) {
pos = textArea.selectionStart - tag.length;
} else {
pos = textArea.selectionStart;
if (textArea) {
// 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);
} else if (editor) {
editor.navigateLeft(tag.length - tag.indexOf(select));
editor.getSelection().selectAWord();
return;
}
}
if (textArea) {
if (textArea.selectionStart === textArea.selectionEnd) {
if (positionBetweenTags) {
pos = textArea.selectionStart - tag.length;
} else {
pos = textArea.selectionStart;
}
 
if (removedLastNewLine) {
pos -= 1;
}
if (removedLastNewLine) {
pos -= 1;
}
 
if (cursorOffset) {
pos -= cursorOffset;
}
if (cursorOffset) {
pos -= cursorOffset;
}
 
return textArea.setSelectionRange(pos, pos);
return textArea.setSelectionRange(pos, pos);
}
} else if (editor && editorSelectionStart.row === editorSelectionEnd.row) {
if (positionBetweenTags) {
editor.navigateLeft(tag.length);
}
}
}
 
Loading
Loading
@@ -85,6 +130,7 @@ export function insertMarkdownText({
selected = '',
wrap,
select,
editor,
}) {
var textToInsert,
selectedSplit,
Loading
Loading
@@ -92,11 +138,20 @@ export function insertMarkdownText({
removedLastNewLine,
removedFirstNewLine,
currentLineEmpty,
lastNewLine;
lastNewLine,
editorSelectionStart,
editorSelectionEnd;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
 
if (editor) {
const selectionRange = editor.getSelectionRange();
editorSelectionStart = selectionRange.start;
editorSelectionEnd = selectionRange.end;
}
// check for link pattern and selected text is an URL
// if so fill in the url part instead of the text part of the pattern.
if (tag === LINK_TAG_PATTERN) {
Loading
Loading
@@ -119,14 +174,27 @@ export function insertMarkdownText({
}
 
// Remove the last newline
if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
removedLastNewLine = true;
selected = selected.replace(/\n$/, '');
if (textArea) {
if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
removedLastNewLine = true;
selected = selected.replace(/\n$/, '');
}
} else if (editor) {
if (editorSelectionStart.row !== editorSelectionEnd.row) {
removedLastNewLine = true;
selected = selected.replace(/\n$/, '');
}
}
 
selectedSplit = selected.split('\n');
 
if (!wrap) {
if (editor && !wrap) {
lastNewLine = editor.getValue().split('\n')[editorSelectionStart.row];
if (/^\s*$/.test(lastNewLine)) {
currentLineEmpty = true;
}
} else if (textArea && !wrap) {
lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
 
// Check whether the current line is empty or consists only of spaces(=handle as empty)
Loading
Loading
@@ -135,13 +203,19 @@ export function insertMarkdownText({
}
}
 
startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
const isBeginning =
(textArea && textArea.selectionStart === 0) ||
(editor && editorSelectionStart.column === 0 && editorSelectionStart.row === 0);
startChar = !wrap && !currentLineEmpty && !isBeginning ? '\n' : '';
 
const textPlaceholder = '{text}';
 
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') {
textToInsert = blockTagText(text, textArea, blockTag, selected);
textToInsert = editor
? editorBlockTagText(text, blockTag, selected, editor)
: blockTagText(text, textArea, blockTag, selected);
} else {
textToInsert = selectedSplit
.map(function(val) {
Loading
Loading
@@ -170,7 +244,11 @@ export function insertMarkdownText({
textToInsert += '\n';
}
 
insertText(textArea, textToInsert);
if (editor) {
editor.insert(textToInsert);
} else {
insertText(textArea, textToInsert);
}
return moveCursor({
textArea,
tag: tag.replace(textPlaceholder, selected),
Loading
Loading
@@ -178,6 +256,9 @@ export function insertMarkdownText({
positionBetweenTags: wrap && selected.length === 0,
removedLastNewLine,
select,
editor,
editorSelectionStart,
editorSelectionEnd,
});
}
 
Loading
Loading
@@ -217,6 +298,25 @@ export function addMarkdownListeners(form) {
});
}
 
export function addEditorMarkdownListeners(editor) {
$('.js-md')
.off('click')
.on('click', function(e) {
const { mdTag, mdBlock, mdPrepend, mdSelect } = $(e.currentTarget).data();
insertMarkdownText({
tag: mdTag,
blockTag: mdBlock,
wrap: !mdPrepend,
select: mdSelect,
selected: editor.getSelectedText(),
text: editor.getValue(),
editor,
});
editor.focus();
});
}
export function removeMarkdownListeners(form) {
return $('.js-md', form).off('click');
}
Loading
Loading
@@ -173,7 +173,7 @@
svg {
width: 14px;
height: 14px;
margin-top: 3px;
vertical-align: middle;
fill: $gl-text-color-secondary;
}
 
Loading
Loading
Loading
Loading
@@ -128,6 +128,10 @@
width: 100%;
}
}
@media(max-width: map-get($grid-breakpoints, md)-1) {
clear: both;
}
}
 
.editor-ref {
Loading
Loading
Loading
Loading
@@ -177,7 +177,8 @@ module BlobHelper
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
'blob-filename' => @blob && @blob.path,
'project-id' => project.id
'project-id' => project.id,
'is-markdown' => @blob && @blob.path && Gitlab::MarkupHelper.gitlab_markdown?(@blob.path)
}
end
 
Loading
Loading
Loading
Loading
@@ -18,17 +18,7 @@
Preview
 
%li.md-header-toolbar.active
= 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") })
= markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
%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")
= render 'projects/blob/markdown_buttons', show_fullscreen_button: true
 
.md-write-holder
= yield
Loading
Loading
- action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create'
- file_name = params[:id].split("/").last ||= ""
- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
 
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
Loading
Loading
@@ -17,6 +19,8 @@
required: true, class: 'form-control new-file-name js-file-path-name-input'
 
.file-buttons
- if is_markdown
= render 'projects/blob/markdown_buttons', show_fullscreen_button: false
= button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
Loading
Loading
.md-header-toolbar.active
= 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") })
= markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
- if show_fullscreen_button
%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")
---
title: Add markdown helper buttons to file editor
merge_request: 23480
author:
type: added
import blobBundle from '~/blob_edit/blob_bundle';
import $ from 'jquery';
 
window.ace = {
config: {
set: () => {},
loadModule: () => {},
},
edit: () => ({ focus: () => {} }),
};
describe('EditBlob', () => {
describe('BlobBundle', () => {
beforeEach(() => {
spyOnDependency(blobBundle, 'EditBlob').and.stub();
setFixtures(`
<div class="js-edit-blob-form">
<div class="js-edit-blob-form" data-blob-filename="blah">
<button class="js-commit-button"></button>
<a class="btn btn-cancel" href="#"></a>
</div>`);
Loading
Loading
Loading
Loading
@@ -13,215 +13,296 @@ describe('init markdown', () => {
textArea.parentNode.removeChild(textArea);
});
 
describe('without selection', () => {
it('inserts the tag on an empty line', () => {
const initialValue = '';
describe('textArea', () => {
describe('without selection', () => {
it('inserts the tag on an empty line', () => {
const initialValue = '';
 
textArea.value = initialValue;
textArea.selectionStart = 0;
textArea.selectionEnd = 0;
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
});
expect(textArea.value).toEqual(`${initialValue}* `);
});
it('inserts the tag on a new line if the current one is not empty', () => {
const initialValue = 'some text';
textArea.value = initialValue;
textArea.selectionStart = 0;
textArea.selectionEnd = 0;
 
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
});
 
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
expect(textArea.value).toEqual(`${initialValue}* `);
});
 
expect(textArea.value).toEqual(`${initialValue}\n* `);
});
it('inserts the tag on a new line if the current one is not empty', () => {
const initialValue = 'some text';
 
it('inserts the tag on the same line if the current line only contains spaces', () => {
const initialValue = ' ';
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
 
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
});
 
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
expect(textArea.value).toEqual(`${initialValue}\n* `);
});
 
expect(textArea.value).toEqual(`${initialValue}* `);
});
it('inserts the tag on the same line if the current line only contains spaces', () => {
const initialValue = ' ';
 
it('inserts the tag on the same line if the current line only contains tabs', () => {
const initialValue = '\t\t\t';
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
 
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
});
 
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
expect(textArea.value).toEqual(`${initialValue}* `);
});
 
expect(textArea.value).toEqual(`${initialValue}* `);
});
it('inserts the tag on the same line if the current line only contains tabs', () => {
const initialValue = '\t\t\t';
 
it('places the cursor inside the tags', () => {
const start = 'lorem ';
const end = ' ipsum';
const tag = '*';
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
 
textArea.value = `${start}${end}`;
textArea.setSelectionRange(start.length, start.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
});
 
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: '',
wrap: true,
expect(textArea.value).toEqual(`${initialValue}* `);
});
 
expect(textArea.value).toEqual(`${start}**${end}`);
it('places the cursor inside the tags', () => {
const start = 'lorem ';
const end = ' ipsum';
const tag = '*';
 
// cursor placement should be between tags
expect(textArea.selectionStart).toBe(start.length + tag.length);
});
});
textArea.value = `${start}${end}`;
textArea.setSelectionRange(start.length, start.length);
 
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);
});
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: '',
wrap: true,
});
 
it('applies the tag to the selected value', () => {
const selectedIndex = text.indexOf(selected);
const tag = '*';
expect(textArea.value).toEqual(`${start}**${end}`);
 
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected,
wrap: true,
// cursor placement should be between tags
expect(textArea.selectionStart).toBe(start.length + tag.length);
});
expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
// cursor placement should be after selection + 2 tag lengths
expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length);
});
 
it('replaces the placeholder in the tag', () => {
insertMarkdownText({
textArea,
text: textArea.value,
tag: '[{text}](url)',
blockTag: null,
selected,
wrap: false,
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);
});
 
expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
});
it('applies the tag to the selected value', () => {
const selectedIndex = text.indexOf(selected);
const tag = '*';
 
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,
wrap: true,
});
 
const expectedText = text.replace(selected, `[${selected}](url)`);
expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
 
expect(textArea.value).toEqual(expectedText);
expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
// cursor placement should be after selection + 2 tag lengths
expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.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);
it('replaces the placeholder in the tag', () => {
insertMarkdownText({
textArea,
text: textArea.value,
tag,
tag: '[{text}](url)',
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);
expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
});
 
it('should support selected urls', () => {
const expectedUrl = 'http://www.gitlab.com';
const expectedSelectionText = 'text';
const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`;
const initialValue = `text ${expectedUrl} text`;
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);
});
 
textArea.value = initialValue;
const selectedIndex = initialValue.indexOf(expectedUrl);
textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.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);
});
 
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: expectedUrl,
wrap: false,
select,
it('should support selected urls', () => {
const expectedUrl = 'http://www.gitlab.com';
const expectedSelectionText = 'text';
const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`;
const initialValue = `text ${expectedUrl} text`;
textArea.value = initialValue;
const selectedIndex = initialValue.indexOf(expectedUrl);
textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length);
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected: expectedUrl,
wrap: false,
select,
});
expect(textArea.value).toEqual(expectedText);
expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1));
expect(textArea.selectionEnd).toEqual(
expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length,
);
});
});
});
});
describe('Ace Editor', () => {
let editor;
beforeEach(() => {
editor = {
getSelectionRange: () => ({
start: 0,
end: 0,
}),
getValue: () => 'this is text \n in two lines',
insert: () => {},
navigateLeft: () => {},
};
});
it('uses ace editor insert text when editor is passed in', () => {
spyOn(editor, 'insert');
 
expect(textArea.value).toEqual(expectedText);
expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1));
expect(textArea.selectionEnd).toEqual(
expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length,
);
insertMarkdownText({
text: editor.getValue,
tag: '*',
blockTag: null,
selected: '',
wrap: false,
editor,
});
expect(editor.insert).toHaveBeenCalled();
});
it('adds block tags on line above and below selection', () => {
spyOn(editor, 'insert');
const selected = 'this text \n is multiple \n lines';
const text = `before \n ${selected} \n after`;
insertMarkdownText({
text,
tag: '',
blockTag: '***',
selected,
wrap: true,
editor,
});
expect(editor.insert).toHaveBeenCalledWith(`***\n${selected}\n***`);
});
it('uses ace editor to navigate back tag length when nothing is selected', () => {
spyOn(editor, 'navigateLeft');
insertMarkdownText({
text: editor.getValue,
tag: '*',
blockTag: null,
selected: '',
wrap: true,
editor,
});
expect(editor.navigateLeft).toHaveBeenCalledWith(1);
});
it('ace editor does not navigate back when there is selected text', () => {
spyOn(editor, 'navigateLeft');
insertMarkdownText({
text: editor.getValue,
tag: '*',
blockTag: null,
selected: 'foobar',
wrap: true,
editor,
});
expect(editor.navigateLeft).not.toHaveBeenCalled();
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment