turndown: Paste single line of code as inline code in markdown.

When pasting any code without any newlines, whether from a code block or
a code span, it will always be pasted as inline code.

That is, a line of code copied from within a code span will retain code
span formatting on paste, and a line of code copied from within a code
block will be pasted as inline code, instead of a 1 line code block.
This commit is contained in:
N-Shar-ma 2023-11-30 03:23:07 +05:30 committed by Tim Abbott
parent 446ba4d6df
commit 79ec61b373
2 changed files with 62 additions and 1 deletions

View File

@ -319,7 +319,7 @@ export function paste_handler_converter(paste_html) {
copied_html_fragment.childNodes.length === 1 &&
copied_html_fragment.firstElementChild &&
copied_html_fragment.firstElementChild.innerHTML;
const outer_elements_to_retain = ["PRE", "UL", "OL", "A"];
const outer_elements_to_retain = ["PRE", "UL", "OL", "A", "CODE"];
// If the entire selection copied is within a single HTML element (like an
// `h1`), we don't want to retain its styling, except when it is needed to
// identify the intended structure of the copied content.
@ -481,6 +481,62 @@ export function paste_handler_converter(paste_html) {
},
});
// We override the original upstream implementation of this rule to turn any
// single line code blocks into inline markdown code. Everything else is the same.
turndownService.addRule("fencedCodeBlock", {
filter(node, options) {
return (
options.codeBlockStyle === "fenced" &&
node.nodeName === "PRE" &&
node.firstChild &&
node.firstChild.nodeName === "CODE"
);
},
replacement(_content, node, options) {
const code = node.firstChild.textContent;
// We convert single line code inside a code block to inline markdown code,
// and the code for this is taken from upstream's `code` rule.
if (!code.includes("\n")) {
if (!code) {
return "";
}
const extraSpace = /^`|^ .*?[^ ].* $|`$/.test(code) ? " " : "";
// Pick the shortest sequence of backticks that is not found in the code
// to be the delimiter.
let delimiter = "`";
const matches = code.match(/`+/gm) || [];
while (matches.includes(delimiter)) {
delimiter = delimiter + "`";
}
return delimiter + extraSpace + code + extraSpace + delimiter;
}
const className = node.firstChild.getAttribute("class") || "";
const language = (className.match(/language-(\S+)/) || [null, ""])[1];
const fenceChar = options.fence.charAt(0);
let fenceSize = 3;
const fenceInCodeRegex = new RegExp("^" + fenceChar + "{3,}", "gm");
let match;
while ((match = fenceInCodeRegex.exec(code))) {
if (match[0].length >= fenceSize) {
fenceSize = match[0].length + 1;
}
}
const fence = fenceChar.repeat(fenceSize);
return (
"\n\n" + fence + language + "\n" + code.replace(/\n$/, "") + "\n" + fence + "\n\n"
);
},
});
let markdown_text = turndownService.turndown(paste_html);
// Checks for escaped ordered list syntax.

View File

@ -35,6 +35,11 @@ run_test("paste_handler_converter", () => {
'zulip code block in python\n\n```Python\nprint("hello world")\n```',
);
// Single line in a code block
input =
'<meta http-equiv="content-type" content="text/html; charset=utf-8"><pre><code>single line</code></pre>';
assert.equal(copy_and_paste.paste_handler_converter(input), "`single line`");
// Raw links without custom text
input =
'<meta http-equiv="content-type" content="text/html; charset=utf-8"><a href="https://zulip.readthedocs.io/en/latest/subsystems/logging.html" target="_blank" title="https://zulip.readthedocs.io/en/latest/subsystems/logging.html" style="color: hsl(200, 100%, 40%); text-decoration: none; cursor: pointer; font-family: &quot;Source Sans 3&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: hsl(0, 0%, 100%);">https://zulip.readthedocs.io/en/latest/subsystems/logging.html</a>';