refactor(web): adopt modular Vue architecture and isolate bearer sessions

This commit is contained in:
eezd committed 2026-10-25 20:47:32 +08:00
1 parent 1b2b2b12ff
commit 3cd641a380
379 files changed
+19736 -8712

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
import type { EditorState, Range } from '@codemirror/state'
import type { DecorationSet } from '@codemirror/view'
import type { SyntaxNode } from '@lezer/common'
import { syntaxTree } from '@codemirror/language'
import { StateField } from '@codemirror/state'
import { Decoration, EditorView } from '@codemirror/view'
import { codeRenderers, notePath, pluginRefresh } from '../../../common/plugins/registry'
import { codeContentRange, fenceLanguage } from './codeblocks'
import { editorFocused, setEditorFocus } from './focus'
import { CodeRendererWidget, MathWidget } from './widgets'
// 公式块与 Mermaid 图表:未编辑时整块替换为渲染结果;光标在块内时显示源码,并在下方实时预览。
// 跨行的块级替换只能由 StateField 提供;与表格一样只处理顶层的块
/** 返回块的源码内容及其相对块起点的偏移(点击渲染结果时光标落在内容开头) */
function blockContent(state: EditorState, node: SyntaxNode): { text: string, offset: number, language?: string } | null {
if (node.name === 'BlockMath') {
const marks = node.getChildren('BlockMathMark')
const from = state.doc.lineAt(node.from).to + 1
const to = marks.length > 1 ? state.doc.lineAt(marks[marks.length - 1].from).from - 1 : node.to
return { text: from <= to ? state.sliceDoc(from, to) : '', offset: Math.min(from, node.to) - node.from }
}
if (node.name === 'FencedCode') {
const info = node.getChild('CodeInfo')
if (!info) return null
const language = fenceLanguage(state, info.from, info.to).toLowerCase()
if (!codeRenderers.has(language)) return null
const { from, to } = codeContentRange(state, node)
return { text: state.sliceDoc(from, to), offset: from - node.from, language }
}
return null
}
function buildBlocks(state: EditorState): DecorationSet {
const out: Range<Decoration>[] = []
const sel = state.field(editorFocused) ? state.selection.ranges : []
for (let node = syntaxTree(state).topNode.firstChild; node; node = node.nextSibling) {
const content = blockContent(state, node)
if (!content) continue
const editing = sel.some(r => r.from <= node.to && r.to >= node.from)
const widget = (editOffset: number | null) =>
node.name === 'BlockMath' ? new MathWidget(content.text, true, editOffset) : new CodeRendererWidget(content.text, editOffset, content.language!, state.facet(notePath).path, codeRenderers.get(content.language!)!)
if (editing) out.push(Decoration.widget({ widget: widget(null), block: true, side: 1 }).range(node.to))
else out.push(Decoration.replace({ widget: widget(content.offset), block: true }).range(node.from, node.to))
}
return Decoration.set(out)
}
export const renderedBlocks = StateField.define<DecorationSet>({
create: buildBlocks,
update(value, tr) {
if (
tr.docChanged
|| tr.selection
|| tr.effects.some(e => e.is(setEditorFocus) || e.is(pluginRefresh))
|| syntaxTree(tr.startState) !== syntaxTree(tr.state)
) {
return buildBlocks(tr.state)
}
return value
},
provide: f => EditorView.decorations.from(f),
})