Don't copy empty elements that were not selected on purpose as GFM
This commit is contained in:
parent
36ede00876
commit
4070d58af7
|
@ -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 = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 = {
|
||||||
'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 = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 = {
|
||||||
'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 = {
|
||||||
'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 = {
|
||||||
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 = {
|
||||||
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(' | ')} |`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -343,7 +348,9 @@ class CopyAsGFM {
|
||||||
return codeEl;
|
return codeEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static nodeToGFM(node) {
|
static nodeToGFM(node, respectWhitespaceParam = false) {
|
||||||
|
let respectWhitespace = respectWhitespaceParam;
|
||||||
|
|
||||||
if (node.nodeType === Node.COMMENT_NODE) {
|
if (node.nodeType === Node.COMMENT_NODE) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -352,7 +359,9 @@ class CopyAsGFM {
|
||||||
return node.textContent;
|
return node.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = this.innerGFM(node);
|
respectWhitespace = respectWhitespace || (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;
|
||||||
|
@ -366,7 +375,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;
|
||||||
|
@ -376,7 +395,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);
|
||||||
|
@ -386,13 +405,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
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
|
||||||
|
@ -352,7 +351,6 @@ describe 'Copy as GFM', feature: true, js: true do
|
||||||
<<-GFM.strip_heredoc,
|
<<-GFM.strip_heredoc,
|
||||||
- Nested
|
- Nested
|
||||||
|
|
||||||
|
|
||||||
- Lists
|
- Lists
|
||||||
GFM
|
GFM
|
||||||
|
|
||||||
|
@ -375,7 +373,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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue