Commit c013d23d authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'dm-copy-as-gfm-without-empty-elements' into 'master'

Don't copy empty elements that were not selected on purpose as GFM

See merge request !11520
parents 8763022d 6ec1f1a7
...@@ -18,12 +18,12 @@ const gfmRules = { ...@@ -18,12 +18,12 @@ const gfmRules = {
}, },
}, },
TaskListFilter: { TaskListFilter: {
'input[type=checkbox].task-list-item-checkbox'(el, text) { 'input[type=checkbox].task-list-item-checkbox'(el) {
return `[${el.checked ? 'x' : ' '}]`; return `[${el.checked ? 'x' : ' '}]`;
}, },
}, },
ReferenceFilter: { ReferenceFilter: {
'.tooltip'(el, text) { '.tooltip'(el) {
return ''; return '';
}, },
'a.gfm:not([data-link=true])'(el, text) { 'a.gfm:not([data-link=true])'(el, text) {
...@@ -39,15 +39,15 @@ const gfmRules = { ...@@ -39,15 +39,15 @@ const gfmRules = {
}, },
}, },
TableOfContentsFilter: { TableOfContentsFilter: {
'ul.section-nav'(el, text) { 'ul.section-nav'(el) {
return '[[_TOC_]]'; return '[[_TOC_]]';
}, },
}, },
EmojiFilter: { EmojiFilter: {
'img.emoji'(el, text) { 'img.emoji'(el) {
return el.getAttribute('alt'); return el.getAttribute('alt');
}, },
'gl-emoji'(el, text) { 'gl-emoji'(el) {
return `:${el.getAttribute('data-name')}:`; return `:${el.getAttribute('data-name')}:`;
}, },
}, },
...@@ -57,13 +57,13 @@ const gfmRules = { ...@@ -57,13 +57,13 @@ const gfmRules = {
}, },
}, },
VideoLinkFilter: { VideoLinkFilter: {
'.video-container'(el, text) { '.video-container'(el) {
const videoEl = el.querySelector('video'); const videoEl = el.querySelector('video');
if (!videoEl) return false; if (!videoEl) return false;
return CopyAsGFM.nodeToGFM(videoEl); return CopyAsGFM.nodeToGFM(videoEl);
}, },
'video'(el, text) { 'video'(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`; return `![${el.dataset.title}](${el.getAttribute('src')})`;
}, },
}, },
...@@ -74,19 +74,19 @@ const gfmRules = { ...@@ -74,19 +74,19 @@ const gfmRules = {
'code.code.math[data-math-style=inline]'(el, text) { 'code.code.math[data-math-style=inline]'(el, text) {
return `$\`${text}\`$`; return `$\`${text}\`$`;
}, },
'span.katex-display span.katex-mathml'(el, text) { 'span.katex-display span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false; if (!mathAnnotation) return false;
return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``; return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
}, },
'span.katex-mathml'(el, text) { 'span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false; if (!mathAnnotation) return false;
return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`; return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
}, },
'span.katex-html'(el, text) { 'span.katex-html'(el) {
// We don't want to include the content of this element in the copied text. // We don't want to include the content of this element in the copied text.
return ''; return '';
}, },
...@@ -95,7 +95,7 @@ const gfmRules = { ...@@ -95,7 +95,7 @@ const gfmRules = {
}, },
}, },
SanitizationFilter: { SanitizationFilter: {
'a[name]:not([href]):empty'(el, text) { 'a[name]:not([href]):empty'(el) {
return el.outerHTML; return el.outerHTML;
}, },
'dl'(el, text) { 'dl'(el, text) {
...@@ -143,7 +143,7 @@ const gfmRules = { ...@@ -143,7 +143,7 @@ const gfmRules = {
}, },
}, },
MarkdownFilter: { MarkdownFilter: {
'br'(el, text) { 'br'(el) {
// Two spaces at the end of a line are turned into a BR // Two spaces at the end of a line are turned into a BR
return ' '; return ' ';
}, },
...@@ -162,7 +162,7 @@ const gfmRules = { ...@@ -162,7 +162,7 @@ const gfmRules = {
'blockquote'(el, text) { 'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
}, },
'img'(el, text) { 'img'(el) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`; return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
}, },
'a.anchor'(el, text) { 'a.anchor'(el, text) {
...@@ -222,10 +222,10 @@ const gfmRules = { ...@@ -222,10 +222,10 @@ const gfmRules = {
'sup'(el, text) { 'sup'(el, text) {
return `^${text}`; return `^${text}`;
}, },
'hr'(el, text) { 'hr'(el) {
return '-----'; return '-----';
}, },
'table'(el, text) { 'table'(el) {
const theadEl = el.querySelector('thead'); const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody'); const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false; if (!theadEl || !tbodyEl) return false;
...@@ -233,11 +233,11 @@ const gfmRules = { ...@@ -233,11 +233,11 @@ const gfmRules = {
const theadText = CopyAsGFM.nodeToGFM(theadEl); const theadText = CopyAsGFM.nodeToGFM(theadEl);
const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl); const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
return theadText + tbodyText; return [theadText, tbodyText].join('\n');
}, },
'thead'(el, text) { 'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => { const cells = _.map(el.querySelectorAll('th'), (cell) => {
let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2; let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = ''; let before = '';
let after = ''; let after = '';
...@@ -262,10 +262,15 @@ const gfmRules = { ...@@ -262,10 +262,15 @@ const gfmRules = {
return before + middle + after; return before + middle + after;
}); });
return `${text}|${cells.join('|')}|`; const separatorRow = `|${cells.join('|')}|`;
return [text, separatorRow].join('\n');
}, },
'tr'(el, text) { 'tr'(el) {
const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim()); const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
return `| ${cells.join(' | ')} |`; return `| ${cells.join(' | ')} |`;
}, },
}, },
...@@ -360,7 +365,7 @@ class CopyAsGFM { ...@@ -360,7 +365,7 @@ class CopyAsGFM {
return codeEl; return codeEl;
} }
static nodeToGFM(node) { static nodeToGFM(node, respectWhitespaceParam = false) {
if (node.nodeType === Node.COMMENT_NODE) { if (node.nodeType === Node.COMMENT_NODE) {
return ''; return '';
} }
...@@ -369,7 +374,9 @@ class CopyAsGFM { ...@@ -369,7 +374,9 @@ class CopyAsGFM {
return node.textContent; return node.textContent;
} }
const text = this.innerGFM(node); const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return text; return text;
...@@ -383,7 +390,17 @@ class CopyAsGFM { ...@@ -383,7 +390,17 @@ class CopyAsGFM {
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue; if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
const result = func(node, text); let result;
if (func.length === 2) {
// if `func` takes 2 arguments, it depends on text.
// if there is no text, we don't need to generate GFM for this node.
if (text.length === 0) continue;
result = func(node, text);
} else {
result = func(node);
}
if (result === false) continue; if (result === false) continue;
return result; return result;
...@@ -393,7 +410,7 @@ class CopyAsGFM { ...@@ -393,7 +410,7 @@ class CopyAsGFM {
return text; return text;
} }
static innerGFM(parentNode) { static innerGFM(parentNode, respectWhitespace = false) {
const nodes = parentNode.childNodes; const nodes = parentNode.childNodes;
const clonedParentNode = parentNode.cloneNode(true); const clonedParentNode = parentNode.cloneNode(true);
...@@ -403,13 +420,19 @@ class CopyAsGFM { ...@@ -403,13 +420,19 @@ class CopyAsGFM {
const node = nodes[i]; const node = nodes[i];
const clonedNode = clonedNodes[i]; const clonedNode = clonedNodes[i];
const text = this.nodeToGFM(node); const text = this.nodeToGFM(node, respectWhitespace);
// `clonedNode.replaceWith(text)` is not yet widely supported // `clonedNode.replaceWith(text)` is not yet widely supported
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode); clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
} }
return clonedParentNode.innerText || clonedParentNode.textContent; let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
if (!respectWhitespace) {
nodeText = nodeText.trim();
}
return nodeText;
} }
} }
......
---
title: Don't copy empty elements that were not selected on purpose as GFM
merge_request:
author:
...@@ -51,7 +51,6 @@ describe 'Copy as GFM', feature: true, js: true do ...@@ -51,7 +51,6 @@ describe 'Copy as GFM', feature: true, js: true do
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
- Manage Git repositories with fine grained access controls that keep your code secure - Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests - Perform code reviews and enhance collaboration with merge requests
...@@ -66,6 +65,19 @@ describe 'Copy as GFM', feature: true, js: true do ...@@ -66,6 +65,19 @@ describe 'Copy as GFM', feature: true, js: true do
GFM GFM
) )
aggregate_failures('an accidentally selected empty element') do
gfm = '# Heading1'
html = <<-HTML.strip_heredoc
<h1>Heading1</h1>
<h2></h2>
HTML
output_gfm = html_to_gfm(html)
expect(output_gfm.strip).to eq(gfm.strip)
end
verify( verify(
'InlineDiffFilter', 'InlineDiffFilter',
...@@ -352,7 +364,6 @@ describe 'Copy as GFM', feature: true, js: true do ...@@ -352,7 +364,6 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc, <<-GFM.strip_heredoc,
- Nested - Nested
- Lists - Lists
GFM GFM
...@@ -375,7 +386,6 @@ describe 'Copy as GFM', feature: true, js: true do ...@@ -375,7 +386,6 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc, <<-GFM.strip_heredoc,
1. Nested 1. Nested
1. Numbered lists 1. Numbered lists
GFM GFM
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment