feat(web): add M0 live preview editor prototype
- CodeMirror 6 editor with Obsidian/Typora-style live preview: block markup revealed per line, inline markup revealed per element - Fenced code highlighting via Shiki (github-dark, JS regex engine) in a Web Worker, languages loaded on demand - Lezer syntax tree to HTML renderer for share pages, checked against the CommonMark 0.31.2 spec (640/652; 12 known parser deviations) - Use markdownLanguage directly to avoid bundling lang-html/css/js
This commit is contained in:
1 parent
4636649576
commit
a1597697e5
19 files changed
+3870
No files matched your search
@@ -0,0 +1,46 @@
|
||||
import { createHighlighterCore, type HighlighterCore } from 'shiki/core'
|
||||
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
||||
import { bundledLanguages } from 'shiki/langs'
|
||||
import githubDark from 'shiki/themes/github-dark.mjs'
|
||||
import type { HighlightRequest, HighlightResponse, TokenRun } from './protocol'
|
||||
|
||||
const THEME = 'github-dark'
|
||||
|
||||
const highlighterReady: Promise<HighlighterCore> = createHighlighterCore({
|
||||
themes: [githubDark],
|
||||
langs: [],
|
||||
engine: createJavaScriptRegexEngine({ forgiving: true }),
|
||||
})
|
||||
|
||||
// 同一语言的并发请求共用一次加载
|
||||
const langLoads = new Map<string, Promise<boolean>>()
|
||||
|
||||
function ensureLanguage(hl: HighlighterCore, lang: string): Promise<boolean> {
|
||||
let load = langLoads.get(lang)
|
||||
if (!load) {
|
||||
const importer = bundledLanguages[lang as keyof typeof bundledLanguages]
|
||||
load = importer
|
||||
? importer().then((mod) => hl.loadLanguage(mod.default)).then(() => true)
|
||||
: Promise.resolve(false)
|
||||
langLoads.set(lang, load)
|
||||
}
|
||||
return load
|
||||
}
|
||||
|
||||
async function highlight(req: HighlightRequest): Promise<TokenRun | null> {
|
||||
const hl = await highlighterReady
|
||||
if (!(await ensureLanguage(hl, req.lang))) return null
|
||||
const run: TokenRun = []
|
||||
for (const line of hl.codeToTokensBase(req.code, { lang: req.lang, theme: THEME })) {
|
||||
for (const t of line) {
|
||||
if (!t.content.trim()) continue
|
||||
run.push(t.offset, t.content.length, t.color ?? '', t.fontStyle ?? 0)
|
||||
}
|
||||
}
|
||||
return run
|
||||
}
|
||||
|
||||
self.addEventListener('message', async (e: MessageEvent<HighlightRequest>) => {
|
||||
const res: HighlightResponse = { id: e.data.id, tokens: await highlight(e.data) }
|
||||
;(self as unknown as Worker).postMessage(res)
|
||||
})
|
||||
Reference in new issue
Block a user