From 066a99b6e9d0849b477858c9aac274c2f81bb367 Mon Sep 17 00:00:00 2001 From: Sam Bigelow Date: Fri, 14 Dec 2018 12:56:25 -0500 Subject: 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) --- app/assets/javascripts/lib/utils/text_markdown.js | 162 +++++++++++++++++----- 1 file changed, 131 insertions(+), 31 deletions(-) (limited to 'app/assets/javascripts/lib/utils/text_markdown.js') diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 1254ec798a6..84a617acb42 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -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 @@ -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({ @@ -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); + } } } @@ -85,6 +130,7 @@ export function insertMarkdownText({ selected = '', wrap, select, + editor, }) { var textToInsert, selectedSplit, @@ -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) { @@ -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) @@ -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) { @@ -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), @@ -178,6 +256,9 @@ export function insertMarkdownText({ positionBetweenTags: wrap && selected.length === 0, removedLastNewLine, select, + editor, + editorSelectionStart, + editorSelectionEnd, }); } @@ -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'); } -- cgit v1.2.3