From ca7f25003c2c8ff9a82c1799b726dab38ab0a6d0 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 14:24:25 +0200 Subject: [PATCH] Tables serialize as markdown by default, HTML only when styled --- src/lib/components/Editor.svelte | 78 ++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index b4667f5..3b20913 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -2671,6 +2671,57 @@ return result.replace(/\n{3,}/g, '\n\n').trim() + '\n'; } + function tableToMarkdown(table: any): string { + const rows: string[][] = []; + let hasHeader = false; + table.forEach((row: any) => { + if (row.type.name !== 'tableRow') return; + const cells: string[] = []; + row.forEach((cell: any) => { + if (cell.type.name === 'tableHeader') hasHeader = true; + cells.push(serializeInline(cell).replace(/\|/g, '\\|').replace(/\n/g, ' ')); + }); + rows.push(cells); + }); + if (rows.length === 0) return ''; + const colCount = Math.max(...rows.map(r => r.length)); + const lines: string[] = []; + rows.forEach((row, i) => { + lines.push('| ' + row.join(' | ') + ' |'); + if (i === 0 && hasHeader) { + lines.push('| ' + Array(colCount).fill('---').join(' | ') + ' |'); + } + }); + return lines.join('\n'); + } + + function resetTableToMarkdown() { + if (!editor) return; + const { state } = editor; + let { tr } = state; + let changed = false; + state.doc.descendants((node: any, pos: number) => { + if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') { + if (node.attrs.backgroundColor || (node.attrs.colspan && node.attrs.colspan > 1) || (node.attrs.rowspan && node.attrs.rowspan > 1)) { + tr = tr.setNodeMarkup(pos, undefined, { + ...node.attrs, + backgroundColor: null, + colspan: 1, + rowspan: 1, + }); + changed = true; + } + } + return true; + }); + if (changed) { + editor.view.dispatch(tr); + $editorDirty = true; + autoSave(); + } + closeTableContextMenu(); + } + function serializeNode(node: any): string { switch (node.type.name) { case 'paragraph': { @@ -2727,11 +2778,23 @@ case 'pageBreak': return '
\n'; case 'table': { - // Preserve tables as raw HTML - const tempDiv = document.createElement('div'); - const frag = DOMSerializer.fromSchema(editor!.schema).serializeNode(node); - tempDiv.appendChild(frag); - return tempDiv.innerHTML + '\n'; + // Check if any cell has styling (background color) or if there are merged cells + let hasStyling = false; + node.descendants((child: any) => { + if (child.type.name === 'tableCell' || child.type.name === 'tableHeader') { + if (child.attrs.backgroundColor) hasStyling = true; + if (child.attrs.colspan && child.attrs.colspan > 1) hasStyling = true; + if (child.attrs.rowspan && child.attrs.rowspan > 1) hasStyling = true; + } + return false; + }); + if (hasStyling) { + const tempDiv = document.createElement('div'); + const frag = DOMSerializer.fromSchema(editor!.schema).serializeNode(node); + tempDiv.appendChild(frag); + return tempDiv.innerHTML + '\n'; + } + return tableToMarkdown(node) + '\n'; } case 'pdfEmbed': { const src = node.attrs.src || ''; @@ -5995,6 +6058,11 @@ {/each}
+ +