wiki-formant 0.13.0 → 0.16.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.
Files changed (137) hide show
  1. package/README.md +12 -25
  2. package/bin/check-classes.mjs +2 -3
  3. package/dist/analytics.d.ts +0 -1
  4. package/dist/analytics.js +0 -1
  5. package/dist/block-views.d.ts +97 -0
  6. package/dist/block-views.js +119 -0
  7. package/dist/blocks.d.ts +0 -1
  8. package/dist/blocks.js +1 -2
  9. package/dist/combobox.d.ts +0 -1
  10. package/dist/combobox.js +0 -1
  11. package/dist/conformance.d.ts +0 -3
  12. package/dist/conformance.js +40 -38
  13. package/dist/crawlers.d.ts +0 -1
  14. package/dist/crawlers.js +0 -1
  15. package/dist/dom.d.ts +0 -1
  16. package/dist/dom.js +0 -1
  17. package/dist/editor.d.ts +101 -0
  18. package/dist/editor.js +218 -0
  19. package/dist/feed.d.ts +0 -1
  20. package/dist/feed.js +0 -1
  21. package/dist/freshness.d.ts +0 -1
  22. package/dist/freshness.js +2 -2
  23. package/dist/headings.d.ts +0 -1
  24. package/dist/headings.js +1 -3
  25. package/dist/html.d.ts +16 -0
  26. package/dist/html.js +23 -0
  27. package/dist/http.d.ts +20 -1
  28. package/dist/http.js +23 -4
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -2
  31. package/dist/license.d.ts +0 -1
  32. package/dist/license.js +0 -1
  33. package/dist/link-check.d.ts +92 -0
  34. package/dist/link-check.js +249 -0
  35. package/dist/links.d.ts +0 -1
  36. package/dist/links.js +1 -6
  37. package/dist/maps.d.ts +0 -1
  38. package/dist/maps.js +2 -2
  39. package/dist/markdown.d.ts +1 -1
  40. package/dist/markdown.js +34 -3
  41. package/dist/mcp.d.ts +17 -7
  42. package/dist/mcp.js +8 -8
  43. package/dist/pagination.d.ts +19 -1
  44. package/dist/pagination.js +21 -1
  45. package/dist/rate-limit.d.ts +18 -1
  46. package/dist/rate-limit.js +22 -1
  47. package/dist/react-server.d.ts +91 -9
  48. package/dist/react-server.js +68 -16
  49. package/dist/react.d.ts +37 -48
  50. package/dist/react.js +44 -23
  51. package/dist/revisions.d.ts +0 -1
  52. package/dist/revisions.js +0 -1
  53. package/dist/rola.d.ts +0 -1
  54. package/dist/rola.js +0 -1
  55. package/dist/seeded.d.ts +11 -0
  56. package/dist/seeded.js +32 -0
  57. package/dist/sidebar.d.ts +0 -1
  58. package/dist/sidebar.js +0 -1
  59. package/dist/taxonomy.d.ts +0 -1
  60. package/dist/taxonomy.js +0 -1
  61. package/dist/text.d.ts +14 -1
  62. package/dist/text.js +15 -2
  63. package/dist/tiptap.d.ts +1 -1
  64. package/dist/tiptap.js +1 -2
  65. package/dist/validation.d.ts +2 -3
  66. package/dist/validation.js +4 -5
  67. package/dist/versioning.d.ts +0 -1
  68. package/dist/versioning.js +0 -1
  69. package/dist/well-known.d.ts +19 -2
  70. package/dist/well-known.js +28 -2
  71. package/dist/x402.d.ts +2 -11
  72. package/dist/x402.js +4 -16
  73. package/package.json +52 -27
  74. package/dist/analytics.d.ts.map +0 -1
  75. package/dist/analytics.js.map +0 -1
  76. package/dist/blocks.d.ts.map +0 -1
  77. package/dist/blocks.js.map +0 -1
  78. package/dist/combobox.d.ts.map +0 -1
  79. package/dist/combobox.js.map +0 -1
  80. package/dist/conformance.d.ts.map +0 -1
  81. package/dist/conformance.js.map +0 -1
  82. package/dist/crawlers.d.ts.map +0 -1
  83. package/dist/crawlers.js.map +0 -1
  84. package/dist/dom.d.ts.map +0 -1
  85. package/dist/dom.js.map +0 -1
  86. package/dist/entities.d.ts +0 -3
  87. package/dist/entities.d.ts.map +0 -1
  88. package/dist/entities.js +0 -30
  89. package/dist/entities.js.map +0 -1
  90. package/dist/feed.d.ts.map +0 -1
  91. package/dist/feed.js.map +0 -1
  92. package/dist/freshness.d.ts.map +0 -1
  93. package/dist/freshness.js.map +0 -1
  94. package/dist/headings.d.ts.map +0 -1
  95. package/dist/headings.js.map +0 -1
  96. package/dist/http.d.ts.map +0 -1
  97. package/dist/http.js.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.js.map +0 -1
  100. package/dist/license.d.ts.map +0 -1
  101. package/dist/license.js.map +0 -1
  102. package/dist/links.d.ts.map +0 -1
  103. package/dist/links.js.map +0 -1
  104. package/dist/maps.d.ts.map +0 -1
  105. package/dist/maps.js.map +0 -1
  106. package/dist/markdown.d.ts.map +0 -1
  107. package/dist/markdown.js.map +0 -1
  108. package/dist/mcp.d.ts.map +0 -1
  109. package/dist/mcp.js.map +0 -1
  110. package/dist/pagination.d.ts.map +0 -1
  111. package/dist/pagination.js.map +0 -1
  112. package/dist/rate-limit.d.ts.map +0 -1
  113. package/dist/rate-limit.js.map +0 -1
  114. package/dist/react-server.d.ts.map +0 -1
  115. package/dist/react-server.js.map +0 -1
  116. package/dist/react.d.ts.map +0 -1
  117. package/dist/react.js.map +0 -1
  118. package/dist/revisions.d.ts.map +0 -1
  119. package/dist/revisions.js.map +0 -1
  120. package/dist/rola.d.ts.map +0 -1
  121. package/dist/rola.js.map +0 -1
  122. package/dist/sidebar.d.ts.map +0 -1
  123. package/dist/sidebar.js.map +0 -1
  124. package/dist/taxonomy.d.ts.map +0 -1
  125. package/dist/taxonomy.js.map +0 -1
  126. package/dist/text.d.ts.map +0 -1
  127. package/dist/text.js.map +0 -1
  128. package/dist/tiptap.d.ts.map +0 -1
  129. package/dist/tiptap.js.map +0 -1
  130. package/dist/validation.d.ts.map +0 -1
  131. package/dist/validation.js.map +0 -1
  132. package/dist/versioning.d.ts.map +0 -1
  133. package/dist/versioning.js.map +0 -1
  134. package/dist/well-known.d.ts.map +0 -1
  135. package/dist/well-known.js.map +0 -1
  136. package/dist/x402.d.ts.map +0 -1
  137. package/dist/x402.js.map +0 -1
@@ -13,9 +13,18 @@ export interface WikiLinkProps {
13
13
  className?: string;
14
14
  title?: string;
15
15
  'aria-current'?: 'true' | 'page' | undefined;
16
+ /** `prev`/`next` on the foot nav — the half that makes the pair machine-readable. */
17
+ rel?: string;
16
18
  children?: ReactNode;
17
19
  }
18
20
  export type WikiLinkComponent = ComponentType<WikiLinkProps>;
21
+ /**
22
+ * The default for anything that does not need client-side navigation.
23
+ *
24
+ * Exported because `block-views` renders links too and had declared the same
25
+ * three lines; a default that exists twice is a default that can diverge.
26
+ */
27
+ export declare const Anchor: WikiLinkComponent;
19
28
  /**
20
29
  * Class names differ per wiki and always will — unlike the rail's `wiki-rail__*`
21
30
  * tree, which is a shared convention, these are each design system's own words
@@ -50,29 +59,102 @@ export interface FacetBarProps {
50
59
  export declare function FacetBar({ link: Link, facets, letters, alphaLabel, classNames, }: FacetBarProps): import("react").JSX.Element | null;
51
60
  export interface BreadcrumbItem {
52
61
  label: string;
53
- href: string;
62
+ /**
63
+ * Omit to render the crumb as plain text rather than a link — a section that
64
+ * groups pages but has no page of its own. A crumb with no href is left out
65
+ * of the `BreadcrumbList` too, since a `ListItem` with no `item` is invalid.
66
+ */
67
+ href?: string;
54
68
  }
55
69
  export interface BreadcrumbsProps {
56
70
  items: BreadcrumbItem[];
57
71
  /**
58
72
  * Origin the JSON-LD `item` URLs are resolved against, without a trailing
59
73
  * slash. Structured-data URLs must be absolute — Google rejects relative
60
- * paths — and this package cannot know the site it is rendering for.
74
+ * paths — and this package cannot know the site it is rendering for. Omit it
75
+ * to render the trail with no structured data at all.
61
76
  */
62
- base: string;
77
+ base?: string;
63
78
  className?: string;
64
79
  /** Defaults to a plain `<a>`, which is what a crumb trail wants. */
65
80
  link?: WikiLinkComponent;
81
+ /**
82
+ * What the final crumb renders as.
83
+ *
84
+ * `h1` is not decoration: on a wiki whose page title IS the last crumb, the
85
+ * trail and the heading are the same words, and rendering both means either
86
+ * saying it twice or shipping a page with no h1 at all. The `breadcrumb-h1`
87
+ * class carries the size difference.
88
+ */
89
+ lastAs?: 'span' | 'h1';
90
+ /**
91
+ * Fewest crumbs worth rendering. Default 2 — one crumb is not a trail.
92
+ *
93
+ * A wiki whose last crumb doubles as the page heading passes 1, because there
94
+ * the single crumb is not a trail either, it is the title.
95
+ */
96
+ minItems?: number;
66
97
  }
67
98
  /**
68
99
  * The trail, plus the `BreadcrumbList` that makes it a rich result.
69
100
  *
70
- * The two are emitted together on purpose: a trail whose JSON-LD is written
101
+ * THE TWO ARE EMITTED TOGETHER ON PURPOSE. A trail whose JSON-LD is written
71
102
  * somewhere else is a trail that will one day disagree with its own markup, and
72
- * Google penalises exactly that.
103
+ * Google penalises exactly that. Both wikis had proved the point before
104
+ * adopting this: each rendered its crumbs in a component and built its
105
+ * `BreadcrumbList` in a different file — a page module in one, a `*-ld.ts` in
106
+ * the other — and one of them carried a comment noting the risk it was taking.
107
+ *
108
+ * Markup is a `<nav>` wrapping an `<ol>`: a breadcrumb trail is an ordered list
109
+ * and assistive technology announces it as one. The separators are list items
110
+ * marked `aria-hidden`, so they are not read out as content.
111
+ */
112
+ export declare function Breadcrumbs({ items, base, className, link: Link, lastAs, minItems, }: BreadcrumbsProps): import("react").JSX.Element | null;
113
+ /**
114
+ * The standard page-top row: the trail, plus optional right-aligned actions.
115
+ *
116
+ * The column it sits in is the one its route declares — the row reads the
117
+ * page's own max-width by inheritance, so a trail can no longer disagree with
118
+ * the content it titles.
119
+ */
120
+ export declare function BreadcrumbsRow({ actions, className, ...props }: BreadcrumbsProps & {
121
+ actions?: ReactNode;
122
+ }): import("react").JSX.Element;
123
+ /** One end of the foot nav: where it goes and what it is called. */
124
+ export interface PageNavRef {
125
+ title: string;
126
+ href: string;
127
+ }
128
+ export interface PageNavProps {
129
+ prev?: PageNavRef | null;
130
+ next?: PageNavRef | null;
131
+ link?: WikiLinkComponent;
132
+ /** The landmark's accessible name. */
133
+ label?: string;
134
+ prevLabel?: string;
135
+ nextLabel?: string;
136
+ /**
137
+ * The arrows. `ReactNode` rather than a string because both wikis draw them
138
+ * with an icon component, and this package is not acquiring an icon peer to
139
+ * hold two glyphs.
140
+ */
141
+ prevGlyph?: ReactNode;
142
+ nextGlyph?: ReactNode;
143
+ }
144
+ /**
145
+ * The sequential read at the foot of an article — the move the infobox rail's
146
+ * lateral links do not cover.
147
+ *
148
+ * Ordering is entirely the caller's; pair this with `adjacentPages` from
149
+ * `wiki-formant/pagination` over a list you already hold. Both wikis had this
150
+ * markup and had already drifted on the parts that matter rather than the parts
151
+ * that show: one carried `rel="prev"`/`rel="next"` and an `aria-label` and the
152
+ * other carried neither, and the one without expressed its right-hand alignment
153
+ * as two inline utilities instead of the modifier its own stylesheet defines.
73
154
  *
74
- * Renders nothing for a single crumb. One item is not a trail, and a
75
- * `BreadcrumbList` of length one is noise in the index.
155
+ * The `page-nav__*` class names are NOT props, for the reason `WikiRail`'s are
156
+ * not: they are the convention both stylesheets already implement, and making
157
+ * them configurable is how a convention forks. The empty `<div>` holds the
158
+ * first article's left column so the next link stays in the right one.
76
159
  */
77
- export declare function Breadcrumbs({ items, base, className, link: Link }: BreadcrumbsProps): import("react").JSX.Element | null;
78
- //# sourceMappingURL=react-server.d.ts.map
160
+ export declare function PageNav({ prev, next, link: Link, label, prevLabel, nextLabel, prevGlyph, nextGlyph, }: PageNavProps): import("react").JSX.Element | null;
@@ -19,8 +19,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
19
19
  // back to a full page load on every press. React is an optional peer, as it is
20
20
  // for `wiki-formant/react`.
21
21
  import { Fragment } from 'react';
22
- /** The default for anything that does not need client-side navigation. */
23
- const Anchor = ({ href, className, children }) => _jsx("a", { href: href, className: className, children: children });
22
+ /**
23
+ * The default for anything that does not need client-side navigation.
24
+ *
25
+ * Exported because `block-views` renders links too and had declared the same
26
+ * three lines; a default that exists twice is a default that can diverge.
27
+ */
28
+ export const Anchor = ({ href, className, children }) => _jsx("a", { href: href, className: className, children: children });
24
29
  /**
25
30
  * The section's second axis, as pressable chips.
26
31
  *
@@ -40,23 +45,70 @@ export function FacetBar({ link: Link, facets, letters, alphaLabel = 'A–Z', cl
40
45
  /**
41
46
  * The trail, plus the `BreadcrumbList` that makes it a rich result.
42
47
  *
43
- * The two are emitted together on purpose: a trail whose JSON-LD is written
48
+ * THE TWO ARE EMITTED TOGETHER ON PURPOSE. A trail whose JSON-LD is written
44
49
  * somewhere else is a trail that will one day disagree with its own markup, and
45
- * Google penalises exactly that.
50
+ * Google penalises exactly that. Both wikis had proved the point before
51
+ * adopting this: each rendered its crumbs in a component and built its
52
+ * `BreadcrumbList` in a different file — a page module in one, a `*-ld.ts` in
53
+ * the other — and one of them carried a comment noting the risk it was taking.
46
54
  *
47
- * Renders nothing for a single crumb. One item is not a trail, and a
48
- * `BreadcrumbList` of length one is noise in the index.
55
+ * Markup is a `<nav>` wrapping an `<ol>`: a breadcrumb trail is an ordered list
56
+ * and assistive technology announces it as one. The separators are list items
57
+ * marked `aria-hidden`, so they are not read out as content.
49
58
  */
50
- export function Breadcrumbs({ items, base, className = '', link: Link = Anchor }) {
51
- if (!items || items.length <= 1)
59
+ export function Breadcrumbs({ items, base, className = '', link: Link = Anchor, lastAs = 'span', minItems = 2, }) {
60
+ if (!items || items.length < minItems)
52
61
  return null;
53
62
  const absolute = (path) => /^https?:\/\//.test(path) ? path : `${base}${path.startsWith('/') ? path : `/${path}`}`;
54
- return (_jsxs(_Fragment, { children: [_jsx("nav", { "aria-label": "Breadcrumb", className: `breadcrumbs ${className}`, children: _jsx("ol", { children: items.map((item, i) => (_jsxs(Fragment, { children: [i > 0 && _jsx("li", { className: "separator", "aria-hidden": "true", children: "/" }), _jsx("li", { children: i === items.length - 1 ? (_jsx("span", { className: "pl-4", "aria-current": "page", children: item.label })) : (_jsx(Link, { href: item.href, children: item.label })) })] }, item.href))) }) }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify({
55
- "@context": "https://schema.org",
56
- "@type": "BreadcrumbList",
57
- "itemListElement": items.map((item, i) => ({
58
- "@type": "ListItem", "position": i + 1, "name": item.label, "item": absolute(item.href)
59
- }))
60
- }) } })] }));
63
+ // A one-item list is noise in the index even where the trail is worth
64
+ // rendering. Every crumb is listed, but only a crumb with an href carries an
65
+ // `item`: schema.org's documented shape for the page you are already on is a
66
+ // final `ListItem` with a name and no URL, so dropping it would lose the leaf
67
+ // the trail exists to name.
68
+ const structured = base && items.length > 1;
69
+ return (_jsxs(_Fragment, { children: [_jsx("nav", { "aria-label": "Breadcrumb", className: `breadcrumbs ${className}`.trim(), children: _jsx("ol", { children: items.map((item, i) => {
70
+ const isLast = i === items.length - 1;
71
+ const Last = lastAs === 'h1' ? 'h1' : 'span';
72
+ return (_jsxs(Fragment, { children: [i > 0 && _jsx("li", { className: "separator", "aria-hidden": "true", children: "/" }), _jsx("li", { children: isLast ? (_jsx(Last, { className: lastAs === 'h1' ? 'breadcrumb-h1' : 'breadcrumb-current', "aria-current": "page", children: item.label })) : item.href ? (_jsx(Link, { href: item.href, className: "breadcrumb-link", title: item.label, children: item.label })) : (_jsx("span", { className: "breadcrumb-current", children: item.label })) })] }, item.href ?? `${item.label}-${i}`));
73
+ }) }) }), structured && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify({
74
+ '@context': 'https://schema.org',
75
+ '@type': 'BreadcrumbList',
76
+ itemListElement: items.map((item, i) => ({
77
+ '@type': 'ListItem',
78
+ position: i + 1,
79
+ name: item.label,
80
+ ...(item.href ? { item: absolute(item.href) } : {}),
81
+ })),
82
+ }) } }))] }));
83
+ }
84
+ /**
85
+ * The standard page-top row: the trail, plus optional right-aligned actions.
86
+ *
87
+ * The column it sits in is the one its route declares — the row reads the
88
+ * page's own max-width by inheritance, so a trail can no longer disagree with
89
+ * the content it titles.
90
+ */
91
+ export function BreadcrumbsRow({ actions, className = '', ...props }) {
92
+ return (_jsxs("div", { className: `breadcrumbs-row ${actions ? 'spread' : ''} ${className}`.trim(), children: [_jsx(Breadcrumbs, { ...props }), actions] }));
93
+ }
94
+ /**
95
+ * The sequential read at the foot of an article — the move the infobox rail's
96
+ * lateral links do not cover.
97
+ *
98
+ * Ordering is entirely the caller's; pair this with `adjacentPages` from
99
+ * `wiki-formant/pagination` over a list you already hold. Both wikis had this
100
+ * markup and had already drifted on the parts that matter rather than the parts
101
+ * that show: one carried `rel="prev"`/`rel="next"` and an `aria-label` and the
102
+ * other carried neither, and the one without expressed its right-hand alignment
103
+ * as two inline utilities instead of the modifier its own stylesheet defines.
104
+ *
105
+ * The `page-nav__*` class names are NOT props, for the reason `WikiRail`'s are
106
+ * not: they are the convention both stylesheets already implement, and making
107
+ * them configurable is how a convention forks. The empty `<div>` holds the
108
+ * first article's left column so the next link stays in the right one.
109
+ */
110
+ export function PageNav({ prev, next, link: Link = Anchor, label = 'Article navigation', prevLabel = 'Previous', nextLabel = 'Next', prevGlyph = '\u2190', nextGlyph = '\u2192', }) {
111
+ if (!prev && !next)
112
+ return null;
113
+ return (_jsxs("nav", { className: "page-nav", "aria-label": label, children: [prev ? (_jsxs(Link, { href: prev.href, className: "page-nav-link", rel: "prev", children: [_jsxs("span", { className: "page-nav-label", children: [prevGlyph, prevLabel] }), _jsx("span", { className: "page-nav-title", children: prev.title })] })) : (_jsx("div", {})), next && (_jsxs(Link, { href: next.href, className: "page-nav-link page-nav-link--end", rel: "next", children: [_jsxs("span", { className: "page-nav-label page-nav-label--end", children: [nextLabel, nextGlyph] }), _jsx("span", { className: "page-nav-title", children: next.title })] }))] }));
61
114
  }
62
- //# sourceMappingURL=react-server.js.map
package/dist/react.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { WikiLinkComponent } from './react-server.js';
2
1
  import { type ComboboxAria } from './combobox.js';
3
2
  import { Component, type CSSProperties, type KeyboardEvent, type ReactNode } from 'react';
4
3
  export interface SidebarOptions {
@@ -307,60 +306,50 @@ export declare class ErrorBoundary extends Component<{
307
306
  componentDidCatch(error: unknown): void;
308
307
  render(): ReactNode;
309
308
  }
310
- /** One entry in the rail's section list, with its href already built. */
311
- export interface RailSection {
312
- href: string;
313
- title: string;
314
- glyph: string;
315
- blurb?: string;
316
- }
317
- export interface WikiRailProps {
318
- /**
319
- * The router's link component.
320
- *
321
- * The rail takes it as a prop rather than importing one. This package has no
322
- * `next` peer dependency and is not getting one — but a rail that falls back
323
- * to a bare `<a>` turns every navigation into a full page load, which is the
324
- * whole reason a wiki has a persistent rail. So the router stays the
325
- * consumer's and arrives through the door.
326
- */
327
- link: WikiLinkComponent;
328
- /** The path being rendered, for the active marker. */
329
- activePath: string;
330
- /** The wiki root's href. */
331
- homeHref: string;
332
- /** The wiki's sections with their hrefs already built, resolved server-side. */
333
- sections: RailSection[];
309
+ export interface RailShellProps {
310
+ children: ReactNode;
334
311
  /**
335
- * The article's headings, empty on the home and section views.
312
+ * Class prefix the stylesheet implements: `"wiki-sidebar"` or `"sidebar"`.
336
313
  *
337
- * Passing them is what lets `TableOfContents` skip its DOM scan and run no
338
- * MutationObserver. A wiki whose block content streams in after the rail
339
- * mounts leaves this empty and lets the scan do its job.
314
+ * The one thing genuinely per-repo here. Everything else — which three states
315
+ * exist, what each means, and that the attribute outranks the class — is a
316
+ * contract both stylesheets already spell out in near-identical comments.
340
317
  */
341
- headings?: TocHeading[];
318
+ prefix: string;
319
+ /** Accessible name. A rail with none is an unlabelled landmark. */
342
320
  label?: string;
343
- toggleLabel?: string;
344
- homeLabel?: string;
345
- homeGlyph?: string;
346
- sectionsLabel?: string;
347
- tocLabel?: string;
321
+ className?: string;
348
322
  }
349
323
  /**
350
- * The persistent left rail: home, table of contents, sections.
324
+ * The collapsible rail's outer shell: the landmark, its three states, and the
325
+ * scroll container, with a tap inside it closing the rail on mobile.
326
+ *
327
+ * THE THREE STATES ARE NOT TWO. `--open` / `--closed` is the reader's choice;
328
+ * `--instant` suppresses the transition until that choice has been read out of
329
+ * storage. Without the third, a rail the reader left closed renders open and
330
+ * then *slides shut* on every single load — and because the fix is invisible
331
+ * once it works, it is the part a reimplementation drops. Both rails that had
332
+ * this written out locally carried the same paragraph of comment explaining it,
333
+ * which is how you can tell it was learned twice.
334
+ *
335
+ * The class is not authoritative and cannot be: the server cannot read
336
+ * localStorage, so `sidebarBootScript` stamps `html[data-sidebar]` before first
337
+ * paint and the stylesheet lets that attribute win. The class is what keeps
338
+ * React's view in sync afterwards.
351
339
  *
352
- * Three wikis had hand-rendered the same tree with the same `wiki-rail__*`
353
- * classes and then drifted on the details — one lost the scroll wrapper, one
354
- * labelled neither `<nav>`, one appended the sections above the contents. The
355
- * class names are NOT props: they are the shared convention the three
356
- * stylesheets already implement, and making them configurable would only let
357
- * that convention fork again. Every string a reader sees is a prop, because
358
- * those genuinely differ.
340
+ * Closing on navigate is scoped to mobile because that is where the rail
341
+ * OVERLAYS the article — on a desktop it is a column beside it, and collapsing
342
+ * it on every click would be a rail that folds itself away as you use it.
343
+ */
344
+ export declare function RailShell({ children, prefix, label, className }: RailShellProps): import("react").JSX.Element;
345
+ /**
346
+ * Is this rail link the one the reader is on?
359
347
  *
360
- * Sections only, never the pages under them. The front page lists every page
361
- * under each section and a section page lists its own; mirroring that here
362
- * would make the rail a second copy of what is on screen.
348
+ * `tree` marks a link that owns its descendants — a section that should stay lit
349
+ * while you read a page inside it. Both rails had this predicate and both
350
+ * matched on the PATH ONLY, deliberately: `?edit=` and `?history=` are modes a
351
+ * page enters, not destinations a rail selects, and reading them would drag
352
+ * `useSearchParams` into a statically-rendered wiki.
363
353
  */
364
- export declare function WikiRail({ link: Link, activePath, homeHref, sections, headings, label, toggleLabel, homeLabel, homeGlyph, sectionsLabel, tocLabel, }: WikiRailProps): import("react").JSX.Element;
354
+ export declare function isRailLinkActive(activePath: string, href: string, tree?: boolean): boolean;
365
355
  export type { WikiLinkComponent, WikiLinkProps } from './react-server.js';
366
- //# sourceMappingURL=react.d.ts.map
package/dist/react.js CHANGED
@@ -574,30 +574,51 @@ export class ErrorBoundary extends Component {
574
574
  }
575
575
  }
576
576
  /**
577
- * The persistent left rail: home, table of contents, sections.
577
+ * The collapsible rail's outer shell: the landmark, its three states, and the
578
+ * scroll container, with a tap inside it closing the rail on mobile.
578
579
  *
579
- * Three wikis had hand-rendered the same tree with the same `wiki-rail__*`
580
- * classes and then drifted on the details — one lost the scroll wrapper, one
581
- * labelled neither `<nav>`, one appended the sections above the contents. The
582
- * class names are NOT props: they are the shared convention the three
583
- * stylesheets already implement, and making them configurable would only let
584
- * that convention fork again. Every string a reader sees is a prop, because
585
- * those genuinely differ.
580
+ * THE THREE STATES ARE NOT TWO. `--open` / `--closed` is the reader's choice;
581
+ * `--instant` suppresses the transition until that choice has been read out of
582
+ * storage. Without the third, a rail the reader left closed renders open and
583
+ * then *slides shut* on every single load — and because the fix is invisible
584
+ * once it works, it is the part a reimplementation drops. Both rails that had
585
+ * this written out locally carried the same paragraph of comment explaining it,
586
+ * which is how you can tell it was learned twice.
586
587
  *
587
- * Sections only, never the pages under them. The front page lists every page
588
- * under each section and a section page lists its own; mirroring that here
589
- * would make the rail a second copy of what is on screen.
588
+ * The class is not authoritative and cannot be: the server cannot read
589
+ * localStorage, so `sidebarBootScript` stamps `html[data-sidebar]` before first
590
+ * paint and the stylesheet lets that attribute win. The class is what keeps
591
+ * React's view in sync afterwards.
592
+ *
593
+ * Closing on navigate is scoped to mobile because that is where the rail
594
+ * OVERLAYS the article — on a desktop it is a column beside it, and collapsing
595
+ * it on every click would be a rail that folds itself away as you use it.
596
+ */
597
+ export function RailShell({ children, prefix, label, className }) {
598
+ const { open, setOpen, isMobile, ready } = useSidebar();
599
+ const close = useCallback(() => {
600
+ if (isMobile)
601
+ setOpen(false);
602
+ }, [isMobile, setOpen]);
603
+ const classes = [
604
+ prefix,
605
+ open ? `${prefix}--open` : `${prefix}--closed`,
606
+ ready ? '' : `${prefix}--instant`,
607
+ className ?? '',
608
+ ]
609
+ .filter(Boolean)
610
+ .join(' ');
611
+ return (_jsx("aside", { className: classes, "aria-label": label, children: _jsx("div", { className: `${prefix}__scroll`, onClick: close, children: children }) }));
612
+ }
613
+ /**
614
+ * Is this rail link the one the reader is on?
615
+ *
616
+ * `tree` marks a link that owns its descendants — a section that should stay lit
617
+ * while you read a page inside it. Both rails had this predicate and both
618
+ * matched on the PATH ONLY, deliberately: `?edit=` and `?history=` are modes a
619
+ * page enters, not destinations a rail selects, and reading them would drag
620
+ * `useSearchParams` into a statically-rendered wiki.
590
621
  */
591
- export function WikiRail({ link: Link, activePath, homeHref, sections, headings = [], label = 'Wiki', toggleLabel = 'Wiki menu', homeLabel = 'Wiki home', homeGlyph = '📖', sectionsLabel = 'Sections', tocLabel = 'On this page', }) {
592
- const { open, toggle } = useSidebar();
593
- const item = (href, active) => `wiki-rail__item${active ? ' wiki-rail__item--active' : ''}`;
594
- return (_jsxs("aside", { className: "wiki-sidebar", "aria-label": label, children: [_jsxs("button", { type: "button", className: "wiki-rail__toggle", onClick: toggle, "aria-expanded": open, children: [_jsx("span", { className: "wiki-rail__glyph", "aria-hidden": true, children: open ? '▾' : '▸' }), toggleLabel] }), _jsxs("div", { className: "wiki-rail__scroll", children: [_jsx("nav", { className: "wiki-rail__group", children: _jsxs(Link, { href: homeHref, className: item(homeHref, activePath === homeHref), children: [_jsx("span", { className: "wiki-rail__glyph", "aria-hidden": true, children: homeGlyph }), homeLabel] }) }), _jsx(TableOfContents, { headings: headings.map(h => ({ id: h.id, text: h.text, level: h.level })), indentRem: 0, offsetVar: "--spacing-lg", offsetFallback: 24, classNames: {
595
- root: 'wiki-rail__group wiki-rail__group--toc',
596
- button: 'wiki-rail__toc-btn',
597
- label: 'wiki-rail__label',
598
- list: 'wiki-rail__toc-list',
599
- item: 'wiki-rail__toc-item',
600
- itemActive: 'wiki-rail__toc-item--active',
601
- }, label: tocLabel }), _jsxs("nav", { className: "wiki-rail__group", "aria-label": sectionsLabel, children: [_jsx("span", { className: "wiki-rail__label", children: sectionsLabel }), sections.map(section => (_jsxs(Link, { href: section.href, title: section.blurb, className: item(section.href, activePath === section.href || activePath.startsWith(`${section.href}/`)), children: [_jsx("span", { className: "wiki-rail__glyph", "aria-hidden": true, children: section.glyph }), section.title] }, section.href)))] })] })] }));
622
+ export function isRailLinkActive(activePath, href, tree = false) {
623
+ return tree ? activePath === href || activePath.startsWith(`${href}/`) : activePath === href;
602
624
  }
603
- //# sourceMappingURL=react.js.map
@@ -96,4 +96,3 @@ export declare function computeRevisionDiff<B extends DiffBlock, L = unknown>(op
96
96
  leafDiff?: DiffOptions<B, L>['leafDiff'];
97
97
  }): RevisionDiff<L>;
98
98
  export {};
99
- //# sourceMappingURL=revisions.d.ts.map
package/dist/revisions.js CHANGED
@@ -170,4 +170,3 @@ export function computeRevisionDiff(opts) {
170
170
  };
171
171
  return { ...partial, summary: changeSummary({ ...partial, metaLabel: opts.metaLabel }) };
172
172
  }
173
- //# sourceMappingURL=revisions.js.map
package/dist/rola.d.ts CHANGED
@@ -125,4 +125,3 @@ export interface RolaAuth {
125
125
  }>;
126
126
  }
127
127
  export declare function createRolaAuth(config: RolaAuthConfig): RolaAuth;
128
- //# sourceMappingURL=rola.d.ts.map
package/dist/rola.js CHANGED
@@ -147,4 +147,3 @@ export function createRolaAuth(config) {
147
147
  },
148
148
  };
149
149
  }
150
- //# sourceMappingURL=rola.js.map
@@ -0,0 +1,11 @@
1
+ /** djb2-ish, 32-bit, non-negative. Stable across processes and runtimes. */
2
+ export declare function hashStr(s: string): number;
3
+ /**
4
+ * Lehmer/Park-Miller, seeded by an integer.
5
+ *
6
+ * Both guards matter and neither is obvious. Without the modulo a seed can
7
+ * exceed the modulus; without the `<= 0` correction a seed of 0 sticks the
8
+ * generator at 0 forever — every call returns the same number and whatever it
9
+ * was laying out degenerates to a single point.
10
+ */
11
+ export declare function seededRandom(seed: number): () => number;
package/dist/seeded.js ADDED
@@ -0,0 +1,32 @@
1
+ // seeded.ts — deterministic pseudo-randomness, for art that must not move.
2
+ //
3
+ // Both wikis generate SVG from a title: a page banner, a fallback icon. The
4
+ // mark has to be identical on the server and in the browser and on the next
5
+ // render forever, so `Math.random` is not available to them and these two are
6
+ // what they use instead. Both had written the same pair, and the comment in one
7
+ // of them recorded that its two guards had been ported from the other — which
8
+ // is a copy documenting itself as a copy.
9
+ /** djb2-ish, 32-bit, non-negative. Stable across processes and runtimes. */
10
+ export function hashStr(s) {
11
+ let h = 0;
12
+ for (let i = 0; i < s.length; i++)
13
+ h = ((h << 5) - h + s.charCodeAt(i)) | 0;
14
+ return Math.abs(h);
15
+ }
16
+ /**
17
+ * Lehmer/Park-Miller, seeded by an integer.
18
+ *
19
+ * Both guards matter and neither is obvious. Without the modulo a seed can
20
+ * exceed the modulus; without the `<= 0` correction a seed of 0 sticks the
21
+ * generator at 0 forever — every call returns the same number and whatever it
22
+ * was laying out degenerates to a single point.
23
+ */
24
+ export function seededRandom(seed) {
25
+ let s = seed % 2147483647;
26
+ if (s <= 0)
27
+ s += 2147483646;
28
+ return () => {
29
+ s = (s * 16807) % 2147483647;
30
+ return s / 2147483647;
31
+ };
32
+ }
package/dist/sidebar.d.ts CHANGED
@@ -35,4 +35,3 @@ export declare const SIDEBAR_ATTRIBUTE = "data-sidebar";
35
35
  * dies before it paints.
36
36
  */
37
37
  export declare function sidebarBootScript(storageKey?: string, breakpoint?: number): string;
38
- //# sourceMappingURL=sidebar.d.ts.map
package/dist/sidebar.js CHANGED
@@ -40,4 +40,3 @@ export function sidebarBootScript(storageKey = 'wiki:sidebar', breakpoint = 1024
40
40
  `var o=v===null?!window.matchMedia("(max-width: ${breakpoint - 1}px)").matches:v==="1";` +
41
41
  `document.documentElement.setAttribute(${JSON.stringify(SIDEBAR_ATTRIBUTE)},o?"open":"closed");}catch(e){}})()`);
42
42
  }
43
- //# sourceMappingURL=sidebar.js.map
@@ -161,4 +161,3 @@ export interface Taxonomy {
161
161
  rankRelated: <T extends FacetablePage>(page: T, siblings: T[], tagPath: string, limit?: number) => RelatedRanking<T>;
162
162
  }
163
163
  export declare function createTaxonomy(config: TaxonomyConfig): Taxonomy;
164
- //# sourceMappingURL=taxonomy.d.ts.map
package/dist/taxonomy.js CHANGED
@@ -235,4 +235,3 @@ export function createTaxonomy(config) {
235
235
  },
236
236
  };
237
237
  }
238
- //# sourceMappingURL=taxonomy.js.map
package/dist/text.d.ts CHANGED
@@ -13,6 +13,20 @@ export type BannerVariant = 'stub' | 'unsourced' | 'outdated' | 'promotional' |
13
13
  * the markdown twin and the MDX export all render the same six strings.
14
14
  */
15
15
  export declare const BANNER_LABELS: Record<BannerVariant, string>;
16
+ /**
17
+ * The banner variants as an editor's option list, in the order a writer meets
18
+ * them: the two that describe a gap first, then the three that describe a flaw.
19
+ *
20
+ * Derived from `BANNER_LABELS` rather than restated. Both editors had typed the
21
+ * same six `{ value, label }` pairs out by hand, which made four places in the
22
+ * workspace holding the same strings — and an editor whose dropdown disagrees
23
+ * with the renderer's label is a page whose banner changes wording when you
24
+ * open it for editing.
25
+ */
26
+ export declare const BANNER_VARIANTS: ReadonlyArray<{
27
+ value: BannerVariant;
28
+ label: string;
29
+ }>;
16
30
  /** A maintenance notice, inline: `[Notice: Needs citations] …`. */
17
31
  export declare function bannerToText(label: string, text?: string | null): string;
18
32
  /** Each tab under its label, tags stripped — highlighted markup is noise here. */
@@ -38,4 +52,3 @@ export declare function collectText(node: unknown, out?: string[]): string[];
38
52
  * than a failure: title-only hits are how the top tiers match.
39
53
  */
40
54
  export declare function matchSnippet(blocks: unknown, query: string, opening: () => string, maxLen?: number): string;
41
- //# sourceMappingURL=text.d.ts.map
package/dist/text.js CHANGED
@@ -7,7 +7,7 @@
7
7
  // As in `blocks.ts`, the dispatch stays with the caller — a `switch` over its
8
8
  // own block union, where a new type is a compile error until it is handled —
9
9
  // and only the bodies live here.
10
- import { decodeEntities } from './entities.js';
10
+ import { decodeEntities } from './markdown.js';
11
11
  /**
12
12
  * HTML to readable text. Links keep their href in parentheses so a model can
13
13
  * still follow a citation; block-level tags become newlines; list items get a
@@ -36,6 +36,20 @@ export const BANNER_LABELS = {
36
36
  cleanup: 'Needs cleanup',
37
37
  coi: 'Conflict of interest',
38
38
  };
39
+ /**
40
+ * The banner variants as an editor's option list, in the order a writer meets
41
+ * them: the two that describe a gap first, then the three that describe a flaw.
42
+ *
43
+ * Derived from `BANNER_LABELS` rather than restated. Both editors had typed the
44
+ * same six `{ value, label }` pairs out by hand, which made four places in the
45
+ * workspace holding the same strings — and an editor whose dropdown disagrees
46
+ * with the renderer's label is a page whose banner changes wording when you
47
+ * open it for editing.
48
+ */
49
+ export const BANNER_VARIANTS = Object.keys(BANNER_LABELS).map(value => ({
50
+ value,
51
+ label: BANNER_LABELS[value],
52
+ }));
39
53
  /** A maintenance notice, inline: `[Notice: Needs citations] …`. */
40
54
  export function bannerToText(label, text) {
41
55
  return `[Notice: ${label}]${text ? ' ' + stripHtml(text) : ''}`;
@@ -109,4 +123,3 @@ export function matchSnippet(blocks, query, opening, maxLen = 200) {
109
123
  const end = Math.min(text.length, start + maxLen);
110
124
  return `${start > 0 ? '…' : ''}${text.slice(start, end).trimEnd()}${end < text.length ? '…' : ''}`;
111
125
  }
112
- //# sourceMappingURL=text.js.map
package/dist/tiptap.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { Node as TiptapNode } from '@tiptap/core';
3
+ /** Local `cn`. Both wikis import one; the package will not depend on one. */
3
4
  export declare const Iframe: TiptapNode<any, any>;
4
5
  /** The stock extension plus the paste rule it does not ship with. */
5
6
  export declare const YouTube: TiptapNode<import("@tiptap/extension-youtube").YoutubeOptions, any>;
@@ -75,4 +76,3 @@ export declare function createTabs({ classNames, icons, tabLabel, }?: TabsOption
75
76
  TabGroup: TiptapNode<any, any>;
76
77
  TabItem: TiptapNode<any, any>;
77
78
  };
78
- //# sourceMappingURL=tiptap.d.ts.map
package/dist/tiptap.js CHANGED
@@ -24,8 +24,8 @@ import TiptapCodeBlock from '@tiptap/extension-code-block';
24
24
  import { onTweetResize, tweetEmbedSrc } from './dom.js';
25
25
  import { toMapEmbedUrl } from './maps.js';
26
26
  import { useClickOutside } from './react.js';
27
+ import { cx } from './html.js';
27
28
  /** Local `cn`. Both wikis import one; the package will not depend on one. */
28
- const cx = (...parts) => parts.filter(Boolean).join(' ');
29
29
  // ---- iframe -----------------------------------------------------------------
30
30
  export const Iframe = TiptapNode.create({
31
31
  name: 'iframe',
@@ -341,4 +341,3 @@ export function createTabs({ classNames = {}, icons = {}, tabLabel = i => `Tab $
341
341
  });
342
342
  return { TabGroup, TabItem };
343
343
  }
344
- //# sourceMappingURL=tiptap.js.map
@@ -12,9 +12,9 @@ export declare function safeLinkHref(raw: string | null | undefined): string | n
12
12
  /** An author-supplied URL is fine iff it is empty (unset) or resolves safely. */
13
13
  export declare const okUrl: (u: unknown) => boolean;
14
14
  /** `[{ id, text, url? }]` — the shape a references block stores. */
15
- export declare function validateReferenceItems(items: unknown, urlCheck?: (u: unknown) => boolean): boolean;
15
+ export declare function validateReferenceItems(items: unknown): boolean;
16
16
  /** `[{ id, heading, links: [{ label, href }] }]` — a link-grid block's groups. */
17
- export declare function validateLinkGroups(groups: unknown, urlCheck?: (u: unknown) => boolean): boolean;
17
+ export declare function validateLinkGroups(groups: unknown): boolean;
18
18
  export interface BlockValidatorOptions {
19
19
  /** True for a type this wiki knows at all. */
20
20
  isKnownType: (type: string) => boolean;
@@ -44,4 +44,3 @@ export declare function duplicateBlockIds<B extends {
44
44
  type: string;
45
45
  id: string;
46
46
  }>(block: B, newId: () => string): B;
47
- //# sourceMappingURL=validation.d.ts.map