@immediately-run/sdk 0.43.1 → 0.44.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/dist/components/MDXComponents.cjs +14 -1
- package/dist/components/MDXComponents.cjs.map +1 -1
- package/dist/components/MDXComponents.js +14 -1
- package/dist/components/MDXComponents.js.map +1 -1
- package/dist/components/WikiLink.cjs +19 -17
- package/dist/components/WikiLink.cjs.map +1 -1
- package/dist/components/WikiLink.js +19 -17
- package/dist/components/WikiLink.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/linkSpace.cjs +63 -0
- package/dist/linkSpace.cjs.map +1 -0
- package/dist/linkSpace.d.cts +44 -0
- package/dist/linkSpace.d.ts +44 -0
- package/dist/linkSpace.js +37 -0
- package/dist/linkSpace.js.map +1 -0
- package/dist/safeContent/renderMdast.cjs +2 -0
- package/dist/safeContent/renderMdast.cjs.map +1 -1
- package/dist/safeContent/renderMdast.js +2 -0
- package/dist/safeContent/renderMdast.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -27,7 +27,10 @@ __export(MDXComponents_exports, {
|
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(MDXComponents_exports);
|
|
29
29
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
var import_react = require("react");
|
|
30
31
|
var import_Admonition = require("./Admonition");
|
|
32
|
+
var import_linkSpace = require("../linkSpace");
|
|
33
|
+
var import_urlUtils = require("../urlUtils");
|
|
31
34
|
var import_HeadingAnchor = require("./HeadingAnchor");
|
|
32
35
|
var import_Link = require("./Link");
|
|
33
36
|
var import_WikiLink = require("./WikiLink");
|
|
@@ -41,7 +44,17 @@ const DEFAULT_MDX_COMPONENTS = {
|
|
|
41
44
|
children,
|
|
42
45
|
...properties
|
|
43
46
|
}) {
|
|
44
|
-
|
|
47
|
+
const { corpusRoot } = (0, import_react.use)(import_linkSpace.LinkSpaceContext);
|
|
48
|
+
let mapped = href;
|
|
49
|
+
if (href && (href.startsWith(import_linkSpace.FS_PREFIX) || corpusRoot !== null && href.startsWith("/"))) {
|
|
50
|
+
const [pathPart, frag] = (0, import_urlUtils.splitHash)(href);
|
|
51
|
+
const resolution = (0, import_linkSpace.resolveLinkTarget)(pathPart, { corpusRoot });
|
|
52
|
+
if (resolution.state !== "resolved") {
|
|
53
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ir-link-broken", "data-state": "broken", title: `Invalid ${import_linkSpace.FS_PREFIX} link: ${href}`, children });
|
|
54
|
+
}
|
|
55
|
+
mapped = `${resolution.path}${frag ? `#${frag}` : ""}`;
|
|
56
|
+
}
|
|
57
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Link.Link, { href: mapped, ...properties, children });
|
|
45
58
|
},
|
|
46
59
|
Admonition: import_Admonition.Admonition,
|
|
47
60
|
HeadingAnchor: import_HeadingAnchor.HeadingAnchor,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/MDXComponents.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport { Admonition } from './Admonition';\nimport { HeadingAnchor } from './HeadingAnchor';\nimport { Link } from './Link';\nimport { WikiLink } from './WikiLink';\n\n// The link primitives moved to ./Link so WikiLink can reuse Link without a\n// MDXComponents ↔ WikiLink import cycle (check:circular). Re-exported here so the\n// public `Link` / `InternalLink` entry points are unchanged.\nexport { InternalLink, Link } from './Link';\nexport { Admonition } from './Admonition';\nexport type { AdmonitionType } from './Admonition';\nexport { HeadingAnchor } from './HeadingAnchor';\nexport { WikiLink } from './WikiLink';\n\n/** Default MDX component overrides passed to {@link MDXProvider} by `boot`. These\n * are the platform's *phantom defaults* (MARKDOWN_SYNTAX_SPEC §11.2): they are\n * always present in the provider — even for a plain-markdown repo that never\n * calls `boot({ mdxComponents })` — so the platform-emitted `Admonition` (§12)\n * and `WikiLink` (§13) components resolve without the MDX missing-reference guard\n * firing, and Markdown links route in-app via {@link Link}. An app overrides any\n * of them by name via `boot({ mdxComponents })`, which *merges* over these\n * defaults (§11.3) — overriding `WikiLink` alone still keeps `a` and `Admonition`. */\nexport const DEFAULT_MDX_COMPONENTS = {\n a({\n href,\n children,\n ...properties\n }: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) {\n return (\n <Link href={
|
|
1
|
+
{"version":3,"sources":["../../src/components/MDXComponents.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Admonition } from './Admonition';\nimport { FS_PREFIX, LinkSpaceContext, normalizeAbsolute, resolveLinkTarget } from '../linkSpace';\nimport { splitHash } from '../urlUtils';\nimport { HeadingAnchor } from './HeadingAnchor';\nimport { Link } from './Link';\nimport { WikiLink } from './WikiLink';\n\n// The link primitives moved to ./Link so WikiLink can reuse Link without a\n// MDXComponents ↔ WikiLink import cycle (check:circular). Re-exported here so the\n// public `Link` / `InternalLink` entry points are unchanged.\nexport { InternalLink, Link } from './Link';\nexport { Admonition } from './Admonition';\nexport type { AdmonitionType } from './Admonition';\nexport { HeadingAnchor } from './HeadingAnchor';\nexport { WikiLink } from './WikiLink';\n\n/** Default MDX component overrides passed to {@link MDXProvider} by `boot`. These\n * are the platform's *phantom defaults* (MARKDOWN_SYNTAX_SPEC §11.2): they are\n * always present in the provider — even for a plain-markdown repo that never\n * calls `boot({ mdxComponents })` — so the platform-emitted `Admonition` (§12)\n * and `WikiLink` (§13) components resolve without the MDX missing-reference guard\n * firing, and Markdown links route in-app via {@link Link}. An app overrides any\n * of them by name via `boot({ mdxComponents })`, which *merges* over these\n * defaults (§11.3) — overriding `WikiLink` alone still keeps `a` and `Admonition`. */\nexport const DEFAULT_MDX_COMPONENTS = {\n a({\n href,\n children,\n ...properties\n }: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) {\n // R3-273 link spaces, same shared resolver as WikiLink: an `$fs:` href is\n // translated to its mount-absolute path; an ABSOLUTE href is corpus-rooted\n // when an enclosing LinkSpaceContext declares a corpusRoot (a non-corpus app\n // declares none and is untouched). Relative and external hrefs pass through —\n // <Link> already routes same-app hrefs and renders the rest as plain anchors.\n const { corpusRoot } = use(LinkSpaceContext);\n let mapped = href;\n if (href && (href.startsWith(FS_PREFIX) || (corpusRoot !== null && href.startsWith('/')))) {\n const [pathPart, frag] = splitHash(href);\n const resolution = resolveLinkTarget(pathPart, { corpusRoot });\n if (resolution.state !== 'resolved') {\n // Malformed `$fs:` (incl. scheme smuggling) — broken text, never an anchor.\n return (\n <span className=\"ir-link-broken\" data-state=\"broken\" title={`Invalid ${FS_PREFIX} link: ${href}`}>\n {children}\n </span>\n );\n }\n mapped = `${resolution.path}${frag ? `#${frag}` : ''}`;\n }\n return (\n <Link href={mapped} {...properties}>\n {children}\n </Link>\n );\n },\n Admonition,\n HeadingAnchor,\n WikiLink,\n} as Record<string, (props: any) => ReactNode>;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4CU;AA5CV,mBAA+B;AAC/B,wBAA2B;AAC3B,uBAAkF;AAClF,sBAA0B;AAC1B,2BAA8B;AAC9B,kBAAqB;AACrB,sBAAyB;AAKzB,IAAAA,eAAmC;AACnC,IAAAC,qBAA2B;AAE3B,IAAAC,wBAA8B;AAC9B,IAAAC,mBAAyB;AAUlB,MAAM,yBAAyB;AAAA,EACpC,EAAE;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GAA8F;AAM5F,UAAM,EAAE,WAAW,QAAI,kBAAI,iCAAgB;AAC3C,QAAI,SAAS;AACb,QAAI,SAAS,KAAK,WAAW,0BAAS,KAAM,eAAe,QAAQ,KAAK,WAAW,GAAG,IAAK;AACzF,YAAM,CAAC,UAAU,IAAI,QAAI,2BAAU,IAAI;AACvC,YAAM,iBAAa,oCAAkB,UAAU,EAAE,WAAW,CAAC;AAC7D,UAAI,WAAW,UAAU,YAAY;AAEnC,eACE,4CAAC,UAAK,WAAU,kBAAiB,cAAW,UAAS,OAAO,WAAW,0BAAS,UAAU,IAAI,IAC3F,UACH;AAAA,MAEJ;AACA,eAAS,GAAG,WAAW,IAAI,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE;AAAA,IACtD;AACA,WACE,4CAAC,oBAAK,MAAM,QAAS,GAAG,YACrB,UACH;AAAA,EAEJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":["import_Link","import_Admonition","import_HeadingAnchor","import_WikiLink"]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import "../chunk-VHAA22YE.js";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { use } from "react";
|
|
3
4
|
import { Admonition } from "./Admonition";
|
|
5
|
+
import { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from "../linkSpace";
|
|
6
|
+
import { splitHash } from "../urlUtils";
|
|
4
7
|
import { HeadingAnchor } from "./HeadingAnchor";
|
|
5
8
|
import { Link } from "./Link";
|
|
6
9
|
import { WikiLink } from "./WikiLink";
|
|
@@ -14,7 +17,17 @@ const DEFAULT_MDX_COMPONENTS = {
|
|
|
14
17
|
children,
|
|
15
18
|
...properties
|
|
16
19
|
}) {
|
|
17
|
-
|
|
20
|
+
const { corpusRoot } = use(LinkSpaceContext);
|
|
21
|
+
let mapped = href;
|
|
22
|
+
if (href && (href.startsWith(FS_PREFIX) || corpusRoot !== null && href.startsWith("/"))) {
|
|
23
|
+
const [pathPart, frag] = splitHash(href);
|
|
24
|
+
const resolution = resolveLinkTarget(pathPart, { corpusRoot });
|
|
25
|
+
if (resolution.state !== "resolved") {
|
|
26
|
+
return /* @__PURE__ */ jsx("span", { className: "ir-link-broken", "data-state": "broken", title: `Invalid ${FS_PREFIX} link: ${href}`, children });
|
|
27
|
+
}
|
|
28
|
+
mapped = `${resolution.path}${frag ? `#${frag}` : ""}`;
|
|
29
|
+
}
|
|
30
|
+
return /* @__PURE__ */ jsx(Link, { href: mapped, ...properties, children });
|
|
18
31
|
},
|
|
19
32
|
Admonition,
|
|
20
33
|
HeadingAnchor,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/MDXComponents.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport { Admonition } from './Admonition';\nimport { HeadingAnchor } from './HeadingAnchor';\nimport { Link } from './Link';\nimport { WikiLink } from './WikiLink';\n\n// The link primitives moved to ./Link so WikiLink can reuse Link without a\n// MDXComponents ↔ WikiLink import cycle (check:circular). Re-exported here so the\n// public `Link` / `InternalLink` entry points are unchanged.\nexport { InternalLink, Link } from './Link';\nexport { Admonition } from './Admonition';\nexport type { AdmonitionType } from './Admonition';\nexport { HeadingAnchor } from './HeadingAnchor';\nexport { WikiLink } from './WikiLink';\n\n/** Default MDX component overrides passed to {@link MDXProvider} by `boot`. These\n * are the platform's *phantom defaults* (MARKDOWN_SYNTAX_SPEC §11.2): they are\n * always present in the provider — even for a plain-markdown repo that never\n * calls `boot({ mdxComponents })` — so the platform-emitted `Admonition` (§12)\n * and `WikiLink` (§13) components resolve without the MDX missing-reference guard\n * firing, and Markdown links route in-app via {@link Link}. An app overrides any\n * of them by name via `boot({ mdxComponents })`, which *merges* over these\n * defaults (§11.3) — overriding `WikiLink` alone still keeps `a` and `Admonition`. */\nexport const DEFAULT_MDX_COMPONENTS = {\n a({\n href,\n children,\n ...properties\n }: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) {\n return (\n <Link href={
|
|
1
|
+
{"version":3,"sources":["../../src/components/MDXComponents.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Admonition } from './Admonition';\nimport { FS_PREFIX, LinkSpaceContext, normalizeAbsolute, resolveLinkTarget } from '../linkSpace';\nimport { splitHash } from '../urlUtils';\nimport { HeadingAnchor } from './HeadingAnchor';\nimport { Link } from './Link';\nimport { WikiLink } from './WikiLink';\n\n// The link primitives moved to ./Link so WikiLink can reuse Link without a\n// MDXComponents ↔ WikiLink import cycle (check:circular). Re-exported here so the\n// public `Link` / `InternalLink` entry points are unchanged.\nexport { InternalLink, Link } from './Link';\nexport { Admonition } from './Admonition';\nexport type { AdmonitionType } from './Admonition';\nexport { HeadingAnchor } from './HeadingAnchor';\nexport { WikiLink } from './WikiLink';\n\n/** Default MDX component overrides passed to {@link MDXProvider} by `boot`. These\n * are the platform's *phantom defaults* (MARKDOWN_SYNTAX_SPEC §11.2): they are\n * always present in the provider — even for a plain-markdown repo that never\n * calls `boot({ mdxComponents })` — so the platform-emitted `Admonition` (§12)\n * and `WikiLink` (§13) components resolve without the MDX missing-reference guard\n * firing, and Markdown links route in-app via {@link Link}. An app overrides any\n * of them by name via `boot({ mdxComponents })`, which *merges* over these\n * defaults (§11.3) — overriding `WikiLink` alone still keeps `a` and `Admonition`. */\nexport const DEFAULT_MDX_COMPONENTS = {\n a({\n href,\n children,\n ...properties\n }: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>) {\n // R3-273 link spaces, same shared resolver as WikiLink: an `$fs:` href is\n // translated to its mount-absolute path; an ABSOLUTE href is corpus-rooted\n // when an enclosing LinkSpaceContext declares a corpusRoot (a non-corpus app\n // declares none and is untouched). Relative and external hrefs pass through —\n // <Link> already routes same-app hrefs and renders the rest as plain anchors.\n const { corpusRoot } = use(LinkSpaceContext);\n let mapped = href;\n if (href && (href.startsWith(FS_PREFIX) || (corpusRoot !== null && href.startsWith('/')))) {\n const [pathPart, frag] = splitHash(href);\n const resolution = resolveLinkTarget(pathPart, { corpusRoot });\n if (resolution.state !== 'resolved') {\n // Malformed `$fs:` (incl. scheme smuggling) — broken text, never an anchor.\n return (\n <span className=\"ir-link-broken\" data-state=\"broken\" title={`Invalid ${FS_PREFIX} link: ${href}`}>\n {children}\n </span>\n );\n }\n mapped = `${resolution.path}${frag ? `#${frag}` : ''}`;\n }\n return (\n <Link href={mapped} {...properties}>\n {children}\n </Link>\n );\n },\n Admonition,\n HeadingAnchor,\n WikiLink,\n} as Record<string, (props: any) => ReactNode>;\n"],"mappings":";AA4CU;AA5CV,SAAoB,WAAW;AAC/B,SAAS,kBAAkB;AAC3B,SAAS,WAAW,kBAAqC,yBAAyB;AAClF,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAKzB,SAAS,cAAc,QAAAA,aAAY;AACnC,SAAS,cAAAC,mBAAkB;AAE3B,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,YAAAC,iBAAgB;AAUlB,MAAM,yBAAyB;AAAA,EACpC,EAAE;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GAA8F;AAM5F,UAAM,EAAE,WAAW,IAAI,IAAI,gBAAgB;AAC3C,QAAI,SAAS;AACb,QAAI,SAAS,KAAK,WAAW,SAAS,KAAM,eAAe,QAAQ,KAAK,WAAW,GAAG,IAAK;AACzF,YAAM,CAAC,UAAU,IAAI,IAAI,UAAU,IAAI;AACvC,YAAM,aAAa,kBAAkB,UAAU,EAAE,WAAW,CAAC;AAC7D,UAAI,WAAW,UAAU,YAAY;AAEnC,eACE,oBAAC,UAAK,WAAU,kBAAiB,cAAW,UAAS,OAAO,WAAW,SAAS,UAAU,IAAI,IAC3F,UACH;AAAA,MAEJ;AACA,eAAS,GAAG,WAAW,IAAI,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE;AAAA,IACtD;AACA,WACE,oBAAC,QAAK,MAAM,QAAS,GAAG,YACrB,UACH;AAAA,EAEJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":["Link","Admonition","HeadingAnchor","WikiLink"]}
|
|
@@ -27,25 +27,11 @@ var import_Link = require("./Link");
|
|
|
27
27
|
var import_Include = require("./Include");
|
|
28
28
|
var import_TinkerableContext = require("../TinkerableContext");
|
|
29
29
|
var import_urlUtils = require("../urlUtils");
|
|
30
|
+
var import_linkSpace = require("../linkSpace");
|
|
30
31
|
const labelFromTarget = (target) => {
|
|
31
32
|
const base = target.split(/[\\/]/).pop() ?? target;
|
|
32
33
|
return base.replace(/\.mdx?$/i, "") || target;
|
|
33
34
|
};
|
|
34
|
-
const normalize = (path) => {
|
|
35
|
-
const out = [];
|
|
36
|
-
for (const seg of path.split("/")) {
|
|
37
|
-
if (seg === "" || seg === ".") continue;
|
|
38
|
-
if (seg === "..") out.pop();
|
|
39
|
-
else out.push(seg);
|
|
40
|
-
}
|
|
41
|
-
return "/" + out.join("/");
|
|
42
|
-
};
|
|
43
|
-
const resolveWikiTarget = (target, currentFile) => {
|
|
44
|
-
if (target.startsWith("/")) return normalize(target);
|
|
45
|
-
if (!currentFile) return void 0;
|
|
46
|
-
const dir = currentFile.slice(0, currentFile.lastIndexOf("/"));
|
|
47
|
-
return normalize(`${dir}/${target}`);
|
|
48
|
-
};
|
|
49
35
|
const WikiLink = ({
|
|
50
36
|
target,
|
|
51
37
|
label,
|
|
@@ -53,6 +39,7 @@ const WikiLink = ({
|
|
|
53
39
|
...rest
|
|
54
40
|
}) => {
|
|
55
41
|
const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
42
|
+
const { corpusRoot } = (0, import_react.use)(import_linkSpace.LinkSpaceContext);
|
|
56
43
|
const renderContext = (0, import_react.use)(import_Include.RenderExportedComponentContext);
|
|
57
44
|
const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;
|
|
58
45
|
const rawTarget = target ?? "";
|
|
@@ -64,7 +51,20 @@ const WikiLink = ({
|
|
|
64
51
|
if (pathPart === "" && frag) {
|
|
65
52
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Link.Link, { href: `#${frag}`, className: "ir-wikilink", "data-state": "anchor", ...rest, children: text });
|
|
66
53
|
}
|
|
67
|
-
const
|
|
54
|
+
const resolution = (0, import_linkSpace.resolveLinkTarget)(pathPart, { currentFile, corpusRoot });
|
|
55
|
+
if (resolution.state === "invalid") {
|
|
56
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
57
|
+
"span",
|
|
58
|
+
{
|
|
59
|
+
className: "ir-wikilink ir-wikilink-broken",
|
|
60
|
+
"data-state": "broken",
|
|
61
|
+
title: `Invalid ${import_linkSpace.FS_PREFIX} target: ${pathPart}`,
|
|
62
|
+
...rest,
|
|
63
|
+
children: text
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const resolved = resolution.state === "resolved" ? resolution.path : void 0;
|
|
68
68
|
const files = filesMetadata ?? {};
|
|
69
69
|
const loaded = Object.keys(files).length > 0;
|
|
70
70
|
if (resolved !== void 0) {
|
|
@@ -88,7 +88,9 @@ const WikiLink = ({
|
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
const translated = pathPart.startsWith(import_linkSpace.FS_PREFIX) || corpusRoot !== null && pathPart.startsWith("/");
|
|
92
|
+
const href = translated && resolved !== void 0 ? `${resolved}${frag ? `#${frag}` : ""}` : rawTarget;
|
|
93
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Link.Link, { href, className: "ir-wikilink", "data-state": "resolved", ...rest, children: text });
|
|
92
94
|
};
|
|
93
95
|
// Annotate the CommonJS export names for ESM import in node:
|
|
94
96
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/WikiLink.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Link } from './Link';\nimport { RenderExportedComponentContext } from './Include';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { splitHash } from '../urlUtils';\n\n/** Derive a human label from a target path: basename without the extension. */\nconst labelFromTarget = (target: string): string => {\n const base = target.split(/[\\\\/]/).pop() ?? target;\n return base.replace(/\\.mdx?$/i, '') || target;\n};\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/components/WikiLink.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Link } from './Link';\nimport { RenderExportedComponentContext } from './Include';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { splitHash } from '../urlUtils';\nimport { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from '../linkSpace';\n\n/** Derive a human label from a target path: basename without the extension. */\nconst labelFromTarget = (target: string): string => {\n const base = target.split(/[\\\\/]/).pop() ?? target;\n return base.replace(/\\.mdx?$/i, '') || target;\n};\n\n/**\n * Default MDX `WikiLink` component — the render target for the `[[target]]` /\n * `[[label|target]]` wiki-link syntax. The transpiler remark plugin (R3-153)\n * compiles that syntax to `<WikiLink target=\"…\" label=\"…\">`, carrying the raw\n * target/label verbatim; **resolution lives here** (MARKDOWN_SYNTAX_SPEC §13.2).\n *\n * Registered in {@link DEFAULT_MDX_COMPONENTS} so wiki-links render even in a\n * plain-markdown repo (§11.2 phantom defaults). Targets are **paths only** —\n * relative (resolved against the current file's directory) or absolute — with\n * **no implicit search path** (§13.3, a deliberate departure from Obsidian).\n *\n * The **current file** — the one the link is *authored in* — is read from the\n * ambient `<Include>` render context. Every MDX file renders through `<Include>`\n * (`FileRouter` renders even the top-level file that way), and Include publishes\n * the rendered module's `EvaluationContext` to its subtree via\n * {@link RenderExportedComponentContext}; the nearest one's\n * `evaluation.module.filepath` is this file's own `/app/…` path. Because that\n * context nests with each `<Include>`, a relative target inside an included\n * fragment resolves against the **fragment**, not the top-level page in the URL.\n *\n * A target may carry a `#fragment` (`[[FILE.mdx#sec-8-9]]`, `[[#sec-8-9]]`): the\n * fragment is **split off** ({@link splitHash}, §13.5) and existence is resolved on\n * the **fragment-stripped path**, so a section citation to an existing file resolves\n * (not \"broken\"). The fragment then rides to navigation, where the scroll-after-nav\n * effect ({@link useScrollAfterNavigation}) lands the reader on the section.\n *\n * The resolved path is checked for **existence** against the live metadata store\n * (keyed by absolute `/app/…` paths) for the states (§13.3, §13.5):\n * - **anchor** — a fragment with no path (`[[#sec-8-9]]`), or a fragment whose path is\n * the current file: a same-page {@link Link} that scrolls in place, no route change.\n * - **self** — the resolved path is the current file (no fragment): inert text, no link.\n * - **broken** — no file at the resolved path (and the store has loaded): rendered\n * as marked text, **not** a throw.\n * - **resolved** — routed through {@link Link} (in-app navigation for a same-app\n * href, a plain `<a>` otherwise), carrying any `#fragment`.\n *\n * The check is **optimistic until the metadata store loads** (an empty store never\n * flashes \"broken\"), and a relative target with no ambient render context (MDX\n * rendered outside `<Include>`) routes optimistically. Such an app overrides this\n * component (§11) for precise resolution.\n */\nexport const WikiLink = ({\n target,\n label,\n children,\n ...rest\n}: {\n target?: string;\n label?: ReactNode;\n children?: ReactNode;\n} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>): ReactNode => {\n const { filesMetadata } = use(TinkerableContext);\n const { corpusRoot } = use(LinkSpaceContext);\n const renderContext = use(RenderExportedComponentContext);\n const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;\n\n const rawTarget = target ?? '';\n const [pathPart, frag] = splitHash(rawTarget);\n const text = children ?? label ?? (rawTarget ? labelFromTarget(pathPart || frag || rawTarget) : '');\n\n // Defensive: the kernel never emits an empty target, but a hand-written\n // `<WikiLink>` might. Render inert text rather than a link to nowhere.\n if (!rawTarget) {\n return (\n <span className=\"ir-wikilink\" {...rest}>\n {text}\n </span>\n );\n }\n\n // Same-page anchor: a fragment with no path (`[[#sec-8-9]]`). Scroll within the\n // current file — no route change (§13.5). <Link> intercepts a bare `#`-href.\n if (pathPart === '' && frag) {\n return (\n <Link href={`#${frag}`} className=\"ir-wikilink\" data-state=\"anchor\" {...rest}>\n {text}\n </Link>\n );\n }\n\n // R3-273 link spaces: resolution is the SHARED resolver (`linkSpace.ts`) —\n // default space (corpus-rooted absolute targets when an enclosing provider\n // declares a corpusRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n // A malformed `$fs:` target renders BROKEN, never an anchor.\n const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });\n if (resolution.state === 'invalid') {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`Invalid ${FS_PREFIX} target: ${pathPart}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n const resolved = resolution.state === 'resolved' ? resolution.path : undefined;\n const files = filesMetadata ?? {};\n const loaded = Object.keys(files).length > 0;\n\n // `resolved === undefined` ⇒ a relative target with no known current file: route\n // it optimistically (can't check existence or self-ness generically).\n if (resolved !== undefined) {\n if (currentFile && resolved === currentFile) {\n // The target IS this file. With a fragment it is a same-page anchor to another\n // of this file's sections; without one it is an inert self-reference.\n if (frag) {\n return (\n <Link href={`#${frag}`} className=\"ir-wikilink\" data-state=\"anchor\" {...rest}>\n {text}\n </Link>\n );\n }\n return (\n <span className=\"ir-wikilink ir-wikilink-self\" data-state=\"self\" {...rest}>\n {text}\n </span>\n );\n }\n // Existence is checked on the FRAGMENT-STRIPPED path (optimistic until loaded).\n const exists = !loaded || resolved in files;\n if (!exists) {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`No file at ${resolved}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n }\n // Resolved cross-file target: route through <Link>. A space-translated target\n // (`$fs:` prefix, or a corpus-rooted absolute) navigates to the RESOLVED path —\n // the raw text is not a routable path in those shapes; everything else carries\n // the raw target bit-for-bit so its `#fragment` rides through navigation to the\n // scroll-after-nav effect (§13.5). The fragment is re-attached either way.\n const translated =\n pathPart.startsWith(FS_PREFIX) || (corpusRoot !== null && pathPart.startsWith('/'));\n const href =\n translated && resolved !== undefined ? `${resolved}${frag ? `#${frag}` : ''}` : rawTarget;\n return (\n <Link href={href} className=\"ir-wikilink\" data-state=\"resolved\" {...rest}>\n {text}\n </Link>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA6EM;AA7EN,mBAA+B;AAC/B,kBAAqB;AACrB,qBAA+C;AAC/C,+BAAkC;AAClC,sBAA0B;AAC1B,uBAA+D;AAG/D,MAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAM,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,KAAK;AAC5C,SAAO,KAAK,QAAQ,YAAY,EAAE,KAAK;AACzC;AA2CO,MAAM,WAAW,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAI+E;AAC7E,QAAM,EAAE,cAAc,QAAI,kBAAI,0CAAiB;AAC/C,QAAM,EAAE,WAAW,QAAI,kBAAI,iCAAgB;AAC3C,QAAM,oBAAgB,kBAAI,6CAA8B;AACxD,QAAM,cAAc,eAAe,mBAAmB,YAAY,QAAQ;AAE1E,QAAM,YAAY,UAAU;AAC5B,QAAM,CAAC,UAAU,IAAI,QAAI,2BAAU,SAAS;AAC5C,QAAM,OAAO,YAAY,UAAU,YAAY,gBAAgB,YAAY,QAAQ,SAAS,IAAI;AAIhG,MAAI,CAAC,WAAW;AACd,WACE,4CAAC,UAAK,WAAU,eAAe,GAAG,MAC/B,gBACH;AAAA,EAEJ;AAIA,MAAI,aAAa,MAAM,MAAM;AAC3B,WACE,4CAAC,oBAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,EAEJ;AAMA,QAAM,iBAAa,oCAAkB,UAAU,EAAE,aAAa,WAAW,CAAC;AAC1E,MAAI,WAAW,UAAU,WAAW;AAClC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,cAAW;AAAA,QACX,OAAO,WAAW,0BAAS,YAAY,QAAQ;AAAA,QAC9C,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,QAAM,WAAW,WAAW,UAAU,aAAa,WAAW,OAAO;AACrE,QAAM,QAAQ,iBAAiB,CAAC;AAChC,QAAM,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS;AAI3C,MAAI,aAAa,QAAW;AAC1B,QAAI,eAAe,aAAa,aAAa;AAG3C,UAAI,MAAM;AACR,eACE,4CAAC,oBAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,MAEJ;AACA,aACE,4CAAC,UAAK,WAAU,gCAA+B,cAAW,QAAQ,GAAG,MAClE,gBACH;AAAA,IAEJ;AAEA,UAAM,SAAS,CAAC,UAAU,YAAY;AACtC,QAAI,CAAC,QAAQ;AACX,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,OAAO,cAAc,QAAQ;AAAA,UAC5B,GAAG;AAAA,UAEH;AAAA;AAAA,MACH;AAAA,IAEJ;AAAA,EACF;AAMA,QAAM,aACJ,SAAS,WAAW,0BAAS,KAAM,eAAe,QAAQ,SAAS,WAAW,GAAG;AACnF,QAAM,OACJ,cAAc,aAAa,SAAY,GAAG,QAAQ,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,KAAK;AAClF,SACE,4CAAC,oBAAK,MAAY,WAAU,eAAc,cAAW,YAAY,GAAG,MACjE,gBACH;AAEJ;","names":[]}
|
|
@@ -5,25 +5,11 @@ import { Link } from "./Link";
|
|
|
5
5
|
import { RenderExportedComponentContext } from "./Include";
|
|
6
6
|
import { TinkerableContext } from "../TinkerableContext";
|
|
7
7
|
import { splitHash } from "../urlUtils";
|
|
8
|
+
import { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from "../linkSpace";
|
|
8
9
|
const labelFromTarget = (target) => {
|
|
9
10
|
const base = target.split(/[\\/]/).pop() ?? target;
|
|
10
11
|
return base.replace(/\.mdx?$/i, "") || target;
|
|
11
12
|
};
|
|
12
|
-
const normalize = (path) => {
|
|
13
|
-
const out = [];
|
|
14
|
-
for (const seg of path.split("/")) {
|
|
15
|
-
if (seg === "" || seg === ".") continue;
|
|
16
|
-
if (seg === "..") out.pop();
|
|
17
|
-
else out.push(seg);
|
|
18
|
-
}
|
|
19
|
-
return "/" + out.join("/");
|
|
20
|
-
};
|
|
21
|
-
const resolveWikiTarget = (target, currentFile) => {
|
|
22
|
-
if (target.startsWith("/")) return normalize(target);
|
|
23
|
-
if (!currentFile) return void 0;
|
|
24
|
-
const dir = currentFile.slice(0, currentFile.lastIndexOf("/"));
|
|
25
|
-
return normalize(`${dir}/${target}`);
|
|
26
|
-
};
|
|
27
13
|
const WikiLink = ({
|
|
28
14
|
target,
|
|
29
15
|
label,
|
|
@@ -31,6 +17,7 @@ const WikiLink = ({
|
|
|
31
17
|
...rest
|
|
32
18
|
}) => {
|
|
33
19
|
const { filesMetadata } = use(TinkerableContext);
|
|
20
|
+
const { corpusRoot } = use(LinkSpaceContext);
|
|
34
21
|
const renderContext = use(RenderExportedComponentContext);
|
|
35
22
|
const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;
|
|
36
23
|
const rawTarget = target ?? "";
|
|
@@ -42,7 +29,20 @@ const WikiLink = ({
|
|
|
42
29
|
if (pathPart === "" && frag) {
|
|
43
30
|
return /* @__PURE__ */ jsx(Link, { href: `#${frag}`, className: "ir-wikilink", "data-state": "anchor", ...rest, children: text });
|
|
44
31
|
}
|
|
45
|
-
const
|
|
32
|
+
const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });
|
|
33
|
+
if (resolution.state === "invalid") {
|
|
34
|
+
return /* @__PURE__ */ jsx(
|
|
35
|
+
"span",
|
|
36
|
+
{
|
|
37
|
+
className: "ir-wikilink ir-wikilink-broken",
|
|
38
|
+
"data-state": "broken",
|
|
39
|
+
title: `Invalid ${FS_PREFIX} target: ${pathPart}`,
|
|
40
|
+
...rest,
|
|
41
|
+
children: text
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const resolved = resolution.state === "resolved" ? resolution.path : void 0;
|
|
46
46
|
const files = filesMetadata ?? {};
|
|
47
47
|
const loaded = Object.keys(files).length > 0;
|
|
48
48
|
if (resolved !== void 0) {
|
|
@@ -66,7 +66,9 @@ const WikiLink = ({
|
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
const translated = pathPart.startsWith(FS_PREFIX) || corpusRoot !== null && pathPart.startsWith("/");
|
|
70
|
+
const href = translated && resolved !== void 0 ? `${resolved}${frag ? `#${frag}` : ""}` : rawTarget;
|
|
71
|
+
return /* @__PURE__ */ jsx(Link, { href, className: "ir-wikilink", "data-state": "resolved", ...rest, children: text });
|
|
70
72
|
};
|
|
71
73
|
export {
|
|
72
74
|
WikiLink
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/WikiLink.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Link } from './Link';\nimport { RenderExportedComponentContext } from './Include';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { splitHash } from '../urlUtils';\n\n/** Derive a human label from a target path: basename without the extension. */\nconst labelFromTarget = (target: string): string => {\n const base = target.split(/[\\\\/]/).pop() ?? target;\n return base.replace(/\\.mdx?$/i, '') || target;\n};\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/components/WikiLink.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Link } from './Link';\nimport { RenderExportedComponentContext } from './Include';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { splitHash } from '../urlUtils';\nimport { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from '../linkSpace';\n\n/** Derive a human label from a target path: basename without the extension. */\nconst labelFromTarget = (target: string): string => {\n const base = target.split(/[\\\\/]/).pop() ?? target;\n return base.replace(/\\.mdx?$/i, '') || target;\n};\n\n/**\n * Default MDX `WikiLink` component — the render target for the `[[target]]` /\n * `[[label|target]]` wiki-link syntax. The transpiler remark plugin (R3-153)\n * compiles that syntax to `<WikiLink target=\"…\" label=\"…\">`, carrying the raw\n * target/label verbatim; **resolution lives here** (MARKDOWN_SYNTAX_SPEC §13.2).\n *\n * Registered in {@link DEFAULT_MDX_COMPONENTS} so wiki-links render even in a\n * plain-markdown repo (§11.2 phantom defaults). Targets are **paths only** —\n * relative (resolved against the current file's directory) or absolute — with\n * **no implicit search path** (§13.3, a deliberate departure from Obsidian).\n *\n * The **current file** — the one the link is *authored in* — is read from the\n * ambient `<Include>` render context. Every MDX file renders through `<Include>`\n * (`FileRouter` renders even the top-level file that way), and Include publishes\n * the rendered module's `EvaluationContext` to its subtree via\n * {@link RenderExportedComponentContext}; the nearest one's\n * `evaluation.module.filepath` is this file's own `/app/…` path. Because that\n * context nests with each `<Include>`, a relative target inside an included\n * fragment resolves against the **fragment**, not the top-level page in the URL.\n *\n * A target may carry a `#fragment` (`[[FILE.mdx#sec-8-9]]`, `[[#sec-8-9]]`): the\n * fragment is **split off** ({@link splitHash}, §13.5) and existence is resolved on\n * the **fragment-stripped path**, so a section citation to an existing file resolves\n * (not \"broken\"). The fragment then rides to navigation, where the scroll-after-nav\n * effect ({@link useScrollAfterNavigation}) lands the reader on the section.\n *\n * The resolved path is checked for **existence** against the live metadata store\n * (keyed by absolute `/app/…` paths) for the states (§13.3, §13.5):\n * - **anchor** — a fragment with no path (`[[#sec-8-9]]`), or a fragment whose path is\n * the current file: a same-page {@link Link} that scrolls in place, no route change.\n * - **self** — the resolved path is the current file (no fragment): inert text, no link.\n * - **broken** — no file at the resolved path (and the store has loaded): rendered\n * as marked text, **not** a throw.\n * - **resolved** — routed through {@link Link} (in-app navigation for a same-app\n * href, a plain `<a>` otherwise), carrying any `#fragment`.\n *\n * The check is **optimistic until the metadata store loads** (an empty store never\n * flashes \"broken\"), and a relative target with no ambient render context (MDX\n * rendered outside `<Include>`) routes optimistically. Such an app overrides this\n * component (§11) for precise resolution.\n */\nexport const WikiLink = ({\n target,\n label,\n children,\n ...rest\n}: {\n target?: string;\n label?: ReactNode;\n children?: ReactNode;\n} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>): ReactNode => {\n const { filesMetadata } = use(TinkerableContext);\n const { corpusRoot } = use(LinkSpaceContext);\n const renderContext = use(RenderExportedComponentContext);\n const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;\n\n const rawTarget = target ?? '';\n const [pathPart, frag] = splitHash(rawTarget);\n const text = children ?? label ?? (rawTarget ? labelFromTarget(pathPart || frag || rawTarget) : '');\n\n // Defensive: the kernel never emits an empty target, but a hand-written\n // `<WikiLink>` might. Render inert text rather than a link to nowhere.\n if (!rawTarget) {\n return (\n <span className=\"ir-wikilink\" {...rest}>\n {text}\n </span>\n );\n }\n\n // Same-page anchor: a fragment with no path (`[[#sec-8-9]]`). Scroll within the\n // current file — no route change (§13.5). <Link> intercepts a bare `#`-href.\n if (pathPart === '' && frag) {\n return (\n <Link href={`#${frag}`} className=\"ir-wikilink\" data-state=\"anchor\" {...rest}>\n {text}\n </Link>\n );\n }\n\n // R3-273 link spaces: resolution is the SHARED resolver (`linkSpace.ts`) —\n // default space (corpus-rooted absolute targets when an enclosing provider\n // declares a corpusRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n // A malformed `$fs:` target renders BROKEN, never an anchor.\n const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });\n if (resolution.state === 'invalid') {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`Invalid ${FS_PREFIX} target: ${pathPart}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n const resolved = resolution.state === 'resolved' ? resolution.path : undefined;\n const files = filesMetadata ?? {};\n const loaded = Object.keys(files).length > 0;\n\n // `resolved === undefined` ⇒ a relative target with no known current file: route\n // it optimistically (can't check existence or self-ness generically).\n if (resolved !== undefined) {\n if (currentFile && resolved === currentFile) {\n // The target IS this file. With a fragment it is a same-page anchor to another\n // of this file's sections; without one it is an inert self-reference.\n if (frag) {\n return (\n <Link href={`#${frag}`} className=\"ir-wikilink\" data-state=\"anchor\" {...rest}>\n {text}\n </Link>\n );\n }\n return (\n <span className=\"ir-wikilink ir-wikilink-self\" data-state=\"self\" {...rest}>\n {text}\n </span>\n );\n }\n // Existence is checked on the FRAGMENT-STRIPPED path (optimistic until loaded).\n const exists = !loaded || resolved in files;\n if (!exists) {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`No file at ${resolved}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n }\n // Resolved cross-file target: route through <Link>. A space-translated target\n // (`$fs:` prefix, or a corpus-rooted absolute) navigates to the RESOLVED path —\n // the raw text is not a routable path in those shapes; everything else carries\n // the raw target bit-for-bit so its `#fragment` rides through navigation to the\n // scroll-after-nav effect (§13.5). The fragment is re-attached either way.\n const translated =\n pathPart.startsWith(FS_PREFIX) || (corpusRoot !== null && pathPart.startsWith('/'));\n const href =\n translated && resolved !== undefined ? `${resolved}${frag ? `#${frag}` : ''}` : rawTarget;\n return (\n <Link href={href} className=\"ir-wikilink\" data-state=\"resolved\" {...rest}>\n {text}\n </Link>\n );\n};\n"],"mappings":";AA6EM;AA7EN,SAAoB,WAAW;AAC/B,SAAS,YAAY;AACrB,SAAS,sCAAsC;AAC/C,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,WAAW,kBAAkB,yBAAyB;AAG/D,MAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAM,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,KAAK;AAC5C,SAAO,KAAK,QAAQ,YAAY,EAAE,KAAK;AACzC;AA2CO,MAAM,WAAW,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAI+E;AAC7E,QAAM,EAAE,cAAc,IAAI,IAAI,iBAAiB;AAC/C,QAAM,EAAE,WAAW,IAAI,IAAI,gBAAgB;AAC3C,QAAM,gBAAgB,IAAI,8BAA8B;AACxD,QAAM,cAAc,eAAe,mBAAmB,YAAY,QAAQ;AAE1E,QAAM,YAAY,UAAU;AAC5B,QAAM,CAAC,UAAU,IAAI,IAAI,UAAU,SAAS;AAC5C,QAAM,OAAO,YAAY,UAAU,YAAY,gBAAgB,YAAY,QAAQ,SAAS,IAAI;AAIhG,MAAI,CAAC,WAAW;AACd,WACE,oBAAC,UAAK,WAAU,eAAe,GAAG,MAC/B,gBACH;AAAA,EAEJ;AAIA,MAAI,aAAa,MAAM,MAAM;AAC3B,WACE,oBAAC,QAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,EAEJ;AAMA,QAAM,aAAa,kBAAkB,UAAU,EAAE,aAAa,WAAW,CAAC;AAC1E,MAAI,WAAW,UAAU,WAAW;AAClC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,cAAW;AAAA,QACX,OAAO,WAAW,SAAS,YAAY,QAAQ;AAAA,QAC9C,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,QAAM,WAAW,WAAW,UAAU,aAAa,WAAW,OAAO;AACrE,QAAM,QAAQ,iBAAiB,CAAC;AAChC,QAAM,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS;AAI3C,MAAI,aAAa,QAAW;AAC1B,QAAI,eAAe,aAAa,aAAa;AAG3C,UAAI,MAAM;AACR,eACE,oBAAC,QAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,MAEJ;AACA,aACE,oBAAC,UAAK,WAAU,gCAA+B,cAAW,QAAQ,GAAG,MAClE,gBACH;AAAA,IAEJ;AAEA,UAAM,SAAS,CAAC,UAAU,YAAY;AACtC,QAAI,CAAC,QAAQ;AACX,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,OAAO,cAAc,QAAQ;AAAA,UAC5B,GAAG;AAAA,UAEH;AAAA;AAAA,MACH;AAAA,IAEJ;AAAA,EACF;AAMA,QAAM,aACJ,SAAS,WAAW,SAAS,KAAM,eAAe,QAAQ,SAAS,WAAW,GAAG;AACnF,QAAM,OACJ,cAAc,aAAa,SAAY,GAAG,QAAQ,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,KAAK;AAClF,SACE,oBAAC,QAAK,MAAY,WAAU,eAAc,cAAW,YAAY,GAAG,MACjE,gBACH;AAEJ;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __reExport(index_exports, require("./components/Include"), module.exports);
|
|
|
29
29
|
var import_SafeInclude = require("./components/SafeInclude");
|
|
30
30
|
__reExport(index_exports, require("./sourceCache"), module.exports);
|
|
31
31
|
__reExport(index_exports, require("./components/MDXComponents"), module.exports);
|
|
32
|
+
__reExport(index_exports, require("./linkSpace"), module.exports);
|
|
32
33
|
__reExport(index_exports, require("./components/MountImage"), module.exports);
|
|
33
34
|
__reExport(index_exports, require("./components/Routes"), module.exports);
|
|
34
35
|
__reExport(index_exports, require("./hooks"), module.exports);
|
|
@@ -69,6 +70,7 @@ __reExport(index_exports, require("./safeContent"), module.exports);
|
|
|
69
70
|
...require("./components/Include"),
|
|
70
71
|
...require("./sourceCache"),
|
|
71
72
|
...require("./components/MDXComponents"),
|
|
73
|
+
...require("./linkSpace"),
|
|
72
74
|
...require("./components/MountImage"),
|
|
73
75
|
...require("./components/Routes"),
|
|
74
76
|
...require("./hooks"),
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAQA,yBAA4B;AAC5B,0BAAc,0BATd;AAUA,0BAAc,uCAVd;AAWA,0BAAc,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './linkSpace';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAQA,yBAA4B;AAC5B,0BAAc,0BATd;AAUA,0BAAc,uCAVd;AAWA,0BAAc,wBAXd;AAYA,0BAAc,oCAZd;AAaA,0BAAc,gCAbd;AAcA,0BAAc,oBAdd;AAeA,0BAAc,mBAfd;AAgBA,0BAAc,oBAhBd;AAiBA,0BAAc,4BAjBd;AAkBA,0BAAc,qBAlBd;AAmBA,0BAAc,yBAnBd;AAoBA,0BAAc,qBApBd;AAqBA,0BAAc,qBArBd;AAsBA,0BAAc,yBAtBd;AAuBA,0BAAc,sBAvBd;AAwBA,0BAAc,kBAxBd;AAyBA,0BAAc,kBAzBd;AA0BA,0BAAc,uBA1Bd;AA2BA,0BAAc,sBA3Bd;AA4BA,0BAAc,kBA5Bd;AA6BA,0BAAc,0BA7Bd;AA8BA,0BAAc,kBA9Bd;AA+BA,0BAAc,yBA/Bd;AAgCA,0BAAc,iBAhCd;AAiCA,0BAAc,oBAjCd;AAkCA,0BAAc,oBAlCd;AAmCA,0BAAc,qBAnCd;AAoCA,0BAAc,sBApCd;AAqCA,0BAAc,wBArCd;AAsCA,0BAAc,oBAtCd;AAuCA,0BAAc,sBAvCd;AAwCA,0BAAc,6BAxCd;AAyCA,0BAAc,2BAzCd;AA0CA,0BAAc,0BA1Cd;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -5,6 +5,7 @@ export { Include, RenderExportedComponent } from './components/Include.cjs';
|
|
|
5
5
|
export { SafeInclude } from './components/SafeInclude.cjs';
|
|
6
6
|
export { SourceCache, SourceReader, createSourceCache } from './sourceCache.cjs';
|
|
7
7
|
export { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents.cjs';
|
|
8
|
+
export { FS_PREFIX, LinkSpace, LinkSpaceContext, ResolvedLinkTarget, normalizeAbsolute, resolveLinkTarget } from './linkSpace.cjs';
|
|
8
9
|
export { MountImage, MountImageProps } from './components/MountImage.cjs';
|
|
9
10
|
export { Route, RouteProps, Routes } from './components/Routes.cjs';
|
|
10
11
|
export { ObjectUrlState, useAllMetadata, useFileMetadata, useMetadataQuery, useObjectUrl } from './hooks.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { Include, RenderExportedComponent } from './components/Include.js';
|
|
|
5
5
|
export { SafeInclude } from './components/SafeInclude.js';
|
|
6
6
|
export { SourceCache, SourceReader, createSourceCache } from './sourceCache.js';
|
|
7
7
|
export { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents.js';
|
|
8
|
+
export { FS_PREFIX, LinkSpace, LinkSpaceContext, ResolvedLinkTarget, normalizeAbsolute, resolveLinkTarget } from './linkSpace.js';
|
|
8
9
|
export { MountImage, MountImageProps } from './components/MountImage.js';
|
|
9
10
|
export { Route, RouteProps, Routes } from './components/Routes.js';
|
|
10
11
|
export { ObjectUrlState, useAllMetadata, useFileMetadata, useMetadataQuery, useObjectUrl } from './hooks.js';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./components/Include";
|
|
|
6
6
|
import { SafeInclude } from "./components/SafeInclude";
|
|
7
7
|
export * from "./sourceCache";
|
|
8
8
|
export * from "./components/MDXComponents";
|
|
9
|
+
export * from "./linkSpace";
|
|
9
10
|
export * from "./components/MountImage";
|
|
10
11
|
export * from "./components/Routes";
|
|
11
12
|
export * from "./hooks";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,mBAAmB;AAC5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './linkSpace';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,mBAAmB;AAC5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var linkSpace_exports = {};
|
|
20
|
+
__export(linkSpace_exports, {
|
|
21
|
+
FS_PREFIX: () => FS_PREFIX,
|
|
22
|
+
LinkSpaceContext: () => LinkSpaceContext,
|
|
23
|
+
normalizeAbsolute: () => normalizeAbsolute,
|
|
24
|
+
resolveLinkTarget: () => resolveLinkTarget
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(linkSpace_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
const FS_PREFIX = "$fs:";
|
|
29
|
+
const LinkSpaceContext = (0, import_react.createContext)({ corpusRoot: null });
|
|
30
|
+
const normalizeAbsolute = (path) => {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const seg of path.split("/")) {
|
|
33
|
+
if (seg === "" || seg === ".") continue;
|
|
34
|
+
if (seg === "..") out.pop();
|
|
35
|
+
else out.push(seg);
|
|
36
|
+
}
|
|
37
|
+
return "/" + out.join("/");
|
|
38
|
+
};
|
|
39
|
+
function resolveLinkTarget(raw, opts = {}) {
|
|
40
|
+
if (raw.startsWith(FS_PREFIX)) {
|
|
41
|
+
const rest = raw.slice(FS_PREFIX.length);
|
|
42
|
+
if (!rest.startsWith("/")) return { state: "invalid" };
|
|
43
|
+
return { state: "resolved", path: normalizeAbsolute(rest) };
|
|
44
|
+
}
|
|
45
|
+
if (raw.startsWith("/")) {
|
|
46
|
+
const corpusRoot = opts.corpusRoot ?? null;
|
|
47
|
+
if (corpusRoot !== null) {
|
|
48
|
+
return { state: "resolved", path: normalizeAbsolute(corpusRoot + normalizeAbsolute(raw)) };
|
|
49
|
+
}
|
|
50
|
+
return { state: "resolved", path: normalizeAbsolute(raw) };
|
|
51
|
+
}
|
|
52
|
+
if (!opts.currentFile) return { state: "unresolvable" };
|
|
53
|
+
const dir = opts.currentFile.slice(0, opts.currentFile.lastIndexOf("/"));
|
|
54
|
+
return { state: "resolved", path: normalizeAbsolute(`${dir}/${raw}`) };
|
|
55
|
+
}
|
|
56
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
57
|
+
0 && (module.exports = {
|
|
58
|
+
FS_PREFIX,
|
|
59
|
+
LinkSpaceContext,
|
|
60
|
+
normalizeAbsolute,
|
|
61
|
+
resolveLinkTarget
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=linkSpace.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/linkSpace.ts"],"sourcesContent":["// Link path spaces (R3-273; REPO_CONTENT_DISPATCH_SPEC §9 decision, 2026-08-17).\n//\n// A document link's target resolves in one of two spaces:\n//\n// - DEFAULT — the enclosing corpus's virtual filesystem. RELATIVE targets resolve\n// against the authoring file (identical in both spaces); ABSOLUTE targets\n// (`/x/y.mdx`) resolve from the corpus root when an enclosing `LinkSpaceContext`\n// declares one, else from the filesystem root. A non-corpus app declares nothing\n// and keeps today's behavior bit-for-bit (its fs root IS its only root).\n//\n// - `$fs:` — the explicit filesystem space: `$fs:/content/x.mdx` resolves from the\n// root of the filesystem the app reads, escaping corpus-relative addressing.\n//\n// `$fs:` changes ADDRESSING, never REACH: resolution is pure path arithmetic and\n// existence is checked against the same in-mount metadata the default space uses —\n// nothing is fetched, and a link can never name what the app cannot already read.\n// A malformed `$fs:` target (anything not mount-absolute — which also catches\n// scheme smuggling like `$fs:javascript:…`) is INVALID and must render as a broken\n// link, never an anchor.\n//\n// Corpus nesting: `LinkSpaceContext` providers nest, and the NEAREST one wins —\n// which is exactly the innermost-enclosing-corpus rule (bundle encapsulation): a\n// document rendered inside a nested corpus resolves against the nested corpus.\n\nimport { createContext } from 'react';\n\nexport const FS_PREFIX = '$fs:';\n\nexport interface LinkSpace {\n /** Absolute filesystem path of the enclosing corpus's root (e.g. `/app/content`),\n * or `null` when the document is not corpus-hosted (default). */\n corpusRoot: string | null;\n}\n\n/** Ambient link space. A corpus-rendering app wraps its document tree in\n * `<LinkSpaceContext value={{ corpusRoot }}>`; nesting a second provider inside a\n * rendered sub-corpus makes the innermost root win. */\nexport const LinkSpaceContext = createContext<LinkSpace>({ corpusRoot: null });\n\n/** Collapse `.`/`..`/empty segments into a clean absolute path. `..` can never\n * climb above the root — a (virtual) root's parent is itself, which is what keeps\n * both the mount space and the corpus space closed under traversal. */\nexport const normalizeAbsolute = (path: string): string => {\n const out: string[] = [];\n for (const seg of path.split('/')) {\n if (seg === '' || seg === '.') continue;\n if (seg === '..') out.pop();\n else out.push(seg);\n }\n return '/' + out.join('/');\n};\n\nexport type ResolvedLinkTarget =\n /** Resolved to an absolute filesystem path (existence NOT checked here). */\n | { state: 'resolved'; path: string }\n /** A relative target with no known authoring file — the caller may route\n * optimistically (it cannot check existence or self-ness generically). */\n | { state: 'unresolvable' }\n /** A malformed `$fs:` target (not mount-absolute; includes scheme smuggling).\n * Callers MUST render this broken/inert — never as an anchor. */\n | { state: 'invalid' };\n\n/**\n * Resolve a raw link target (a wikilink target or an in-app href's path half —\n * fragment already split off) to an absolute filesystem path. THE shared resolver:\n * the default `WikiLink`, the markdown `a` override, and safe-content consumers\n * all route through this one function so the two render pipelines cannot drift.\n */\nexport function resolveLinkTarget(\n raw: string,\n opts: { currentFile?: string; corpusRoot?: string | null } = {},\n): ResolvedLinkTarget {\n if (raw.startsWith(FS_PREFIX)) {\n const rest = raw.slice(FS_PREFIX.length);\n // Must be mount-absolute. This single rule also fails `$fs:javascript:…`,\n // `$fs:https://…`, and every other smuggled scheme closed.\n if (!rest.startsWith('/')) return { state: 'invalid' };\n return { state: 'resolved', path: normalizeAbsolute(rest) };\n }\n if (raw.startsWith('/')) {\n const corpusRoot = opts.corpusRoot ?? null;\n if (corpusRoot !== null) {\n // Clamp the corpus-relative half FIRST (the virtual FS is closed — `/../x`\n // stays inside the corpus), THEN anchor it at the corpus root.\n return { state: 'resolved', path: normalizeAbsolute(corpusRoot + normalizeAbsolute(raw)) };\n }\n return { state: 'resolved', path: normalizeAbsolute(raw) };\n }\n // Relative: against the authoring file's directory — the same in both spaces.\n if (!opts.currentFile) return { state: 'unresolvable' };\n const dir = opts.currentFile.slice(0, opts.currentFile.lastIndexOf('/'));\n return { state: 'resolved', path: normalizeAbsolute(`${dir}/${raw}`) };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBA,mBAA8B;AAEvB,MAAM,YAAY;AAWlB,MAAM,uBAAmB,4BAAyB,EAAE,YAAY,KAAK,CAAC;AAKtE,MAAM,oBAAoB,CAAC,SAAyB;AACzD,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AACjC,QAAI,QAAQ,MAAM,QAAQ,IAAK;AAC/B,QAAI,QAAQ,KAAM,KAAI,IAAI;AAAA,QACrB,KAAI,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,MAAM,IAAI,KAAK,GAAG;AAC3B;AAkBO,SAAS,kBACd,KACA,OAA6D,CAAC,GAC1C;AACpB,MAAI,IAAI,WAAW,SAAS,GAAG;AAC7B,UAAM,OAAO,IAAI,MAAM,UAAU,MAAM;AAGvC,QAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO,EAAE,OAAO,UAAU;AACrD,WAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,IAAI,EAAE;AAAA,EAC5D;AACA,MAAI,IAAI,WAAW,GAAG,GAAG;AACvB,UAAM,aAAa,KAAK,cAAc;AACtC,QAAI,eAAe,MAAM;AAGvB,aAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,aAAa,kBAAkB,GAAG,CAAC,EAAE;AAAA,IAC3F;AACA,WAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,GAAG,EAAE;AAAA,EAC3D;AAEA,MAAI,CAAC,KAAK,YAAa,QAAO,EAAE,OAAO,eAAe;AACtD,QAAM,MAAM,KAAK,YAAY,MAAM,GAAG,KAAK,YAAY,YAAY,GAAG,CAAC;AACvE,SAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,GAAG,GAAG,IAAI,GAAG,EAAE,EAAE;AACvE;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare const FS_PREFIX = "$fs:";
|
|
4
|
+
interface LinkSpace {
|
|
5
|
+
/** Absolute filesystem path of the enclosing corpus's root (e.g. `/app/content`),
|
|
6
|
+
* or `null` when the document is not corpus-hosted (default). */
|
|
7
|
+
corpusRoot: string | null;
|
|
8
|
+
}
|
|
9
|
+
/** Ambient link space. A corpus-rendering app wraps its document tree in
|
|
10
|
+
* `<LinkSpaceContext value={{ corpusRoot }}>`; nesting a second provider inside a
|
|
11
|
+
* rendered sub-corpus makes the innermost root win. */
|
|
12
|
+
declare const LinkSpaceContext: react.Context<LinkSpace>;
|
|
13
|
+
/** Collapse `.`/`..`/empty segments into a clean absolute path. `..` can never
|
|
14
|
+
* climb above the root — a (virtual) root's parent is itself, which is what keeps
|
|
15
|
+
* both the mount space and the corpus space closed under traversal. */
|
|
16
|
+
declare const normalizeAbsolute: (path: string) => string;
|
|
17
|
+
type ResolvedLinkTarget =
|
|
18
|
+
/** Resolved to an absolute filesystem path (existence NOT checked here). */
|
|
19
|
+
{
|
|
20
|
+
state: 'resolved';
|
|
21
|
+
path: string;
|
|
22
|
+
}
|
|
23
|
+
/** A relative target with no known authoring file — the caller may route
|
|
24
|
+
* optimistically (it cannot check existence or self-ness generically). */
|
|
25
|
+
| {
|
|
26
|
+
state: 'unresolvable';
|
|
27
|
+
}
|
|
28
|
+
/** A malformed `$fs:` target (not mount-absolute; includes scheme smuggling).
|
|
29
|
+
* Callers MUST render this broken/inert — never as an anchor. */
|
|
30
|
+
| {
|
|
31
|
+
state: 'invalid';
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a raw link target (a wikilink target or an in-app href's path half —
|
|
35
|
+
* fragment already split off) to an absolute filesystem path. THE shared resolver:
|
|
36
|
+
* the default `WikiLink`, the markdown `a` override, and safe-content consumers
|
|
37
|
+
* all route through this one function so the two render pipelines cannot drift.
|
|
38
|
+
*/
|
|
39
|
+
declare function resolveLinkTarget(raw: string, opts?: {
|
|
40
|
+
currentFile?: string;
|
|
41
|
+
corpusRoot?: string | null;
|
|
42
|
+
}): ResolvedLinkTarget;
|
|
43
|
+
|
|
44
|
+
export { FS_PREFIX, type LinkSpace, LinkSpaceContext, type ResolvedLinkTarget, normalizeAbsolute, resolveLinkTarget };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
declare const FS_PREFIX = "$fs:";
|
|
4
|
+
interface LinkSpace {
|
|
5
|
+
/** Absolute filesystem path of the enclosing corpus's root (e.g. `/app/content`),
|
|
6
|
+
* or `null` when the document is not corpus-hosted (default). */
|
|
7
|
+
corpusRoot: string | null;
|
|
8
|
+
}
|
|
9
|
+
/** Ambient link space. A corpus-rendering app wraps its document tree in
|
|
10
|
+
* `<LinkSpaceContext value={{ corpusRoot }}>`; nesting a second provider inside a
|
|
11
|
+
* rendered sub-corpus makes the innermost root win. */
|
|
12
|
+
declare const LinkSpaceContext: react.Context<LinkSpace>;
|
|
13
|
+
/** Collapse `.`/`..`/empty segments into a clean absolute path. `..` can never
|
|
14
|
+
* climb above the root — a (virtual) root's parent is itself, which is what keeps
|
|
15
|
+
* both the mount space and the corpus space closed under traversal. */
|
|
16
|
+
declare const normalizeAbsolute: (path: string) => string;
|
|
17
|
+
type ResolvedLinkTarget =
|
|
18
|
+
/** Resolved to an absolute filesystem path (existence NOT checked here). */
|
|
19
|
+
{
|
|
20
|
+
state: 'resolved';
|
|
21
|
+
path: string;
|
|
22
|
+
}
|
|
23
|
+
/** A relative target with no known authoring file — the caller may route
|
|
24
|
+
* optimistically (it cannot check existence or self-ness generically). */
|
|
25
|
+
| {
|
|
26
|
+
state: 'unresolvable';
|
|
27
|
+
}
|
|
28
|
+
/** A malformed `$fs:` target (not mount-absolute; includes scheme smuggling).
|
|
29
|
+
* Callers MUST render this broken/inert — never as an anchor. */
|
|
30
|
+
| {
|
|
31
|
+
state: 'invalid';
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a raw link target (a wikilink target or an in-app href's path half —
|
|
35
|
+
* fragment already split off) to an absolute filesystem path. THE shared resolver:
|
|
36
|
+
* the default `WikiLink`, the markdown `a` override, and safe-content consumers
|
|
37
|
+
* all route through this one function so the two render pipelines cannot drift.
|
|
38
|
+
*/
|
|
39
|
+
declare function resolveLinkTarget(raw: string, opts?: {
|
|
40
|
+
currentFile?: string;
|
|
41
|
+
corpusRoot?: string | null;
|
|
42
|
+
}): ResolvedLinkTarget;
|
|
43
|
+
|
|
44
|
+
export { FS_PREFIX, type LinkSpace, LinkSpaceContext, type ResolvedLinkTarget, normalizeAbsolute, resolveLinkTarget };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "./chunk-VHAA22YE.js";
|
|
2
|
+
import { createContext } from "react";
|
|
3
|
+
const FS_PREFIX = "$fs:";
|
|
4
|
+
const LinkSpaceContext = createContext({ corpusRoot: null });
|
|
5
|
+
const normalizeAbsolute = (path) => {
|
|
6
|
+
const out = [];
|
|
7
|
+
for (const seg of path.split("/")) {
|
|
8
|
+
if (seg === "" || seg === ".") continue;
|
|
9
|
+
if (seg === "..") out.pop();
|
|
10
|
+
else out.push(seg);
|
|
11
|
+
}
|
|
12
|
+
return "/" + out.join("/");
|
|
13
|
+
};
|
|
14
|
+
function resolveLinkTarget(raw, opts = {}) {
|
|
15
|
+
if (raw.startsWith(FS_PREFIX)) {
|
|
16
|
+
const rest = raw.slice(FS_PREFIX.length);
|
|
17
|
+
if (!rest.startsWith("/")) return { state: "invalid" };
|
|
18
|
+
return { state: "resolved", path: normalizeAbsolute(rest) };
|
|
19
|
+
}
|
|
20
|
+
if (raw.startsWith("/")) {
|
|
21
|
+
const corpusRoot = opts.corpusRoot ?? null;
|
|
22
|
+
if (corpusRoot !== null) {
|
|
23
|
+
return { state: "resolved", path: normalizeAbsolute(corpusRoot + normalizeAbsolute(raw)) };
|
|
24
|
+
}
|
|
25
|
+
return { state: "resolved", path: normalizeAbsolute(raw) };
|
|
26
|
+
}
|
|
27
|
+
if (!opts.currentFile) return { state: "unresolvable" };
|
|
28
|
+
const dir = opts.currentFile.slice(0, opts.currentFile.lastIndexOf("/"));
|
|
29
|
+
return { state: "resolved", path: normalizeAbsolute(`${dir}/${raw}`) };
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
FS_PREFIX,
|
|
33
|
+
LinkSpaceContext,
|
|
34
|
+
normalizeAbsolute,
|
|
35
|
+
resolveLinkTarget
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=linkSpace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/linkSpace.ts"],"sourcesContent":["// Link path spaces (R3-273; REPO_CONTENT_DISPATCH_SPEC §9 decision, 2026-08-17).\n//\n// A document link's target resolves in one of two spaces:\n//\n// - DEFAULT — the enclosing corpus's virtual filesystem. RELATIVE targets resolve\n// against the authoring file (identical in both spaces); ABSOLUTE targets\n// (`/x/y.mdx`) resolve from the corpus root when an enclosing `LinkSpaceContext`\n// declares one, else from the filesystem root. A non-corpus app declares nothing\n// and keeps today's behavior bit-for-bit (its fs root IS its only root).\n//\n// - `$fs:` — the explicit filesystem space: `$fs:/content/x.mdx` resolves from the\n// root of the filesystem the app reads, escaping corpus-relative addressing.\n//\n// `$fs:` changes ADDRESSING, never REACH: resolution is pure path arithmetic and\n// existence is checked against the same in-mount metadata the default space uses —\n// nothing is fetched, and a link can never name what the app cannot already read.\n// A malformed `$fs:` target (anything not mount-absolute — which also catches\n// scheme smuggling like `$fs:javascript:…`) is INVALID and must render as a broken\n// link, never an anchor.\n//\n// Corpus nesting: `LinkSpaceContext` providers nest, and the NEAREST one wins —\n// which is exactly the innermost-enclosing-corpus rule (bundle encapsulation): a\n// document rendered inside a nested corpus resolves against the nested corpus.\n\nimport { createContext } from 'react';\n\nexport const FS_PREFIX = '$fs:';\n\nexport interface LinkSpace {\n /** Absolute filesystem path of the enclosing corpus's root (e.g. `/app/content`),\n * or `null` when the document is not corpus-hosted (default). */\n corpusRoot: string | null;\n}\n\n/** Ambient link space. A corpus-rendering app wraps its document tree in\n * `<LinkSpaceContext value={{ corpusRoot }}>`; nesting a second provider inside a\n * rendered sub-corpus makes the innermost root win. */\nexport const LinkSpaceContext = createContext<LinkSpace>({ corpusRoot: null });\n\n/** Collapse `.`/`..`/empty segments into a clean absolute path. `..` can never\n * climb above the root — a (virtual) root's parent is itself, which is what keeps\n * both the mount space and the corpus space closed under traversal. */\nexport const normalizeAbsolute = (path: string): string => {\n const out: string[] = [];\n for (const seg of path.split('/')) {\n if (seg === '' || seg === '.') continue;\n if (seg === '..') out.pop();\n else out.push(seg);\n }\n return '/' + out.join('/');\n};\n\nexport type ResolvedLinkTarget =\n /** Resolved to an absolute filesystem path (existence NOT checked here). */\n | { state: 'resolved'; path: string }\n /** A relative target with no known authoring file — the caller may route\n * optimistically (it cannot check existence or self-ness generically). */\n | { state: 'unresolvable' }\n /** A malformed `$fs:` target (not mount-absolute; includes scheme smuggling).\n * Callers MUST render this broken/inert — never as an anchor. */\n | { state: 'invalid' };\n\n/**\n * Resolve a raw link target (a wikilink target or an in-app href's path half —\n * fragment already split off) to an absolute filesystem path. THE shared resolver:\n * the default `WikiLink`, the markdown `a` override, and safe-content consumers\n * all route through this one function so the two render pipelines cannot drift.\n */\nexport function resolveLinkTarget(\n raw: string,\n opts: { currentFile?: string; corpusRoot?: string | null } = {},\n): ResolvedLinkTarget {\n if (raw.startsWith(FS_PREFIX)) {\n const rest = raw.slice(FS_PREFIX.length);\n // Must be mount-absolute. This single rule also fails `$fs:javascript:…`,\n // `$fs:https://…`, and every other smuggled scheme closed.\n if (!rest.startsWith('/')) return { state: 'invalid' };\n return { state: 'resolved', path: normalizeAbsolute(rest) };\n }\n if (raw.startsWith('/')) {\n const corpusRoot = opts.corpusRoot ?? null;\n if (corpusRoot !== null) {\n // Clamp the corpus-relative half FIRST (the virtual FS is closed — `/../x`\n // stays inside the corpus), THEN anchor it at the corpus root.\n return { state: 'resolved', path: normalizeAbsolute(corpusRoot + normalizeAbsolute(raw)) };\n }\n return { state: 'resolved', path: normalizeAbsolute(raw) };\n }\n // Relative: against the authoring file's directory — the same in both spaces.\n if (!opts.currentFile) return { state: 'unresolvable' };\n const dir = opts.currentFile.slice(0, opts.currentFile.lastIndexOf('/'));\n return { state: 'resolved', path: normalizeAbsolute(`${dir}/${raw}`) };\n}\n"],"mappings":";AAwBA,SAAS,qBAAqB;AAEvB,MAAM,YAAY;AAWlB,MAAM,mBAAmB,cAAyB,EAAE,YAAY,KAAK,CAAC;AAKtE,MAAM,oBAAoB,CAAC,SAAyB;AACzD,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AACjC,QAAI,QAAQ,MAAM,QAAQ,IAAK;AAC/B,QAAI,QAAQ,KAAM,KAAI,IAAI;AAAA,QACrB,KAAI,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,MAAM,IAAI,KAAK,GAAG;AAC3B;AAkBO,SAAS,kBACd,KACA,OAA6D,CAAC,GAC1C;AACpB,MAAI,IAAI,WAAW,SAAS,GAAG;AAC7B,UAAM,OAAO,IAAI,MAAM,UAAU,MAAM;AAGvC,QAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO,EAAE,OAAO,UAAU;AACrD,WAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,IAAI,EAAE;AAAA,EAC5D;AACA,MAAI,IAAI,WAAW,GAAG,GAAG;AACvB,UAAM,aAAa,KAAK,cAAc;AACtC,QAAI,eAAe,MAAM;AAGvB,aAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,aAAa,kBAAkB,GAAG,CAAC,EAAE;AAAA,IAC3F;AACA,WAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,GAAG,EAAE;AAAA,EAC3D;AAEA,MAAI,CAAC,KAAK,YAAa,QAAO,EAAE,OAAO,eAAe;AACtD,QAAM,MAAM,KAAK,YAAY,MAAM,GAAG,KAAK,YAAY,YAAY,GAAG,CAAC;AACvE,SAAO,EAAE,OAAO,YAAY,MAAM,kBAAkB,GAAG,GAAG,IAAI,GAAG,EAAE,EAAE;AACvE;","names":[]}
|
|
@@ -24,6 +24,7 @@ module.exports = __toCommonJS(renderMdast_exports);
|
|
|
24
24
|
var import_react = require("react");
|
|
25
25
|
var import_sanitizeUrl = require("./sanitizeUrl");
|
|
26
26
|
var import_wikilink = require("./wikilink");
|
|
27
|
+
var import_linkSpace = require("../linkSpace");
|
|
27
28
|
function literalProps(attributes) {
|
|
28
29
|
const props = {};
|
|
29
30
|
for (const attr of attributes ?? []) {
|
|
@@ -41,6 +42,7 @@ function elementFor(tag, components) {
|
|
|
41
42
|
}
|
|
42
43
|
function renderWiki(token, opts) {
|
|
43
44
|
const label = token.label ?? token.target;
|
|
45
|
+
if ((0, import_linkSpace.resolveLinkTarget)(token.target).state === "invalid") return label;
|
|
44
46
|
const href = opts.resolveWikiLink?.(token.target);
|
|
45
47
|
const safe = href !== void 0 ? (0, import_sanitizeUrl.sanitizeUrl)(href) : void 0;
|
|
46
48
|
if (safe === void 0) return label;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/**\n * The element to build for a link/image: the host's registered override when it has one,\n * else the intrinsic tag.\n *\n * **Why this is safe.** `components` is the HOST's registry — the same one `<Component/>`\n * syntax resolves against — never author input. An author cannot add to it or choose one\n * here, because the lookup is by a FIXED key (`'a'`/`'img'`), not by anything read out of\n * the document. The URL is still `sanitizeUrl`'d before it is handed over, and only props\n * this renderer controls are passed. The trust boundary is unchanged: author text still\n * reaches nothing but children and a sanitized URL.\n *\n * **Why it is needed.** Without it, a safe-rendered document's links are raw `<a href>`,\n * so a click performs a REAL navigation. Inside an app's sandboxed iframe that is fatal —\n * the frame navigates away from the app, and in a routed host app it dies with\n * `Failed to construct 'URL': Invalid URL`. Hosts route links through a component for\n * exactly this reason, and the compiled-MDX path has always honoured that component; the\n * safe path silently ignored it, so one document behaved differently on the two renderers.\n */\nfunction elementFor(\n tag: 'a' | 'img',\n components: RenderMdastOptions['components'],\n): React.ElementType {\n return (components?.[tag] as React.ElementType) ?? tag;\n}\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, opts: RenderMdastOptions): ReactNode {\n const label = token.label ?? token.target;\n const href = opts.resolveWikiLink?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement(\n elementFor('a', opts.components),\n { href: safe, 'data-wikilink': token.target, key: nextKey() },\n label,\n );\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n // R3-213: the no-acorn path has no hast stage, so the heading id the shared\n // `remarkHeadingAnchors` plugin sets via `data.hProperties.id` (+ the `data-slug`\n // fallback, R3-211) must be translated to React props HERE, or headings render\n // id-less and deep-linking (`#sec-8-9`) breaks in the safe path. These are\n // plugin-computed literal strings, not author input — safe to pass through.\n const hp = (node.data as { hProperties?: Record<string, unknown> } | undefined)?.hProperties;\n const headingProps: Record<string, string> = {};\n if (typeof hp?.id === 'string') headingProps.id = hp.id;\n if (typeof hp?.['data-slug'] === 'string') headingProps['data-slug'] = hp['data-slug'];\n return createElement(tag, { key, ...headingProps }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement(\n elementFor('a', opts.components),\n { key, href, title: node.title ?? undefined },\n ...renderChildren(node, opts),\n );\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement(elementFor('img', opts.components), {\n key,\n src,\n alt: node.alt ?? '',\n title: node.title ?? undefined,\n });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport { resolveLinkTarget } from '../linkSpace';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/**\n * The element to build for a link/image: the host's registered override when it has one,\n * else the intrinsic tag.\n *\n * **Why this is safe.** `components` is the HOST's registry — the same one `<Component/>`\n * syntax resolves against — never author input. An author cannot add to it or choose one\n * here, because the lookup is by a FIXED key (`'a'`/`'img'`), not by anything read out of\n * the document. The URL is still `sanitizeUrl`'d before it is handed over, and only props\n * this renderer controls are passed. The trust boundary is unchanged: author text still\n * reaches nothing but children and a sanitized URL.\n *\n * **Why it is needed.** Without it, a safe-rendered document's links are raw `<a href>`,\n * so a click performs a REAL navigation. Inside an app's sandboxed iframe that is fatal —\n * the frame navigates away from the app, and in a routed host app it dies with\n * `Failed to construct 'URL': Invalid URL`. Hosts route links through a component for\n * exactly this reason, and the compiled-MDX path has always honoured that component; the\n * safe path silently ignored it, so one document behaved differently on the two renderers.\n */\nfunction elementFor(\n tag: 'a' | 'img',\n components: RenderMdastOptions['components'],\n): React.ElementType {\n return (components?.[tag] as React.ElementType) ?? tag;\n}\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, opts: RenderMdastOptions): ReactNode {\n const label = token.label ?? token.target;\n // R3-273: a malformed `$fs:` target (not mount-absolute — catches scheme\n // smuggling) is inert BEFORE the injected resolver ever sees it, so every\n // consumer fails closed identically. Consumers implement their resolver on the\n // same shared `resolveLinkTarget` for well-formed targets.\n if (resolveLinkTarget(token.target).state === 'invalid') return label;\n const href = opts.resolveWikiLink?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement(\n elementFor('a', opts.components),\n { href: safe, 'data-wikilink': token.target, key: nextKey() },\n label,\n );\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n // R3-213: the no-acorn path has no hast stage, so the heading id the shared\n // `remarkHeadingAnchors` plugin sets via `data.hProperties.id` (+ the `data-slug`\n // fallback, R3-211) must be translated to React props HERE, or headings render\n // id-less and deep-linking (`#sec-8-9`) breaks in the safe path. These are\n // plugin-computed literal strings, not author input — safe to pass through.\n const hp = (node.data as { hProperties?: Record<string, unknown> } | undefined)?.hProperties;\n const headingProps: Record<string, string> = {};\n if (typeof hp?.id === 'string') headingProps.id = hp.id;\n if (typeof hp?.['data-slug'] === 'string') headingProps['data-slug'] = hp['data-slug'];\n return createElement(tag, { key, ...headingProps }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement(\n elementFor('a', opts.components),\n { key, href, title: node.title ?? undefined },\n ...renderChildren(node, opts),\n );\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement(elementFor('img', opts.components), {\n key,\n src,\n alt: node.alt ?? '',\n title: node.title ?? undefined,\n });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,6 +2,7 @@ import "../chunk-VHAA22YE.js";
|
|
|
2
2
|
import { createElement, Fragment } from "react";
|
|
3
3
|
import { sanitizeUrl } from "./sanitizeUrl";
|
|
4
4
|
import { splitWikiLinks } from "./wikilink";
|
|
5
|
+
import { resolveLinkTarget } from "../linkSpace";
|
|
5
6
|
function literalProps(attributes) {
|
|
6
7
|
const props = {};
|
|
7
8
|
for (const attr of attributes ?? []) {
|
|
@@ -19,6 +20,7 @@ function elementFor(tag, components) {
|
|
|
19
20
|
}
|
|
20
21
|
function renderWiki(token, opts) {
|
|
21
22
|
const label = token.label ?? token.target;
|
|
23
|
+
if (resolveLinkTarget(token.target).state === "invalid") return label;
|
|
22
24
|
const href = opts.resolveWikiLink?.(token.target);
|
|
23
25
|
const safe = href !== void 0 ? sanitizeUrl(href) : void 0;
|
|
24
26
|
if (safe === void 0) return label;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/**\n * The element to build for a link/image: the host's registered override when it has one,\n * else the intrinsic tag.\n *\n * **Why this is safe.** `components` is the HOST's registry — the same one `<Component/>`\n * syntax resolves against — never author input. An author cannot add to it or choose one\n * here, because the lookup is by a FIXED key (`'a'`/`'img'`), not by anything read out of\n * the document. The URL is still `sanitizeUrl`'d before it is handed over, and only props\n * this renderer controls are passed. The trust boundary is unchanged: author text still\n * reaches nothing but children and a sanitized URL.\n *\n * **Why it is needed.** Without it, a safe-rendered document's links are raw `<a href>`,\n * so a click performs a REAL navigation. Inside an app's sandboxed iframe that is fatal —\n * the frame navigates away from the app, and in a routed host app it dies with\n * `Failed to construct 'URL': Invalid URL`. Hosts route links through a component for\n * exactly this reason, and the compiled-MDX path has always honoured that component; the\n * safe path silently ignored it, so one document behaved differently on the two renderers.\n */\nfunction elementFor(\n tag: 'a' | 'img',\n components: RenderMdastOptions['components'],\n): React.ElementType {\n return (components?.[tag] as React.ElementType) ?? tag;\n}\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, opts: RenderMdastOptions): ReactNode {\n const label = token.label ?? token.target;\n const href = opts.resolveWikiLink?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement(\n elementFor('a', opts.components),\n { href: safe, 'data-wikilink': token.target, key: nextKey() },\n label,\n );\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n // R3-213: the no-acorn path has no hast stage, so the heading id the shared\n // `remarkHeadingAnchors` plugin sets via `data.hProperties.id` (+ the `data-slug`\n // fallback, R3-211) must be translated to React props HERE, or headings render\n // id-less and deep-linking (`#sec-8-9`) breaks in the safe path. These are\n // plugin-computed literal strings, not author input — safe to pass through.\n const hp = (node.data as { hProperties?: Record<string, unknown> } | undefined)?.hProperties;\n const headingProps: Record<string, string> = {};\n if (typeof hp?.id === 'string') headingProps.id = hp.id;\n if (typeof hp?.['data-slug'] === 'string') headingProps['data-slug'] = hp['data-slug'];\n return createElement(tag, { key, ...headingProps }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement(\n elementFor('a', opts.components),\n { key, href, title: node.title ?? undefined },\n ...renderChildren(node, opts),\n );\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement(elementFor('img', opts.components), {\n key,\n src,\n alt: node.alt ?? '',\n title: node.title ?? undefined,\n });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport { resolveLinkTarget } from '../linkSpace';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/**\n * The element to build for a link/image: the host's registered override when it has one,\n * else the intrinsic tag.\n *\n * **Why this is safe.** `components` is the HOST's registry — the same one `<Component/>`\n * syntax resolves against — never author input. An author cannot add to it or choose one\n * here, because the lookup is by a FIXED key (`'a'`/`'img'`), not by anything read out of\n * the document. The URL is still `sanitizeUrl`'d before it is handed over, and only props\n * this renderer controls are passed. The trust boundary is unchanged: author text still\n * reaches nothing but children and a sanitized URL.\n *\n * **Why it is needed.** Without it, a safe-rendered document's links are raw `<a href>`,\n * so a click performs a REAL navigation. Inside an app's sandboxed iframe that is fatal —\n * the frame navigates away from the app, and in a routed host app it dies with\n * `Failed to construct 'URL': Invalid URL`. Hosts route links through a component for\n * exactly this reason, and the compiled-MDX path has always honoured that component; the\n * safe path silently ignored it, so one document behaved differently on the two renderers.\n */\nfunction elementFor(\n tag: 'a' | 'img',\n components: RenderMdastOptions['components'],\n): React.ElementType {\n return (components?.[tag] as React.ElementType) ?? tag;\n}\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, opts: RenderMdastOptions): ReactNode {\n const label = token.label ?? token.target;\n // R3-273: a malformed `$fs:` target (not mount-absolute — catches scheme\n // smuggling) is inert BEFORE the injected resolver ever sees it, so every\n // consumer fails closed identically. Consumers implement their resolver on the\n // same shared `resolveLinkTarget` for well-formed targets.\n if (resolveLinkTarget(token.target).state === 'invalid') return label;\n const href = opts.resolveWikiLink?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement(\n elementFor('a', opts.components),\n { href: safe, 'data-wikilink': token.target, key: nextKey() },\n label,\n );\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n // R3-213: the no-acorn path has no hast stage, so the heading id the shared\n // `remarkHeadingAnchors` plugin sets via `data.hProperties.id` (+ the `data-slug`\n // fallback, R3-211) must be translated to React props HERE, or headings render\n // id-less and deep-linking (`#sec-8-9`) breaks in the safe path. These are\n // plugin-computed literal strings, not author input — safe to pass through.\n const hp = (node.data as { hProperties?: Record<string, unknown> } | undefined)?.hProperties;\n const headingProps: Record<string, string> = {};\n if (typeof hp?.id === 'string') headingProps.id = hp.id;\n if (typeof hp?.['data-slug'] === 'string') headingProps['data-slug'] = hp['data-slug'];\n return createElement(tag, { key, ...headingProps }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement(\n elementFor('a', opts.components),\n { key, href, title: node.title ?? undefined },\n ...renderChildren(node, opts),\n );\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement(elementFor('img', opts.components), {\n key,\n src,\n alt: node.alt ?? '',\n title: node.title ?? undefined,\n });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
|
package/dist/version.cjs
CHANGED
|
@@ -21,7 +21,7 @@ __export(version_exports, {
|
|
|
21
21
|
SDK_VERSION: () => SDK_VERSION
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(version_exports);
|
|
24
|
-
const SDK_VERSION = "0.
|
|
24
|
+
const SDK_VERSION = "0.44.0";
|
|
25
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26
26
|
0 && (module.exports = {
|
|
27
27
|
SDK_VERSION
|
package/dist/version.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.44.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
|
package/dist/version.d.cts
CHANGED
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.44.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
|
package/package.json
CHANGED