refactor: 移除 Markdown 编辑器中的表格预览组件
continuous-integration/drone/push Build is passing

This commit is contained in:
seahi committed 2026-09-23 11:07:16 +08:00
1 parent 3c628e404d
commit 07e45a9ea2
2 files changed
+2 -140

No files matched your search

@@ -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) => {
+1 -138
View File
@@ -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<Decoration>[] = [];
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<DecorationSet>({
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)
});