@aparte/plugin-streaming-markdown 0.27.4 → 0.28.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/README.md +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -25,6 +25,20 @@ provider registered, core runs the whole string through this parser too. A one-s
|
|
|
25
25
|
`@aparte/plugin-marked`) is optional — register one and finished and restored messages are rendered
|
|
26
26
|
through it, at that renderer's full fidelity.
|
|
27
27
|
|
|
28
|
+
**Maths**: register a maths renderer and formulas are drawn as the reply streams — nothing else to
|
|
29
|
+
configure; without one, `$` and `\(` stay text. A display formula opened by `$$` or `\[` on its own
|
|
30
|
+
line is drawn as it arrives, any other once its closer has come; the rules are Pandoc's, so
|
|
31
|
+
`$5 and $10` stays two prices. See the [maths guide](https://apartejs.dev/guides/math/).
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import 'katex/dist/katex.min.css';
|
|
35
|
+
import { setupKatex } from '@aparte/plugin-katex';
|
|
36
|
+
import { setupStreamingMarkdownProvider } from '@aparte/plugin-streaming-markdown';
|
|
37
|
+
|
|
38
|
+
setupKatex();
|
|
39
|
+
setupStreamingMarkdownProvider();
|
|
40
|
+
```
|
|
41
|
+
|
|
28
42
|
**Security**: the streaming path writes DOM directly, bypassing the one-shot sanitizer, so it enforces
|
|
29
43
|
the URL policy live — a streamed `[x](javascript:…)` never produces a clickable `javascript:` link — and
|
|
30
44
|
a finished message is passed through core's sanitizer either way.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,YAAY,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,YAAY,EAAC,MAAM,cAAc,CAAC;AAuDhF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,GAAE,YAAiC,GAAG,IAAI,CA+E9F"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { aparteGlobalConfig, isSafeUrl } from "@aparte/core";
|
|
2
|
-
import { default_renderer, HREF, SRC, LANG, parser, parser_end, parser_write } from "@paulrichez/streaming-markdown";
|
|
2
|
+
import { default_renderer, Attr, HREF, SRC, LANG, Token, parser, parser_end, parser_write } from "@paulrichez/streaming-markdown";
|
|
3
|
+
const OPEN = "data-aparte-open";
|
|
3
4
|
const EXTERNAL_URL = /^(?:https?:)?[/\\]{2}|^https?:[/\\]/i;
|
|
4
5
|
const CONTROL_WS = /[\u0000-\u0020]+/g;
|
|
5
6
|
const LANG_TAIL = /[^A-Za-z0-9_+#.-].*$/s;
|
|
7
|
+
function asReference(text, display) {
|
|
8
|
+
if (!/^\s*\d+(?:[.,]\d+)?\s*$/.test(text)) return null;
|
|
9
|
+
return display ? `[${text.trim()}]` : `(${text.trim()})`;
|
|
10
|
+
}
|
|
6
11
|
function setupStreamingMarkdownProvider(config = aparteGlobalConfig) {
|
|
7
12
|
config.setStreamingMarkdownProvider((target) => {
|
|
8
13
|
const renderer = default_renderer(target);
|
|
9
14
|
const originalSetAttr = renderer.set_attr;
|
|
10
15
|
renderer.set_attr = (data, type, value) => {
|
|
16
|
+
if (type === Attr.Unclosed) {
|
|
17
|
+
const node = data.nodes[data.index];
|
|
18
|
+
node.replaceWith(`${value === "\\[" ? "[" : value}
|
|
19
|
+
${node.textContent}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
11
22
|
if (type === HREF && !isSafeUrl(value, "a")) return;
|
|
12
23
|
if (type === SRC && !isSafeUrl(value, "img")) return;
|
|
13
24
|
if (type === LANG) {
|
|
@@ -24,13 +35,38 @@ function setupStreamingMarkdownProvider(config = aparteGlobalConfig) {
|
|
|
24
35
|
}
|
|
25
36
|
}
|
|
26
37
|
};
|
|
27
|
-
const
|
|
38
|
+
const math = config.hasCodeLanguageRenderer("math");
|
|
39
|
+
const originalAddToken = renderer.add_token;
|
|
40
|
+
const originalEndToken = renderer.end_token;
|
|
41
|
+
renderer.add_token = (data, type) => {
|
|
42
|
+
if (type !== Token.Equation_Block && type !== Token.Equation_Inline) {
|
|
43
|
+
originalAddToken(data, type);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const code = document.createElement("code");
|
|
47
|
+
const kind = type === Token.Equation_Block ? "math-display" : "math-inline";
|
|
48
|
+
code.className = `language-math ${kind}`;
|
|
49
|
+
code.setAttribute(OPEN, "");
|
|
50
|
+
const parent = data.nodes[data.index];
|
|
51
|
+
data.nodes[++data.index] = parent.appendChild(code);
|
|
52
|
+
};
|
|
53
|
+
renderer.end_token = (data) => {
|
|
54
|
+
const node = data.nodes[data.index];
|
|
55
|
+
originalEndToken(data);
|
|
56
|
+
if (!node.hasAttribute(OPEN)) return;
|
|
57
|
+
node.removeAttribute(OPEN);
|
|
58
|
+
const text = String(node.textContent);
|
|
59
|
+
const reference = asReference(text, node.classList.contains("math-display"));
|
|
60
|
+
if (reference !== null) node.replaceWith(reference);
|
|
61
|
+
};
|
|
62
|
+
const p = parser(renderer, { math });
|
|
28
63
|
return {
|
|
29
64
|
write: (chunk) => {
|
|
30
65
|
parser_write(p, chunk);
|
|
31
66
|
},
|
|
32
67
|
end: () => {
|
|
33
68
|
parser_end(p);
|
|
69
|
+
for (const code of target.querySelectorAll(`code[${OPEN}]`)) code.replaceWith(String(code.textContent));
|
|
34
70
|
}
|
|
35
71
|
};
|
|
36
72
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { aparteGlobalConfig, isSafeUrl , type AparteConfig} from '@aparte/core';\nimport { parser, parser_write, parser_end, default_renderer, HREF, SRC, LANG } from '@paulrichez/streaming-markdown';\n\n/*\n * The external-link rule, copied — deliberately and minimally — from its OWNER,\n * core's `config/sanitize.ts`. Core exports `isSafeUrl` (used above) but not this\n * pair, and the streaming path needs the same answer live: the one-shot\n * re-sanitisation at settle sends a model's external link to its own tab, so\n * without this every streamed link is a bare, clickable, frame-navigating anchor\n * for the whole length of the reply. If core ever exports the predicate, this\n * goes and the import comes back.\n *\n * `//host` and a value written with leading whitespace resolve off-site just the\n * same, which is why the value is normalised before the test — the same\n * normalisation the accept path (`isSafeUrl`) applies. So do two spellings a\n * reader does not expect: a BACKSLASH is a slash to a URL parser on a special\n * scheme, and a SINGLE slash after an explicit scheme differing from the page's\n * enters authority state. Measured with Node's WHATWG URL against base\n * `https://site.example/chat/`: `/\\evil.example` → `https://evil.example/`,\n * `http:/evil.example` and `http:/\\evil.example` → `http://evil.example/`.\n */\nconst EXTERNAL_URL = /^(?:https?:)?[/\\\\]{2}|^https?:[/\\\\]/i;\n// eslint-disable-next-line no-control-regex -- stripping C0 control chars is intentional (anti-obfuscation)\nconst CONTROL_WS = /[\\u0000-\\u0020]+/g;\n\n/*\n * The leading run of a fence's info string that may become a language name.\n *\n * `smd` maps its LANG attribute to `class` and hands the info string over\n * VERBATIM — everything after the three backticks up to the newline, spaces\n * included. So a legal fence dressed model text in core's own recipes\n * (```aparte-approval-option aparte-btn aparte-btn--solid), which is the forgery\n * core's sanitizer comment was written for, and a `position: fixed` one\n * (`.aparte-select-dropdown`, `.aparte-sidebar__scrim`) repainted the page around\n * the chat.\n *\n * Truncated rather than filtered: the first character a language name cannot hold\n * ends the token, so `py<script>` is `py` and never `pyscript`. `+`, `#` and `.`\n * stay because `c++`, `f#` and `asp.net` are languages a highlighter knows. Spelled\n * as the TAIL to remove, so the token is one expression with no branch to miss.\n */\nconst LANG_TAIL = /[^A-Za-z0-9_+#.-].*$/s;\n\n/**\n * Register `@paulrichez/streaming-markdown` as aparté's incremental (streaming) Markdown\n * renderer.\n *\n * Once registered, the chat bubble feeds each streamed token chunk to a\n * persistent incremental parser that parses only the new text and APPENDS DOM\n * nodes — O(n) over the whole message, with no per-token re-parse or\n * `innerHTML` rebuild. Finished / re-rendered messages still go through the\n * one-shot `setMarkdownProvider` (e.g. `@aparte/plugin-marked`).\n *\n * Framework-agnostic — vanilla DOM, no framework imports.\n *\n * **Security**: the streaming path writes DOM nodes directly and therefore\n * bypasses the one-shot `aparteGlobalConfig.sanitizeHtml`. To keep the same URL policy\n * live (an attacker-controlled `[x](javascript:…)` streamed token would produce\n * a clickable `javascript:` link before the final re-render sanitises it), the\n * renderer's `set_attr` is wrapped to drop any `href`/`src` whose scheme fails\n * {@link isSafeUrl}, and to send an external `href` to its own tab\n * (`target=\"_blank\" rel=\"noopener noreferrer\"`) as the one-shot path does — a\n * link is clickable from the moment it streams in, and a bare anchor navigates\n * the frame the chat lives in. The one-shot re-render at `end()` remains the\n * full-fidelity re-sanitisation.\n *\n * Call once at application startup.\n */\nexport function setupStreamingMarkdownProvider(config: AparteConfig = aparteGlobalConfig): void {\n config.setStreamingMarkdownProvider((target: HTMLElement) => {\n const renderer = default_renderer(target);\n const originalSetAttr = renderer.set_attr;\n renderer.set_attr = (data, type, value) => {\n if (type === HREF && !isSafeUrl(value, 'a')) return;\n if (type === SRC && !isSafeUrl(value, 'img')) return;\n if (type === LANG) {\n // `language-<token>` is what every one-shot renderer emits for a\n // fence, so this is not merely a filter: the streamed DOM and the\n // settled DOM stop diverging, `language-*` is the one prefix the\n // sanitizer's class policy deliberately exempts, and\n // `highlightMarkdownFences` can read a streamed fence's language.\n const token = value.trim().replace(LANG_TAIL, '');\n if (token) originalSetAttr(data, type, `language-${token}`);\n return;\n }\n originalSetAttr(data, type, value);\n if (type === HREF && EXTERNAL_URL.test(value.replace(CONTROL_WS, ''))) {\n const node = data.nodes[data.index];\n if (node && node.tagName === 'A') {\n node.setAttribute('target', '_blank');\n node.setAttribute('rel', 'noopener noreferrer');\n }\n }\n };\n const p = parser(renderer);\n return {\n write: (chunk: string): void => { parser_write(p, chunk); },\n end: (): void => { parser_end(p); },\n };\n });\n}\n"],"names":[],"mappings":";;AAqBA,MAAM,eAAe;AAErB,MAAM,aAAa;AAkBnB,MAAM,YAAY;AA2BX,SAAS,+BAA+B,SAAuB,oBAA0B;AAC5F,SAAO,6BAA6B,CAAC,WAAwB;AACzD,UAAM,WAAW,iBAAiB,MAAM;AACxC,UAAM,kBAAkB,SAAS;AACjC,aAAS,WAAW,CAAC,MAAM,MAAM,UAAU;AACvC,UAAI,SAAS,QAAQ,CAAC,UAAU,OAAO,GAAG,EAAG;AAC7C,UAAI,SAAS,OAAO,CAAC,UAAU,OAAO,KAAK,EAAG;AAC9C,UAAI,SAAS,MAAM;AAMf,cAAM,QAAQ,MAAM,KAAA,EAAO,QAAQ,WAAW,EAAE;AAChD,YAAI,MAAO,iBAAgB,MAAM,MAAM,YAAY,KAAK,EAAE;AAC1D;AAAA,MACJ;AACA,sBAAgB,MAAM,MAAM,KAAK;AACjC,UAAI,SAAS,QAAQ,aAAa,KAAK,MAAM,QAAQ,YAAY,EAAE,CAAC,GAAG;AACnE,cAAM,OAAO,KAAK,MAAM,KAAK,KAAK;AAClC,YAAI,QAAQ,KAAK,YAAY,KAAK;AAC9B,eAAK,aAAa,UAAU,QAAQ;AACpC,eAAK,aAAa,OAAO,qBAAqB;AAAA,QAClD;AAAA,MACJ;AAAA,IACJ;AACA,UAAM,IAAI,OAAO,QAAQ;AACzB,WAAO;AAAA,MACH,OAAO,CAAC,UAAwB;AAAE,qBAAa,GAAG,KAAK;AAAA,MAAG;AAAA,MAC1D,KAAK,MAAY;AAAE,mBAAW,CAAC;AAAA,MAAG;AAAA,IAAA;AAAA,EAE1C,CAAC;AACL;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { aparteGlobalConfig, isSafeUrl , type AparteConfig} from '@aparte/core';\nimport { parser, parser_write, parser_end, default_renderer, Attr, HREF, SRC, LANG, Token } from '@paulrichez/streaming-markdown';\n\n/** What core's code-language engine reads as a formula still arriving. */\nconst OPEN = 'data-aparte-open';\n\n/*\n * The external-link rule, copied — deliberately and minimally — from its OWNER,\n * core's `config/sanitize.ts`. Core exports `isSafeUrl` (used above) but not this\n * pair, and the streaming path needs the same answer live: the one-shot\n * re-sanitisation at settle sends a model's external link to its own tab, so\n * without this every streamed link is a bare, clickable, frame-navigating anchor\n * for the whole length of the reply. If core ever exports the predicate, this\n * goes and the import comes back.\n *\n * `//host` and a value written with leading whitespace resolve off-site just the\n * same, which is why the value is normalised before the test — the same\n * normalisation the accept path (`isSafeUrl`) applies. So do two spellings a\n * reader does not expect: a BACKSLASH is a slash to a URL parser on a special\n * scheme, and a SINGLE slash after an explicit scheme differing from the page's\n * enters authority state. Measured with Node's WHATWG URL against base\n * `https://site.example/chat/`: `/\\evil.example` → `https://evil.example/`,\n * `http:/evil.example` and `http:/\\evil.example` → `http://evil.example/`.\n */\nconst EXTERNAL_URL = /^(?:https?:)?[/\\\\]{2}|^https?:[/\\\\]/i;\n// eslint-disable-next-line no-control-regex -- stripping C0 control chars is intentional (anti-obfuscation)\nconst CONTROL_WS = /[\\u0000-\\u0020]+/g;\n\n/*\n * The leading run of a fence's info string that may become a language name.\n *\n * `smd` maps its LANG attribute to `class` and hands the info string over\n * VERBATIM — everything after the three backticks up to the newline, spaces\n * included. So a legal fence dressed model text in core's own recipes\n * (```aparte-approval-option aparte-btn aparte-btn--solid), which is the forgery\n * core's sanitizer comment was written for, and a `position: fixed` one\n * (`.aparte-select-dropdown`, `.aparte-sidebar__scrim`) repainted the page around\n * the chat.\n *\n * Truncated rather than filtered: the first character a language name cannot hold\n * ends the token, so `py<script>` is `py` and never `pyscript`. `+`, `#` and `.`\n * stay because `c++`, `f#` and `asp.net` are languages a highlighter knows. Spelled\n * as the TAIL to remove, so the token is one expression with no branch to miss.\n */\nconst LANG_TAIL = /[^A-Za-z0-9_+#.-].*$/s;\n\n/**\n * A formula that holds nothing but a number is a reference in brackets — `\\[1\\]`,\n * `\\(2\\)` — as `@aparte/plugin-marked` reads it, so it is written back as one.\n */\nfunction asReference(text: string, display: boolean): string | null {\n if (!/^\\s*\\d+(?:[.,]\\d+)?\\s*$/.test(text)) return null;\n return display ? `[${text.trim()}]` : `(${text.trim()})`;\n}\n\n/**\n * Register `@paulrichez/streaming-markdown` as aparté's incremental (streaming) Markdown\n * renderer.\n *\n * Once registered, the chat bubble feeds each streamed token chunk to a\n * persistent incremental parser that parses only the new text and APPENDS DOM\n * nodes — O(n) over the whole message, with no per-token re-parse or\n * `innerHTML` rebuild. Finished / re-rendered messages still go through the\n * one-shot `setMarkdownProvider` (e.g. `@aparte/plugin-marked`).\n *\n * Framework-agnostic — vanilla DOM, no framework imports.\n *\n * **Security**: the streaming path writes DOM nodes directly and therefore\n * bypasses the one-shot `aparteGlobalConfig.sanitizeHtml`. To keep the same URL policy\n * live (an attacker-controlled `[x](javascript:…)` streamed token would produce\n * a clickable `javascript:` link before the final re-render sanitises it), the\n * renderer's `set_attr` is wrapped to drop any `href`/`src` whose scheme fails\n * {@link isSafeUrl}, and to send an external `href` to its own tab\n * (`target=\"_blank\" rel=\"noopener noreferrer\"`) as the one-shot path does — a\n * link is clickable from the moment it streams in, and a bare anchor navigates\n * the frame the chat lives in. The one-shot re-render at `end()` remains the\n * full-fidelity re-sanitisation.\n *\n * Call once at application startup.\n */\nexport function setupStreamingMarkdownProvider(config: AparteConfig = aparteGlobalConfig): void {\n config.setStreamingMarkdownProvider((target: HTMLElement) => {\n const renderer = default_renderer(target);\n const originalSetAttr = renderer.set_attr;\n renderer.set_attr = (data, type, value) => {\n if (type === Attr.Unclosed) {\n // A display block a blank line or a block's line ended before its closer\n // came: it was not maths after all, and reads as text, opener included.\n const node = data.nodes[data.index]!;\n node.replaceWith(`${value === '\\\\[' ? '[' : value}\\n${node.textContent}`);\n return;\n }\n if (type === HREF && !isSafeUrl(value, 'a')) return;\n if (type === SRC && !isSafeUrl(value, 'img')) return;\n if (type === LANG) {\n // `language-<token>` is what every one-shot renderer emits for a\n // fence, so this is not merely a filter: the streamed DOM and the\n // settled DOM stop diverging, `language-*` is the one prefix the\n // sanitizer's class policy deliberately exempts, and\n // `highlightMarkdownFences` can read a streamed fence's language.\n const token = value.trim().replace(LANG_TAIL, '');\n if (token) originalSetAttr(data, type, `language-${token}`);\n return;\n }\n originalSetAttr(data, type, value);\n if (type === HREF && EXTERNAL_URL.test(value.replace(CONTROL_WS, ''))) {\n const node = data.nodes[data.index];\n if (node && node.tagName === 'A') {\n node.setAttribute('target', '_blank');\n node.setAttribute('rel', 'noopener noreferrer');\n }\n }\n };\n /*\n * Maths is read when a maths renderer is registered (`@aparte/plugin-katex`),\n * decided once per reply; without one, `$` and `\\(` stay text. A formula becomes\n * the placeholder core's code-language engine draws — the markup\n * `@aparte/plugin-marked` writes — marked open until its closer arrives. The parser\n * writes one only once it is maths by Pandoc's rules, so a price never becomes one.\n * One the reply ended in the middle of goes back to text, as the settled reply\n * reads it.\n */\n const math = config.hasCodeLanguageRenderer('math');\n const originalAddToken = renderer.add_token;\n const originalEndToken = renderer.end_token;\n renderer.add_token = (data, type) => {\n if (type !== Token.Equation_Block && type !== Token.Equation_Inline) {\n originalAddToken(data, type);\n return;\n }\n // Inside its paragraph, as the parser writes it, so no <pre>: the class is what\n // tells the engine it is a display formula.\n const code = document.createElement('code');\n const kind = type === Token.Equation_Block ? 'math-display' : 'math-inline';\n code.className = `language-math ${kind}`;\n code.setAttribute(OPEN, '');\n const parent = data.nodes[data.index]!;\n data.nodes[++data.index] = parent.appendChild(code);\n };\n renderer.end_token = (data) => {\n const node = data.nodes[data.index]!;\n originalEndToken(data);\n if (!node.hasAttribute(OPEN)) return;\n node.removeAttribute(OPEN);\n const text = String(node.textContent);\n const reference = asReference(text, node.classList.contains('math-display'));\n if (reference !== null) node.replaceWith(reference);\n };\n const p = parser(renderer, { math });\n return {\n write: (chunk: string): void => { parser_write(p, chunk); },\n end: (): void => {\n parser_end(p);\n // The parser closes nothing at the end: a formula still open there never got\n // its closer.\n for (const code of target.querySelectorAll(`code[${OPEN}]`)) code.replaceWith(String(code.textContent));\n },\n };\n });\n}\n"],"names":[],"mappings":";;AAIA,MAAM,OAAO;AAoBb,MAAM,eAAe;AAErB,MAAM,aAAa;AAkBnB,MAAM,YAAY;AAMlB,SAAS,YAAY,MAAc,SAAiC;AAChE,MAAI,CAAC,0BAA0B,KAAK,IAAI,EAAG,QAAO;AAClD,SAAO,UAAU,IAAI,KAAK,KAAA,CAAM,MAAM,IAAI,KAAK,KAAA,CAAM;AACzD;AA2BO,SAAS,+BAA+B,SAAuB,oBAA0B;AAC5F,SAAO,6BAA6B,CAAC,WAAwB;AACzD,UAAM,WAAW,iBAAiB,MAAM;AACxC,UAAM,kBAAkB,SAAS;AACjC,aAAS,WAAW,CAAC,MAAM,MAAM,UAAU;AACvC,UAAI,SAAS,KAAK,UAAU;AAGxB,cAAM,OAAO,KAAK,MAAM,KAAK,KAAK;AAClC,aAAK,YAAY,GAAG,UAAU,QAAQ,MAAM,KAAK;AAAA,EAAK,KAAK,WAAW,EAAE;AACxE;AAAA,MACJ;AACA,UAAI,SAAS,QAAQ,CAAC,UAAU,OAAO,GAAG,EAAG;AAC7C,UAAI,SAAS,OAAO,CAAC,UAAU,OAAO,KAAK,EAAG;AAC9C,UAAI,SAAS,MAAM;AAMf,cAAM,QAAQ,MAAM,KAAA,EAAO,QAAQ,WAAW,EAAE;AAChD,YAAI,MAAO,iBAAgB,MAAM,MAAM,YAAY,KAAK,EAAE;AAC1D;AAAA,MACJ;AACA,sBAAgB,MAAM,MAAM,KAAK;AACjC,UAAI,SAAS,QAAQ,aAAa,KAAK,MAAM,QAAQ,YAAY,EAAE,CAAC,GAAG;AACnE,cAAM,OAAO,KAAK,MAAM,KAAK,KAAK;AAClC,YAAI,QAAQ,KAAK,YAAY,KAAK;AAC9B,eAAK,aAAa,UAAU,QAAQ;AACpC,eAAK,aAAa,OAAO,qBAAqB;AAAA,QAClD;AAAA,MACJ;AAAA,IACJ;AAUA,UAAM,OAAO,OAAO,wBAAwB,MAAM;AAClD,UAAM,mBAAmB,SAAS;AAClC,UAAM,mBAAmB,SAAS;AAClC,aAAS,YAAY,CAAC,MAAM,SAAS;AACjC,UAAI,SAAS,MAAM,kBAAkB,SAAS,MAAM,iBAAiB;AACjE,yBAAiB,MAAM,IAAI;AAC3B;AAAA,MACJ;AAGA,YAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,YAAM,OAAO,SAAS,MAAM,iBAAiB,iBAAiB;AAC9D,WAAK,YAAY,iBAAiB,IAAI;AACtC,WAAK,aAAa,MAAM,EAAE;AAC1B,YAAM,SAAS,KAAK,MAAM,KAAK,KAAK;AACpC,WAAK,MAAM,EAAE,KAAK,KAAK,IAAI,OAAO,YAAY,IAAI;AAAA,IACtD;AACA,aAAS,YAAY,CAAC,SAAS;AAC3B,YAAM,OAAO,KAAK,MAAM,KAAK,KAAK;AAClC,uBAAiB,IAAI;AACrB,UAAI,CAAC,KAAK,aAAa,IAAI,EAAG;AAC9B,WAAK,gBAAgB,IAAI;AACzB,YAAM,OAAO,OAAO,KAAK,WAAW;AACpC,YAAM,YAAY,YAAY,MAAM,KAAK,UAAU,SAAS,cAAc,CAAC;AAC3E,UAAI,cAAc,KAAM,MAAK,YAAY,SAAS;AAAA,IACtD;AACA,UAAM,IAAI,OAAO,UAAU,EAAE,MAAM;AACnC,WAAO;AAAA,MACH,OAAO,CAAC,UAAwB;AAAE,qBAAa,GAAG,KAAK;AAAA,MAAG;AAAA,MAC1D,KAAK,MAAY;AACb,mBAAW,CAAC;AAGZ,mBAAW,QAAQ,OAAO,iBAAiB,QAAQ,IAAI,GAAG,EAAG,MAAK,YAAY,OAAO,KAAK,WAAW,CAAC;AAAA,MAC1G;AAAA,IAAA;AAAA,EAER,CAAC;AACL;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aparte/plugin-streaming-markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"homepage": "https://apartejs.dev/plugins/streaming-markdown/",
|
|
5
5
|
"description": "Incremental (token-by-token) Markdown rendering for aparté via @paulrichez/streaming-markdown, with a live URL-scheme guard.",
|
|
6
6
|
"type": "module",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"node": ">=18"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@paulrichez/streaming-markdown": "^0.3.
|
|
28
|
+
"@paulrichez/streaming-markdown": "^0.3.2"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@aparte/core": ">=0.
|
|
31
|
+
"@aparte/core": ">=0.28.0 <1.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "^5.4.0",
|
|
35
35
|
"vite": "^6.0.0",
|
|
36
|
-
"@aparte/core": "0.
|
|
36
|
+
"@aparte/core": "0.28.0"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"aparte",
|