建立前端与 Tauri 桌面端的首个版本提交,包含核心编辑器、项目文件读写、测试与构建配置。 补充 Git 忽略规则和换行规范,排除依赖、构建产物、本地运行日志与临时验证文件,方便在其他电脑继续开发。
62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
import { Extension } from "@tiptap/core"
|
|
import { Plugin, PluginKey } from "@tiptap/pm/state"
|
|
import { Decoration, DecorationSet } from "@tiptap/pm/view"
|
|
import type { Node as ProseMirrorNode } from "@tiptap/pm/model"
|
|
|
|
export const selectedBlockPluginKey = new PluginKey<DecorationSet>("blockFlowSelectedBlock")
|
|
|
|
export const SelectedBlockExtension = Extension.create({
|
|
name: "blockFlowSelectedBlock",
|
|
|
|
addStorage() {
|
|
return {
|
|
selectedBlockId: null as string | null
|
|
}
|
|
},
|
|
|
|
addProseMirrorPlugins() {
|
|
return [
|
|
new Plugin({
|
|
key: selectedBlockPluginKey,
|
|
state: {
|
|
init: (_, state) => buildSelectedBlockDecorations(state.doc, this.storage.selectedBlockId as string | null),
|
|
apply: (transaction, previous, _oldState, newState) => {
|
|
const selectedBlockId = transaction.getMeta(selectedBlockPluginKey) as string | null | undefined
|
|
if (selectedBlockId === undefined && !transaction.docChanged) {
|
|
return previous
|
|
}
|
|
|
|
return buildSelectedBlockDecorations(
|
|
newState.doc,
|
|
selectedBlockId === undefined ? (this.storage.selectedBlockId as string | null) : selectedBlockId
|
|
)
|
|
}
|
|
},
|
|
props: {
|
|
decorations(state) {
|
|
return selectedBlockPluginKey.getState(state)
|
|
}
|
|
}
|
|
})
|
|
]
|
|
}
|
|
})
|
|
|
|
function buildSelectedBlockDecorations(doc: ProseMirrorNode, selectedBlockId: string | null): DecorationSet {
|
|
if (selectedBlockId === null) {
|
|
return DecorationSet.empty
|
|
}
|
|
|
|
const decorations: Decoration[] = []
|
|
doc.descendants((node, position) => {
|
|
if (node.attrs.id === selectedBlockId) {
|
|
decorations.push(Decoration.node(position, position + node.nodeSize, { class: "bf-selected-block" }))
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return DecorationSet.create(doc, decorations)
|
|
}
|