brookmd 0.22.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 +1229 -0
- package/LICENSE +21 -0
- package/README.md +1265 -0
- package/dist/block-props.d.ts +18 -0
- package/dist/block-props.js +75 -0
- package/dist/client.d.ts +370 -0
- package/dist/client.js +754 -0
- package/dist/decorate.d.ts +24 -0
- package/dist/decorate.js +71 -0
- package/dist/dom.d.ts +130 -0
- package/dist/dom.js +627 -0
- package/dist/element.d.ts +20 -0
- package/dist/element.js +288 -0
- package/dist/hi.d.ts +12 -0
- package/dist/hi.js +215 -0
- package/dist/html-to-react.d.ts +61 -0
- package/dist/html-to-react.js +338 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +18 -0
- package/dist/morph.d.ts +28 -0
- package/dist/morph.js +166 -0
- package/dist/react.d.ts +236 -0
- package/dist/react.js +539 -0
- package/dist/renderers/CodeBlock.d.ts +7 -0
- package/dist/renderers/CodeBlock.js +75 -0
- package/dist/renderers/Math.d.ts +14 -0
- package/dist/renderers/Math.js +15 -0
- package/dist/renderers/Mermaid.d.ts +13 -0
- package/dist/renderers/Mermaid.js +15 -0
- package/dist/server-react.d.ts +32 -0
- package/dist/server-react.js +48 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.js +82 -0
- package/dist/solid.d.ts +104 -0
- package/dist/solid.js +54 -0
- package/dist/styles.css +188 -0
- package/dist/svelte.d.ts +80 -0
- package/dist/svelte.js +59 -0
- package/dist/types-core.d.ts +436 -0
- package/dist/types-core.js +0 -0
- package/dist/types-react.d.ts +13 -0
- package/dist/types-react.js +0 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/url-safety.d.ts +12 -0
- package/dist/url-safety.js +45 -0
- package/dist/vue.d.ts +94 -0
- package/dist/vue.js +79 -0
- package/dist/wasm/LICENSE +21 -0
- package/dist/wasm/README.md +71 -0
- package/dist/wasm/brook_md_core.d.ts +166 -0
- package/dist/wasm/brook_md_core.js +512 -0
- package/dist/wasm/brook_md_core_bg.wasm +0 -0
- package/dist/wasm/brook_md_core_bg.wasm.d.ts +26 -0
- package/dist/worker-core.d.ts +65 -0
- package/dist/worker-core.js +155 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +49 -0
- package/package.json +87 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Decorator } from "./types-core.js";
|
|
2
|
+
/** Ancestor tags inside which decoration is skipped by default. */
|
|
3
|
+
export declare const DEFAULT_SKIP: readonly string[];
|
|
4
|
+
/** A run of the original text, or one matched span the caller turns into a node. */
|
|
5
|
+
export type DecorateSegment = {
|
|
6
|
+
type: "text";
|
|
7
|
+
text: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: "match";
|
|
10
|
+
decorator: Decorator;
|
|
11
|
+
matchText: string;
|
|
12
|
+
groups: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Split one inline text node's string into segments per the active decorators.
|
|
16
|
+
* Decorators apply in order; each one only re-scans the still-TEXT segments left
|
|
17
|
+
* by the previous ones (so a match is never decorated twice). `ancestors` is the
|
|
18
|
+
* chain of enclosing tag names — a decorator whose `skipInside` (default
|
|
19
|
+
* {@link DEFAULT_SKIP}) intersects it is not applied here.
|
|
20
|
+
*
|
|
21
|
+
* Returns `null` when nothing matched at all, so the caller can take the
|
|
22
|
+
* zero-allocation fast path of emitting the original text node unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decorateSegments(text: string, decorators: Decorator[], ancestors: string[]): DecorateSegment[] | null;
|
package/dist/decorate.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const DEFAULT_SKIP = ["a", "code", "pre", "kbd"];
|
|
2
|
+
function escapeRegExp(s) {
|
|
3
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4
|
+
}
|
|
5
|
+
function toGlobalRegExp(match) {
|
|
6
|
+
if (typeof match === "string") return new RegExp(escapeRegExp(match), "g");
|
|
7
|
+
let flags = match.flags.replace("y", "");
|
|
8
|
+
if (!flags.includes("g")) flags += "g";
|
|
9
|
+
return new RegExp(match.source, flags);
|
|
10
|
+
}
|
|
11
|
+
function skippedByAncestor(ancestors, skip) {
|
|
12
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
13
|
+
const a = ancestors[i].toLowerCase();
|
|
14
|
+
for (let j = 0; j < skip.length; j++) {
|
|
15
|
+
if (a === skip[j].toLowerCase()) return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function splitOne(text, dec, out) {
|
|
21
|
+
const re = toGlobalRegExp(dec.match);
|
|
22
|
+
let last = 0;
|
|
23
|
+
let matched = false;
|
|
24
|
+
let m;
|
|
25
|
+
while ((m = re.exec(text)) !== null) {
|
|
26
|
+
const matchText = m[0];
|
|
27
|
+
if (matchText.length === 0) {
|
|
28
|
+
re.lastIndex++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
matched = true;
|
|
32
|
+
if (m.index > last) out.push({ type: "text", text: text.slice(last, m.index) });
|
|
33
|
+
const groups = [];
|
|
34
|
+
for (let i = 1; i < m.length; i++) groups.push(m[i] ?? "");
|
|
35
|
+
out.push({ type: "match", decorator: dec, matchText, groups });
|
|
36
|
+
last = m.index + matchText.length;
|
|
37
|
+
}
|
|
38
|
+
if (!matched) return false;
|
|
39
|
+
if (last < text.length) out.push({ type: "text", text: text.slice(last) });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
function decorateSegments(text, decorators, ancestors) {
|
|
43
|
+
let segments = null;
|
|
44
|
+
for (let d = 0; d < decorators.length; d++) {
|
|
45
|
+
const dec = decorators[d];
|
|
46
|
+
if (skippedByAncestor(ancestors, dec.skipInside ?? DEFAULT_SKIP)) continue;
|
|
47
|
+
const source = segments ?? [{ type: "text", text }];
|
|
48
|
+
let changedThisDecorator = false;
|
|
49
|
+
const next = [];
|
|
50
|
+
for (let i = 0; i < source.length; i++) {
|
|
51
|
+
const seg = source[i];
|
|
52
|
+
if (seg.type !== "text") {
|
|
53
|
+
next.push(seg);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const before = next.length;
|
|
57
|
+
if (splitOne(seg.text, dec, next)) {
|
|
58
|
+
changedThisDecorator = true;
|
|
59
|
+
} else {
|
|
60
|
+
next.length = before;
|
|
61
|
+
next.push(seg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (changedThisDecorator) segments = next;
|
|
65
|
+
}
|
|
66
|
+
return segments;
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
DEFAULT_SKIP,
|
|
70
|
+
decorateSegments
|
|
71
|
+
};
|
package/dist/dom.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { BrookClient } from "./client.js";
|
|
2
|
+
import type { Block, BlockComponentProps, Decorator, RenderMetricsHook, UrlTransform } from "./types-core.js";
|
|
3
|
+
/**
|
|
4
|
+
* Framework-neutral DOM renderer for a {@link BrookClient}. Mounts the streaming
|
|
5
|
+
* document into a container and keeps it in sync via direct DOM mutation,
|
|
6
|
+
* mirroring the JSX renderer's block model: each block is keyed by its stable
|
|
7
|
+
* parser-assigned id, and a committed block's node is reused untouched on every
|
|
8
|
+
* later patch (the parity analogue of the JSX renderer's block memo). Only the
|
|
9
|
+
* streaming tail is rebuilt.
|
|
10
|
+
*
|
|
11
|
+
* This is the foundation the Web Component / Vue / Svelte / Solid bindings
|
|
12
|
+
* build on; it imports only neutral modules and carries no framework dependency.
|
|
13
|
+
*
|
|
14
|
+
* ## Custom components
|
|
15
|
+
*
|
|
16
|
+
* Pass `components` to override a whole block kind (or a component tag). Keys
|
|
17
|
+
* are capitalized block-kind names (`CodeBlock`, `Table`, `Mermaid`…) or, for
|
|
18
|
+
* `Component` blocks, the tag name (e.g. `Thinking`) with `Component` as the
|
|
19
|
+
* generic fallback. A component receives {@link BlockComponentProps} and returns
|
|
20
|
+
* an `HTMLElement` or an HTML string. There is no tag-level override path (no
|
|
21
|
+
* `table`/`a`/`code` keys) — that requires an HTML→tree pass the DOM renderer
|
|
22
|
+
* doesn't carry.
|
|
23
|
+
*/
|
|
24
|
+
export interface MountHandle {
|
|
25
|
+
destroy(): void;
|
|
26
|
+
refresh(): void;
|
|
27
|
+
/**
|
|
28
|
+
* The id of the streaming **tail** block — the one block that may re-render on
|
|
29
|
+
* the next patch (a committed block's node is frozen, so its id never appears
|
|
30
|
+
* here). Returns `null` when no block is open (idle / fully committed).
|
|
31
|
+
*
|
|
32
|
+
* Purely derived from the live snapshot; reading it renders nothing and mutates
|
|
33
|
+
* nothing. It exists so a fine-grained framework binding (Solid `createMemo`,
|
|
34
|
+
* Vue `computed`, Svelte `derived`) can narrow a reactive cell to *just the tail*
|
|
35
|
+
* for its own scheduling/diagnostics — the DOM is already updated by the
|
|
36
|
+
* renderer's own subscribe loop, so this never changes what is drawn.
|
|
37
|
+
*/
|
|
38
|
+
openBlockId(): number | null;
|
|
39
|
+
}
|
|
40
|
+
export type DomBlockComponent = (props: BlockComponentProps) => HTMLElement | string;
|
|
41
|
+
/** Override map: capitalized block-kind / component-tag keys only. */
|
|
42
|
+
export type DomComponents = Record<string, DomBlockComponent>;
|
|
43
|
+
export interface MountOptions {
|
|
44
|
+
components?: DomComponents;
|
|
45
|
+
/**
|
|
46
|
+
* Optional HTML sanitizer applied to every generic block's HTML before it is
|
|
47
|
+
* injected via `innerHTML` — **including the streaming (open/speculative)
|
|
48
|
+
* tail**. The built-in code/math/mermaid renderers operate on already-escaped
|
|
49
|
+
* content and are not run through it (same as the JSX renderer). When omitted,
|
|
50
|
+
* rendering is byte-identical and zero-cost.
|
|
51
|
+
*/
|
|
52
|
+
sanitize?: (html: string) => string;
|
|
53
|
+
/**
|
|
54
|
+
* Skip layout/paint for off-screen *closed* blocks via CSS
|
|
55
|
+
* `content-visibility: auto` (for very long documents). Off by default.
|
|
56
|
+
*/
|
|
57
|
+
virtualize?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Keep a bottom snap target so the view follows the streaming tail. CSS-only:
|
|
60
|
+
* emits a sentinel with `scroll-snap-align: end`; you add
|
|
61
|
+
* `scroll-snap-type: y proximity` to your scroll container. Off by default.
|
|
62
|
+
*/
|
|
63
|
+
stickToBottom?: boolean;
|
|
64
|
+
/** Use the built-in code highlighter. Default true; suppressed when a
|
|
65
|
+
* `components.CodeBlock` override is supplied. */
|
|
66
|
+
highlightCode?: boolean;
|
|
67
|
+
/** Coalesce patches into one DOM write per animation frame. Default true. */
|
|
68
|
+
batch?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Opt-in (default false). When a generic open/streaming block grows, morph its
|
|
71
|
+
* existing DOM subtree **in place** toward the new HTML instead of rebuilding
|
|
72
|
+
* the whole node with `innerHTML`. The browser then only repaints/relayouts
|
|
73
|
+
* the parts that changed, and focus/text-selection inside the streaming tail
|
|
74
|
+
* survive a token append. The default path (full rebuild) is byte-identical
|
|
75
|
+
* and unchanged; this only affects generic blocks rendered via the `innerHTML`
|
|
76
|
+
* fast path (not code/math/mermaid/component overrides). The morphed subtree is
|
|
77
|
+
* equivalent to the rebuilt one. */
|
|
78
|
+
morphOpenBlocks?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Wrap or replace matched inline **text** while streaming (parity with the
|
|
81
|
+
* React `decorators` prop). Each {@link Decorator} runs POST-render over the
|
|
82
|
+
* block's real TEXT nodes via a `TreeWalker` (after `innerHTML`), once per
|
|
83
|
+
* committed block, honoring `skipInside` (default `a`/`code`/`pre`/`kbd`).
|
|
84
|
+
*
|
|
85
|
+
* **Trusted surface.** A decorator's `replace` may return a `Node` or a string;
|
|
86
|
+
* a returned Node is inserted as-is and is NOT sanitized (a `javascript:` href
|
|
87
|
+
* on a user-built `<a>` reaches the DOM). Route hrefs through the exported
|
|
88
|
+
* {@link safeUrl}. Enabling decorators moves a block onto the walk path (off the
|
|
89
|
+
* `innerHTML`/prefix-append/morph fast paths) — still O(n) per block.
|
|
90
|
+
*/
|
|
91
|
+
decorators?: Decorator[];
|
|
92
|
+
/**
|
|
93
|
+
* Rewrite `href`/`src`/`poster` URLs as blocks render (parity with the React
|
|
94
|
+
* `urlTransform` prop). The output is re-sanitized
|
|
95
|
+
* (`safeUrl(urlTransform(safeUrl(v)))`) so it can never introduce a dangerous
|
|
96
|
+
* scheme. O(1) per attribute.
|
|
97
|
+
*/
|
|
98
|
+
urlTransform?: UrlTransform;
|
|
99
|
+
/** Appended to the root's `className` (the `brook-md` class is always present). */
|
|
100
|
+
className?: string;
|
|
101
|
+
/** Set on the root element. */
|
|
102
|
+
id?: string;
|
|
103
|
+
/** Set on the root element (e.g. `"article"`, `"log"`). */
|
|
104
|
+
role?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Make the root a live region so screen readers announce streamed content.
|
|
107
|
+
* `"polite"` coalesces rapid updates (does not read every token). Off by default.
|
|
108
|
+
*/
|
|
109
|
+
ariaLive?: "off" | "polite" | "assertive";
|
|
110
|
+
/** Live-region atomicity; pair with `ariaLive`. Off by default. */
|
|
111
|
+
ariaAtomic?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Optional render-churn probe. Fires once per ACTUAL node build/rebuild of a
|
|
114
|
+
* block — never for a committed block whose node is reused untouched on a
|
|
115
|
+
* tail-only patch. The callback gets the block id and a {@link RenderMetrics}
|
|
116
|
+
* sample (per-block `renderCount`/rebuild count, `speculativeToggleCount`,
|
|
117
|
+
* `lastRenderMs`, `kind`). Zero overhead when omitted, and advances
|
|
118
|
+
* `client.getMetrics().rebuildCount`.
|
|
119
|
+
*/
|
|
120
|
+
onRenderMetrics?: RenderMetricsHook;
|
|
121
|
+
}
|
|
122
|
+
export declare function mountBrookMarkdown(client: BrookClient, container: HTMLElement, options?: MountOptions): MountHandle;
|
|
123
|
+
/**
|
|
124
|
+
* Derive the streaming tail's block id from an ordered snapshot: the id of the
|
|
125
|
+
* last block when it is open, else `null`. The open block is always the tail by
|
|
126
|
+
* construction (the parser only keeps the final block speculative/open), so this
|
|
127
|
+
* is an O(1) read of the last element — no scan. Shared so the framework
|
|
128
|
+
* adapters expose the same "what may re-render next" signal as the DOM handle.
|
|
129
|
+
*/
|
|
130
|
+
export declare function tailOpenBlockId(snapshot: readonly Block[]): number | null;
|