diff --git a/src/lib/components/admin/MarkdownEditor.svelte b/src/lib/components/admin/MarkdownEditor.svelte index 309eb78..25e525e 100644 --- a/src/lib/components/admin/MarkdownEditor.svelte +++ b/src/lib/components/admin/MarkdownEditor.svelte @@ -21,7 +21,7 @@ } from 'lucide-svelte'; import { addToast } from '$lib/stores/toast.svelte'; import { imagePreviewPlugin } from '$lib/editor/imagePreviewPlugin'; - import { tableFormatterPlugin, tablePreviewField } from '$lib/editor/tableFormatterPlugin'; + import { tableFormatterPlugin } from '$lib/editor/tableFormatterPlugin'; let { value = $bindable(''), @@ -123,7 +123,6 @@ }), imagePreviewPlugin, tableFormatterPlugin, - tablePreviewField, customTheme, EditorView.lineWrapping, EditorView.updateListener.of((update) => { diff --git a/src/lib/editor/tableFormatterPlugin.ts b/src/lib/editor/tableFormatterPlugin.ts index b3f3d95..190ec95 100644 --- a/src/lib/editor/tableFormatterPlugin.ts +++ b/src/lib/editor/tableFormatterPlugin.ts @@ -1,6 +1,4 @@ -import { ViewPlugin, ViewUpdate, EditorView, WidgetType, Decoration } from '@codemirror/view'; -import type { DecorationSet } from '@codemirror/view'; -import { StateField, type Transaction, type EditorState, Range } from '@codemirror/state'; +import { ViewPlugin, ViewUpdate, EditorView } from '@codemirror/view'; import { syntaxTree } from '@codemirror/language'; // Helper to count CJK chars as 2 for better alignment @@ -166,138 +164,3 @@ export const tableFormatterPlugin = ViewPlugin.fromClass( } } ); - -class TablePreviewWidget extends WidgetType { - constructor(readonly tableStr: string) { - super(); - } - - eq(other: TablePreviewWidget) { - return this.tableStr === other.tableStr; - } - - toDOM() { - const lines = this.tableStr.trim().split('\n'); - const rows = lines.map((line) => { - const content = line.trim(); - const cols = content.split('|'); - if (cols.length > 0 && cols[0].trim() === '') cols.shift(); - if (cols.length > 0 && cols[cols.length - 1].trim() === '') cols.pop(); - return cols.map((c) => c.trim()); - }); - - const container = document.createElement('div'); - container.className = 'cm-table-preview-wrapper'; - container.style.userSelect = 'none'; - container.contentEditable = 'false'; - container.style.overflowX = 'auto'; - container.style.margin = '1.5rem 0'; - container.style.cursor = 'pointer'; - container.title = '点击编辑表格 (Click to edit table)'; - - if (rows.length === 0) return container; - - const table = document.createElement('table'); - table.style.width = '100%'; - table.style.borderCollapse = 'collapse'; - - const alignments: string[] = []; - if (rows.length > 1) { - rows[1].forEach((col) => { - const left = col.startsWith(':'); - const right = col.endsWith(':'); - if (left && right) alignments.push('center'); - else if (right) alignments.push('right'); - else alignments.push('left'); - }); - } - - rows.forEach((row, rowIndex) => { - if (rowIndex === 1) return; // Skip separator - - const tr = document.createElement('tr'); - const isHeader = rowIndex === 0; - - row.forEach((col, colIndex) => { - const td = document.createElement(isHeader ? 'th' : 'td'); - // Basic markdown parsing for links and strong could be done here, but textContent is safer - td.textContent = col; - td.style.border = '1px solid var(--border-color, #e5e7eb)'; - td.style.padding = '0.3rem 0.8rem'; - td.style.textAlign = alignments[colIndex] || 'left'; - if (isHeader) { - td.style.backgroundColor = 'rgba(0, 0, 0, 0.03)'; - td.style.fontWeight = '600'; - } - tr.appendChild(td); - }); - table.appendChild(tr); - }); - - container.appendChild(table); - return container; - } -} - -function getTableDecorations(state: EditorState) { - const widgets: Range[] = []; - const pos = state.selection.main.head; - let currentTableRange: { from: number; to: number } | null = null; - - // First find if the cursor is currently inside ANY table - syntaxTree(state).iterate({ - from: pos, - to: pos, - enter: (node: { name: string; from: number; to: number }) => { - if (node.name === 'Table') { - currentTableRange = { from: node.from, to: node.to }; - return false; - } - } - }); - - // Then find all tables and replace those that the cursor is NOT inside - syntaxTree(state).iterate({ - enter: (node: { name: string; from: number; to: number }) => { - if (node.name === 'Table') { - const isCurrent = - currentTableRange && - node.from === currentTableRange.from && - node.to === currentTableRange.to; - - if (!isCurrent) { - // Ensure block replace decorations cover entire lines - const startLine = state.doc.lineAt(node.from); - const endLine = state.doc.lineAt(node.to); - const from = startLine.from; - const to = endLine.to; - - const tableStr = state.doc.sliceString(from, to); - // Skip malformed tables or empty ones - if (tableStr.trim().length > 0) { - const deco = Decoration.replace({ - widget: new TablePreviewWidget(tableStr), - block: true - }); - widgets.push(deco.range(from, to)); - } - } - } - } - }); - - return Decoration.set(widgets.sort((a, b) => a.from - b.from)); -} - -export const tablePreviewField = StateField.define({ - create(state) { - return getTableDecorations(state); - }, - update(value, tr: Transaction) { - if (tr.docChanged || tr.selection) { - return getTableDecorations(tr.state); - } - return value; - }, - provide: (f) => EditorView.decorations.from(f) -});