brookmd 0.27.0 → 0.29.0
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/CHANGELOG.md +96 -0
- package/README.md +100 -0
- package/dist/client.d.ts +139 -0
- package/dist/client.js +251 -2
- package/dist/dom.d.ts +17 -0
- package/dist/dom.js +196 -18
- package/dist/element.js +3 -1
- package/dist/hi-inc.d.ts +25 -0
- package/dist/hi-inc.js +6 -0
- package/dist/html-to-react.d.ts +2 -0
- package/dist/html-to-react.js +7 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/react-splice.d.ts +32 -0
- package/dist/react-splice.js +33 -0
- package/dist/react.d.ts +11 -0
- package/dist/react.js +29 -8
- package/dist/renderers/CodeBlock.d.ts +13 -1
- package/dist/renderers/CodeBlock.js +51 -8
- package/dist/server.js +1 -0
- package/dist/splice.d.ts +126 -0
- package/dist/splice.js +212 -0
- package/dist/types-core.d.ts +16 -0
- package/dist/wasm/brook_md_core.d.ts +13 -0
- package/dist/wasm/brook_md_core.js +15 -0
- package/dist/wasm/brook_md_core_bg.wasm +0 -0
- package/dist/wasm/brook_md_core_bg.wasm.d.ts +1 -0
- package/dist/worker.js +1 -0
- package/package.json +1 -1
package/dist/dom.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { highlightDeferred } from "./hi-defer.js";
|
|
2
2
|
import { createInc, incHighlight, incSeed } from "./hi-inc.js";
|
|
3
3
|
import { morph } from "./morph.js";
|
|
4
|
+
import { newIncCode, paintIncCode, spliceHtml, spliceKeep } from "./splice.js";
|
|
4
5
|
import { blockProps, extractLang } from "./block-props.js";
|
|
5
6
|
import { decorateSegments } from "./decorate.js";
|
|
6
7
|
import { safeUrl } from "./url-safety.js";
|
|
8
|
+
let keyedAttempts = 0;
|
|
9
|
+
let keyedHits = 0;
|
|
10
|
+
function __keyedStats() {
|
|
11
|
+
return { attempts: keyedAttempts, hits: keyedHits };
|
|
12
|
+
}
|
|
13
|
+
function __resetKeyedStats() {
|
|
14
|
+
keyedAttempts = 0;
|
|
15
|
+
keyedHits = 0;
|
|
16
|
+
}
|
|
7
17
|
const INTRINSIC_PX = {
|
|
8
18
|
Paragraph: 80,
|
|
9
19
|
Heading: 44,
|
|
@@ -32,6 +42,7 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
32
42
|
const streamingHighlight = options.streamingHighlight !== false;
|
|
33
43
|
const batch = options.batch !== false && typeof requestAnimationFrame === "function";
|
|
34
44
|
const morphOpenBlocks = options.morphOpenBlocks === true;
|
|
45
|
+
const fullRebuild = options.__fullRebuild === true;
|
|
35
46
|
const root = document.createElement("div");
|
|
36
47
|
root.className = options.className ? `brook-md ${options.className}` : "brook-md";
|
|
37
48
|
if (options.id) root.id = options.id;
|
|
@@ -70,6 +81,7 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
70
81
|
id: b.id,
|
|
71
82
|
node: void 0,
|
|
72
83
|
html: b.html,
|
|
84
|
+
block: b,
|
|
73
85
|
open: b.open,
|
|
74
86
|
speculative: b.speculative,
|
|
75
87
|
kind: b.kind.type,
|
|
@@ -87,6 +99,13 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
87
99
|
continue;
|
|
88
100
|
}
|
|
89
101
|
const t0 = onRenderMetrics && hasPerf ? performance.now() : 0;
|
|
102
|
+
if (existing.codeInc && existing.inc && b.open && existing.open && b.kind.type === "CodeBlock" && existing.kind === "CodeBlock" && syncIncCode(existing, b)) {
|
|
103
|
+
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
104
|
+
existing.html = b.html;
|
|
105
|
+
existing.block = b;
|
|
106
|
+
existing.speculative = b.speculative;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
90
109
|
if (existing.table && b.open && b.kind.type === "Table") {
|
|
91
110
|
const data = tableData(b);
|
|
92
111
|
if (data) {
|
|
@@ -96,15 +115,38 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
96
115
|
existing.node.classList.toggle("brook-speculative", b.speculative);
|
|
97
116
|
}
|
|
98
117
|
existing.html = b.html;
|
|
118
|
+
existing.block = b;
|
|
99
119
|
existing.open = b.open;
|
|
100
120
|
existing.speculative = b.speculative;
|
|
101
121
|
continue;
|
|
102
122
|
}
|
|
103
123
|
}
|
|
124
|
+
if (!fullRebuild && existing.list && b.open && b.kind.type === "List" && existing.kind === "List") {
|
|
125
|
+
const ld = b.kind.data;
|
|
126
|
+
if (ld && syncKeyedList(existing.list, ld)) {
|
|
127
|
+
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
128
|
+
existing.node.className = genericClassName(b);
|
|
129
|
+
existing.html = b.html;
|
|
130
|
+
existing.block = b;
|
|
131
|
+
existing.open = b.open;
|
|
132
|
+
existing.speculative = b.speculative;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!fullRebuild && existing.container && b.open && existing.kind === b.kind.type && (b.kind.type === "Blockquote" || b.kind.type === "Alert") && syncKeyedContainer(existing.container, b)) {
|
|
137
|
+
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
138
|
+
existing.node.className = genericClassName(b);
|
|
139
|
+
existing.html = b.html;
|
|
140
|
+
existing.block = b;
|
|
141
|
+
existing.open = b.open;
|
|
142
|
+
existing.speculative = b.speculative;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
104
145
|
if (morphOpenBlocks && b.open && existing.open && existing.generic && existing.kind === b.kind.type && usesGenericPath(b)) {
|
|
105
146
|
morph(existing.node, sanitize ? sanitize(b.html) : b.html);
|
|
106
147
|
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
107
148
|
existing.html = b.html;
|
|
149
|
+
existing.block = b;
|
|
108
150
|
existing.speculative = b.speculative;
|
|
109
151
|
existing.node.className = genericClassName(b);
|
|
110
152
|
existing.generic = !sanitize;
|
|
@@ -114,9 +156,35 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
114
156
|
existing.node.insertAdjacentHTML("beforeend", b.html.slice(existing.html.length));
|
|
115
157
|
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
116
158
|
existing.html = b.html;
|
|
159
|
+
existing.block = b;
|
|
117
160
|
continue;
|
|
118
161
|
}
|
|
162
|
+
if (!fullRebuild && !sanitize && !hasInlineTransforms && existing.generic && b.open && existing.open && existing.kind === b.kind.type && usesGenericPath(b)) {
|
|
163
|
+
const keep = spliceKeep(existing.block, b);
|
|
164
|
+
if (keep !== void 0 && spliceHtml(existing.node, existing.html, b.html, keep)) {
|
|
165
|
+
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
166
|
+
existing.html = b.html;
|
|
167
|
+
existing.block = b;
|
|
168
|
+
existing.speculative = b.speculative;
|
|
169
|
+
existing.node.className = genericClassName(b);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!fullRebuild && existing.plainCode && b.open && existing.open && b.kind.type === "CodeBlock" && existing.kind === "CodeBlock") {
|
|
174
|
+
const keep = spliceKeep(existing.block, b);
|
|
175
|
+
if (keep !== void 0 && spliceHtml(existing.plainCode, existing.html, b.html, keep)) {
|
|
176
|
+
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
177
|
+
existing.html = b.html;
|
|
178
|
+
existing.block = b;
|
|
179
|
+
existing.speculative = b.speculative;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
119
183
|
existing.table = void 0;
|
|
184
|
+
existing.list = void 0;
|
|
185
|
+
existing.container = void 0;
|
|
186
|
+
existing.codeInc = void 0;
|
|
187
|
+
existing.plainCode = void 0;
|
|
120
188
|
if (existing.inc && b.kind.type !== "CodeBlock") existing.inc = void 0;
|
|
121
189
|
if (existing.highlight) {
|
|
122
190
|
existing.highlight.cancel();
|
|
@@ -127,6 +195,7 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
127
195
|
existing.node = node;
|
|
128
196
|
if (onRenderMetrics) noteRender(existing, b, t0);
|
|
129
197
|
existing.html = b.html;
|
|
198
|
+
existing.block = b;
|
|
130
199
|
existing.open = b.open;
|
|
131
200
|
existing.speculative = b.speculative;
|
|
132
201
|
existing.kind = b.kind.type;
|
|
@@ -137,6 +206,8 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
137
206
|
if (!seen.has(id)) {
|
|
138
207
|
if (mb.highlight) mb.highlight.cancel();
|
|
139
208
|
mb.inc = void 0;
|
|
209
|
+
mb.codeInc = void 0;
|
|
210
|
+
mb.plainCode = void 0;
|
|
140
211
|
mb.node.remove();
|
|
141
212
|
mounted.delete(id);
|
|
142
213
|
}
|
|
@@ -206,13 +277,13 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
206
277
|
if (data) return buildKeyedTable(b, data, mb);
|
|
207
278
|
}
|
|
208
279
|
if (b.open && kind === "List" && !hasInlineTransforms) {
|
|
209
|
-
const keyed = renderKeyedList(b);
|
|
280
|
+
const keyed = renderKeyedList(b, mb);
|
|
210
281
|
if (keyed) return keyed;
|
|
211
282
|
}
|
|
212
283
|
const node = document.createElement("div");
|
|
213
284
|
node.className = genericClassName(b);
|
|
214
285
|
if (b.open && !sanitize && !hasInlineTransforms && (kind === "Blockquote" || kind === "Alert")) {
|
|
215
|
-
const wrapper = renderKeyedContainer(b);
|
|
286
|
+
const wrapper = renderKeyedContainer(b, mb);
|
|
216
287
|
if (wrapper) {
|
|
217
288
|
node.appendChild(wrapper);
|
|
218
289
|
return node;
|
|
@@ -224,47 +295,122 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
224
295
|
lastRenderGeneric = !sanitize && !hasInlineTransforms;
|
|
225
296
|
return node;
|
|
226
297
|
}
|
|
227
|
-
function renderKeyedList(b) {
|
|
298
|
+
function renderKeyedList(b, mb) {
|
|
228
299
|
const ld = b.kind.data;
|
|
229
300
|
const items = ld?.items;
|
|
230
301
|
if (!Array.isArray(items) || items.length === 0) return null;
|
|
231
302
|
const node = document.createElement("div");
|
|
232
|
-
node.className =
|
|
233
|
-
const
|
|
234
|
-
|
|
303
|
+
node.className = genericClassName(b);
|
|
304
|
+
const ordered = !!ld?.ordered;
|
|
305
|
+
const list = document.createElement(ordered ? "ol" : "ul");
|
|
306
|
+
if (ordered && ld.start !== void 0 && ld.start !== 1) {
|
|
235
307
|
list.setAttribute("start", String(ld.start));
|
|
236
308
|
}
|
|
237
|
-
|
|
309
|
+
const rendered = new Array(items.length);
|
|
310
|
+
for (let i = 0; i < items.length; i++) {
|
|
238
311
|
const li = document.createElement("li");
|
|
239
|
-
li.innerHTML = sanitize ? sanitize(
|
|
312
|
+
li.innerHTML = sanitize ? sanitize(items[i].html) : items[i].html;
|
|
240
313
|
list.appendChild(li);
|
|
314
|
+
rendered[i] = items[i].html;
|
|
241
315
|
}
|
|
242
316
|
node.appendChild(list);
|
|
317
|
+
mb.list = { list, ordered, start: ld?.start, items: rendered };
|
|
243
318
|
return node;
|
|
244
319
|
}
|
|
245
|
-
function
|
|
320
|
+
function syncKeyedList(km, ld) {
|
|
321
|
+
keyedAttempts++;
|
|
322
|
+
const items = ld.items;
|
|
323
|
+
if (!Array.isArray(items) || items.length === 0) return false;
|
|
324
|
+
if (!!ld.ordered !== km.ordered) return false;
|
|
325
|
+
const cur = km.items;
|
|
326
|
+
const kids = km.list.children;
|
|
327
|
+
if (kids.length !== cur.length) return false;
|
|
328
|
+
if (ld.start !== km.start) {
|
|
329
|
+
if (km.ordered && ld.start !== void 0 && ld.start !== 1) {
|
|
330
|
+
km.list.setAttribute("start", String(ld.start));
|
|
331
|
+
} else {
|
|
332
|
+
km.list.removeAttribute("start");
|
|
333
|
+
}
|
|
334
|
+
km.start = ld.start;
|
|
335
|
+
}
|
|
336
|
+
const n = items.length;
|
|
337
|
+
while (cur.length > n) {
|
|
338
|
+
km.list.removeChild(km.list.lastElementChild);
|
|
339
|
+
cur.pop();
|
|
340
|
+
}
|
|
341
|
+
for (let i = 0; i < cur.length; i++) {
|
|
342
|
+
if (cur[i] === items[i].html) continue;
|
|
343
|
+
kids[i].innerHTML = sanitize ? sanitize(items[i].html) : items[i].html;
|
|
344
|
+
cur[i] = items[i].html;
|
|
345
|
+
}
|
|
346
|
+
for (let i = cur.length; i < n; i++) {
|
|
347
|
+
const li = document.createElement("li");
|
|
348
|
+
li.innerHTML = sanitize ? sanitize(items[i].html) : items[i].html;
|
|
349
|
+
km.list.appendChild(li);
|
|
350
|
+
cur.push(items[i].html);
|
|
351
|
+
}
|
|
352
|
+
keyedHits++;
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
function renderKeyedContainer(b, mb) {
|
|
246
356
|
const nested = b.kind.data?.nested;
|
|
247
357
|
if (!Array.isArray(nested)) return null;
|
|
248
358
|
const tagName = b.kind.type === "Alert" ? "div" : "blockquote";
|
|
249
359
|
const wrapper = document.createElement(tagName);
|
|
250
360
|
applyOpenTagAttrs(wrapper, b.html);
|
|
361
|
+
let offset = 0;
|
|
362
|
+
let title = "";
|
|
251
363
|
if (b.kind.type === "Alert") {
|
|
252
|
-
|
|
364
|
+
title = alertTitleHtml(b.html);
|
|
253
365
|
if (title) {
|
|
254
366
|
const t = document.createElement("div");
|
|
255
367
|
t.innerHTML = title;
|
|
256
368
|
const titleNode = t.firstElementChild;
|
|
257
|
-
if (titleNode)
|
|
369
|
+
if (titleNode) {
|
|
370
|
+
wrapper.appendChild(titleNode);
|
|
371
|
+
offset = 1;
|
|
372
|
+
}
|
|
258
373
|
}
|
|
259
374
|
}
|
|
375
|
+
const rendered = new Array(nested.length);
|
|
260
376
|
for (let i = 0; i < nested.length; i++) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const inner = child.firstElementChild;
|
|
264
|
-
wrapper.appendChild(inner ?? child);
|
|
377
|
+
wrapper.appendChild(nestedChild(nested[i].html));
|
|
378
|
+
rendered[i] = nested[i].html;
|
|
265
379
|
}
|
|
380
|
+
mb.container = { wrapper, offset, openTag: openTagOf(b.html), title, nested: rendered };
|
|
266
381
|
return wrapper;
|
|
267
382
|
}
|
|
383
|
+
function nestedChild(html) {
|
|
384
|
+
const child = document.createElement("div");
|
|
385
|
+
child.innerHTML = html;
|
|
386
|
+
return child.firstElementChild ?? child;
|
|
387
|
+
}
|
|
388
|
+
function syncKeyedContainer(kc, b) {
|
|
389
|
+
keyedAttempts++;
|
|
390
|
+
const nested = b.kind.data?.nested;
|
|
391
|
+
if (!Array.isArray(nested)) return false;
|
|
392
|
+
if (openTagOf(b.html) !== kc.openTag) return false;
|
|
393
|
+
if (b.kind.type === "Alert" && alertTitleHtml(b.html) !== kc.title) return false;
|
|
394
|
+
const cur = kc.nested;
|
|
395
|
+
const kids = kc.wrapper.children;
|
|
396
|
+
if (kids.length !== kc.offset + cur.length) return false;
|
|
397
|
+
const n = nested.length;
|
|
398
|
+
while (cur.length > n) {
|
|
399
|
+
kc.wrapper.removeChild(kc.wrapper.lastElementChild);
|
|
400
|
+
cur.pop();
|
|
401
|
+
}
|
|
402
|
+
for (let i = 0; i < cur.length; i++) {
|
|
403
|
+
if (cur[i] === nested[i].html) continue;
|
|
404
|
+
kc.wrapper.replaceChild(nestedChild(nested[i].html), kids[kc.offset + i]);
|
|
405
|
+
cur[i] = nested[i].html;
|
|
406
|
+
}
|
|
407
|
+
for (let i = cur.length; i < n; i++) {
|
|
408
|
+
kc.wrapper.appendChild(nestedChild(nested[i].html));
|
|
409
|
+
cur.push(nested[i].html);
|
|
410
|
+
}
|
|
411
|
+
keyedHits++;
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
268
414
|
function buildKeyedTable(b, data, mb) {
|
|
269
415
|
const node = document.createElement("div");
|
|
270
416
|
node.className = "brook-block brook-block-table brook-open" + (b.speculative ? " brook-speculative" : "");
|
|
@@ -352,6 +498,8 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
352
498
|
}
|
|
353
499
|
function renderCodeBlock(b, mb) {
|
|
354
500
|
const lang = extractLang(b.html) || "text";
|
|
501
|
+
mb.codeInc = void 0;
|
|
502
|
+
mb.plainCode = void 0;
|
|
355
503
|
const text = b.open ? "" : codeText(b);
|
|
356
504
|
let openMarkup = null;
|
|
357
505
|
if (b.open && streamingHighlight) {
|
|
@@ -386,7 +534,9 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
386
534
|
const body = document.createElement("div");
|
|
387
535
|
body.className = "brook-code-body";
|
|
388
536
|
if (highlighted !== null) {
|
|
389
|
-
body.appendChild(
|
|
537
|
+
body.appendChild(
|
|
538
|
+
openMarkup !== null && mb.inc !== void 0 && !fullRebuild ? incPre(lang, mb, mb.inc, openMarkup) : highlightedPre(lang, highlighted)
|
|
539
|
+
);
|
|
390
540
|
} else {
|
|
391
541
|
const div = document.createElement("div");
|
|
392
542
|
div.tabIndex = 0;
|
|
@@ -394,12 +544,14 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
394
544
|
div.setAttribute("aria-label", `${lang} code`);
|
|
395
545
|
div.innerHTML = b.html;
|
|
396
546
|
body.appendChild(div);
|
|
547
|
+
if (b.open) mb.plainCode = div;
|
|
397
548
|
if (run !== null && run.rest !== null) {
|
|
398
549
|
mb.highlight = run;
|
|
399
550
|
run.rest.then((markup) => {
|
|
400
551
|
if (markup === null || dead) return;
|
|
401
552
|
if (mb.highlight !== run || mb.node !== block) return;
|
|
402
553
|
mb.highlight = void 0;
|
|
554
|
+
mb.plainCode = void 0;
|
|
403
555
|
body.replaceChild(highlightedPre(lang, markup), div);
|
|
404
556
|
});
|
|
405
557
|
}
|
|
@@ -407,6 +559,25 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
407
559
|
block.appendChild(body);
|
|
408
560
|
return block;
|
|
409
561
|
}
|
|
562
|
+
function syncIncCode(mb, b) {
|
|
563
|
+
const ic = mb.codeInc;
|
|
564
|
+
if ((extractLang(b.html) || "text") !== ic.lang) return false;
|
|
565
|
+
const markup = incHighlight(mb.inc, codeText(b));
|
|
566
|
+
if (markup === null) return false;
|
|
567
|
+
return paintIncCode(ic, mb.inc, markup);
|
|
568
|
+
}
|
|
569
|
+
function incPre(lang, mb, st, markup) {
|
|
570
|
+
const pre = document.createElement("pre");
|
|
571
|
+
pre.tabIndex = 0;
|
|
572
|
+
pre.setAttribute("role", "region");
|
|
573
|
+
pre.setAttribute("aria-label", `${lang} code`);
|
|
574
|
+
const code = document.createElement("code");
|
|
575
|
+
const ic = newIncCode(code, lang, st);
|
|
576
|
+
if (paintIncCode(ic, st, markup)) mb.codeInc = ic;
|
|
577
|
+
else code.innerHTML = markup;
|
|
578
|
+
pre.appendChild(code);
|
|
579
|
+
return pre;
|
|
580
|
+
}
|
|
410
581
|
function highlightedPre(lang, markup) {
|
|
411
582
|
const pre = document.createElement("pre");
|
|
412
583
|
pre.tabIndex = 0;
|
|
@@ -515,6 +686,8 @@ function mountBrookMarkdown(client, container, options = {}) {
|
|
|
515
686
|
mb.highlight = void 0;
|
|
516
687
|
}
|
|
517
688
|
mb.inc = void 0;
|
|
689
|
+
mb.codeInc = void 0;
|
|
690
|
+
mb.plainCode = void 0;
|
|
518
691
|
}
|
|
519
692
|
root.remove();
|
|
520
693
|
},
|
|
@@ -608,9 +781,12 @@ function decodeCodeText(html) {
|
|
|
608
781
|
return m[1].replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
609
782
|
}
|
|
610
783
|
const CONTAINER_ATTR_RE = /([a-zA-Z][a-zA-Z0-9-]*)="([^"]*)"/g;
|
|
611
|
-
function
|
|
784
|
+
function openTagOf(html) {
|
|
612
785
|
const gt = html.indexOf(">");
|
|
613
|
-
|
|
786
|
+
return gt < 0 ? html : html.slice(0, gt);
|
|
787
|
+
}
|
|
788
|
+
function applyOpenTagAttrs(el, html) {
|
|
789
|
+
const open = openTagOf(html);
|
|
614
790
|
let m;
|
|
615
791
|
CONTAINER_ATTR_RE.lastIndex = 0;
|
|
616
792
|
while (m = CONTAINER_ATTR_RE.exec(open)) {
|
|
@@ -669,6 +845,8 @@ function applyUrlTransformDom(root, urlTransform) {
|
|
|
669
845
|
});
|
|
670
846
|
}
|
|
671
847
|
export {
|
|
848
|
+
__keyedStats,
|
|
849
|
+
__resetKeyedStats,
|
|
672
850
|
mountBrookMarkdown,
|
|
673
851
|
tailOpenBlockId
|
|
674
852
|
};
|
package/dist/element.js
CHANGED
|
@@ -20,7 +20,8 @@ const CONFIG_ATTRS = [
|
|
|
20
20
|
"soft-breaks",
|
|
21
21
|
"a11y",
|
|
22
22
|
"unsafe-html",
|
|
23
|
-
"block-html"
|
|
23
|
+
"block-html",
|
|
24
|
+
"retain-committed-html"
|
|
24
25
|
];
|
|
25
26
|
function defineBrookMarkdown(tag = "brook-markdown") {
|
|
26
27
|
if (typeof customElements === "undefined") return;
|
|
@@ -162,6 +163,7 @@ function defineBrookMarkdown(tag = "brook-markdown") {
|
|
|
162
163
|
set("a11y", "a11y");
|
|
163
164
|
set("unsafe-html", "unsafeHtml");
|
|
164
165
|
set("block-html", "blockHtml");
|
|
166
|
+
set("retain-committed-html", "retainCommittedHtml");
|
|
165
167
|
const tags = this.getAttribute("component-tags");
|
|
166
168
|
if (tags !== null) {
|
|
167
169
|
const list = tags.split(/[\s,]+/).filter(Boolean);
|
package/dist/hi-inc.d.ts
CHANGED
|
@@ -53,6 +53,31 @@ export interface IncState {
|
|
|
53
53
|
c: number;
|
|
54
54
|
/** Markup for `[0, c)`. Always a byte-prefix of the block's final markup. */
|
|
55
55
|
frozenHtml: string;
|
|
56
|
+
/**
|
|
57
|
+
* Bumped whenever `frozenHtml` is TRUNCATED or cleared — the one checkpoint of
|
|
58
|
+
* rewind {@link adopt} performs, or a restart. It never moves while the prefix
|
|
59
|
+
* merely GROWS.
|
|
60
|
+
*
|
|
61
|
+
* A renderer that mirrors the frozen prefix into the DOM append-only reads it
|
|
62
|
+
* as the "may I splice?" token: same rev ⇒ the prefix only grew, so appending
|
|
63
|
+
* `frozenHtml.slice(alreadyWritten)` is exact; a changed rev means the prefix
|
|
64
|
+
* was rewritten underneath and the mirror must be re-seeded. Length alone is
|
|
65
|
+
* NOT enough — one call can rewind to `c0` and then re-freeze past the old
|
|
66
|
+
* length, which looks like a plain append but is not one.
|
|
67
|
+
*/
|
|
68
|
+
frozenRev: number;
|
|
69
|
+
/**
|
|
70
|
+
* The length `frozenHtml` was TRUNCATED to at the last {@link frozenRev} bump:
|
|
71
|
+
* `frozenLen0` for {@link adopt}'s one-checkpoint rewind, `0` for a restart.
|
|
72
|
+
*
|
|
73
|
+
* The rev alone says "rewritten"; this says *from where*, which is what lets a
|
|
74
|
+
* DOM mirror rewind to a boundary it already holds instead of re-seeding the
|
|
75
|
+
* whole run. It is load-bearing that this is reported rather than inferred:
|
|
76
|
+
* `adopt` frequently rewinds and then re-freezes PAST the old length within
|
|
77
|
+
* the same call, so the observable `frozenHtml.length` can come back unchanged
|
|
78
|
+
* while its bytes have moved.
|
|
79
|
+
*/
|
|
80
|
+
frozenCut: number;
|
|
56
81
|
/** The checkpoint BEFORE `c`, and the `frozenHtml` length that went with it —
|
|
57
82
|
* the one step of rewind a tail revision needs (see {@link adopt}). */
|
|
58
83
|
c0: number;
|
package/dist/hi-inc.js
CHANGED
|
@@ -72,6 +72,8 @@ function createInc(lang) {
|
|
|
72
72
|
lang: key,
|
|
73
73
|
c: 0,
|
|
74
74
|
frozenHtml: "",
|
|
75
|
+
frozenRev: 0,
|
|
76
|
+
frozenCut: 0,
|
|
75
77
|
c0: 0,
|
|
76
78
|
frozenLen0: 0,
|
|
77
79
|
opener: null,
|
|
@@ -97,6 +99,8 @@ function adopt(st, d) {
|
|
|
97
99
|
}
|
|
98
100
|
if (st.c0 > 0 && d >= st.c0 + GAP) {
|
|
99
101
|
st.frozenHtml = st.frozenHtml.slice(0, st.frozenLen0);
|
|
102
|
+
st.frozenRev++;
|
|
103
|
+
st.frozenCut = st.frozenLen0;
|
|
100
104
|
st.c = st.c0;
|
|
101
105
|
st.c0 = 0;
|
|
102
106
|
st.frozenLen0 = 0;
|
|
@@ -117,6 +121,8 @@ function dropTail(st) {
|
|
|
117
121
|
function reset(st) {
|
|
118
122
|
st.c = 0;
|
|
119
123
|
st.frozenHtml = "";
|
|
124
|
+
st.frozenRev++;
|
|
125
|
+
st.frozenCut = 0;
|
|
120
126
|
st.c0 = 0;
|
|
121
127
|
st.frozenLen0 = 0;
|
|
122
128
|
st.opener = null;
|
package/dist/html-to-react.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ type HNode = {
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function parseStyle(css: string): Record<string, string>;
|
|
21
21
|
export declare function getParseCount(): number;
|
|
22
|
+
/** @internal Test-only. Characters of markup handed to the tokenizer so far. */
|
|
23
|
+
export declare function getParseChars(): number;
|
|
22
24
|
export declare function resetParseCount(): void;
|
|
23
25
|
export declare function parseTrustedHtml(html: string): HNode[];
|
|
24
26
|
/**
|
package/dist/html-to-react.js
CHANGED
|
@@ -131,14 +131,20 @@ function parseOpenTag(html, start) {
|
|
|
131
131
|
return { tag, attrs, selfClose: false, next: i };
|
|
132
132
|
}
|
|
133
133
|
let parseCount = 0;
|
|
134
|
+
let parseChars = 0;
|
|
134
135
|
function getParseCount() {
|
|
135
136
|
return parseCount;
|
|
136
137
|
}
|
|
138
|
+
function getParseChars() {
|
|
139
|
+
return parseChars;
|
|
140
|
+
}
|
|
137
141
|
function resetParseCount() {
|
|
138
142
|
parseCount = 0;
|
|
143
|
+
parseChars = 0;
|
|
139
144
|
}
|
|
140
145
|
function parseTrustedHtml(html) {
|
|
141
146
|
parseCount++;
|
|
147
|
+
parseChars += html.length;
|
|
142
148
|
const root = [];
|
|
143
149
|
const stack = [];
|
|
144
150
|
let i = 0;
|
|
@@ -357,6 +363,7 @@ function wrapLink(text, attrs) {
|
|
|
357
363
|
}
|
|
358
364
|
export {
|
|
359
365
|
decodeEntities,
|
|
366
|
+
getParseChars,
|
|
360
367
|
getParseCount,
|
|
361
368
|
htmlToReact,
|
|
362
369
|
parseStyle,
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* // ... wherever your tokens land: client.append(deltaText);
|
|
16
16
|
* client.finalize();
|
|
17
17
|
*/
|
|
18
|
-
export { BrookClient, BrookPool, getDefaultPool } from "./client.js";
|
|
18
|
+
export { BrookClient, BrookPool, getDefaultPool, sourceFingerprint } from "./client.js";
|
|
19
|
+
export type { PersistableSnapshot } from "./client.js";
|
|
19
20
|
export { BrookMarkdown, useBrookStream, useBrookMarkdownString } from "./react.js";
|
|
20
21
|
export { highlight, supportedLangs } from "./hi.js";
|
|
21
22
|
export { htmlToReact, parseTrustedHtml, safeUrl, wrapLink } from "./html-to-react.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BrookClient, BrookPool, getDefaultPool } from "./client.js";
|
|
1
|
+
import { BrookClient, BrookPool, getDefaultPool, sourceFingerprint } from "./client.js";
|
|
2
2
|
import { BrookMarkdown, useBrookStream, useBrookMarkdownString } from "./react.js";
|
|
3
3
|
import { highlight, supportedLangs } from "./hi.js";
|
|
4
4
|
import { htmlToReact, parseTrustedHtml, safeUrl, wrapLink } from "./html-to-react.js";
|
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
htmlToReact,
|
|
12
12
|
parseTrustedHtml,
|
|
13
13
|
safeUrl,
|
|
14
|
+
sourceFingerprint,
|
|
14
15
|
supportedLangs,
|
|
15
16
|
useBrookMarkdownString,
|
|
16
17
|
useBrookStream,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type MutableRefObject } from "react";
|
|
2
|
+
import type { Block } from "./types-core.js";
|
|
3
|
+
/**
|
|
4
|
+
* Let a layout effect own an element's children so an OPEN block's patch is
|
|
5
|
+
* applied incrementally (see splice.ts) instead of re-setting the whole
|
|
6
|
+
* `dangerouslySetInnerHTML` every time it grows.
|
|
7
|
+
*
|
|
8
|
+
* ## How React and the effect share the node
|
|
9
|
+
*
|
|
10
|
+
* The returned string is the html captured on the FIRST render and never
|
|
11
|
+
* changes again. Render it as the node's `__html` and React writes the element
|
|
12
|
+
* exactly once, at mount — its own `lastHtml !== nextHtml` check then keeps it
|
|
13
|
+
* from touching the children on any later commit, and the effect owns them from
|
|
14
|
+
* there.
|
|
15
|
+
*
|
|
16
|
+
* That is what makes this safe under concurrent rendering: a render that is
|
|
17
|
+
* thrown away commits nothing and runs no effect, and the effect's own
|
|
18
|
+
* bookkeeping makes a repeat run (StrictMode's double-invoke) a no-op. It also
|
|
19
|
+
* leaves SSR and hydration byte-identical, because the first markup React
|
|
20
|
+
* produces is still the block's full html.
|
|
21
|
+
*
|
|
22
|
+
* The caller hands the node BACK to React by rendering a different element
|
|
23
|
+
* (a closed block's plain `<div dangerouslySetInnerHTML>`), which remounts the
|
|
24
|
+
* subtree and re-renders the settled html in one pass.
|
|
25
|
+
*
|
|
26
|
+
* @param hostRef ref attached to the element whose children are managed
|
|
27
|
+
* @param block the block's CURRENT version (identity matters — `spliceKeep`
|
|
28
|
+
* is keyed on it)
|
|
29
|
+
* @param enabled false to stay entirely out of the way
|
|
30
|
+
* @returns the html to render as `__html`, or `null` when not managing
|
|
31
|
+
*/
|
|
32
|
+
export declare function useHtmlSplice(hostRef: MutableRefObject<HTMLElement | null>, block: Block | undefined, enabled: boolean): string | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
2
|
+
import { spliceHtml, spliceKeep } from "./splice.js";
|
|
3
|
+
const useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
4
|
+
function useHtmlSplice(hostRef, block, enabled) {
|
|
5
|
+
const seed = useRef(block);
|
|
6
|
+
const applied = useRef(null);
|
|
7
|
+
useIsoLayoutEffect(() => {
|
|
8
|
+
const base = seed.current;
|
|
9
|
+
if (!enabled || block === void 0 || base === void 0) {
|
|
10
|
+
applied.current = null;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const node = hostRef.current;
|
|
14
|
+
if (node === null) return;
|
|
15
|
+
let prev = applied.current;
|
|
16
|
+
if (prev === null || prev.node !== node) prev = { node, block: base };
|
|
17
|
+
if (prev.block === block) {
|
|
18
|
+
applied.current = prev;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const keep = spliceKeep(prev.block, block);
|
|
22
|
+
if (keep !== void 0 && spliceHtml(node, prev.block.html, block.html, keep)) {
|
|
23
|
+
applied.current = { node, block };
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
node.innerHTML = block.html;
|
|
27
|
+
applied.current = { node, block };
|
|
28
|
+
});
|
|
29
|
+
return enabled && seed.current !== void 0 ? seed.current.html : null;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
useHtmlSplice
|
|
33
|
+
};
|
package/dist/react.d.ts
CHANGED
|
@@ -144,6 +144,15 @@ interface BrookMarkdownProps {
|
|
|
144
144
|
* the block — an override bypasses the built-in highlighter entirely.
|
|
145
145
|
*/
|
|
146
146
|
streamingHighlight?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* @internal TEST-ONLY. Turn off the incremental apply paths (the open code
|
|
149
|
+
* block's frozen/tail mirror and the open generic block's delta splice) so
|
|
150
|
+
* every patch re-renders the whole block through React, exactly as it did
|
|
151
|
+
* before they existed. The DOM-parity fuzz renders one tree with it on and one
|
|
152
|
+
* with it off and asserts their markup matches after every commit. Not part of
|
|
153
|
+
* the supported API.
|
|
154
|
+
*/
|
|
155
|
+
__fullRebuild?: boolean;
|
|
147
156
|
/** Appended to the root's `className` (the `brook-md` class is always present). */
|
|
148
157
|
className?: string;
|
|
149
158
|
/** Set on the root element. */
|
|
@@ -281,6 +290,8 @@ interface BlockViewProps {
|
|
|
281
290
|
sanitize?: (html: string) => string;
|
|
282
291
|
childMemo?: boolean;
|
|
283
292
|
streamingHighlight?: boolean;
|
|
293
|
+
/** @internal TEST-ONLY — see BrookMarkdownProps.__fullRebuild. */
|
|
294
|
+
__fullRebuild?: boolean;
|
|
284
295
|
onRenderMetrics?: RenderMetricsHook;
|
|
285
296
|
decorators?: Decorator[];
|
|
286
297
|
urlTransform?: UrlTransform;
|