Files
RawMark/web/src/plugins/registry.ts
T

60 lines
2.8 KiB
TypeScript

import { Facet, StateEffect, type Extension } from '@codemirror/state'
import type { MarkdownExtension } from '@lezer/markdown'
import { shallowReactive } from 'vue'
import type { CodeBlockRenderer, PluginView } from '../plugin-api'
import { renderMermaid } from '../markdown/render'
export const pluginsPaused = typeof location !== 'undefined' && new URLSearchParams(location.search).has('disable-plugins')
export const pluginRefresh = StateEffect.define<null>()
export const notePath = Facet.define<{ path: string }, { path: string }>({ combine: (values) => values[0] ?? { path: '' } })
export const editorExtensions = new Map<symbol, { owner: string; value: Extension }>()
export const markdownSyntax = new Map<symbol, { owner: string; value: MarkdownExtension }>()
export interface OwnedView extends PluginView { owner: string; fail: (error: unknown) => void }
export interface OwnedRenderer { owner: string; render: CodeBlockRenderer; fail: (error: unknown) => void }
export const codeRenderers = new Map<string, OwnedRenderer>()
export const sidebarViews = shallowReactive(new Map<string, OwnedView>())
export const settingTabs = shallowReactive(new Map<string, OwnedView>())
export const pluginEvents = new EventTarget()
let reconfigure: () => void = () => {}
let editorFailure: (error: unknown) => void = (error) => { throw error }
export function setPluginEditorHandlers(change: () => void, failure: (error: unknown) => void) {
reconfigure = change
editorFailure = failure
}
export function reportEditorFailure(error: unknown) { editorFailure(error) }
export function pluginsChanged() { reconfigure() }
export function registerEntry<K, V>(map: Map<K, V>, key: K, value: V): () => void {
if (map.has(key)) throw new Error('插件重复注册,不能覆盖已有功能')
map.set(key, value)
try { pluginsChanged() } catch (error) { map.delete(key); pluginsChanged(); throw error }
return () => {
if (map.get(key) !== value) return
map.delete(key)
pluginsChanged()
}
}
export function registerCodeRenderer(language: string, value: OwnedRenderer) {
const key = language.trim().toLowerCase()
if (!/^[a-z0-9][a-z0-9_+-]*$/.test(key)) throw new Error('代码块语言名无效')
return registerEntry(codeRenderers, key, value)
}
registerCodeRenderer('mermaid', {
owner: 'rawmark-mermaid',
async render({ source, container, signal }) {
if (!source.trim()) { container.textContent = '空图表'; return }
try {
const svg = await renderMermaid(source)
if (!signal.aborted) container.innerHTML = svg
} catch (error) {
if (signal.aborted) return
container.className = 'cm-rm-render-error'
container.textContent = error instanceof Error ? error.message : '图表语法错误'
}
},
fail: () => window.dispatchEvent(new CustomEvent('rawmark-plugin-notice', { detail: '内置图表渲染失败,请重试' })),
})