wiki-formant 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -1
- package/dist/analytics.d.ts +22 -0
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +31 -1
- package/dist/analytics.js.map +1 -1
- package/dist/dom.d.ts +69 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +167 -0
- package/dist/dom.js.map +1 -0
- package/dist/react.d.ts +216 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +470 -0
- package/dist/react.js.map +1 -0
- package/dist/sidebar.d.ts +38 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +43 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/taxonomy.d.ts +52 -0
- package/dist/taxonomy.d.ts.map +1 -1
- package/dist/taxonomy.js +95 -42
- package/dist/taxonomy.js.map +1 -1
- package/dist/tiptap.d.ts +78 -0
- package/dist/tiptap.d.ts.map +1 -0
- package/dist/tiptap.js +344 -0
- package/dist/tiptap.js.map +1 -0
- package/package.json +54 -3
package/dist/react.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// react.tsx — the collapsible wiki sidebar, and the table of contents inside it.
|
|
4
|
+
//
|
|
5
|
+
// This is the one part of the package that needs React, so it is the one part
|
|
6
|
+
// behind its own subpath: `wiki-formant/react`. React is an OPTIONAL PEER
|
|
7
|
+
// dependency, so a consumer that only wants the taxonomy or the MCP transport
|
|
8
|
+
// still installs a package with no runtime dependencies at all.
|
|
9
|
+
//
|
|
10
|
+
// What is shared here is behaviour, not appearance. Every class name is passed
|
|
11
|
+
// in, because the three wikis style their rails differently and always will;
|
|
12
|
+
// what they should not differ on is what the rail DOES. Icons are passed in for
|
|
13
|
+
// the same reason — and because a component library that hardcodes an icon set
|
|
14
|
+
// makes its consumers install that icon set.
|
|
15
|
+
//
|
|
16
|
+
// Two bugs are fixed here rather than propagated. Both were in the version this
|
|
17
|
+
// was lifted from:
|
|
18
|
+
//
|
|
19
|
+
// 1. `useEffect(() => setOpen(!isMobile), [isMobile])` ran on every matchMedia
|
|
20
|
+
// change, so resizing a window across the breakpoint silently discarded a
|
|
21
|
+
// collapse the reader had chosen. The breakpoint now supplies the DEFAULT
|
|
22
|
+
// only, and never overrides a choice already made.
|
|
23
|
+
// 2. The store started closed and an effect opened it on mount, so every
|
|
24
|
+
// desktop load flashed a closed rail before opening it. SSR cannot read
|
|
25
|
+
// localStorage, so no amount of React fixes this on its own: the answer is
|
|
26
|
+
// `sidebarBootScript`, a blocking inline script that stamps the remembered
|
|
27
|
+
// state on <html> before first paint. The hook keeps that attribute in
|
|
28
|
+
// sync afterwards, so CSS has one source of truth either side of hydration.
|
|
29
|
+
import { resolveSidebarOpen, SIDEBAR_ATTRIBUTE } from './sidebar.js';
|
|
30
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
31
|
+
const readStored = (key) => {
|
|
32
|
+
try {
|
|
33
|
+
const raw = window.localStorage.getItem(key);
|
|
34
|
+
return raw === null ? null : raw === '1';
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Private browsing, or site data blocked. A rail that cannot remember is
|
|
38
|
+
// fine; a rail that throws on load is not.
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Collapse state for a wiki rail: remembered across loads, defaulted from the
|
|
44
|
+
* viewport only when the reader has never chosen.
|
|
45
|
+
*
|
|
46
|
+
* The reader's choice outranks the breakpoint. Someone who collapses the rail
|
|
47
|
+
* on a laptop and then narrows the window has still collapsed the rail.
|
|
48
|
+
*/
|
|
49
|
+
export function useCollapsibleSidebar(options = {}) {
|
|
50
|
+
const { storageKey = 'wiki:sidebar', breakpoint = 1024 } = options;
|
|
51
|
+
// Start open on the server and on the first client paint. SSR has no viewport
|
|
52
|
+
// and no storage, so any other guess is a guaranteed mismatch on some loads.
|
|
53
|
+
const [open, setOpenState] = useState(true);
|
|
54
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
55
|
+
const [ready, setReady] = useState(false);
|
|
56
|
+
// Set as soon as the reader touches the control, so a later viewport change
|
|
57
|
+
// cannot overwrite what they asked for.
|
|
58
|
+
const chosen = useRef(false);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
61
|
+
const stored = readStored(storageKey);
|
|
62
|
+
const apply = (matches) => {
|
|
63
|
+
setIsMobile(matches);
|
|
64
|
+
// The breakpoint supplies a default, never an override. This is bug 1.
|
|
65
|
+
setOpenState(current => resolveSidebarOpen({ chosen: chosen.current, current, stored, isMobile: matches }));
|
|
66
|
+
};
|
|
67
|
+
apply(mql.matches);
|
|
68
|
+
setReady(true);
|
|
69
|
+
const handler = (e) => apply(e.matches);
|
|
70
|
+
mql.addEventListener('change', handler);
|
|
71
|
+
return () => mql.removeEventListener('change', handler);
|
|
72
|
+
}, [storageKey, breakpoint]);
|
|
73
|
+
const setOpen = useCallback((next) => {
|
|
74
|
+
chosen.current = true;
|
|
75
|
+
setOpenState(next);
|
|
76
|
+
try {
|
|
77
|
+
window.localStorage.setItem(storageKey, next ? '1' : '0');
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* storage unavailable — the choice still holds for this page */
|
|
81
|
+
}
|
|
82
|
+
}, [storageKey]);
|
|
83
|
+
const toggle = useCallback(() => setOpen(!open), [open, setOpen]);
|
|
84
|
+
// One source of truth either side of hydration: the boot script writes this
|
|
85
|
+
// attribute before first paint, and from here on the hook owns it.
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
document.documentElement.setAttribute(SIDEBAR_ATTRIBUTE, open ? 'open' : 'closed');
|
|
88
|
+
}, [open]);
|
|
89
|
+
return { open, setOpen, toggle, isMobile, ready };
|
|
90
|
+
}
|
|
91
|
+
// ---- sharing that state across the layout -----------------------------------
|
|
92
|
+
// The rail and the button that toggles it are rarely siblings — the button
|
|
93
|
+
// usually lives in the header — so the state has to span them. A context is the
|
|
94
|
+
// smallest thing that does, and it means a consumer needs no store of its own.
|
|
95
|
+
const SidebarContext = createContext(null);
|
|
96
|
+
/** Wrap the layout that contains both the rail and its toggle. */
|
|
97
|
+
export function SidebarProvider({ children, ...options }) {
|
|
98
|
+
const state = useCollapsibleSidebar(options);
|
|
99
|
+
return createElement(SidebarContext.Provider, { value: state }, children);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The rail's collapse state. Throws outside a provider rather than silently
|
|
103
|
+
* handing back a second, unconnected copy — two sources of truth for one rail
|
|
104
|
+
* is the failure this context exists to prevent.
|
|
105
|
+
*/
|
|
106
|
+
export function useSidebar() {
|
|
107
|
+
const ctx = useContext(SidebarContext);
|
|
108
|
+
if (!ctx)
|
|
109
|
+
throw new Error('useSidebar must be used within a <SidebarProvider>');
|
|
110
|
+
return ctx;
|
|
111
|
+
}
|
|
112
|
+
const sameHeadings = (a, b) => a.length === b.length && a.every((h, i) => h.id === b[i].id && h.text === b[i].text);
|
|
113
|
+
/**
|
|
114
|
+
* An "on this page" rail with scroll-spy, read from the rendered article.
|
|
115
|
+
*
|
|
116
|
+
* Both wikis this came from had written the same thing: a debounced
|
|
117
|
+
* MutationObserver to survive content arriving late, an IntersectionObserver to
|
|
118
|
+
* track the section under the viewport top, and the same reference-stability
|
|
119
|
+
* trick so an unrelated DOM mutation (a price ticker, an RSS feed) does not tear
|
|
120
|
+
* the observer down.
|
|
121
|
+
*/
|
|
122
|
+
export function TableOfContents({ containerSelector, headings: providedHeadings, resetKey, classNames = {}, icon, label = 'On this page', slug, offsetVar, offsetFallback = 64, indentRem = 0.75, empty = null, }) {
|
|
123
|
+
const [scanned, setScanned] = useState([]);
|
|
124
|
+
const [expanded, setExpanded] = useState(true);
|
|
125
|
+
const [activeId, setActiveId] = useState('');
|
|
126
|
+
const headings = providedHeadings ?? scanned;
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
// Nothing to scan for when the caller already knows the headings.
|
|
129
|
+
if (providedHeadings || !containerSelector)
|
|
130
|
+
return;
|
|
131
|
+
let timer;
|
|
132
|
+
const update = () => {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
// Debounced: block content can arrive in bursts, and re-reading the whole
|
|
135
|
+
// article per mutation is what makes a rail janky.
|
|
136
|
+
timer = setTimeout(() => {
|
|
137
|
+
const root = document.querySelector(containerSelector);
|
|
138
|
+
if (!root)
|
|
139
|
+
return;
|
|
140
|
+
const used = new Set();
|
|
141
|
+
const next = [];
|
|
142
|
+
for (const el of Array.from(root.querySelectorAll('h1, h2, h3'))) {
|
|
143
|
+
const text = el.textContent?.trim() ?? '';
|
|
144
|
+
if (!text)
|
|
145
|
+
continue;
|
|
146
|
+
let id = el.id;
|
|
147
|
+
if (!id) {
|
|
148
|
+
if (!slug)
|
|
149
|
+
continue; // ids are the server's business here
|
|
150
|
+
const base = slug(text) || 'section';
|
|
151
|
+
id = base;
|
|
152
|
+
let n = 2;
|
|
153
|
+
while (used.has(id) || document.getElementById(id))
|
|
154
|
+
id = `${base}-${n++}`;
|
|
155
|
+
el.id = id;
|
|
156
|
+
}
|
|
157
|
+
used.add(id);
|
|
158
|
+
next.push({ id, text, level: Number(el.tagName[1]) });
|
|
159
|
+
}
|
|
160
|
+
// Keep the same array reference when nothing changed, so the scroll-spy
|
|
161
|
+
// effect below is not torn down by an unrelated mutation.
|
|
162
|
+
setScanned(prev => (sameHeadings(prev, next) ? prev : next));
|
|
163
|
+
}, 200);
|
|
164
|
+
};
|
|
165
|
+
update();
|
|
166
|
+
const root = document.querySelector(containerSelector);
|
|
167
|
+
const observer = new MutationObserver(update);
|
|
168
|
+
if (root)
|
|
169
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
170
|
+
return () => {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
observer.disconnect();
|
|
173
|
+
};
|
|
174
|
+
}, [containerSelector, resetKey, slug, providedHeadings]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!headings.length)
|
|
177
|
+
return;
|
|
178
|
+
const order = new Map(headings.map((h, i) => [h.id, i]));
|
|
179
|
+
const visible = new Set();
|
|
180
|
+
const styles = getComputedStyle(document.documentElement);
|
|
181
|
+
const rem = parseFloat(styles.fontSize) || 16;
|
|
182
|
+
const raw = offsetVar ? styles.getPropertyValue(offsetVar).trim() : '';
|
|
183
|
+
// A custom property may be in rem or px; both appear across these wikis.
|
|
184
|
+
const parsed = parseFloat(raw);
|
|
185
|
+
const offset = !raw || Number.isNaN(parsed)
|
|
186
|
+
? offsetFallback
|
|
187
|
+
: raw.endsWith('rem') ? parsed * rem : parsed;
|
|
188
|
+
const topOffset = Math.round(offset + rem * 0.5);
|
|
189
|
+
const observer = new IntersectionObserver(entries => {
|
|
190
|
+
for (const e of entries) {
|
|
191
|
+
if (e.isIntersecting)
|
|
192
|
+
visible.add(e.target.id);
|
|
193
|
+
else
|
|
194
|
+
visible.delete(e.target.id);
|
|
195
|
+
}
|
|
196
|
+
if (!visible.size)
|
|
197
|
+
return;
|
|
198
|
+
// The earliest visible heading in document order is the section the
|
|
199
|
+
// reader is in, not whichever entry fired last.
|
|
200
|
+
let bestId = '';
|
|
201
|
+
let bestIdx = Infinity;
|
|
202
|
+
for (const id of visible) {
|
|
203
|
+
const idx = order.get(id) ?? Infinity;
|
|
204
|
+
if (idx < bestIdx) {
|
|
205
|
+
bestIdx = idx;
|
|
206
|
+
bestId = id;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (bestId)
|
|
210
|
+
setActiveId(bestId);
|
|
211
|
+
}, { rootMargin: `-${topOffset}px 0px -75% 0px`, threshold: 0 });
|
|
212
|
+
for (const h of headings) {
|
|
213
|
+
const el = document.getElementById(h.id);
|
|
214
|
+
if (el)
|
|
215
|
+
observer.observe(el);
|
|
216
|
+
}
|
|
217
|
+
return () => observer.disconnect();
|
|
218
|
+
}, [headings, offsetVar, offsetFallback]);
|
|
219
|
+
// Derived, not seeded from an effect: until the observer reports a section,
|
|
220
|
+
// the first heading is the one the reader is in.
|
|
221
|
+
const currentId = useMemo(() => activeId || headings[0]?.id, [activeId, headings]);
|
|
222
|
+
if (!headings.length)
|
|
223
|
+
return _jsx(_Fragment, { children: empty });
|
|
224
|
+
return (_jsxs("div", { className: classNames.root, children: [_jsxs("button", { type: "button", onClick: () => setExpanded(v => !v), className: classNames.button, "aria-expanded": expanded, children: [icon?.(expanded), _jsx("span", { className: classNames.label, children: label })] }), expanded && (_jsx("nav", { className: classNames.list, "aria-label": label, children: headings.map(h => (_jsx("button", { type: "button", onClick: () => {
|
|
225
|
+
setActiveId(h.id);
|
|
226
|
+
document.getElementById(h.id)?.scrollIntoView({ behavior: 'smooth' });
|
|
227
|
+
}, className: currentId === h.id
|
|
228
|
+
? [classNames.item, classNames.itemActive].filter(Boolean).join(' ')
|
|
229
|
+
: classNames.item, "aria-current": currentId === h.id ? 'location' : undefined, style: { paddingLeft: `${(h.level - 1) * indentRem}rem` }, children: h.text }, h.id))) }))] }));
|
|
230
|
+
}
|
|
231
|
+
// ---- outside clicks ---------------------------------------------------------
|
|
232
|
+
/**
|
|
233
|
+
* Calls `onClose` on any mousedown outside the returned ref's element.
|
|
234
|
+
*
|
|
235
|
+
* The `offsetParent` check is the whole reason this is shared. All three wikis
|
|
236
|
+
* had written this hook; only one had that line, and without it a container
|
|
237
|
+
* that is `display: none` at the current breakpoint still claims outside-clicks
|
|
238
|
+
* — so a tap anywhere on a phone dismisses a popover the reader is looking at,
|
|
239
|
+
* because the hidden desktop copy of it answered first.
|
|
240
|
+
*
|
|
241
|
+
* `onClose` is an effect dependency: wrap it in `useCallback` or hoist it, or
|
|
242
|
+
* the listener is torn down and rebuilt on every render.
|
|
243
|
+
*/
|
|
244
|
+
export function useClickOutside(onClose) {
|
|
245
|
+
const ref = useRef(null);
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
const handler = (e) => {
|
|
248
|
+
const el = ref.current;
|
|
249
|
+
if (el && el.offsetParent !== null && !el.contains(e.target))
|
|
250
|
+
onClose();
|
|
251
|
+
};
|
|
252
|
+
document.addEventListener('mousedown', handler);
|
|
253
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
254
|
+
}, [onClose]);
|
|
255
|
+
return ref;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The debounced-search state machine behind every typeahead on these sites.
|
|
259
|
+
*
|
|
260
|
+
* Five surfaces across the three wikis had three implementations, and no two
|
|
261
|
+
* agreed on what a search field does. This is their union, because each had a
|
|
262
|
+
* piece the others were missing:
|
|
263
|
+
*
|
|
264
|
+
* - a **request-id guard**, so a slow response cannot paint over a newer one.
|
|
265
|
+
* Two of the five surfaces had no guard at all: type fast enough and the
|
|
266
|
+
* results you are reading are for a query you have already replaced.
|
|
267
|
+
* - **keyboard navigation**. Two surfaces had none — the header dropdown of a
|
|
268
|
+
* wiki whose search is its primary navigation was mouse-only.
|
|
269
|
+
* - **abort and a per-query cache**, which only the third had. The cache is
|
|
270
|
+
* per mount, not module-level, so a stale result cannot outlive the page.
|
|
271
|
+
*
|
|
272
|
+
* The debounce is inlined rather than pulled from a package: this one is nine
|
|
273
|
+
* lines, and the point of the package is to have no runtime dependencies.
|
|
274
|
+
*/
|
|
275
|
+
export function useTypeahead({ fetch, onPick, onEscape, delay = 200, minLength = 1, cache = true, }) {
|
|
276
|
+
const [query, setQuery] = useState('');
|
|
277
|
+
const [debounced, setDebounced] = useState('');
|
|
278
|
+
const [items, setItems] = useState([]);
|
|
279
|
+
const [highlight, setHighlight] = useState(0);
|
|
280
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
281
|
+
const requestId = useRef(0);
|
|
282
|
+
const cached = useRef(new Map());
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
const timer = setTimeout(() => setDebounced(query), delay);
|
|
285
|
+
return () => clearTimeout(timer);
|
|
286
|
+
}, [query, delay]);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
const q = debounced.trim();
|
|
289
|
+
if (q.length < minLength) {
|
|
290
|
+
// Bump the id so an in-flight request for a longer query cannot land
|
|
291
|
+
// after the field has been cleared.
|
|
292
|
+
requestId.current++;
|
|
293
|
+
setItems([]);
|
|
294
|
+
setIsSearching(false);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const hit = cache ? cached.current.get(q) : undefined;
|
|
298
|
+
if (hit) {
|
|
299
|
+
requestId.current++;
|
|
300
|
+
setItems(hit);
|
|
301
|
+
setHighlight(0);
|
|
302
|
+
setIsSearching(false);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const id = ++requestId.current;
|
|
306
|
+
const controller = new AbortController();
|
|
307
|
+
setIsSearching(true);
|
|
308
|
+
fetch(q, controller.signal).then(rows => {
|
|
309
|
+
if (id !== requestId.current)
|
|
310
|
+
return;
|
|
311
|
+
if (cache)
|
|
312
|
+
cached.current.set(q, rows);
|
|
313
|
+
setItems(rows);
|
|
314
|
+
setHighlight(0);
|
|
315
|
+
setIsSearching(false);
|
|
316
|
+
}, () => {
|
|
317
|
+
// An abort is the expected path for every superseded query, so it must
|
|
318
|
+
// not surface as an error. A real failure reads as "no results".
|
|
319
|
+
if (id !== requestId.current)
|
|
320
|
+
return;
|
|
321
|
+
setItems([]);
|
|
322
|
+
setIsSearching(false);
|
|
323
|
+
});
|
|
324
|
+
return () => controller.abort();
|
|
325
|
+
}, [debounced, fetch, minLength, cache]);
|
|
326
|
+
const clearItems = useCallback(() => setItems([]), []);
|
|
327
|
+
const reset = useCallback(() => {
|
|
328
|
+
requestId.current++;
|
|
329
|
+
setQuery('');
|
|
330
|
+
setDebounced('');
|
|
331
|
+
setItems([]);
|
|
332
|
+
setHighlight(0);
|
|
333
|
+
setIsSearching(false);
|
|
334
|
+
}, []);
|
|
335
|
+
const onKeyDown = (e) => {
|
|
336
|
+
if (e.key === 'Escape') {
|
|
337
|
+
setItems([]);
|
|
338
|
+
onEscape?.();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (!items.length)
|
|
342
|
+
return;
|
|
343
|
+
if (e.key === 'ArrowDown') {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
setHighlight(h => (h + 1) % items.length);
|
|
346
|
+
}
|
|
347
|
+
else if (e.key === 'ArrowUp') {
|
|
348
|
+
e.preventDefault();
|
|
349
|
+
setHighlight(h => (h - 1 + items.length) % items.length);
|
|
350
|
+
}
|
|
351
|
+
else if (e.key === 'Enter') {
|
|
352
|
+
e.preventDefault();
|
|
353
|
+
const target = items[highlight];
|
|
354
|
+
if (target)
|
|
355
|
+
onPick(target);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
return {
|
|
359
|
+
query,
|
|
360
|
+
setQuery,
|
|
361
|
+
debouncedQuery: debounced,
|
|
362
|
+
items,
|
|
363
|
+
highlight,
|
|
364
|
+
setHighlight,
|
|
365
|
+
isSearching,
|
|
366
|
+
onKeyDown,
|
|
367
|
+
clearItems,
|
|
368
|
+
reset,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Wikipedia-style hover cards over a rendered article.
|
|
373
|
+
*
|
|
374
|
+
* One delegated listener on `document` rather than a component per link, which
|
|
375
|
+
* is what makes this viable over an article holding hundreds of anchors. Two
|
|
376
|
+
* wikis had written the same ~90 lines: the same 350ms intent delay, the same
|
|
377
|
+
* 200ms grace so the cursor can cross the gap into the card, the same
|
|
378
|
+
* viewport-clamp arithmetic, the same per-href cache. They differed in exactly
|
|
379
|
+
* three things, and those three are the options above.
|
|
380
|
+
*/
|
|
381
|
+
export function useLinkPreview({ eligible, fetch, cardClassName = 'link-preview-card', cardWidth = 320, cardHeight = 240, showDelay = 350, hideDelay = 200, margin = 8, }) {
|
|
382
|
+
const [card, setCard] = useState(null);
|
|
383
|
+
const cache = useRef(new Map());
|
|
384
|
+
const showTimer = useRef(undefined);
|
|
385
|
+
const hideTimer = useRef(undefined);
|
|
386
|
+
const activeHref = useRef(null);
|
|
387
|
+
// The handlers below are rebuilt whenever these change, so they are read from
|
|
388
|
+
// a ref instead — a caller passing an inline `eligible` arrow would otherwise
|
|
389
|
+
// tear down and rebind the document listeners on every render.
|
|
390
|
+
const opts = useRef({ eligible, fetch });
|
|
391
|
+
opts.current = { eligible, fetch };
|
|
392
|
+
const hide = useCallback(() => {
|
|
393
|
+
hideTimer.current = setTimeout(() => {
|
|
394
|
+
activeHref.current = null;
|
|
395
|
+
setCard(null);
|
|
396
|
+
}, hideDelay);
|
|
397
|
+
}, [hideDelay]);
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
const clearTimers = () => {
|
|
400
|
+
clearTimeout(showTimer.current);
|
|
401
|
+
clearTimeout(hideTimer.current);
|
|
402
|
+
};
|
|
403
|
+
const onOver = (e) => {
|
|
404
|
+
const anchor = e.target?.closest('a');
|
|
405
|
+
if (!anchor || !opts.current.eligible(anchor))
|
|
406
|
+
return;
|
|
407
|
+
const href = (anchor.getAttribute('href') ?? '').replace(/[#?].*$/, '');
|
|
408
|
+
if (!href)
|
|
409
|
+
return;
|
|
410
|
+
// Re-entering the link the card is already for: cancel the pending hide
|
|
411
|
+
// rather than refetching and re-placing an identical card.
|
|
412
|
+
if (activeHref.current === href) {
|
|
413
|
+
clearTimeout(hideTimer.current);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
clearTimers();
|
|
417
|
+
activeHref.current = href;
|
|
418
|
+
showTimer.current = setTimeout(async () => {
|
|
419
|
+
let data = cache.current.get(href);
|
|
420
|
+
if (data === undefined) {
|
|
421
|
+
try {
|
|
422
|
+
data = await opts.current.fetch(href);
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
data = null;
|
|
426
|
+
}
|
|
427
|
+
cache.current.set(href, data);
|
|
428
|
+
}
|
|
429
|
+
// The cursor may have moved on during the fetch.
|
|
430
|
+
if (activeHref.current !== href || data === null)
|
|
431
|
+
return;
|
|
432
|
+
const rect = anchor.getBoundingClientRect();
|
|
433
|
+
const above = rect.bottom + cardHeight > window.innerHeight && rect.top > cardHeight;
|
|
434
|
+
setCard({
|
|
435
|
+
data,
|
|
436
|
+
left: Math.max(margin, Math.min(rect.left, window.innerWidth - cardWidth - margin)),
|
|
437
|
+
top: above ? rect.top - margin : rect.bottom + margin,
|
|
438
|
+
above,
|
|
439
|
+
});
|
|
440
|
+
}, showDelay);
|
|
441
|
+
};
|
|
442
|
+
const onOut = (e) => {
|
|
443
|
+
// Moving into the card itself is not leaving.
|
|
444
|
+
if (e.relatedTarget?.closest(`.${cardClassName}`))
|
|
445
|
+
return;
|
|
446
|
+
clearTimers();
|
|
447
|
+
hide();
|
|
448
|
+
};
|
|
449
|
+
document.addEventListener('mouseover', onOver);
|
|
450
|
+
document.addEventListener('mouseout', onOut);
|
|
451
|
+
return () => {
|
|
452
|
+
clearTimers();
|
|
453
|
+
document.removeEventListener('mouseover', onOver);
|
|
454
|
+
document.removeEventListener('mouseout', onOut);
|
|
455
|
+
};
|
|
456
|
+
}, [cardClassName, cardWidth, cardHeight, showDelay, margin, hide]);
|
|
457
|
+
return {
|
|
458
|
+
preview: card?.data ?? null,
|
|
459
|
+
cardProps: {
|
|
460
|
+
style: {
|
|
461
|
+
left: card?.left,
|
|
462
|
+
top: card?.top,
|
|
463
|
+
transform: card?.above ? 'translateY(-100%)' : undefined,
|
|
464
|
+
},
|
|
465
|
+
onMouseEnter: () => clearTimeout(hideTimer.current),
|
|
466
|
+
onMouseLeave: hide,
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the rail should be open, given what is known.
|
|
3
|
+
*
|
|
4
|
+
* The whole of both bug fixes lives in these three lines, so it is a named
|
|
5
|
+
* function with tests rather than a condition buried in an effect:
|
|
6
|
+
*
|
|
7
|
+
* - a choice the reader has made outranks everything, forever;
|
|
8
|
+
* - a remembered choice outranks the viewport;
|
|
9
|
+
* - the viewport is consulted only when nothing else has an opinion.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveSidebarOpen({ chosen, current, stored, isMobile, }: {
|
|
12
|
+
/** Has the reader touched the control this session? */
|
|
13
|
+
chosen: boolean;
|
|
14
|
+
/** What the rail is showing now. */
|
|
15
|
+
current: boolean;
|
|
16
|
+
/** What storage remembers, or null if it has never been written. */
|
|
17
|
+
stored: boolean | null;
|
|
18
|
+
/** Is the viewport below the breakpoint? */
|
|
19
|
+
isMobile: boolean;
|
|
20
|
+
}): boolean;
|
|
21
|
+
/** The attribute the boot script and the hook both write to `<html>`. */
|
|
22
|
+
export declare const SIDEBAR_ATTRIBUTE = "data-sidebar";
|
|
23
|
+
/**
|
|
24
|
+
* A blocking inline script for the document head, so the rail's first paint
|
|
25
|
+
* already matches what the reader last chose.
|
|
26
|
+
*
|
|
27
|
+
* Server rendering cannot see `localStorage`, so a remembered-closed rail would
|
|
28
|
+
* otherwise paint open and then animate shut on every single load. This is the
|
|
29
|
+
* same trick a theme switcher uses for exactly the same reason. Stamp it with
|
|
30
|
+
* `dangerouslySetInnerHTML` in `<head>`, and style against
|
|
31
|
+
* `html[data-sidebar='closed']` rather than against the React class alone.
|
|
32
|
+
*
|
|
33
|
+
* It is wrapped in try/catch because a browser with site data blocked throws on
|
|
34
|
+
* the read, and a rail that cannot remember is much better than a page that
|
|
35
|
+
* dies before it paints.
|
|
36
|
+
*/
|
|
37
|
+
export declare function sidebarBootScript(storageKey?: string, breakpoint?: number): string;
|
|
38
|
+
//# sourceMappingURL=sidebar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar.d.ts","sourceRoot":"","sources":["../src/sidebar.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE;IACD,uDAAuD;IACvD,MAAM,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAGV;AAED,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,SAAiB,EAAE,UAAU,SAAO,GAAG,MAAM,CAMxF"}
|
package/dist/sidebar.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// sidebar.ts — the framework-free half of the collapsible wiki rail.
|
|
2
|
+
//
|
|
3
|
+
// Separate from `react.tsx` because that file carries `'use client'`, which
|
|
4
|
+
// makes every one of its exports a client reference — and `sidebarBootScript`
|
|
5
|
+
// has to be callable from a server component to reach the document head.
|
|
6
|
+
// Splitting it also lets the collapse rule be tested without a DOM.
|
|
7
|
+
/**
|
|
8
|
+
* Whether the rail should be open, given what is known.
|
|
9
|
+
*
|
|
10
|
+
* The whole of both bug fixes lives in these three lines, so it is a named
|
|
11
|
+
* function with tests rather than a condition buried in an effect:
|
|
12
|
+
*
|
|
13
|
+
* - a choice the reader has made outranks everything, forever;
|
|
14
|
+
* - a remembered choice outranks the viewport;
|
|
15
|
+
* - the viewport is consulted only when nothing else has an opinion.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveSidebarOpen({ chosen, current, stored, isMobile, }) {
|
|
18
|
+
if (chosen)
|
|
19
|
+
return current;
|
|
20
|
+
return stored ?? !isMobile;
|
|
21
|
+
}
|
|
22
|
+
/** The attribute the boot script and the hook both write to `<html>`. */
|
|
23
|
+
export const SIDEBAR_ATTRIBUTE = 'data-sidebar';
|
|
24
|
+
/**
|
|
25
|
+
* A blocking inline script for the document head, so the rail's first paint
|
|
26
|
+
* already matches what the reader last chose.
|
|
27
|
+
*
|
|
28
|
+
* Server rendering cannot see `localStorage`, so a remembered-closed rail would
|
|
29
|
+
* otherwise paint open and then animate shut on every single load. This is the
|
|
30
|
+
* same trick a theme switcher uses for exactly the same reason. Stamp it with
|
|
31
|
+
* `dangerouslySetInnerHTML` in `<head>`, and style against
|
|
32
|
+
* `html[data-sidebar='closed']` rather than against the React class alone.
|
|
33
|
+
*
|
|
34
|
+
* It is wrapped in try/catch because a browser with site data blocked throws on
|
|
35
|
+
* the read, and a rail that cannot remember is much better than a page that
|
|
36
|
+
* dies before it paints.
|
|
37
|
+
*/
|
|
38
|
+
export function sidebarBootScript(storageKey = 'wiki:sidebar', breakpoint = 1024) {
|
|
39
|
+
return (`(function(){try{var v=localStorage.getItem(${JSON.stringify(storageKey)});` +
|
|
40
|
+
`var o=v===null?!window.matchMedia("(max-width: ${breakpoint - 1}px)").matches:v==="1";` +
|
|
41
|
+
`document.documentElement.setAttribute(${JSON.stringify(SIDEBAR_ATTRIBUTE)},o?"open":"closed");}catch(e){}})()`);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=sidebar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,yEAAyE;AACzE,oEAAoE;AAEpE;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,EACjC,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,GAUT;IACC,IAAI,MAAM;QAAE,OAAO,OAAO,CAAC;IAC3B,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC;AAC7B,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAU,GAAG,cAAc,EAAE,UAAU,GAAG,IAAI;IAC9E,OAAO,CACL,8CAA8C,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI;QAC5E,kDAAkD,UAAU,GAAG,CAAC,wBAAwB;QACxF,yCAAyC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,qCAAqC,CAChH,CAAC;AACJ,CAAC"}
|
package/dist/taxonomy.d.ts
CHANGED
|
@@ -55,6 +55,39 @@ export interface MetadataRow extends MetadataKeyDefinition {
|
|
|
55
55
|
value: string;
|
|
56
56
|
href?: string;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* One pressable control on a browsing axis, with its destination already built.
|
|
60
|
+
*
|
|
61
|
+
* The three wikis each rendered the same taxonomy output their own way and had
|
|
62
|
+
* already begun to disagree about what the row CONTAINS rather than how it
|
|
63
|
+
* looks: one shipped no A–Z index at all, one had no way back to the unfiltered
|
|
64
|
+
* set, and two marked an active chip with `aria-pressed` on an `<a>` — which is
|
|
65
|
+
* not a toggle button and does not take that attribute. The decisions are here;
|
|
66
|
+
* the markup stays theirs.
|
|
67
|
+
*/
|
|
68
|
+
export interface Control {
|
|
69
|
+
/** The value this control selects. Empty on the reset control. */
|
|
70
|
+
value: string;
|
|
71
|
+
/** What to render. Same as `value` except on the reset control. */
|
|
72
|
+
label: string;
|
|
73
|
+
/** Pages reachable through this control, counted under the sibling state. */
|
|
74
|
+
count: number;
|
|
75
|
+
/** Built through the one `href` builder, so no control drops a sibling axis. */
|
|
76
|
+
href: string;
|
|
77
|
+
active: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* True on the single control that clears its axis. It leads the row rather
|
|
80
|
+
* than being appended, so a consumer that maps the array cannot ship a
|
|
81
|
+
* narrowed view with nothing to press to widen it.
|
|
82
|
+
*/
|
|
83
|
+
reset?: boolean;
|
|
84
|
+
}
|
|
85
|
+
/** One facet's controls, ready to render as a labelled row. */
|
|
86
|
+
export interface FacetControlGroup {
|
|
87
|
+
key: string;
|
|
88
|
+
label: string;
|
|
89
|
+
options: Control[];
|
|
90
|
+
}
|
|
58
91
|
/** State a category URL can carry. `href` receives this and owns the format. */
|
|
59
92
|
export interface CategoryState {
|
|
60
93
|
sort?: string;
|
|
@@ -99,6 +132,25 @@ export interface Taxonomy {
|
|
|
99
132
|
filterPages: <T extends FacetablePage>(pages: T[], filters: FacetFilters, letter?: string) => T[];
|
|
100
133
|
buildFacets: <T extends FacetablePage>(tagPath: string, pages: T[], filters: FacetFilters, letter?: string) => Facet[];
|
|
101
134
|
alphaIndex: <T extends FacetablePage>(pages: T[], filters: FacetFilters) => FacetValue[];
|
|
135
|
+
/**
|
|
136
|
+
* `buildFacets` with every chip's destination and active state resolved.
|
|
137
|
+
* Prefer this to `buildFacets` in a view: the href arithmetic is the part
|
|
138
|
+
* that was being rebuilt, and getting it wrong drops the reader's letter or
|
|
139
|
+
* sort on the next press.
|
|
140
|
+
*/
|
|
141
|
+
facetControls: <T extends FacetablePage>(tagPath: string, pages: T[], state?: CategoryState) => FacetControlGroup[];
|
|
142
|
+
/**
|
|
143
|
+
* The A–Z row, reset control first, or `[]` for a set that still fits on a
|
|
144
|
+
* screen. Folds in the `needsAlphaIndex` threshold so a consumer cannot show
|
|
145
|
+
* an index it did not need or, as one did, omit one it did.
|
|
146
|
+
*/
|
|
147
|
+
alphaControls: <T extends FacetablePage>(tagPath: string, pages: T[], state?: CategoryState) => Control[];
|
|
148
|
+
/**
|
|
149
|
+
* A `?letter=` narrowed to a bucket that exists. A letter with no pages under
|
|
150
|
+
* the active filters would otherwise empty the grid with no control marked to
|
|
151
|
+
* explain it.
|
|
152
|
+
*/
|
|
153
|
+
resolveLetter: <T extends FacetablePage>(pages: T[], filters: FacetFilters, letter?: string) => string | undefined;
|
|
102
154
|
/** A page's populated metadata keys as facts-panel rows, facet values linked. */
|
|
103
155
|
metadataRows: (tagPath: string, page: {
|
|
104
156
|
metadata?: unknown;
|