文件
mynode/packages/editor/ProseMirror/utils/postprocessEditorOutput.ts
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

113 行
3.8 KiB
TypeScript

import trimEmptyParagraphs from './trimEmptyParagraphs';
const fixResourceUrls = (container: HTMLElement) => {
const resources = container.querySelectorAll<HTMLImageElement>('img[data-resource-id]');
for (const resource of resources) {
const resourceId = resource.getAttribute('data-resource-id');
resource.src = `:/${resourceId}`;
}
};
const hasNonElementContent = (parent: Element) => {
for (const child of parent.childNodes) {
if (child.nodeType === Node.TEXT_NODE && child.textContent.trim()) return true;
}
return false;
};
const hasSingleParagraphChild = (parent: Element) => {
if (hasNonElementContent(parent)) return false;
const firstChild = parent.firstElementChild;
if (firstChild?.tagName !== 'P') return false;
return Array.from(parent.children).filter(el => el.tagName === 'P').length === 1;
};
const unwrapSingleParagraph = (parent: Element) => {
if (hasSingleParagraphChild(parent)) {
parent.firstElementChild.replaceWith(...parent.firstElementChild.childNodes);
}
};
const removeListItemWrapperParagraphs = (container: HTMLElement) => {
const listItems = container.querySelectorAll<HTMLLIElement>('li');
for (const item of listItems) {
trimEmptyParagraphs(item);
unwrapSingleParagraph(item);
}
};
const removeChecklistItemWrapperParagraphs = (container: HTMLElement) => {
const listItems = container.querySelectorAll<HTMLLIElement>('li');
for (const item of listItems) {
const input = item.firstElementChild;
if (input?.tagName !== 'INPUT') continue;
const content = input.nextElementSibling;
if (!content || content.tagName !== 'DIV') continue;
trimEmptyParagraphs(content);
// Hoist nested sublists out of the <div> so Turndown sees <li>
// as their direct parent and uses single newlines.
const nestedLists = content.querySelectorAll(':scope > ul, :scope > ol');
for (let i = nestedLists.length - 1; i >= 0; i--) {
content.after(nestedLists[i]);
}
// Replace <div><p>text</p></div> with <span>text</span>
if (hasSingleParagraphChild(content)) {
const span = document.createElement('span');
span.replaceChildren(...content.firstElementChild.childNodes);
content.replaceWith(span);
}
}
};
const restoreOriginalLinks = (container: HTMLElement) => {
// Restore HREFs
const links = container.querySelectorAll<HTMLAnchorElement>('a[href="#"][data-original-href]');
for (const link of links) {
link.href = link.getAttribute('data-original-href');
link.removeAttribute('data-original-href');
}
};
const removeTableItemExtraPadding = (container: HTMLElement) => {
const cells = container.querySelectorAll<HTMLTableCellElement>('th, td');
for (const cell of cells) {
// Table cells can exist in Markdown without the need for invisible
// content.
// Remove single nonbreaking space padding:
if (cell.textContent === '\u00A0') {
cell.textContent = '';
}
}
};
const postprocessEditorOutput = (node: Node|DocumentFragment) => {
// By default, if `src` is specified on an image, the browser will try to load the image, even if it isn't added
// to the DOM. (A similar problem is described here: https://stackoverflow.com/q/62019538).
// Since :/resourceId isn't a valid image URI, this results in a large number of warnings. As a workaround,
// move the element to a temporary document before processing:
const dom = document.implementation.createHTMLDocument();
node = dom.importNode(node, true);
let html: HTMLElement;
if ((node instanceof HTMLElement)) {
html = node;
} else {
const container = document.createElement('div');
container.appendChild(node);
html = container;
}
fixResourceUrls(html);
restoreOriginalLinks(html);
removeListItemWrapperParagraphs(html);
removeChecklistItemWrapperParagraphs(html);
removeTableItemExtraPadding(html);
return html;
};
export default postprocessEditorOutput;