This commit is contained in:
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,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)
|
||||
});
|
||||
Reference in new issue
Block a user