{"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  // R3-482: the link-space root reads new-then-old — `bundleRoot` when the provider\n  // states it at all (an explicit `null` is a VALUE: \"no bundle root\", NOT a miss),\n  // the deprecated `corpusRoot` spelling only when `bundleRoot` is absent. The same\n  // presence rule as mdx-plugins' statedBundleRoot; twin read in MDXComponents.tsx.\n  const space = use(LinkSpaceContext);\n  const bundleRoot = space.bundleRoot !== undefined ? space.bundleRoot : space.corpusRoot ?? null;\n  // R3-783: forward the chroot flag too (BUNDLE_LAYERS_SPEC §9) — under a\n  // bundle-chroot'd grant `$fs:` collapses to the scoped root. Without this the\n  // provider's `bundleChrooted` was decided-but-not-consumed on the generic path.\n  const bundleChrooted = space.bundleChrooted ?? false;\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 (bundle-rooted absolute targets when an enclosing provider\n  // declares a bundleRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n  // A malformed `$fs:` target renders BROKEN, never an anchor.\n  const resolution = resolveLinkTarget(pathPart, { currentFile, bundleRoot, bundleChrooted });\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 className=\"ir-wikilink ir-wikilink-broken\" data-state=\"broken\" title={`No file at ${resolved}`} {...rest}>\n          {text}\n        </span>\n      );\n    }\n  }\n  // Resolved cross-file target: route through <Link>. A space-translated target\n  // (`$fs:` prefix, or a bundle-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 = pathPart.startsWith(FS_PREFIX) || (bundleRoot !== null && pathPart.startsWith('/'));\n  const href = 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;AAsFM;AAtFN,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;AAK/C,QAAM,YAAQ,kBAAI,iCAAgB;AAClC,QAAM,aAAa,MAAM,eAAe,SAAY,MAAM,aAAa,MAAM,cAAc;AAI3F,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,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,YAAY,eAAe,CAAC;AAC1F,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,4CAAC,UAAK,WAAU,kCAAiC,cAAW,UAAS,OAAO,cAAc,QAAQ,IAAK,GAAG,MACvG,gBACH;AAAA,IAEJ;AAAA,EACF;AAMA,QAAM,aAAa,SAAS,WAAW,0BAAS,KAAM,eAAe,QAAQ,SAAS,WAAW,GAAG;AACpG,QAAM,OAAO,cAAc,aAAa,SAAY,GAAG,QAAQ,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,KAAK;AAC7F,SACE,4CAAC,oBAAK,MAAY,WAAU,eAAc,cAAW,YAAY,GAAG,MACjE,gBACH;AAEJ;","names":[]}