markstream-solid 0.0.0-stage → 2.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -2
- package/dist/client/extended-BTbixd9o.js +38 -0
- package/dist/client/index.js +5640 -0
- package/dist/client/katexWorkerClient-ZLzkPVv_.js +260 -0
- package/dist/client/languageIcon-CDTPjsAQ.js +280 -0
- package/dist/client/mermaidWorkerClient-BAvGYsCO.js +243 -0
- package/dist/client/utils/index.js +31 -0
- package/dist/client/utils/katex-threshold.js +32 -0
- package/dist/client/utils/performance-monitor.js +73 -0
- package/dist/client/utils/safeRaf.js +27 -0
- package/dist/client/workers/katexCdnWorker.js +177 -0
- package/dist/client/workers/katexRenderer.worker.js +45 -0
- package/dist/client/workers/katexWorkerClient.js +16 -0
- package/dist/client/workers/mermaidCdnWorker.js +226 -0
- package/dist/client/workers/mermaidParser.worker.js +73 -0
- package/dist/client/workers/mermaidWorkerClient.js +13 -0
- package/dist/components/BuiltinNodes.d.ts +42 -0
- package/dist/components/CodeBlockNode.d.ts +10 -0
- package/dist/components/DiagramBlockNode.d.ts +27 -0
- package/dist/components/HtmlPreviewFrame.d.ts +10 -0
- package/dist/components/MarkstreamProvider.d.ts +5 -0
- package/dist/components/MarkstreamVirtualTimeline.d.ts +35 -0
- package/dist/components/MathNode.d.ts +6 -0
- package/dist/components/NodeRenderer.d.ts +27 -0
- package/dist/components/NodeSlot.d.ts +11 -0
- package/dist/components/PreCodeBlock.d.ts +3 -0
- package/dist/components/PreCodeNode.d.ts +9 -0
- package/dist/components/RenderNode.d.ts +11 -0
- package/dist/components/StreamingTextNode.d.ts +2 -0
- package/dist/components/ToolbarButton.d.ts +14 -0
- package/dist/components/ToolbarIcon.d.ts +18 -0
- package/dist/components/Tooltip.d.ts +13 -0
- package/dist/components/lifecycle.d.ts +6 -0
- package/dist/components/mathHeightCache.d.ts +5 -0
- package/dist/components/rendererModeDefaults.d.ts +4 -0
- package/dist/components/shared/diagram-height.d.ts +16 -0
- package/dist/components/shared/node-helpers.d.ts +78 -0
- package/dist/components/shared/node-outlet-helpers.d.ts +10 -0
- package/dist/components/shared/render-window.d.ts +18 -0
- package/dist/components/shared/reuseParsedNodes.d.ts +3 -0
- package/dist/components/viewport.d.ts +5 -0
- package/dist/components/virtualLayoutKey.d.ts +13 -0
- package/dist/configuration.d.ts +9 -0
- package/dist/customComponents.d.ts +36 -0
- package/dist/enhanceRenderedHtml.d.ts +25 -0
- package/dist/hydrateCustomTagContent.d.ts +2 -0
- package/dist/i18n/useSafeI18n.d.ts +4 -0
- package/dist/icon-themes/index.d.ts +2 -0
- package/dist/icon-themes/material/core.d.ts +2 -0
- package/dist/icon-themes/material/extended.d.ts +2 -0
- package/dist/icon-themes/material/index.d.ts +2 -0
- package/dist/icon-themes/registry.d.ts +9 -0
- package/dist/icon-themes/types.d.ts +11 -0
- package/dist/index.css +3279 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.px.css +3279 -0
- package/dist/index.tailwind.css +3279 -0
- package/dist/optional/d2.d.ts +17 -0
- package/dist/optional/infographic.d.ts +31 -0
- package/dist/optional/katex.d.ts +9 -0
- package/dist/optional/mermaid.d.ts +27 -0
- package/dist/optional/streamDiffs.d.ts +29 -0
- package/dist/renderMarkdownHtml.d.ts +30 -0
- package/dist/sanitizeHtmlContent.d.ts +2 -0
- package/dist/server/extended-CKyCqQt3.js +69 -0
- package/dist/server/index.js +6264 -0
- package/dist/server/languageIcon-TXkIEBPf.js +353 -0
- package/dist/server/utils/index.js +31 -0
- package/dist/server/utils/katex-threshold.js +41 -0
- package/dist/server/utils/performance-monitor.js +120 -0
- package/dist/server/utils/safeRaf.js +28 -0
- package/dist/server/workers/katexCdnWorker.js +190 -0
- package/dist/server/workers/katexWorkerClient.js +373 -0
- package/dist/server/workers/mermaidCdnWorker.js +238 -0
- package/dist/server/workers/mermaidWorkerClient.js +333 -0
- package/dist/streamDiffsWorker.d.ts +72 -0
- package/dist/tailwind.d.ts +2 -0
- package/dist/tailwind.js +3 -0
- package/dist/tooltip/singletonTooltip.d.ts +7 -0
- package/dist/types/component-props.d.ts +253 -0
- package/dist/types/node-renderer-props.d.ts +333 -0
- package/dist/useSmoothMarkdownStream.d.ts +22 -0
- package/dist/utils/clipboard.d.ts +1 -0
- package/dist/utils/d2RenderSalt.d.ts +1 -0
- package/dist/utils/footnoteTarget.d.ts +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/katex-threshold.d.ts +20 -0
- package/dist/utils/languageIcon.d.ts +8 -0
- package/dist/utils/mermaidRenderId.d.ts +1 -0
- package/dist/utils/normalizeKaTeXRenderInput.d.ts +1 -0
- package/dist/utils/performance-monitor.d.ts +121 -0
- package/dist/utils/resolveLanguageIcon.d.ts +2 -0
- package/dist/utils/safeRaf.d.ts +2 -0
- package/dist/utils/virtualItemMeasurement.d.ts +5 -0
- package/dist/virtual-adapter.d.ts +165 -0
- package/dist/workers/katexCdnWorker.d.ts +20 -0
- package/dist/workers/katexRenderer.worker.d.ts +1 -0
- package/dist/workers/katexWorkerClient.d.ts +35 -0
- package/dist/workers/mermaidCdnWorker.d.ts +15 -0
- package/dist/workers/mermaidParser.worker.d.ts +1 -0
- package/dist/workers/mermaidWorkerClient.d.ts +15 -0
- package/package.json +149 -3
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { applyMermaidThemeDirective } from "markstream-core";
|
|
2
|
+
function stringifyForWorker(value) {
|
|
3
|
+
return JSON.stringify(value);
|
|
4
|
+
}
|
|
5
|
+
function buildMermaidCDNWorkerSource(options) {
|
|
6
|
+
const mode = options.mode ?? "module";
|
|
7
|
+
const mermaidUrlLiteral = stringifyForWorker(options.mermaidUrl);
|
|
8
|
+
const initLiteral = stringifyForWorker({
|
|
9
|
+
startOnLoad: false,
|
|
10
|
+
securityLevel: "strict",
|
|
11
|
+
flowchart: { htmlLabels: false },
|
|
12
|
+
...options.initializeOptions || {}
|
|
13
|
+
});
|
|
14
|
+
const sharedLogic = `
|
|
15
|
+
let DEBUG = false
|
|
16
|
+
let mermaid = null
|
|
17
|
+
let mermaidLoadError = null
|
|
18
|
+
|
|
19
|
+
const applyMermaidThemeDirective = ${applyMermaidThemeDirective.toString()}
|
|
20
|
+
|
|
21
|
+
function normalizeMermaidModule(mod) {
|
|
22
|
+
if (!mod)
|
|
23
|
+
return mod
|
|
24
|
+
const candidate = mod && mod.default ? mod.default : mod
|
|
25
|
+
if (candidate && (typeof candidate.render === 'function' || typeof candidate.parse === 'function' || typeof candidate.initialize === 'function'))
|
|
26
|
+
return candidate
|
|
27
|
+
if (candidate && candidate.mermaidAPI && (typeof candidate.mermaidAPI.render === 'function' || typeof candidate.mermaidAPI.parse === 'function')) {
|
|
28
|
+
const api = candidate.mermaidAPI
|
|
29
|
+
return {
|
|
30
|
+
...candidate,
|
|
31
|
+
render: api.render ? api.render.bind(api) : undefined,
|
|
32
|
+
parse: api.parse ? api.parse.bind(api) : undefined,
|
|
33
|
+
initialize: (opts) => {
|
|
34
|
+
if (typeof candidate.initialize === 'function')
|
|
35
|
+
return candidate.initialize(opts)
|
|
36
|
+
return api.initialize ? api.initialize(opts) : undefined
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (mod && mod.mermaid && typeof mod.mermaid.parse === 'function')
|
|
41
|
+
return mod.mermaid
|
|
42
|
+
return candidate
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function applyThemeTo(code, theme) {
|
|
46
|
+
const themeValue = theme === 'dark' ? 'dark' : 'default'
|
|
47
|
+
const themeConfig = \`%%{init: {"theme": "\${themeValue}"}}%%\\n\`
|
|
48
|
+
return applyMermaidThemeDirective(code || '', themeConfig)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findHeaderIndex(lines) {
|
|
52
|
+
const headerRe = /^(?:graph|flowchart|flowchart\\s+tb|flowchart\\s+lr|sequenceDiagram|gantt|classDiagram|stateDiagram(?:-v2)?|erDiagram|journey|pie|quadrantChart|timeline|xychart(?:-beta)?)\\b/
|
|
53
|
+
let inFrontmatter = lines[0]?.trimEnd() === '---'
|
|
54
|
+
for (let i = 0; i < lines.length; i++) {
|
|
55
|
+
const line = (lines[i] || '').trim()
|
|
56
|
+
if (inFrontmatter) {
|
|
57
|
+
if (i > 0 && lines[i].trimEnd() === '---')
|
|
58
|
+
inFrontmatter = false
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (!line || line.startsWith('%%'))
|
|
62
|
+
continue
|
|
63
|
+
if (headerRe.test(line))
|
|
64
|
+
return i
|
|
65
|
+
}
|
|
66
|
+
return -1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function canParse(code, theme) {
|
|
70
|
+
const themed = applyThemeTo(code, theme)
|
|
71
|
+
if (mermaid && typeof mermaid.parse === 'function') {
|
|
72
|
+
await mermaid.parse(themed)
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
throw new Error('mermaid.parse not available in worker')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function findLastRenderablePrefix(baseCode, theme) {
|
|
79
|
+
const lines = String(baseCode || '').split(/\\r\\n?|\\n/)
|
|
80
|
+
const headerIndex = findHeaderIndex(lines)
|
|
81
|
+
if (headerIndex === -1)
|
|
82
|
+
return null
|
|
83
|
+
const head = lines.slice(0, headerIndex + 1)
|
|
84
|
+
await canParse(head.join('\\n'), theme)
|
|
85
|
+
|
|
86
|
+
let low = headerIndex + 1
|
|
87
|
+
let high = lines.length
|
|
88
|
+
let lastGood = headerIndex + 1
|
|
89
|
+
let tries = 0
|
|
90
|
+
const MAX_TRIES = 12
|
|
91
|
+
|
|
92
|
+
while (low <= high && tries < MAX_TRIES) {
|
|
93
|
+
const mid = Math.floor((low + high) / 2)
|
|
94
|
+
const candidate = [...head, ...lines.slice(headerIndex + 1, mid)].join('\\n')
|
|
95
|
+
tries += 1
|
|
96
|
+
try {
|
|
97
|
+
await canParse(candidate, theme)
|
|
98
|
+
lastGood = mid
|
|
99
|
+
low = mid + 1
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
high = mid - 1
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [...head, ...lines.slice(headerIndex + 1, lastGood)].join('\\n')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function initMermaidOnce() {
|
|
110
|
+
if (!mermaid)
|
|
111
|
+
return
|
|
112
|
+
try {
|
|
113
|
+
if (typeof mermaid.initialize === 'function')
|
|
114
|
+
mermaid.initialize(${initLiteral})
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (DEBUG)
|
|
118
|
+
console.warn('[markstream-solid:mermaid-cdn-worker] initialize failed', e)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
globalThis.addEventListener('message', async (ev) => {
|
|
123
|
+
const msg = ev.data || {}
|
|
124
|
+
if (msg.type === 'init') {
|
|
125
|
+
DEBUG = !!msg.debug
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const id = msg.id
|
|
130
|
+
const action = msg.action
|
|
131
|
+
const payload = msg.payload || {}
|
|
132
|
+
|
|
133
|
+
if (!mermaid) {
|
|
134
|
+
const error = mermaidLoadError ? String(mermaidLoadError?.message || mermaidLoadError) : 'Mermaid is not available in worker'
|
|
135
|
+
globalThis.postMessage({ id, ok: false, error })
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
if (action === 'canParse') {
|
|
141
|
+
const ok = await canParse(payload.code, payload.theme)
|
|
142
|
+
globalThis.postMessage({ id, ok: true, result: ok })
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (action === 'findPrefix') {
|
|
146
|
+
const result = await findLastRenderablePrefix(payload.code, payload.theme)
|
|
147
|
+
globalThis.postMessage({ id, ok: true, result })
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
globalThis.postMessage({ id, ok: false, error: 'Unknown action' })
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
globalThis.postMessage({ id, ok: false, error: String(e?.message || e) })
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
`.trimStart();
|
|
157
|
+
if (mode === "module") {
|
|
158
|
+
return `
|
|
159
|
+
${sharedLogic}
|
|
160
|
+
|
|
161
|
+
let loadPromise = null
|
|
162
|
+
async function loadMermaid() {
|
|
163
|
+
if (mermaid || mermaidLoadError)
|
|
164
|
+
return
|
|
165
|
+
if (!loadPromise) {
|
|
166
|
+
loadPromise = (async () => {
|
|
167
|
+
try {
|
|
168
|
+
const mod = await import(${mermaidUrlLiteral})
|
|
169
|
+
mermaid = normalizeMermaidModule(mod) || null
|
|
170
|
+
initMermaidOnce()
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
mermaidLoadError = e
|
|
174
|
+
}
|
|
175
|
+
})()
|
|
176
|
+
}
|
|
177
|
+
await loadPromise
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await loadMermaid()
|
|
181
|
+
`.trimStart();
|
|
182
|
+
}
|
|
183
|
+
return `
|
|
184
|
+
${sharedLogic}
|
|
185
|
+
|
|
186
|
+
function loadMermaidClassic() {
|
|
187
|
+
if (mermaid || mermaidLoadError)
|
|
188
|
+
return
|
|
189
|
+
try {
|
|
190
|
+
importScripts(${mermaidUrlLiteral})
|
|
191
|
+
mermaid = normalizeMermaidModule(globalThis.mermaid) || null
|
|
192
|
+
initMermaidOnce()
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
mermaidLoadError = e
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
loadMermaidClassic()
|
|
200
|
+
`.trimStart();
|
|
201
|
+
}
|
|
202
|
+
function createMermaidWorkerFromCDN(options) {
|
|
203
|
+
const source = buildMermaidCDNWorkerSource(options);
|
|
204
|
+
if (typeof Worker === "undefined" || typeof URL === "undefined" || typeof Blob === "undefined") {
|
|
205
|
+
return {
|
|
206
|
+
worker: null,
|
|
207
|
+
dispose: () => {
|
|
208
|
+
},
|
|
209
|
+
source
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const blob = new Blob([source], { type: "text/javascript" });
|
|
213
|
+
const url = URL.createObjectURL(blob);
|
|
214
|
+
let revoked = false;
|
|
215
|
+
const dispose = () => {
|
|
216
|
+
if (revoked)
|
|
217
|
+
return;
|
|
218
|
+
revoked = true;
|
|
219
|
+
try {
|
|
220
|
+
URL.revokeObjectURL(url);
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const mode = options.mode ?? "module";
|
|
225
|
+
const workerOptions = mode === "module" ? { ...options.workerOptions ?? {}, type: "module" } : options.workerOptions;
|
|
226
|
+
const worker = new Worker(url, workerOptions);
|
|
227
|
+
if (options.debug) {
|
|
228
|
+
try {
|
|
229
|
+
worker.postMessage({ type: "init", debug: true });
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { worker, dispose, source };
|
|
234
|
+
}
|
|
235
|
+
export {
|
|
236
|
+
buildMermaidCDNWorkerSource,
|
|
237
|
+
createMermaidWorkerFromCDN
|
|
238
|
+
};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const defaultMermaidLoader = () => import("mermaid");
|
|
2
|
+
const GLOBAL_MERMAID_KEY = "__MARKSTREAM_SOLID_MERMAID__";
|
|
3
|
+
let cachedMermaid = null;
|
|
4
|
+
let importAttempted = false;
|
|
5
|
+
let lastInitKey = null;
|
|
6
|
+
let pendingImport = null;
|
|
7
|
+
let mermaidLoader = defaultMermaidLoader;
|
|
8
|
+
function computeInitKey(config) {
|
|
9
|
+
const securityLevel = String(config?.securityLevel ?? "strict");
|
|
10
|
+
const htmlLabels = config?.flowchart?.htmlLabels;
|
|
11
|
+
return `${securityLevel}|htmlLabels:${htmlLabels === false ? "0" : "1"}`;
|
|
12
|
+
}
|
|
13
|
+
function normalizeMermaidModule(mod) {
|
|
14
|
+
if (!mod)
|
|
15
|
+
return mod;
|
|
16
|
+
const candidate = mod?.default ?? mod;
|
|
17
|
+
if (candidate && (typeof candidate.render === "function" || typeof candidate.parse === "function" || typeof candidate.initialize === "function"))
|
|
18
|
+
return candidate;
|
|
19
|
+
if (candidate?.mermaidAPI && (typeof candidate.mermaidAPI.render === "function" || typeof candidate.mermaidAPI.parse === "function")) {
|
|
20
|
+
const api = candidate.mermaidAPI;
|
|
21
|
+
return {
|
|
22
|
+
...candidate,
|
|
23
|
+
render: api.render.bind(api),
|
|
24
|
+
parse: api.parse ? api.parse.bind(api) : void 0,
|
|
25
|
+
initialize: (opts) => {
|
|
26
|
+
if (typeof candidate.initialize === "function")
|
|
27
|
+
return candidate.initialize(opts);
|
|
28
|
+
return api.initialize ? api.initialize(opts) : void 0;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (mod?.mermaid && typeof mod.mermaid.render === "function")
|
|
33
|
+
return mod.mermaid;
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
function patchInitialize(target) {
|
|
37
|
+
if (!target)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const originalInitialize = target?.initialize;
|
|
41
|
+
target.initialize = (opts) => {
|
|
42
|
+
const merged = { suppressErrorRendering: true, ...opts || {} };
|
|
43
|
+
if (typeof originalInitialize === "function")
|
|
44
|
+
return originalInitialize.call(target, merged);
|
|
45
|
+
if (target?.mermaidAPI && typeof target.mermaidAPI.initialize === "function")
|
|
46
|
+
return target.mermaidAPI.initialize(merged);
|
|
47
|
+
return void 0;
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function ensureInitialized(instance, config) {
|
|
53
|
+
if (!instance || !config)
|
|
54
|
+
return;
|
|
55
|
+
const key = computeInitKey(config);
|
|
56
|
+
if (lastInitKey === key)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
if (typeof instance.initialize === "function")
|
|
60
|
+
instance.initialize(config);
|
|
61
|
+
else if (instance.mermaidAPI?.initialize)
|
|
62
|
+
instance.mermaidAPI.initialize(config);
|
|
63
|
+
lastInitKey = key;
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function getGlobalMermaid() {
|
|
68
|
+
try {
|
|
69
|
+
const globalStore = globalThis;
|
|
70
|
+
return normalizeMermaidModule(globalStore?.[GLOBAL_MERMAID_KEY] ?? globalStore?.mermaid);
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function setGlobalMermaid(instance) {
|
|
76
|
+
try {
|
|
77
|
+
;
|
|
78
|
+
globalThis[GLOBAL_MERMAID_KEY] = instance;
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function resetCachedMermaid() {
|
|
83
|
+
cachedMermaid = null;
|
|
84
|
+
importAttempted = false;
|
|
85
|
+
lastInitKey = null;
|
|
86
|
+
pendingImport = null;
|
|
87
|
+
try {
|
|
88
|
+
delete globalThis[GLOBAL_MERMAID_KEY];
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function setMermaidLoader(loader) {
|
|
93
|
+
mermaidLoader = loader;
|
|
94
|
+
resetCachedMermaid();
|
|
95
|
+
}
|
|
96
|
+
function enableMermaid(loader) {
|
|
97
|
+
setMermaidLoader(loader ?? defaultMermaidLoader);
|
|
98
|
+
}
|
|
99
|
+
function disableMermaid() {
|
|
100
|
+
setMermaidLoader(null);
|
|
101
|
+
}
|
|
102
|
+
function isMermaidEnabled() {
|
|
103
|
+
return typeof mermaidLoader === "function";
|
|
104
|
+
}
|
|
105
|
+
async function getMermaid(initConfig) {
|
|
106
|
+
if (cachedMermaid) {
|
|
107
|
+
ensureInitialized(cachedMermaid, initConfig);
|
|
108
|
+
return cachedMermaid;
|
|
109
|
+
}
|
|
110
|
+
const globalMermaid = getGlobalMermaid();
|
|
111
|
+
if (globalMermaid) {
|
|
112
|
+
patchInitialize(globalMermaid);
|
|
113
|
+
cachedMermaid = globalMermaid;
|
|
114
|
+
setGlobalMermaid(cachedMermaid);
|
|
115
|
+
ensureInitialized(cachedMermaid, initConfig);
|
|
116
|
+
return cachedMermaid;
|
|
117
|
+
}
|
|
118
|
+
if (pendingImport) {
|
|
119
|
+
const instance = await pendingImport;
|
|
120
|
+
if (instance)
|
|
121
|
+
ensureInitialized(instance, initConfig);
|
|
122
|
+
return instance;
|
|
123
|
+
}
|
|
124
|
+
if (importAttempted)
|
|
125
|
+
return null;
|
|
126
|
+
const loader = mermaidLoader;
|
|
127
|
+
if (!loader) {
|
|
128
|
+
importAttempted = true;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
pendingImport = (async () => {
|
|
132
|
+
try {
|
|
133
|
+
const mod = await loader();
|
|
134
|
+
const instance = normalizeMermaidModule(mod);
|
|
135
|
+
if (!instance) {
|
|
136
|
+
importAttempted = true;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
patchInitialize(instance);
|
|
140
|
+
cachedMermaid = instance;
|
|
141
|
+
setGlobalMermaid(cachedMermaid);
|
|
142
|
+
return cachedMermaid;
|
|
143
|
+
} catch {
|
|
144
|
+
importAttempted = true;
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
try {
|
|
149
|
+
const instance = await pendingImport;
|
|
150
|
+
if (instance)
|
|
151
|
+
ensureInitialized(instance, initConfig);
|
|
152
|
+
return instance;
|
|
153
|
+
} finally {
|
|
154
|
+
pendingImport = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let worker = null;
|
|
158
|
+
let workerInitError = null;
|
|
159
|
+
const rpcMap = /* @__PURE__ */ new Map();
|
|
160
|
+
let maxConcurrency = 5;
|
|
161
|
+
let debugClient = false;
|
|
162
|
+
function setMermaidWorkerClientDebug(enabled) {
|
|
163
|
+
debugClient = !!enabled;
|
|
164
|
+
}
|
|
165
|
+
function setMermaidWorkerMaxConcurrency(value) {
|
|
166
|
+
if (Number.isFinite(value) && value > 0)
|
|
167
|
+
maxConcurrency = Math.floor(value);
|
|
168
|
+
}
|
|
169
|
+
function getMermaidWorkerLoad() {
|
|
170
|
+
return { inFlight: rpcMap.size, max: maxConcurrency };
|
|
171
|
+
}
|
|
172
|
+
const MERMAID_WORKER_BUSY_CODE = "WORKER_BUSY";
|
|
173
|
+
const MERMAID_DISABLED_CODE = "MERMAID_DISABLED";
|
|
174
|
+
function setMermaidWorker(nextWorker) {
|
|
175
|
+
worker = nextWorker;
|
|
176
|
+
workerInitError = null;
|
|
177
|
+
const current = nextWorker;
|
|
178
|
+
worker.onmessage = (event) => {
|
|
179
|
+
if (worker !== current)
|
|
180
|
+
return;
|
|
181
|
+
const { id, ok, result, error } = event.data || {};
|
|
182
|
+
const active = rpcMap.get(id);
|
|
183
|
+
if (!active)
|
|
184
|
+
return;
|
|
185
|
+
if (ok === false || error)
|
|
186
|
+
active.reject(new Error(error || "Unknown error"));
|
|
187
|
+
else
|
|
188
|
+
active.resolve(result);
|
|
189
|
+
};
|
|
190
|
+
worker.onerror = (event) => {
|
|
191
|
+
if (worker !== current)
|
|
192
|
+
return;
|
|
193
|
+
if (rpcMap.size === 0) {
|
|
194
|
+
console.debug?.("[markstream-solid:mermaidWorkerClient] Worker error (idle):", event?.message || event);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
if (debugClient)
|
|
199
|
+
console.error("[markstream-solid:mermaidWorkerClient] Worker error:", event?.message || event);
|
|
200
|
+
else
|
|
201
|
+
console.debug?.("[markstream-solid:mermaidWorkerClient] Worker error:", event?.message || event);
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
for (const [, active] of rpcMap.entries())
|
|
205
|
+
active.reject(new Error(`Worker error: ${event.message}`));
|
|
206
|
+
rpcMap.clear();
|
|
207
|
+
};
|
|
208
|
+
worker.onmessageerror = (event) => {
|
|
209
|
+
if (worker !== current)
|
|
210
|
+
return;
|
|
211
|
+
if (rpcMap.size === 0) {
|
|
212
|
+
console.debug?.("[markstream-solid:mermaidWorkerClient] Worker messageerror (idle):", event);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const [, active] of rpcMap.entries())
|
|
216
|
+
active.reject(new Error("Worker messageerror"));
|
|
217
|
+
rpcMap.clear();
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function clearMermaidWorker() {
|
|
221
|
+
if (worker) {
|
|
222
|
+
try {
|
|
223
|
+
for (const [, active] of rpcMap.entries())
|
|
224
|
+
active.reject(new Error("Worker cleared"));
|
|
225
|
+
rpcMap.clear();
|
|
226
|
+
worker.terminate?.();
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
worker = null;
|
|
231
|
+
workerInitError = null;
|
|
232
|
+
}
|
|
233
|
+
function ensureWorker() {
|
|
234
|
+
if (worker)
|
|
235
|
+
return worker;
|
|
236
|
+
workerInitError = new Error("[markstream-solid:mermaidWorkerClient] No worker instance set. Please inject a Worker via setMermaidWorker().");
|
|
237
|
+
workerInitError.name = "WorkerInitError";
|
|
238
|
+
workerInitError.code = "WORKER_INIT_ERROR";
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function callWorker(action, payload, timeout = 1400) {
|
|
242
|
+
if (!isMermaidEnabled()) {
|
|
243
|
+
const error = new Error("Mermaid rendering disabled");
|
|
244
|
+
error.name = "MermaidDisabled";
|
|
245
|
+
error.code = MERMAID_DISABLED_CODE;
|
|
246
|
+
return Promise.reject(error);
|
|
247
|
+
}
|
|
248
|
+
if (workerInitError)
|
|
249
|
+
return Promise.reject(workerInitError);
|
|
250
|
+
const activeWorker = ensureWorker();
|
|
251
|
+
if (!activeWorker)
|
|
252
|
+
return Promise.reject(workerInitError);
|
|
253
|
+
if (rpcMap.size >= maxConcurrency) {
|
|
254
|
+
const error = new Error("Worker busy");
|
|
255
|
+
error.name = "WorkerBusy";
|
|
256
|
+
error.code = MERMAID_WORKER_BUSY_CODE;
|
|
257
|
+
error.inFlight = rpcMap.size;
|
|
258
|
+
error.max = maxConcurrency;
|
|
259
|
+
return Promise.reject(error);
|
|
260
|
+
}
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
const id = Math.random().toString(36).slice(2);
|
|
263
|
+
let settled = false;
|
|
264
|
+
let timeoutId = null;
|
|
265
|
+
const cleanup = () => {
|
|
266
|
+
if (settled)
|
|
267
|
+
return;
|
|
268
|
+
settled = true;
|
|
269
|
+
if (timeoutId != null)
|
|
270
|
+
clearTimeout(timeoutId);
|
|
271
|
+
rpcMap.delete(id);
|
|
272
|
+
};
|
|
273
|
+
rpcMap.set(id, {
|
|
274
|
+
resolve: (value) => {
|
|
275
|
+
cleanup();
|
|
276
|
+
resolve(value);
|
|
277
|
+
},
|
|
278
|
+
reject: (error) => {
|
|
279
|
+
cleanup();
|
|
280
|
+
reject(error);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
try {
|
|
284
|
+
activeWorker.postMessage({ id, action, payload });
|
|
285
|
+
} catch (error) {
|
|
286
|
+
rpcMap.delete(id);
|
|
287
|
+
reject(error);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
timeoutId = setTimeout(() => {
|
|
291
|
+
const error = new Error("Worker call timed out");
|
|
292
|
+
error.name = "WorkerTimeout";
|
|
293
|
+
error.code = "WORKER_TIMEOUT";
|
|
294
|
+
const pending = rpcMap.get(id);
|
|
295
|
+
pending?.reject(error);
|
|
296
|
+
}, timeout);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
async function canParseOffthread(code, theme, timeout = 1400) {
|
|
300
|
+
return await callWorker("canParse", { code, theme }, timeout);
|
|
301
|
+
}
|
|
302
|
+
async function findPrefixOffthread(code, theme, timeout = 1400) {
|
|
303
|
+
return await callWorker("findPrefix", { code, theme }, timeout);
|
|
304
|
+
}
|
|
305
|
+
function terminateWorker() {
|
|
306
|
+
if (worker) {
|
|
307
|
+
try {
|
|
308
|
+
for (const [, active] of rpcMap.entries())
|
|
309
|
+
active.reject(new Error("Worker terminated"));
|
|
310
|
+
rpcMap.clear();
|
|
311
|
+
worker.terminate();
|
|
312
|
+
} finally {
|
|
313
|
+
worker = null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
export {
|
|
318
|
+
MERMAID_DISABLED_CODE,
|
|
319
|
+
MERMAID_WORKER_BUSY_CODE,
|
|
320
|
+
canParseOffthread,
|
|
321
|
+
clearMermaidWorker,
|
|
322
|
+
disableMermaid as d,
|
|
323
|
+
enableMermaid as e,
|
|
324
|
+
findPrefixOffthread,
|
|
325
|
+
getMermaid as g,
|
|
326
|
+
getMermaidWorkerLoad,
|
|
327
|
+
isMermaidEnabled as i,
|
|
328
|
+
setMermaidLoader as s,
|
|
329
|
+
setMermaidWorker,
|
|
330
|
+
setMermaidWorkerClientDebug,
|
|
331
|
+
setMermaidWorkerMaxConcurrency,
|
|
332
|
+
terminateWorker
|
|
333
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-injected `WorkerPoolManager` for stream-diffs / @pierre/diffs
|
|
3
|
+
* highlighting.
|
|
4
|
+
*
|
|
5
|
+
* markstream-solid deliberately does NOT bundle or spawn the highlight worker
|
|
6
|
+
* itself — worker assets are bundler-specific (`?worker` in Vite, etc.) and
|
|
7
|
+
* would be fragile inside a multi-bundler library. Instead, the host app
|
|
8
|
+
* creates the pool with its own bundler and injects it here; every enhanced
|
|
9
|
+
* code block then forwards it as the `workerManager` runtime option so Shiki
|
|
10
|
+
* tokenization runs off the main thread.
|
|
11
|
+
*
|
|
12
|
+
* When no pool is injected, highlighting stays on the main thread exactly as
|
|
13
|
+
* before. A broken/terminated pool is non-fatal: `@pierre/diffs` falls back to
|
|
14
|
+
* its main-thread highlighter when the pool reports itself unavailable.
|
|
15
|
+
*/
|
|
16
|
+
export interface StreamDiffsWorkerPoolLike {
|
|
17
|
+
/**
|
|
18
|
+
* True while the pool is usable. `@pierre/diffs` checks this to decide
|
|
19
|
+
* between worker-based and main-thread highlighting.
|
|
20
|
+
*/
|
|
21
|
+
isWorkingPool?: () => boolean;
|
|
22
|
+
/** Resolves once workers are spawned and initialized. */
|
|
23
|
+
initialize?: (languages?: string[]) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Updates worker-side render options (theme). Called by markstream-solid on
|
|
26
|
+
* theme changes so worker-generated tokens match the active theme.
|
|
27
|
+
*/
|
|
28
|
+
setRenderOptions?: (options: {
|
|
29
|
+
theme?: string | {
|
|
30
|
+
dark: string;
|
|
31
|
+
light: string;
|
|
32
|
+
};
|
|
33
|
+
}) => Promise<void> | void;
|
|
34
|
+
terminate?: () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Inject (or clear) the shared worker pool. Pass `null`/`undefined` to disable
|
|
38
|
+
* worker-based highlighting and fall back to the main thread.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import DiffsWorker from '@pierre/diffs/worker/worker.js?worker'
|
|
43
|
+
* import { getOrCreateWorkerPoolSingleton } from '@pierre/diffs/worker'
|
|
44
|
+
* import { setStreamDiffsWorkerPool } from 'markstream-solid'
|
|
45
|
+
*
|
|
46
|
+
* const pool = getOrCreateWorkerPoolSingleton({
|
|
47
|
+
* poolOptions: {
|
|
48
|
+
* poolSize: 4,
|
|
49
|
+
* workerFactory: () => new DiffsWorker(),
|
|
50
|
+
* },
|
|
51
|
+
* highlighterOptions: {
|
|
52
|
+
* theme: { dark: 'pierre-dark', light: 'pierre-light' },
|
|
53
|
+
* },
|
|
54
|
+
* })
|
|
55
|
+
* setStreamDiffsWorkerPool(pool)
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function setStreamDiffsWorkerPool(pool: StreamDiffsWorkerPoolLike | null | undefined): void;
|
|
59
|
+
export declare function getStreamDiffsWorkerPool(): StreamDiffsWorkerPoolLike | null;
|
|
60
|
+
/** Clear the injected pool without terminating it (host keeps ownership). */
|
|
61
|
+
export declare function clearStreamDiffsWorkerPool(): void;
|
|
62
|
+
/** Terminate the injected pool (if it exposes `terminate`) and clear it. */
|
|
63
|
+
export declare function terminateStreamDiffsWorkerPool(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Forward the active theme to the worker pool so worker-produced tokens match
|
|
66
|
+
* the requested theme. No-op when no pool is injected. Failures are non-fatal:
|
|
67
|
+
* tokens still render, and colors catch up on the next sync.
|
|
68
|
+
*/
|
|
69
|
+
export declare function syncStreamDiffsWorkerTheme(theme: string | {
|
|
70
|
+
dark: string;
|
|
71
|
+
light: string;
|
|
72
|
+
}): Promise<void>;
|
package/dist/tailwind.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
2
|
+
export declare function showTooltipForAnchor(anchor: HTMLElement | null, content: string, placement?: TooltipPlacement, immediate?: boolean, _origin?: {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
} | undefined, isDark?: boolean | null): void;
|
|
6
|
+
export declare function hideTooltip(immediate?: boolean): void;
|
|
7
|
+
export declare function isTooltipVisible(): boolean;
|