copy_and_paste: Fix formatting of code pasted from VS Code.

Since the html copied from VS Code is not structured as code but as
styled divs for each line, any code copied from it would be pasted
unformatted with blank lines between each line of code.

This has now been fixed, by detecting VS Code style code blocks by the
CSS property `white-space: pre` and pasting the text within as is, as
code.
This commit is contained in:
N-Shar-ma 2024-02-29 21:01:01 +05:30 committed by Tim Abbott
parent c440b4575e
commit 4a1423b34a
2 changed files with 27 additions and 9 deletions

View File

@ -322,6 +322,16 @@ function within_single_element(html_fragment) {
); );
} }
export function is_white_space_pre(paste_html) {
const html_fragment = new DOMParser()
.parseFromString(paste_html, "text/html")
.querySelector("body");
return (
within_single_element(html_fragment) &&
html_fragment.firstElementChild.style.whiteSpace === "pre"
);
}
export function paste_handler_converter(paste_html) { export function paste_handler_converter(paste_html) {
const copied_html_fragment = new DOMParser() const copied_html_fragment = new DOMParser()
.parseFromString(paste_html, "text/html") .parseFromString(paste_html, "text/html")
@ -563,7 +573,7 @@ export function paste_handler(event) {
if (clipboardData.getData) { if (clipboardData.getData) {
const $textarea = $(event.currentTarget); const $textarea = $(event.currentTarget);
const paste_text = clipboardData.getData("text"); const paste_text = clipboardData.getData("text");
const paste_html = clipboardData.getData("text/html"); let paste_html = clipboardData.getData("text/html");
// Trim the paste_text to accommodate sloppy copying // Trim the paste_text to accommodate sloppy copying
const trimmed_paste_text = paste_text.trim(); const trimmed_paste_text = paste_text.trim();
@ -589,6 +599,12 @@ export function paste_handler(event) {
) { ) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (is_white_space_pre(paste_html)) {
// Copied content styled with `white-space: pre` is pasted as is
// but formatted as code. We need this for content copied from
// VS Code like sources.
paste_html = "<pre><code>" + paste_text + "</code></pre>";
}
const text = paste_handler_converter(paste_html); const text = paste_handler_converter(paste_html);
compose_ui.insert_and_scroll_into_view(text, $textarea); compose_ui.insert_and_scroll_into_view(text, $textarea);
} }

View File

@ -8,6 +8,16 @@ const {page_params} = require("./lib/zpage_params");
const copy_and_paste = zrequire("copy_and_paste"); const copy_and_paste = zrequire("copy_and_paste");
run_test("is_white_space_pre", () => {
// Copied HTML from VS Code
let paste_html = `<div style="color: #cccccc;background-color: #1f1f1f;font-family: 'Droid Sans Mono', 'monospace', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #cccccc;"> </span><span style="color: #c586c0;">if</span><span style="color: #cccccc;"> (</span><span style="color: #9cdcfe;">message_lists</span><span style="color: #cccccc;">.</span><span style="color: #9cdcfe;">current</span><span style="color: #cccccc;"> </span><span style="color: #d4d4d4;">===</span><span style="color: #cccccc;"> </span><span style="color: #569cd6;">undefined</span><span style="color: #cccccc;">) {</span></div><div><span style="color: #cccccc;"> </span><span style="color: #c586c0;">return</span><span style="color: #cccccc;">;</span></div><div><span style="color: #cccccc;"> }</span></div></div>`;
assert.ok(copy_and_paste.is_white_space_pre(paste_html));
// Negative test case
paste_html = "<div><div>Hello</div><div>World!</div></div>";
assert.ok(!copy_and_paste.is_white_space_pre(paste_html));
});
run_test("paste_handler_converter", () => { run_test("paste_handler_converter", () => {
page_params.development_environment = true; page_params.development_environment = true;
@ -118,14 +128,6 @@ run_test("paste_handler_converter", () => {
'<div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z ace-ltr focused-line" dir="auto" id="editor-3-ace-line-41"><span>Test list:</span></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-42"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 1</span></li></ul></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-43"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 2</span></li></ul></div>'; '<div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z ace-ltr focused-line" dir="auto" id="editor-3-ace-line-41"><span>Test list:</span></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-42"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 1</span></li></ul></div><div class="ace-line gutter-author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90za3z66zs0z65zz65zq8z75zlaz81zcz66zj6g2mz78zz76zmz66z22z75zfcz69zz66z line-list-type-bullet ace-ltr" dir="auto" id="editor-3-ace-line-43"><ul class="listtype-bullet listindent1 list-bullet1"><li><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="end"></span><span class="ace-line-pocket" data-faketext="" contenteditable="false"></span><span class="ace-line-pocket-zws" data-faketext="" data-contentcollector-ignore-space-at="start"></span><span>Item 2</span></li></ul></div>';
assert.equal(copy_and_paste.paste_handler_converter(input), "Test list:\n* Item 1\n* Item 2"); assert.equal(copy_and_paste.paste_handler_converter(input), "Test list:\n* Item 1\n* Item 2");
// Pasting code from VS Code / Gmail
input =
'<meta http-equiv="content-type" content="text/html; charset=utf-8"><div style="color: #ffffff;background-color: #002451;font-family: Consolas, &quot;Courier New&quot;, monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #ebbbff;">const</span><span style="color: #ffffff;"> </span><span style="color: #ff9da4;">compose_ui</span><span style="color: #ffffff;"> </span><span style="color: #99ffff;">=</span><span style="color: #ffffff;"> </span><span style="color: #bbdaff;">mock_esm</span><span style="color: #ffffff;">(</span><span style="color: #d1f1a9;">"../src/compose_ui"</span><span style="color: #ffffff;">);</span></div><div><span style="color: #bbdaff;">set_global</span><span style="color: #ffffff;">(</span><span style="color: #d1f1a9;">"document"</span><span style="color: #ffffff;">, </span><span style="color: #ff9da4;">document</span><span style="color: #ffffff;">);</span></div></div>';
assert.equal(
copy_and_paste.paste_handler_converter(input),
'const compose_ui = mock_esm("../src/compose_ui");\n\nset_global("document", document);',
);
// Pasting from Google Sheets (remove <style> elements completely) // Pasting from Google Sheets (remove <style> elements completely)
input = input =
'<meta http-equiv="content-type" content="text/html; charset=utf-8"><style type="text/css"><!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}--></style><span style="font-size:10pt;font-family:Arial;font-style:normal;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:123}" data-sheets-userformat="{&quot;2&quot;:769,&quot;3&quot;:{&quot;1&quot;:0},&quot;11&quot;:3,&quot;12&quot;:0}">123</span>'; '<meta http-equiv="content-type" content="text/html; charset=utf-8"><style type="text/css"><!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}--></style><span style="font-size:10pt;font-family:Arial;font-style:normal;text-align:right;" data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:123}" data-sheets-userformat="{&quot;2&quot;:769,&quot;3&quot;:{&quot;1&quot;:0},&quot;11&quot;:3,&quot;12&quot;:0}">123</span>';