wiki-formant 0.4.1 → 0.8.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 (45) hide show
  1. package/README.md +159 -2
  2. package/dist/analytics.d.ts +22 -0
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/analytics.js +31 -1
  5. package/dist/analytics.js.map +1 -1
  6. package/dist/combobox.d.ts +50 -0
  7. package/dist/combobox.d.ts.map +1 -0
  8. package/dist/combobox.js +59 -0
  9. package/dist/combobox.js.map +1 -0
  10. package/dist/conformance.d.ts.map +1 -1
  11. package/dist/conformance.js +8 -0
  12. package/dist/conformance.js.map +1 -1
  13. package/dist/crawlers.d.ts +45 -0
  14. package/dist/crawlers.d.ts.map +1 -0
  15. package/dist/crawlers.js +80 -0
  16. package/dist/crawlers.js.map +1 -0
  17. package/dist/dom.d.ts +69 -0
  18. package/dist/dom.d.ts.map +1 -0
  19. package/dist/dom.js +167 -0
  20. package/dist/dom.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/pagination.d.ts +10 -1
  26. package/dist/pagination.d.ts.map +1 -1
  27. package/dist/pagination.js +13 -5
  28. package/dist/pagination.js.map +1 -1
  29. package/dist/react.d.ts +226 -0
  30. package/dist/react.d.ts.map +1 -0
  31. package/dist/react.js +476 -0
  32. package/dist/react.js.map +1 -0
  33. package/dist/sidebar.d.ts +38 -0
  34. package/dist/sidebar.d.ts.map +1 -0
  35. package/dist/sidebar.js +43 -0
  36. package/dist/sidebar.js.map +1 -0
  37. package/dist/taxonomy.d.ts +52 -0
  38. package/dist/taxonomy.d.ts.map +1 -1
  39. package/dist/taxonomy.js +95 -42
  40. package/dist/taxonomy.js.map +1 -1
  41. package/dist/tiptap.d.ts +78 -0
  42. package/dist/tiptap.d.ts.map +1 -0
  43. package/dist/tiptap.js +344 -0
  44. package/dist/tiptap.js.map +1 -0
  45. package/package.json +62 -3
package/dist/dom.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ export interface CopyButtonOptions {
2
+ /** Class on the injected button. Style it in your own stylesheet. */
3
+ className?: string;
4
+ label?: string;
5
+ /** How long the tick shows before reverting to the copy glyph, in ms. */
6
+ revertAfter?: number;
7
+ }
8
+ /**
9
+ * Give every `<pre>` under `root` a copy button, once.
10
+ *
11
+ * Idempotent by inspection rather than by selector: both wikis called this
12
+ * through `pre:not(:has(.code-copy-btn))`, which puts the guard at the call
13
+ * site — where it can be, and was, retyped. Checking inside means a caller
14
+ * that passes a plain `pre` selector still cannot double up, and it drops the
15
+ * `:has()` dependency along the way.
16
+ *
17
+ * Returns the number of buttons added, so a caller can skip work when zero.
18
+ */
19
+ export declare function addCopyButtons(root: ParentNode, options?: CopyButtonOptions): number;
20
+ /**
21
+ * The one place this origin is written down. It is both the embed host and the
22
+ * allow-list the resize listener checks, and it was previously spelled out at
23
+ * four call sites across the two wikis.
24
+ */
25
+ export declare const TWITTER_ORIGIN = "https://platform.twitter.com";
26
+ /** The embed iframe's src. `dnt=true` opts the embed out of Twitter's tracking. */
27
+ export declare const tweetEmbedSrc: (tweetId: string | number) => string;
28
+ /**
29
+ * Subscribe to the height the embed posts back once it has laid itself out.
30
+ *
31
+ * The iframe renders at an unknown height and there is no other way to learn
32
+ * it. The caller decides which iframes the height applies to; this only owns
33
+ * the origin check and the message shape. Returns a disposer.
34
+ */
35
+ export declare function onTweetResize(resize: (height: number) => void): () => void;
36
+ /**
37
+ * Point every un-hydrated `[data-twitter-embed]` placeholder under `root` at
38
+ * the real embed URL.
39
+ *
40
+ * Stored article HTML carries the placeholder markup; the iframe only gets a
41
+ * live `src` once it is in the document. Marked with `data-init` so a re-render
42
+ * of the same content does not reload every embed on the page.
43
+ */
44
+ export declare function hydrateTweetEmbeds(root: ParentNode): void;
45
+ /** Apply a measured height to every embed iframe under `root`. */
46
+ export declare function sizeTweetEmbeds(root: ParentNode, height: number): void;
47
+ export interface TabGroupClassNames {
48
+ list?: string;
49
+ panels?: string;
50
+ button?: string;
51
+ panel?: string;
52
+ /** Added to the open button and its panel. */
53
+ active?: string;
54
+ }
55
+ /**
56
+ * Turn stored `[data-tabs]` markup into a working tab group.
57
+ *
58
+ * The editor stores tabs as nested `[data-tab-item]` divs, which is the right
59
+ * thing to persist — it survives a markdown twin, a plain-HTML render and a
60
+ * reader with JavaScript off, which all show every tab's content in order.
61
+ * Making one of them pressable is a reader-side job, and it belongs beside the
62
+ * other passes rather than inside a component: it is the same shape as the copy
63
+ * buttons, and the second wiki to render tabs would otherwise write it again.
64
+ *
65
+ * Idempotent via `data-tabs-init`, so a re-render does not rebuild the group and
66
+ * silently reset it to the first tab.
67
+ */
68
+ export declare function activateTabGroups(root: ParentNode, classNames?: TabGroupClassNames): void;
69
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAgCxF;AAID;;;;GAIG;AACH,eAAO,MAAM,cAAc,iCAAiC,CAAC;AAE7D,mFAAmF;AACnF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,MAAM,KAAG,MACI,CAAC;AAE9D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAU1E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAazD;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAItE;AAID,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,GAAE,kBAAuB,GAAG,IAAI,CAkD7F"}
package/dist/dom.js ADDED
@@ -0,0 +1,167 @@
1
+ // dom.ts — the imperative passes a rendered article needs after it is in the DOM.
2
+ //
3
+ // Not in `react.tsx`, because none of this is React: it runs against an element
4
+ // a `dangerouslySetInnerHTML` just filled, and both wikis call it from an effect
5
+ // they already had. Keeping it out of the `'use client'` file also means a
6
+ // consumer can call it from anywhere it has an element.
7
+ //
8
+ // Both routines were duplicated. `addCopyButton` was BYTE-IDENTICAL in the two
9
+ // BlockRenderers, down to the SVG path data. The Twitter embed was worse: one
10
+ // wiki had extracted it to a module, the other wrote the origin allow-list and
11
+ // the embed URL out at three separate call sites — a duplicated origin check is
12
+ // the kind that goes stale quietly.
13
+ // ---- copy buttons -----------------------------------------------------------
14
+ const COPY_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
15
+ const CHECK_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
16
+ /**
17
+ * Give every `<pre>` under `root` a copy button, once.
18
+ *
19
+ * Idempotent by inspection rather than by selector: both wikis called this
20
+ * through `pre:not(:has(.code-copy-btn))`, which puts the guard at the call
21
+ * site — where it can be, and was, retyped. Checking inside means a caller
22
+ * that passes a plain `pre` selector still cannot double up, and it drops the
23
+ * `:has()` dependency along the way.
24
+ *
25
+ * Returns the number of buttons added, so a caller can skip work when zero.
26
+ */
27
+ export function addCopyButtons(root, options = {}) {
28
+ const { className = 'code-copy-btn', label = 'Copy code', revertAfter = 2000 } = options;
29
+ let added = 0;
30
+ for (const pre of Array.from(root.querySelectorAll('pre'))) {
31
+ if (pre.querySelector(`.${className}`))
32
+ continue;
33
+ const btn = document.createElement('button');
34
+ btn.type = 'button';
35
+ btn.className = className;
36
+ btn.setAttribute('aria-label', label);
37
+ btn.innerHTML = COPY_SVG;
38
+ btn.onclick = () => {
39
+ const code = pre.querySelector('code')?.textContent || pre.textContent || '';
40
+ // A rejected clipboard write (permissions, insecure origin) must not
41
+ // leave an unhandled rejection behind — the button simply does nothing.
42
+ navigator.clipboard.writeText(code).then(() => {
43
+ btn.innerHTML = CHECK_SVG;
44
+ setTimeout(() => {
45
+ btn.innerHTML = COPY_SVG;
46
+ }, revertAfter);
47
+ }, () => { });
48
+ };
49
+ pre.style.position = 'relative';
50
+ pre.appendChild(btn);
51
+ added++;
52
+ }
53
+ return added;
54
+ }
55
+ // ---- twitter embeds ---------------------------------------------------------
56
+ /**
57
+ * The one place this origin is written down. It is both the embed host and the
58
+ * allow-list the resize listener checks, and it was previously spelled out at
59
+ * four call sites across the two wikis.
60
+ */
61
+ export const TWITTER_ORIGIN = 'https://platform.twitter.com';
62
+ /** The embed iframe's src. `dnt=true` opts the embed out of Twitter's tracking. */
63
+ export const tweetEmbedSrc = (tweetId) => `${TWITTER_ORIGIN}/embed/Tweet.html?id=${tweetId}&dnt=true`;
64
+ /**
65
+ * Subscribe to the height the embed posts back once it has laid itself out.
66
+ *
67
+ * The iframe renders at an unknown height and there is no other way to learn
68
+ * it. The caller decides which iframes the height applies to; this only owns
69
+ * the origin check and the message shape. Returns a disposer.
70
+ */
71
+ export function onTweetResize(resize) {
72
+ const handleMessage = (e) => {
73
+ if (e.origin !== TWITTER_ORIGIN)
74
+ return;
75
+ const data = e.data?.['twttr.embed'];
76
+ if (data?.method !== 'twttr.private.resize')
77
+ return;
78
+ const height = data.params?.[0]?.height;
79
+ if (height)
80
+ resize(height);
81
+ };
82
+ window.addEventListener('message', handleMessage);
83
+ return () => window.removeEventListener('message', handleMessage);
84
+ }
85
+ /**
86
+ * Point every un-hydrated `[data-twitter-embed]` placeholder under `root` at
87
+ * the real embed URL.
88
+ *
89
+ * Stored article HTML carries the placeholder markup; the iframe only gets a
90
+ * live `src` once it is in the document. Marked with `data-init` so a re-render
91
+ * of the same content does not reload every embed on the page.
92
+ */
93
+ export function hydrateTweetEmbeds(root) {
94
+ for (const container of Array.from(root.querySelectorAll('[data-twitter-embed]:not([data-init])'))) {
95
+ container.setAttribute('data-init', '');
96
+ const tweetId = container.getAttribute('data-tweet-id');
97
+ if (!tweetId)
98
+ continue;
99
+ const iframe = container.querySelector('iframe');
100
+ if (iframe) {
101
+ iframe.src = tweetEmbedSrc(tweetId);
102
+ iframe.setAttribute('scrolling', 'no');
103
+ }
104
+ }
105
+ }
106
+ /** Apply a measured height to every embed iframe under `root`. */
107
+ export function sizeTweetEmbeds(root, height) {
108
+ for (const iframe of Array.from(root.querySelectorAll('[data-twitter-embed] iframe'))) {
109
+ iframe.style.height = `${height}px`;
110
+ }
111
+ }
112
+ /**
113
+ * Turn stored `[data-tabs]` markup into a working tab group.
114
+ *
115
+ * The editor stores tabs as nested `[data-tab-item]` divs, which is the right
116
+ * thing to persist — it survives a markdown twin, a plain-HTML render and a
117
+ * reader with JavaScript off, which all show every tab's content in order.
118
+ * Making one of them pressable is a reader-side job, and it belongs beside the
119
+ * other passes rather than inside a component: it is the same shape as the copy
120
+ * buttons, and the second wiki to render tabs would otherwise write it again.
121
+ *
122
+ * Idempotent via `data-tabs-init`, so a re-render does not rebuild the group and
123
+ * silently reset it to the first tab.
124
+ */
125
+ export function activateTabGroups(root, classNames = {}) {
126
+ const { list = 'tabs-list', panels = 'tabs-panels', button = 'tab-button', panel = 'tab-panel', active = 'active', } = classNames;
127
+ for (const group of Array.from(root.querySelectorAll('[data-tabs]:not([data-tabs-init])'))) {
128
+ const items = group.querySelectorAll('[data-tab-item]');
129
+ if (!items.length)
130
+ continue;
131
+ group.setAttribute('data-tabs-init', '');
132
+ const tabList = document.createElement('div');
133
+ tabList.className = list;
134
+ tabList.setAttribute('role', 'tablist');
135
+ const tabPanels = document.createElement('div');
136
+ tabPanels.className = panels;
137
+ items.forEach((item, i) => {
138
+ const btn = document.createElement('button');
139
+ btn.type = 'button';
140
+ btn.className = i === 0 ? `${button} ${active}` : button;
141
+ btn.setAttribute('role', 'tab');
142
+ btn.setAttribute('aria-selected', String(i === 0));
143
+ btn.textContent = item.getAttribute('data-tab-title') || `Tab ${i + 1}`;
144
+ btn.onclick = () => {
145
+ for (const b of Array.from(tabList.children)) {
146
+ b.classList.remove(active);
147
+ b.setAttribute('aria-selected', 'false');
148
+ }
149
+ for (const p of Array.from(tabPanels.children))
150
+ p.classList.remove(active);
151
+ btn.classList.add(active);
152
+ btn.setAttribute('aria-selected', 'true');
153
+ tabPanels.children[i]?.classList.add(active);
154
+ };
155
+ tabList.appendChild(btn);
156
+ const body = document.createElement('div');
157
+ body.className = i === 0 ? `${panel} ${active}` : panel;
158
+ body.setAttribute('role', 'tabpanel');
159
+ body.innerHTML = item.innerHTML;
160
+ tabPanels.appendChild(body);
161
+ });
162
+ group.innerHTML = '';
163
+ group.appendChild(tabList);
164
+ group.appendChild(tabPanels);
165
+ }
166
+ }
167
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -13,4 +13,6 @@ export * from './blocks.js';
13
13
  export * from './text.js';
14
14
  export * from './maps.js';
15
15
  export * from './freshness.js';
16
+ export * from './combobox.js';
17
+ export * from './crawlers.js';
16
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -18,4 +18,6 @@ export * from './blocks.js';
18
18
  export * from './text.js';
19
19
  export * from './maps.js';
20
20
  export * from './freshness.js';
21
+ export * from './combobox.js';
22
+ export * from './crawlers.js';
21
23
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,mBAAmB;AAEnB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,mBAAmB;AAEnB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
@@ -9,9 +9,18 @@ export interface PaginatedResponse<T> extends Pagination {
9
9
  }
10
10
  export declare const MAX_PAGE_SIZE = 100;
11
11
  export declare const DEFAULT_PAGE_SIZE = 20;
12
- /** `page` clamped to ≥1, `pageSize` clamped to 1–100. Never trust either. */
12
+ /**
13
+ * `page` clamped to ≥1, `pageSize` clamped to 1–`max`. Never trust either.
14
+ *
15
+ * `max` defaults to 100 and exists because the cap is a per-route decision, not
16
+ * a package-wide one: a route whose rows are whole pages caps lower than one
17
+ * whose rows are titles. Without it the one consumer that caps at 50 could not
18
+ * express itself here, so it kept its own clamp — and then disagreed with
19
+ * itself, capping at 50 in the REST route and 100 in the MCP tool beside it.
20
+ */
13
21
  export declare function parsePagination(searchParams: URLSearchParams, defaults?: {
14
22
  pageSize?: number;
23
+ max?: number;
15
24
  }): Pagination;
16
25
  export declare function paginatedResponse<T>(items: T[], total: number, page: number, pageSize: number): PaginatedResponse<T>;
17
26
  /** The `skip`/`take` an ORM wants, from the same clamped pair. */
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,UAAU;IACtD,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,YAAY,EAAE,eAAe,EAC7B,QAAQ,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,UAAU,CAYZ;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,iBAAiB,CAAC,CAAC,CAAC,CAEtB;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvF"}
1
+ {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC,CAAE,SAAQ,UAAU;IACtD,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,eAAe,EAC7B,QAAQ,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,UAAU,CASZ;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,iBAAiB,CAAC,CAAC,CAAC,CAEtB;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvF"}
@@ -5,15 +5,23 @@
5
5
  // codes against, so reshaping it per repo is a breaking change nobody declared.
6
6
  export const MAX_PAGE_SIZE = 100;
7
7
  export const DEFAULT_PAGE_SIZE = 20;
8
- /** `page` clamped to ≥1, `pageSize` clamped to 1–100. Never trust either. */
8
+ /**
9
+ * `page` clamped to ≥1, `pageSize` clamped to 1–`max`. Never trust either.
10
+ *
11
+ * `max` defaults to 100 and exists because the cap is a per-route decision, not
12
+ * a package-wide one: a route whose rows are whole pages caps lower than one
13
+ * whose rows are titles. Without it the one consumer that caps at 50 could not
14
+ * express itself here, so it kept its own clamp — and then disagreed with
15
+ * itself, capping at 50 in the REST route and 100 in the MCP tool beside it.
16
+ */
9
17
  export function parsePagination(searchParams, defaults) {
18
+ const max = defaults?.max ?? MAX_PAGE_SIZE;
19
+ const fallbackSize = Math.min(max, defaults?.pageSize ?? DEFAULT_PAGE_SIZE);
10
20
  const rawPage = parseInt(searchParams.get('page') || '1', 10);
11
- const rawSize = parseInt(searchParams.get('pageSize') || String(defaults?.pageSize ?? DEFAULT_PAGE_SIZE), 10);
21
+ const rawSize = parseInt(searchParams.get('pageSize') || String(fallbackSize), 10);
12
22
  return {
13
23
  page: Number.isFinite(rawPage) ? Math.max(1, rawPage) : 1,
14
- pageSize: Number.isFinite(rawSize)
15
- ? Math.min(MAX_PAGE_SIZE, Math.max(1, rawSize))
16
- : (defaults?.pageSize ?? DEFAULT_PAGE_SIZE),
24
+ pageSize: Number.isFinite(rawSize) ? Math.min(max, Math.max(1, rawSize)) : fallbackSize,
17
25
  };
18
26
  }
19
27
  export function paginatedResponse(items, total, page, pageSize) {
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAahF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAC7B,YAA6B,EAC7B,QAAgC;IAEhC,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,QAAQ,CACtB,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,QAAQ,IAAI,iBAAiB,CAAC,EAC/E,EAAE,CACH,CAAC;IACF,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAChC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAC/C,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,IAAI,iBAAiB,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAU,EACV,KAAa,EACb,IAAY,EACZ,QAAgB;IAEhB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC;AACnF,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAc;IACrD,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACzD,CAAC"}
1
+ {"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAahF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,YAA6B,EAC7B,QAA8C;IAE9C,MAAM,GAAG,GAAG,QAAQ,EAAE,GAAG,IAAI,aAAa,CAAC;IAC3C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,IAAI,iBAAiB,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC;IACnF,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY;KACxF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAU,EACV,KAAa,EACb,IAAY,EACZ,QAAgB;IAEhB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC;AACnF,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAc;IACrD,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACzD,CAAC"}
@@ -0,0 +1,226 @@
1
+ import { type ComboboxAria } from './combobox.js';
2
+ import { type CSSProperties, type KeyboardEvent, type ReactNode } from 'react';
3
+ export interface SidebarOptions {
4
+ /**
5
+ * Where the reader's choice is remembered, in `localStorage`. Give each wiki
6
+ * its own key; they are different sites and may deserve different answers.
7
+ */
8
+ storageKey?: string;
9
+ /** Below this width (px) the rail defaults to closed. */
10
+ breakpoint?: number;
11
+ }
12
+ export interface SidebarState {
13
+ open: boolean;
14
+ setOpen: (open: boolean) => void;
15
+ toggle: () => void;
16
+ /** True below `breakpoint`. Drives "close the rail after following a link". */
17
+ isMobile: boolean;
18
+ /**
19
+ * False until the effect has read storage and matchMedia. Render the rail
20
+ * without its open/closed transition while this is false, so a remembered
21
+ * closed rail does not animate shut on every load.
22
+ */
23
+ ready: boolean;
24
+ }
25
+ /**
26
+ * Collapse state for a wiki rail: remembered across loads, defaulted from the
27
+ * viewport only when the reader has never chosen.
28
+ *
29
+ * The reader's choice outranks the breakpoint. Someone who collapses the rail
30
+ * on a laptop and then narrows the window has still collapsed the rail.
31
+ */
32
+ export declare function useCollapsibleSidebar(options?: SidebarOptions): SidebarState;
33
+ export interface SidebarProviderProps extends SidebarOptions {
34
+ children: ReactNode;
35
+ }
36
+ /** Wrap the layout that contains both the rail and its toggle. */
37
+ export declare function SidebarProvider({ children, ...options }: SidebarProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SidebarState | null>>;
38
+ /**
39
+ * The rail's collapse state. Throws outside a provider rather than silently
40
+ * handing back a second, unconnected copy — two sources of truth for one rail
41
+ * is the failure this context exists to prevent.
42
+ */
43
+ export declare function useSidebar(): SidebarState;
44
+ export interface TocHeading {
45
+ id: string;
46
+ text: string;
47
+ /** 1 for `<h1>`, 2 for `<h2>`, and so on. */
48
+ level: number;
49
+ }
50
+ export interface TocClassNames {
51
+ root?: string;
52
+ button?: string;
53
+ label?: string;
54
+ list?: string;
55
+ item?: string;
56
+ /** Applied alongside `item` on the section currently under the viewport top. */
57
+ itemActive?: string;
58
+ }
59
+ export interface TableOfContentsProps {
60
+ /**
61
+ * The element holding the article. A wiki whose content streams in as blocks
62
+ * has to read the DOM, which is why a selector is the default route — by the
63
+ * time the rail mounts, the article may not be there yet.
64
+ *
65
+ * Optional when `headings` is given: a wiki that renders its article on the
66
+ * server already knows them and should not pay for a MutationObserver to
67
+ * rediscover what it just wrote.
68
+ */
69
+ containerSelector?: string;
70
+ /**
71
+ * Headings known ahead of time. Supplying these skips the DOM scan entirely;
72
+ * scroll-spy still runs, since that needs the rendered elements either way.
73
+ */
74
+ headings?: TocHeading[];
75
+ /** Re-query and re-observe when this changes. Pass the pathname. */
76
+ resetKey?: string;
77
+ classNames?: TocClassNames;
78
+ /** Rendered before the label. Receives the current expanded state. */
79
+ icon?: (expanded: boolean) => ReactNode;
80
+ label?: string;
81
+ /**
82
+ * Mint an id for a heading that lacks one. Wikis that inject ids server-side
83
+ * (see `wiki-formant/headings`) should leave this off — an id is a URL, and
84
+ * one minted in the browser is not the one the server published.
85
+ */
86
+ slug?: (text: string) => string;
87
+ /**
88
+ * CSS custom property holding the sticky header's height, used to place the
89
+ * scroll-spy line. e.g. `'--header-height'`.
90
+ */
91
+ offsetVar?: string;
92
+ /** Fallback offset in px when `offsetVar` is unset or unparseable. */
93
+ offsetFallback?: number;
94
+ /** Indent per heading level, in rem. Set 0 for a flat list. */
95
+ indentRem?: number;
96
+ /** Rendered instead of nothing when the article has no headings. */
97
+ empty?: ReactNode;
98
+ }
99
+ /**
100
+ * An "on this page" rail with scroll-spy, read from the rendered article.
101
+ *
102
+ * Both wikis this came from had written the same thing: a debounced
103
+ * MutationObserver to survive content arriving late, an IntersectionObserver to
104
+ * track the section under the viewport top, and the same reference-stability
105
+ * trick so an unrelated DOM mutation (a price ticker, an RSS feed) does not tear
106
+ * the observer down.
107
+ */
108
+ export declare function TableOfContents({ containerSelector, headings: providedHeadings, resetKey, classNames, icon, label, slug, offsetVar, offsetFallback, indentRem, empty, }: TableOfContentsProps): import("react").JSX.Element;
109
+ /**
110
+ * Calls `onClose` on any mousedown outside the returned ref's element.
111
+ *
112
+ * The `offsetParent` check is the whole reason this is shared. All three wikis
113
+ * had written this hook; only one had that line, and without it a container
114
+ * that is `display: none` at the current breakpoint still claims outside-clicks
115
+ * — so a tap anywhere on a phone dismisses a popover the reader is looking at,
116
+ * because the hidden desktop copy of it answered first.
117
+ *
118
+ * `onClose` is an effect dependency: wrap it in `useCallback` or hoist it, or
119
+ * the listener is torn down and rebuilt on every render.
120
+ */
121
+ export declare function useClickOutside<T extends HTMLElement>(onClose: () => void): import("react").RefObject<T | null>;
122
+ export interface TypeaheadOptions<T> {
123
+ /**
124
+ * Runs the search. MUST be referentially stable — a module function, a server
125
+ * action, or something wrapped in `useCallback` — because it is an effect
126
+ * dependency and an inline arrow re-runs the search on every render.
127
+ *
128
+ * The `signal` is optional to implement: a REST fetcher should forward it, a
129
+ * server action simply declares one parameter and ignores it.
130
+ */
131
+ fetch: (query: string, signal: AbortSignal) => Promise<T[]>;
132
+ onPick: (item: T) => void;
133
+ /** Runs after Escape has cleared the list, for surface-level dismissal. */
134
+ onEscape?: () => void;
135
+ /** Debounce, in ms. */
136
+ delay?: number;
137
+ /** Queries shorter than this never reach `fetch`. */
138
+ minLength?: number;
139
+ /** Remember results per query for this mount. */
140
+ cache?: boolean;
141
+ }
142
+ export interface TypeaheadState<T> {
143
+ query: string;
144
+ setQuery: (query: string) => void;
145
+ debouncedQuery: string;
146
+ items: T[];
147
+ highlight: number;
148
+ setHighlight: (index: number) => void;
149
+ isSearching: boolean;
150
+ onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
151
+ /** Drop the results, keep the query. For an outside click. */
152
+ clearItems: () => void;
153
+ /** Drop everything. For a pick, or a closing popover. */
154
+ reset: () => void;
155
+ /**
156
+ * The listbox contract, ready to spread: `inputProps` on the field,
157
+ * `listProps` on the results container, `optionProps(i)` on each row.
158
+ *
159
+ * Pass a `listKey` when one hook feeds more than one rendered list — a header
160
+ * with a desktop and a mobile field renders both into the same document, and
161
+ * one id set across the two duplicates every option id.
162
+ */
163
+ combobox: (listKey?: string) => ComboboxAria;
164
+ }
165
+ /**
166
+ * The debounced-search state machine behind every typeahead on these sites.
167
+ *
168
+ * Five surfaces across the three wikis had three implementations, and no two
169
+ * agreed on what a search field does. This is their union, because each had a
170
+ * piece the others were missing:
171
+ *
172
+ * - a **request-id guard**, so a slow response cannot paint over a newer one.
173
+ * Two of the five surfaces had no guard at all: type fast enough and the
174
+ * results you are reading are for a query you have already replaced.
175
+ * - **keyboard navigation**. Two surfaces had none — the header dropdown of a
176
+ * wiki whose search is its primary navigation was mouse-only.
177
+ * - **abort and a per-query cache**, which only the third had. The cache is
178
+ * per mount, not module-level, so a stale result cannot outlive the page.
179
+ *
180
+ * The debounce is inlined rather than pulled from a package: this one is nine
181
+ * lines, and the point of the package is to have no runtime dependencies.
182
+ */
183
+ export declare function useTypeahead<T>({ fetch, onPick, onEscape, delay, minLength, cache, }: TypeaheadOptions<T>): TypeaheadState<T>;
184
+ export interface LinkPreviewOptions<T> {
185
+ /**
186
+ * Which anchors get a card. Both wikis scope this to the article container
187
+ * (`a.closest('.prose-content')`) as well as testing the href, and the scope
188
+ * is exactly the part that differs — so the whole predicate is yours.
189
+ */
190
+ eligible: (anchor: HTMLAnchorElement) => boolean;
191
+ /** Resolve a preview for a href, or null for "no card". REST or server action. */
192
+ fetch: (href: string) => Promise<T | null>;
193
+ /** Must match the class on the element you spread `cardProps` onto. */
194
+ cardClassName?: string;
195
+ cardWidth?: number;
196
+ /** Used to decide whether the card flips above the link. */
197
+ cardHeight?: number;
198
+ /** Hover this long before fetching, in ms. */
199
+ showDelay?: number;
200
+ /** Grace period for the cursor to travel from link to card, in ms. */
201
+ hideDelay?: number;
202
+ /** Minimum gap from the viewport edge, in px. */
203
+ margin?: number;
204
+ }
205
+ export interface LinkPreviewState<T> {
206
+ /** The resolved preview, or null when no card should render. */
207
+ preview: T | null;
208
+ /** Spread onto your card element: position, and the keep-open handlers. */
209
+ cardProps: {
210
+ style: CSSProperties;
211
+ onMouseEnter: () => void;
212
+ onMouseLeave: () => void;
213
+ };
214
+ }
215
+ /**
216
+ * Wikipedia-style hover cards over a rendered article.
217
+ *
218
+ * One delegated listener on `document` rather than a component per link, which
219
+ * is what makes this viable over an article holding hundreds of anchors. Two
220
+ * wikis had written the same ~90 lines: the same 350ms intent delay, the same
221
+ * 200ms grace so the cursor can cross the gap into the card, the same
222
+ * viewport-clamp arithmetic, the same per-href cache. They differed in exactly
223
+ * three things, and those three are the options above.
224
+ */
225
+ export declare function useLinkPreview<T>({ eligible, fetch, cardClassName, cardWidth, cardHeight, showDelay, hideDelay, margin, }: LinkPreviewOptions<T>): LinkPreviewState<T>;
226
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}