fix: editor fixes (#2067)

* autojoiner

* fix marked

* return clipboardTextSerializer as markdown

* fix clipboardTextSerializer for single lines

* cleanup two preceeding spaces in ordered lists item

* fix extra paragraph in task list

* don't zip sinple page exports
This commit is contained in:
Philip Okugbe
2026-03-29 02:19:09 +01:00
committed by GitHub
parent a42ac3d450
commit 412962204c
7 changed files with 217 additions and 34 deletions
@@ -5,18 +5,23 @@ import { mathInlineExtension } from "./math-inline.marked";
marked.use({
renderer: {
// @ts-ignore
list(body: string, isOrdered: boolean, start: number) {
if (isOrdered) {
const startAttr = start !== 1 ? ` start="${start}"` : "";
return `<ol ${startAttr}>\n${body}</ol>\n`;
list({ ordered, start, items }) {
let body = "";
for (const item of items) {
body += this.listitem(item);
}
const dataType = body.includes(`<input`) ? ' data-type="taskList"' : "";
if (ordered) {
const startAttr = start !== 1 ? ` start="${start}"` : "";
return `<ol${startAttr}>\n${body}</ol>\n`;
}
const isTaskList = items.some((item) => item.task);
const dataType = isTaskList ? ' data-type="taskList"' : "";
return `<ul${dataType}>\n${body}</ul>\n`;
},
// @ts-ignore
listitem({ text, raw, task: isTask, checked: isChecked }): string {
listitem({ tokens, task: isTask, checked: isChecked }) {
const text = this.parser.parse(tokens);
if (!isTask) {
return `<li>${text}</li>\n`;
}
@@ -21,6 +21,7 @@ export function htmlToMarkdown(html: string): string {
callout,
preserveDetail,
listParagraph,
orderedListItem,
mathInline,
mathBlock,
iframeEmbed,
@@ -41,6 +42,40 @@ function listParagraph(turndownService: _TurndownService) {
});
}
function orderedListItem(turndownService: _TurndownService) {
turndownService.addRule('orderedListItem', {
filter: function (node: HTMLInputElement) {
return node.nodeName === 'LI' && node.getAttribute('data-type') !== 'taskItem';
},
replacement: (content: string, node: HTMLInputElement, options: any) => {
const parent = node.parentNode as HTMLElement;
if (parent.nodeName !== 'OL' && parent.nodeName !== 'UL') {
return content;
}
content = content
.replace(/^\n+/, '')
.replace(/\n+$/, '\n')
.replace(/\n/gm, '\n ');
let prefix: string;
if (parent.nodeName === 'OL') {
const start = parseInt(parent.getAttribute('start') || '1', 10);
const index = Array.prototype.indexOf.call(parent.children, node);
prefix = `${start + index}. `;
} else {
prefix = `${options.bulletListMarker} `;
}
return (
prefix +
content +
(node.nextSibling && !/\n$/.test(content) ? '\n' : '')
);
},
});
}
function callout(turndownService: _TurndownService) {
turndownService.addRule('callout', {
filter: function (node: HTMLInputElement) {
@@ -63,25 +98,17 @@ function taskList(turndownService: _TurndownService) {
node.parentNode.nodeName === 'UL'
);
},
replacement: function (content: string, node: HTMLInputElement) {
const checkbox = node.querySelector(
'input[type="checkbox"]',
) as HTMLInputElement;
const isChecked = checkbox.checked;
replacement: function (_content: string, node: HTMLInputElement) {
const isChecked = node.getAttribute('data-checked') === 'true';
const div = node.querySelector('div');
const text = div ? div.textContent.trim() : node.textContent.trim();
// Process content like regular list items
content = content
.replace(/^\n+/, '') // remove leading newlines
.replace(/\n+$/, '\n') // replace trailing newlines with just a single one
.replace(/\n/gm, '\n '); // indent nested content with 2 spaces
// Create the checkbox prefix
const prefix = `- ${isChecked ? '[x]' : '[ ]'} `;
return (
prefix +
content +
(node.nextSibling && !/\n$/.test(content) ? '\n' : '')
text +
(node.nextSibling && !/\n$/.test(text) ? '\n' : '')
);
},
});