diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-05-24 10:02:17 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2017-05-24 10:02:17 +0000 |
commit | b39b02d4a422ca240916a28bb4600a3a741b6194 (patch) | |
tree | 929cad643e3bc8dc8585578237bded93a83b2972 | |
parent | 4c78eff2e32ce09fb9d0bc87e89a321b795543c2 (diff) | |
parent | 6127192783171f6fc2b64c674dd21d0544b62707 (diff) | |
download | gitlab-ce-b39b02d4a422ca240916a28bb4600a3a741b6194.tar.gz |
Merge branch 'dm-paste-code-inside-gfm-code' into 'master'
Don't wrap pasted code when it's already inside code tags
Closes #32507
See merge request !11524
-rw-r--r-- | app/assets/javascripts/copy_as_gfm.js | 31 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 6 | ||||
-rw-r--r-- | changelogs/unreleased/dm-paste-code-inside-gfm-code.yml | 4 | ||||
-rw-r--r-- | spec/javascripts/copy_as_gfm_spec.js | 49 |
4 files changed, 81 insertions, 9 deletions
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js index 459cdd53f9b..cba4b656363 100644 --- a/app/assets/javascripts/copy_as_gfm.js +++ b/app/assets/javascripts/copy_as_gfm.js @@ -273,12 +273,12 @@ const gfmRules = { class CopyAsGFM { constructor() { - $(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); - $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); - $(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this)); + $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); + $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); + $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM); } - copyAsGFM(e, transformer) { + static copyAsGFM(e, transformer) { const clipboardData = e.originalEvent.clipboardData; if (!clipboardData) return; @@ -292,19 +292,36 @@ class CopyAsGFM { e.stopPropagation(); clipboardData.setData('text/plain', el.textContent); - clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el)); + clipboardData.setData('text/x-gfm', this.nodeToGFM(el)); } - pasteGFM(e) { + static pasteGFM(e) { const clipboardData = e.originalEvent.clipboardData; if (!clipboardData) return; + const text = clipboardData.getData('text/plain'); const gfm = clipboardData.getData('text/x-gfm'); if (!gfm) return; e.preventDefault(); - window.gl.utils.insertText(e.target, gfm); + window.gl.utils.insertText(e.target, (textBefore, textAfter) => { + // If the text before the cursor contains an odd number of backticks, + // we are either inside an inline code span that starts with 1 backtick + // or a code block that starts with 3 backticks. + // This logic still holds when there are one or more _closed_ code spans + // or blocks that will have 2 or 6 backticks. + // This will break down when the actual code block contains an uneven + // number of backticks, but this is a rare edge case. + const backtickMatch = textBefore.match(/`/g); + const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1; + + if (insideCodeBlock) { + return text; + } + + return gfm; + }); } static transformGFMSelection(documentFragment) { diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 7e62773ae6c..a537267643e 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -198,10 +198,12 @@ const textBefore = value.substring(0, selectionStart); const textAfter = value.substring(selectionEnd, value.length); - const newText = textBefore + text + textAfter; + + const insertedText = text instanceof Function ? text(textBefore, textAfter) : text; + const newText = textBefore + insertedText + textAfter; target.value = newText; - target.selectionStart = target.selectionEnd = selectionStart + text.length; + target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; // Trigger autosave $(target).trigger('input'); diff --git a/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml b/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml new file mode 100644 index 00000000000..d078ca449a5 --- /dev/null +++ b/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml @@ -0,0 +1,4 @@ +--- +title: Don't wrap pasted code when it's already inside code tags +merge_request: +author: diff --git a/spec/javascripts/copy_as_gfm_spec.js b/spec/javascripts/copy_as_gfm_spec.js new file mode 100644 index 00000000000..1a850bb56ab --- /dev/null +++ b/spec/javascripts/copy_as_gfm_spec.js @@ -0,0 +1,49 @@ +require('~/copy_as_gfm'); + +(() => { + describe('gl.CopyAsGFM', () => { + describe('gl.CopyAsGFM.pasteGFM', () => { + function callPasteGFM() { + const e = { + originalEvent: { + clipboardData: { + getData(mimeType) { + // When GFM code is copied, we put the regular plain text + // on the clipboard as `text/plain`, and the GFM as `text/x-gfm`. + // This emulates the behavior of `getData` with that data. + if (mimeType === 'text/plain') { + return 'code'; + } + if (mimeType === 'text/x-gfm') { + return '`code`'; + } + return null; + }, + }, + }, + preventDefault() {}, + }; + + window.gl.CopyAsGFM.pasteGFM(e); + } + + it('wraps pasted code when not already in code tags', () => { + spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => { + const insertedText = textFunc('This is code: ', ''); + expect(insertedText).toEqual('`code`'); + }); + + callPasteGFM(); + }); + + it('does not wrap pasted code when already in code tags', () => { + spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => { + const insertedText = textFunc('This is code: `', '`'); + expect(insertedText).toEqual('code'); + }); + + callPasteGFM(); + }); + }); + }); +})(); |