summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorSam Bigelow <sbigelow@gitlab.com>2018-12-14 12:56:25 -0500
committerSam Bigelow <sbigelow@gitlab.com>2019-01-02 08:24:32 -0800
commit066a99b6e9d0849b477858c9aac274c2f81bb367 (patch)
treeb98cd6b7ea66d64c4e7aa1dbb86055b48bed59cc /app/assets
parent28cffb9f41836a84d3323e640fe31f92f37bccd9 (diff)
downloadgitlab-ce-066a99b6e9d0849b477858c9aac274c2f81bb367.tar.gz
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)
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js9
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js20
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js162
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/pages/editor.scss4
5 files changed, 159 insertions, 38 deletions
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index b07f951346e..5f64175362d 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -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');
@@ -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
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 6e19548eed2..011898a5e7a 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -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;
@@ -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,
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');
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 5609a2086e6..eb191cf4d39 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -173,7 +173,7 @@
svg {
width: 14px;
height: 14px;
- margin-top: 3px;
+ vertical-align: middle;
fill: $gl-text-color-secondary;
}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index f46ff360496..5a988b184b6 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -128,6 +128,10 @@
width: 100%;
}
}
+
+ @media(max-width: map-get($grid-breakpoints, md)-1) {
+ clear: both;
+ }
}
.editor-ref {