diff --git a/src/lib/editor/imagePreviewPlugin.ts b/src/lib/editor/imagePreviewPlugin.ts new file mode 100644 index 0000000..159599e --- /dev/null +++ b/src/lib/editor/imagePreviewPlugin.ts @@ -0,0 +1,91 @@ +import { WidgetType, EditorView, ViewPlugin, Decoration, ViewUpdate } from '@codemirror/view'; +import type { DecorationSet } from '@codemirror/view'; +import { syntaxTree } from '@codemirror/language'; +import { Range } from '@codemirror/state'; + +class ImagePreviewWidget extends WidgetType { + constructor(readonly url: string) { + super(); + } + + eq(other: ImagePreviewWidget) { + return this.url === other.url; + } + + toDOM() { + const wrap = document.createElement('div'); + wrap.className = 'cm-image-preview-wrapper'; + wrap.style.userSelect = 'none'; + wrap.contentEditable = 'false'; + + const img = document.createElement('img'); + img.src = this.url; + img.className = 'cm-image-preview'; + img.style.maxWidth = '100%'; + img.style.maxHeight = '400px'; + img.style.objectFit = 'contain'; + img.style.display = 'block'; + img.style.marginTop = '8px'; + img.style.marginBottom = '8px'; + img.style.borderRadius = '4px'; + img.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; + + wrap.appendChild(img); + return wrap; + } +} + +function getImageDecorations(view: EditorView) { + const widgets: Range[] = []; + const { state } = view; + + for (const { from, to } of view.visibleRanges) { + syntaxTree(state).iterate({ + from, + to, + enter: (node: any) => { + if (node.name === 'Image') { + // 查找图片语法中的 URL 节点 + const urlNode = node.node.getChild('URL'); + if (urlNode) { + let url = state.doc.sliceString(urlNode.from, urlNode.to); + // 处理可能的带尖括号的 URL + if (url.startsWith('<') && url.endsWith('>')) { + url = url.slice(1, -1); + } + + const deco = Decoration.widget({ + widget: new ImagePreviewWidget(url), + block: true, // 作为块级元素显示在下方 + side: 1 // 插入在节点之后 + }); + + widgets.push(deco.range(node.to)); + } + } + } + }); + } + + return Decoration.set(widgets.sort((a, b) => a.from - b.from)); +} + +export const imagePreviewPlugin = ViewPlugin.fromClass( + class { + decorations: DecorationSet; + + constructor(view: EditorView) { + this.decorations = getImageDecorations(view); + } + + update(update: ViewUpdate) { + // 文档改变或视口改变时重新计算 + if (update.docChanged || update.viewportChanged) { + this.decorations = getImageDecorations(update.view); + } + } + }, + { + decorations: (v) => v.decorations + } +);