import * as TurndownService from '@joplin/turndown'; import * as TurndownPluginGfm from '@joplin/turndown-plugin-gfm'; export function turndown(html: string): string { const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', hr: '---', bulletListMarker: '-', }); const tables = TurndownPluginGfm.tables; const strikethrough = TurndownPluginGfm.strikethrough; const highlightedCodeBlock = TurndownPluginGfm.highlightedCodeBlock; turndownService.use([ tables, strikethrough, highlightedCodeBlock, taskList, callout, preserveDetail, listParagraph, mathInline, mathBlock, iframeEmbed, ]); return turndownService.turndown(html).replaceAll('
', ' '); } function listParagraph(turndownService: TurndownService) { turndownService.addRule('paragraph', { filter: ['p'], replacement: (content: any, node: HTMLInputElement) => { if (node.parentElement?.nodeName === 'LI') { return content; } return `\n\n${content}\n\n`; }, }); } function callout(turndownService: TurndownService) { turndownService.addRule('callout', { filter: function (node: HTMLInputElement) { return ( node.nodeName === 'DIV' && node.getAttribute('data-type') === 'callout' ); }, replacement: function (content: any, node: HTMLInputElement) { const calloutType = node.getAttribute('data-callout-type'); return `\n\n:::${calloutType}\n${content.trim()}\n:::\n\n`; }, }); } function taskList(turndownService: TurndownService) { turndownService.addRule('taskListItem', { filter: function (node: HTMLInputElement) { return ( node.getAttribute('data-type') === 'taskItem' && node.parentNode.nodeName === 'UL' ); }, replacement: function (content: any, node: HTMLInputElement) { const checkbox = node.querySelector( 'input[type="checkbox"]', ) as HTMLInputElement; const isChecked = checkbox.checked; return `- ${isChecked ? '[x]' : '[ ]'} ${content.trim()} \n`; }, }); } function preserveDetail(turndownService: TurndownService) { turndownService.addRule('preserveDetail', { filter: function (node: HTMLInputElement) { return node.nodeName === 'DETAILS'; }, replacement: function (content: any, node: HTMLInputElement) { const summary = node.querySelector(':scope > summary'); let detailSummary = ''; if (summary) { detailSummary = `${turndownService.turndown(summary.innerHTML)}`; } const detailsContent = Array.from(node.childNodes) .filter(child => child.nodeName !== 'SUMMARY') .map(child => (child.nodeType === 1 ? turndownService.turndown((child as HTMLElement).outerHTML) : child.textContent)) .join(''); return `\n
\n${detailSummary}\n\n${detailsContent}\n\n
\n`; }, }); } function mathInline(turndownService: TurndownService) { turndownService.addRule('mathInline', { filter: function (node: HTMLInputElement) { return ( node.nodeName === 'SPAN' && node.getAttribute('data-type') === 'mathInline' ); }, replacement: function (content: any, node: HTMLInputElement) { return `$${content}$`; }, }); } function mathBlock(turndownService: TurndownService) { turndownService.addRule('mathBlock', { filter: function (node: HTMLInputElement) { return ( node.nodeName === 'DIV' && node.getAttribute('data-type') === 'mathBlock' ); }, replacement: function (content: any, node: HTMLInputElement) { return `\n$$\n${content}\n$$\n`; }, }); } function iframeEmbed(turndownService: TurndownService) { turndownService.addRule('iframeEmbed', { filter: function (node: HTMLInputElement) { return node.nodeName === 'IFRAME'; }, replacement: function (content: any, node: HTMLInputElement) { const src = node.getAttribute('src'); return '[' + src + '](' + src + ')'; }, }); }