wiki-formant 0.47.1 → 0.49.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 (79) hide show
  1. package/README.md +95 -26
  2. package/bin/maintenance-log.mjs +198 -0
  3. package/bin/mark-verified.mjs +75 -0
  4. package/bin/prisma-models.mjs +53 -0
  5. package/dist/analytics.d.ts +0 -25
  6. package/dist/analytics.js +10 -49
  7. package/dist/base.css +23 -2
  8. package/dist/blocks.d.ts +7 -6
  9. package/dist/blocks.js +11 -3
  10. package/dist/chart.d.ts +17 -8
  11. package/dist/chart.js +53 -6
  12. package/dist/combobox.d.ts +0 -4
  13. package/dist/combobox.js +2 -0
  14. package/dist/crawlers.d.ts +0 -24
  15. package/dist/crawlers.js +9 -3
  16. package/dist/dom.d.ts +0 -5
  17. package/dist/dom.js +2 -0
  18. package/dist/editor.d.ts +0 -43
  19. package/dist/editor.js +7 -1
  20. package/dist/feed.d.ts +0 -14
  21. package/dist/feed.js +4 -0
  22. package/dist/freshness.d.ts +0 -7
  23. package/dist/freshness.js +3 -0
  24. package/dist/headers.d.ts +0 -8
  25. package/dist/headers.js +3 -1
  26. package/dist/headings.d.ts +0 -8
  27. package/dist/headings.js +2 -0
  28. package/dist/history.d.ts +0 -6
  29. package/dist/history.js +2 -0
  30. package/dist/http.d.ts +0 -11
  31. package/dist/http.js +5 -0
  32. package/dist/license.d.ts +0 -8
  33. package/dist/license.js +2 -0
  34. package/dist/link-check.d.ts +0 -21
  35. package/dist/link-check.js +6 -2
  36. package/dist/links.d.ts +0 -5
  37. package/dist/links.js +2 -0
  38. package/dist/maintenance.d.ts +61 -0
  39. package/dist/maintenance.js +137 -0
  40. package/dist/maps.d.ts +0 -6
  41. package/dist/maps.js +2 -0
  42. package/dist/markdown.d.ts +0 -2
  43. package/dist/markdown.js +1 -1
  44. package/dist/mcp.d.ts +0 -14
  45. package/dist/mcp.js +1 -1
  46. package/dist/metadata.d.ts +14 -11
  47. package/dist/metadata.js +33 -0
  48. package/dist/pagination.d.ts +0 -5
  49. package/dist/pagination.js +0 -4
  50. package/dist/passkey.js +2 -15
  51. package/dist/rate-limit.d.ts +5 -16
  52. package/dist/rate-limit.js +10 -1
  53. package/dist/react-server.d.ts +0 -12
  54. package/dist/react-server.js +2 -0
  55. package/dist/revisions.d.ts +0 -7
  56. package/dist/revisions.js +2 -0
  57. package/dist/rola.d.ts +74 -0
  58. package/dist/rola.js +97 -0
  59. package/dist/search.d.ts +0 -12
  60. package/dist/search.js +4 -0
  61. package/dist/sidebar.d.ts +0 -16
  62. package/dist/sidebar.js +2 -0
  63. package/dist/stats.d.ts +0 -4
  64. package/dist/stats.js +2 -2
  65. package/dist/taxonomy.d.ts +0 -4
  66. package/dist/taxonomy.js +2 -2
  67. package/dist/text.d.ts +16 -22
  68. package/dist/text.js +39 -5
  69. package/dist/tiptap.d.ts +5 -4
  70. package/dist/tiptap.js +2 -1
  71. package/dist/upload.d.ts +31 -0
  72. package/dist/upload.js +91 -0
  73. package/dist/validation.d.ts +0 -6
  74. package/dist/validation.js +3 -3
  75. package/dist/well-known.d.ts +0 -20
  76. package/dist/well-known.js +3 -0
  77. package/package.json +37 -8
  78. package/prisma/analytics.prisma +57 -0
  79. package/prisma/passkey.prisma +21 -0
@@ -4,15 +4,6 @@ import { type HeaderReader, type RateLimitOptions } from './rate-limit.js';
4
4
  * Prisma: `(query, ...values) => prisma.$queryRawUnsafe(query, ...values)`.
5
5
  */
6
6
  export type Sql = (query: string, ...values: unknown[]) => Promise<unknown>;
7
- /** iPadOS reports itself as a Mac, so its views count as a computer's. */
8
- export declare const deviceOf: (ua: string) => "phone" | "tablet" | "computer";
9
- /**
10
- * Where a visit came from: its `utm_source` when the link carried one, else the
11
- * referring host, and null for a referrer that is this site or none.
12
- */
13
- export declare function sourceOf(referrer: unknown, utm: unknown, host: string): string | null;
14
- /** A browser path as stored: decoded, one leading slash, no trailing one. Null when it will not decode. */
15
- export declare function normalisePath(path: string): string | null;
16
7
  export interface ViewInput {
17
8
  /** The beacon request's headers: the address, browser, host and country come from them. */
18
9
  headers: HeaderReader;
@@ -22,8 +13,6 @@ export interface ViewInput {
22
13
  utm?: unknown;
23
14
  site?: string;
24
15
  }
25
- /** Count one page view, unless a bot sent it. Never rejects. */
26
- export declare function recordView(sql: Sql, { headers, path, referrer, utm, site }: ViewInput): Promise<void>;
27
16
  export interface EventInput {
28
17
  name: string;
29
18
  props?: Record<string, string | number>;
@@ -170,20 +159,6 @@ export interface SearchQueryInput {
170
159
  /** Which search box, when a site has more than one. */
171
160
  surface?: string;
172
161
  }
173
- /**
174
- * Props for a "Search Query" event – the human-side twin of `mcpCallProps`.
175
- *
176
- * A wiki instruments its agent surface and then counts every question an agent
177
- * asks while recording none of the questions a person asks. The queries that
178
- * return nothing are the valuable ones: they name a gap in the corpus in the
179
- * reader's own words, which is otherwise only ever guessed at.
180
- *
181
- * Returns `null` for a query with no content, so an empty field cannot fire an
182
- * event. The text is untrusted, so it is collapsed, lowercased for aggregation
183
- * and truncated – the same 64-character bound search itself applies, which
184
- * keeps the prop and the query that produced it the same string.
185
- */
186
- export declare function searchQueryProps({ query, results, surface, }: SearchQueryInput): Record<string, string> | null;
187
162
  export interface TrackerOptions {
188
163
  sql: Sql;
189
164
  /** `after` in Next: the write runs once the response has gone. */
package/dist/analytics.js CHANGED
@@ -14,52 +14,9 @@
14
14
  //
15
15
  // The package owns the SQL; the app owns the connection. `Sql` is one function
16
16
  // that runs a parameterised statement, which Prisma, `pg` and postgres.js can
17
- // all supply in a line. The app declares the four tables in its own schema,
18
- // so `prisma db push` keeps them:
19
- //
20
- // model View {
21
- // id BigInt @id @default(autoincrement())
22
- // siteId String @default("")
23
- // path String
24
- // source String?
25
- // country String?
26
- // device String
27
- // visitor String
28
- // createdAt DateTime @default(now()) @db.Timestamptz(3)
29
- // @@index([siteId, createdAt])
30
- // }
31
- //
32
- // model Event {
33
- // id BigInt @id @default(autoincrement())
34
- // siteId String @default("")
35
- // name String
36
- // path String?
37
- // props Json @default("{}")
38
- // visitor String?
39
- // createdAt DateTime @default(now()) @db.Timestamptz(3)
40
- // @@index([siteId, name, createdAt])
41
- // }
42
- //
43
- // model Salt {
44
- // day String @id
45
- // value String
46
- // }
47
- //
48
- // model ViewDay {
49
- // siteId String @default("")
50
- // day DateTime @db.Date
51
- // visitors Int
52
- // pageviews Int
53
- // visits Int
54
- // bounces Int
55
- // seconds Int
56
- // pages Json @default("{}")
57
- // entryPages Json @default("{}")
58
- // sources Json @default("{}")
59
- // countries Json @default("{}")
60
- // devices Json @default("{}")
61
- // @@id([siteId, day])
62
- // }
17
+ // all supply in a line. The four tables it needs are Prisma models shipped in
18
+ // `prisma/analytics.prisma`, which the `prisma-models` bin copies into the
19
+ // app's schema folder, so `prisma db push` keeps them.
63
20
  //
64
21
  // `ViewDay` holds days a site counted somewhere else before it counted its
65
22
  // own, one row a day with each breakdown as `{ value: visitors }`. Nothing
@@ -80,12 +37,14 @@ const KEEP_DAYS = 400;
80
37
  const DAY_MS = 86_400_000;
81
38
  /** A script that runs a page's JavaScript and says what it is. */
82
39
  const BOT = /bot|crawl|spider|slurp|headless|lighthouse|prerender|preview/i;
83
- /** iPadOS reports itself as a Mac, so its views count as a computer's. */
40
+ /** iPadOS reports itself as a Mac, so its views count as a computer's. @internal */
84
41
  export const deviceOf = (ua) => /iPad|Tablet|Android(?!.*Mobile)/i.test(ua) ? 'tablet' : /Mobi|iPhone|Android/i.test(ua) ? 'phone' : 'computer';
85
42
  const bareHost = (host) => host.toLowerCase().replace(/:\d+$/, '').replace(/^www\./, '');
86
43
  /**
87
44
  * Where a visit came from: its `utm_source` when the link carried one, else the
88
45
  * referring host, and null for a referrer that is this site or none.
46
+ *
47
+ * @internal
89
48
  */
90
49
  export function sourceOf(referrer, utm, host) {
91
50
  if (typeof utm === 'string' && utm.trim())
@@ -100,7 +59,7 @@ export function sourceOf(referrer, utm, host) {
100
59
  return null;
101
60
  }
102
61
  }
103
- /** A browser path as stored: decoded, one leading slash, no trailing one. Null when it will not decode. */
62
+ /** A browser path as stored: decoded, one leading slash, no trailing one. Null when it will not decode. @internal */
104
63
  export function normalisePath(path) {
105
64
  try {
106
65
  const p = decodeURIComponent(path.split(/[?#]/)[0].slice(0, 500));
@@ -136,7 +95,7 @@ async function visitorOf(sql, site, headers) {
136
95
  }
137
96
  /** Analytics must not be able to fail a response, so a failed write is logged and dropped. */
138
97
  const quietly = (task) => task().then(() => undefined, (e) => console.error(`analytics: ${e.message}`));
139
- /** Count one page view, unless a bot sent it. Never rejects. */
98
+ /** Count one page view, unless a bot sent it. Never rejects. @internal */
140
99
  export function recordView(sql, { headers, path, referrer, utm, site = '' }) {
141
100
  const ua = headers.get('user-agent') ?? '';
142
101
  const page = normalisePath(path);
@@ -340,6 +299,8 @@ export function mcpCallProps(request, body, server) {
340
299
  * event. The text is untrusted, so it is collapsed, lowercased for aggregation
341
300
  * and truncated – the same 64-character bound search itself applies, which
342
301
  * keeps the prop and the query that produced it the same string.
302
+ *
303
+ * @internal
343
304
  */
344
305
  export function searchQueryProps({ query, results, surface, }) {
345
306
  const q = (query ?? '').replace(/\s+/g, ' ').trim().toLowerCase().slice(0, 64);
package/dist/base.css CHANGED
@@ -40,6 +40,27 @@
40
40
  .columns-layout > .column-view { align-self: stretch; }
41
41
  }
42
42
 
43
+ /* ---- the block views and the infobox ---------------------------------------
44
+ The structure three or four sites had each restated around this markup;
45
+ padding, gaps, radii and colour stay theirs. A stats block is two columns,
46
+ three or four from 40rem; a code-tab strip is a row of buttons; a banner sets
47
+ its icon beside its text; a long address in an infobox wraps. */
48
+ .stat-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
49
+ @media (min-width: 40rem) {
50
+ .stat-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
51
+ .stat-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
52
+ }
53
+ .stat-card { display: flex; flex-direction: column; }
54
+ .code-tabs { overflow: hidden; }
55
+ .code-tabs-list { display: flex; }
56
+ .code-tabs-btn { cursor: pointer; }
57
+ .editorial-banner { display: flex; }
58
+ .infobox { overflow-wrap: anywhere; }
59
+ .infobox-series { display: flex; flex-direction: column; }
60
+ .heading-anchor,
61
+ .ref-backlink,
62
+ .reference-link { text-decoration: none; }
63
+
43
64
  /* ---- stored tab groups (activateTabGroups) --------------------------------
44
65
  Only the active panel shows. Before the script runs, the first tab's content
45
66
  stands in for the group rather than every tab at once. */
@@ -174,7 +195,7 @@ pre:hover > .code-copy-btn,
174
195
  `TimeChart`: the canvas, a loading or empty state over it, and the range and
175
196
  step buttons pinned to its top-left corner. A site gives the buttons and the
176
197
  box their look; pressed is all this marks. */
177
- .chart { position: relative; margin: 0; }
198
+ .chart { position: relative; margin: 0; overflow: hidden; }
178
199
  /* Its own stacking context: the library's canvases stack themselves, and the buttons go over them. */
179
200
  .chart-canvas { position: relative; z-index: 0; height: var(--chart-height, 15rem); }
180
201
  /* The chart lays itself out in a <table>; a site's table styles are not for it. */
@@ -192,7 +213,7 @@ pre:hover > .code-copy-btn,
192
213
  .chart-controls > div { display: flex; gap: 2px; }
193
214
  /* A rule between groups: ranges, then steps, then the candles switch. */
194
215
  .chart-controls > * + * { border-inline-start: 1px solid color-mix(in srgb, currentColor 25%, transparent); padding-inline-start: 0.5rem; }
195
- .chart-controls :is(button, a) { font: inherit; color: inherit; text-decoration: none; background: none; cursor: pointer; opacity: 0.6; }
216
+ .chart-controls :is(button, a) { padding: 0.125rem 0.375rem; font: inherit; color: inherit; text-decoration: none; background: none; cursor: pointer; opacity: 0.6; }
196
217
  /* An icon sits on the text's line, so an icon button is as tall as a labelled one. */
197
218
  .chart-icon { display: inline-block; width: 1em; height: 1em; vertical-align: -0.125em; }
198
219
  /* A button is pressed; a link (a stats page's window) is the page shown. */
package/dist/blocks.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { type RelativeTimeStyle } from './freshness.js';
2
2
  import type { BlockGroup } from './revisions.js';
3
+ /**
4
+ * The languages a code block or code tab offers, and the one a new one opens
5
+ * on. Two wikis carried this list character for character; a wiki with its own
6
+ * passes it to `createCodeBlock` and its tab editor.
7
+ */
8
+ export declare const CODE_LANGS: readonly ["javascript", "typescript", "css", "json", "bash", "python", "rust", "sql", "html", "xml", "jsx", "tsx", "markdown", "yaml", "toml"];
9
+ export declare const DEFAULT_LANG = "rust";
3
10
  export interface CodeTab {
4
11
  label: string;
5
12
  language?: string;
@@ -137,14 +144,8 @@ export declare function someBlock<B>(blocks: readonly B[], predicate: (block: B)
137
144
  * contains a fence cannot close this one early.
138
145
  */
139
146
  export declare function codeTabsToMarkdown(tabs: readonly CodeTab[]): string;
140
- /** A maintenance notice as a blockquote. `label` is the caller's display string. */
141
- export declare function bannerToMarkdown(label: string, text?: string | null): string;
142
- /** A numbered citation list, or `''` when there is nothing to cite. */
143
- export declare function referencesToMarkdown(items: readonly ReferenceItem[], title?: string): string;
144
147
  /** Metric cards as a bullet list, or `''` when empty. */
145
148
  export declare function statsToMarkdown(items: readonly StatItem[]): string;
146
- /** Grouped links under their headings, with an optional lead paragraph. */
147
- export declare function linkGridToMarkdown(groups: readonly LinkGridGroup[], intro?: string | null): string;
148
149
  /** A flat bullet list of links — resolved page lists, feed items, link rails. */
149
150
  export declare function linkList(items: readonly LinkGridLink[]): string;
150
151
  /**
package/dist/blocks.js CHANGED
@@ -13,6 +13,14 @@
13
13
  import { htmlToMarkdown, inlineToMarkdown } from './markdown.js';
14
14
  import { relativeTime } from './freshness.js';
15
15
  import { BANNER_LABELS, bannerToText, codeTabsToText, linkGridToText, pageListToText, referencesToText, statsToText, stripHtml, } from './text.js';
16
+ // ---- the shapes the standard block types carry ------------------------------
17
+ /**
18
+ * The languages a code block or code tab offers, and the one a new one opens
19
+ * on. Two wikis carried this list character for character; a wiki with its own
20
+ * passes it to `createCodeBlock` and its tab editor.
21
+ */
22
+ export const CODE_LANGS = ['javascript', 'typescript', 'css', 'json', 'bash', 'python', 'rust', 'sql', 'html', 'xml', 'jsx', 'tsx', 'markdown', 'yaml', 'toml'];
23
+ export const DEFAULT_LANG = 'rust';
16
24
  export function pageRef(page, now, style = 'long') {
17
25
  return {
18
26
  title: page.title,
@@ -163,11 +171,11 @@ export function codeTabsToMarkdown(tabs) {
163
171
  })
164
172
  .join('\n\n');
165
173
  }
166
- /** A maintenance notice as a blockquote. `label` is the caller's display string. */
174
+ /** A maintenance notice as a blockquote. `label` is the caller's display string. @internal */
167
175
  export function bannerToMarkdown(label, text) {
168
176
  return `> **[${label}]**${text ? ` ${inlineToMarkdown(text)}` : ''}`;
169
177
  }
170
- /** A numbered citation list, or `''` when there is nothing to cite. */
178
+ /** A numbered citation list, or `''` when there is nothing to cite. @internal */
171
179
  export function referencesToMarkdown(items, title = 'References') {
172
180
  if (!items.length)
173
181
  return '';
@@ -180,7 +188,7 @@ export function statsToMarkdown(items) {
180
188
  return '';
181
189
  return items.map(s => `- **${s.value}${s.suffix ?? ''}** — ${s.label}`).join('\n');
182
190
  }
183
- /** Grouped links under their headings, with an optional lead paragraph. */
191
+ /** Grouped links under their headings, with an optional lead paragraph. @internal */
184
192
  export function linkGridToMarkdown(groups, intro) {
185
193
  const lead = intro ? `${inlineToMarkdown(intro)}\n\n` : '';
186
194
  const rendered = groups.map(g => [
package/dist/chart.d.ts CHANGED
@@ -18,6 +18,16 @@ export type ChartResolution = 'hour' | '4h' | 'day';
18
18
  export type ChartStep = 'day' | 'week' | 'month';
19
19
  /** Points by resolution, from `from` (Unix seconds, 0 for all time), oldest first. */
20
20
  export type ChartLoader = (resolution: ChartResolution, from: number) => Promise<readonly ChartPoint[]>;
21
+ /** Something that happened at a time, in Unix seconds, drawn on the point that holds it. */
22
+ export type ChartMarker = {
23
+ time: number;
24
+ label: string;
25
+ };
26
+ /** The range and step a reader has picked, for a page that keeps them in its URL. */
27
+ export type ChartView = {
28
+ range: ChartRange;
29
+ step: ChartStep | null;
30
+ };
21
31
  /** Every range, shortest first. A chart fed daily points offers the ones from 30D up. */
22
32
  export declare const CHART_RANGES: readonly ChartRange[];
23
33
  /** A step is offered once it makes four points, and the default is the finest that makes 200 or fewer. */
@@ -25,13 +35,6 @@ export declare function chartSteps(span: number): {
25
35
  steps: ChartStep[];
26
36
  fallback: ChartStep;
27
37
  };
28
- /**
29
- * Points, oldest first, one per step. A price keeps the last value in each
30
- * step, as a candle does its close: the first open, the highest high, the
31
- * lowest low. A count takes the mean of its days, so the week in progress does
32
- * not read as a collapse. Volume is summed.
33
- */
34
- export declare function bucketChart(points: readonly ChartPoint[], step: ChartStep, aggregate: 'last' | 'mean'): ChartPoint[];
35
38
  /** Compact from a thousand up, four significant figures below. */
36
39
  export declare const formatChartValue: (n: number) => string;
37
40
  export interface TimeChartProps {
@@ -58,6 +61,12 @@ export interface TimeChartProps {
58
61
  * window links, which load another digest rather than slice this one.
59
62
  */
60
63
  controls?: ReactNode;
64
+ /** Events drawn on the points that hold them. */
65
+ markers?: readonly ChartMarker[];
66
+ /** The step shown first, where the range can make it. */
67
+ step?: ChartStep;
68
+ /** Called with the range and step on mount and on every change. */
69
+ onView?: (view: ChartView) => void;
61
70
  className?: string;
62
71
  }
63
72
  /**
@@ -65,4 +74,4 @@ export interface TimeChartProps {
65
74
  * `.chart-controls button[aria-pressed]`, and a site's stylesheet gives them
66
75
  * and the box their look; the line and fill are the canvas box's `color`.
67
76
  */
68
- export declare function TimeChart({ series, label, ranges, range: initial, aggregate, fromZero, format, candles: initialCandles, height, controls, className, }: TimeChartProps): import("react").JSX.Element;
77
+ export declare function TimeChart({ series, label, ranges, range: initial, aggregate, fromZero, format, candles: initialCandles, height, controls, markers, step: initialStep, onView, className, }: TimeChartProps): import("react").JSX.Element;
package/dist/chart.js CHANGED
@@ -8,7 +8,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  // loaded on mount, so the page paints without it. Every colour is the canvas
9
9
  // box's own `color`, its axis `--chart-axis`, and a candle `--chart-up` or
10
10
  // `--chart-down`, each if set. A count's line is curved; a price's is straight,
11
- // since a curve between two closes draws prices nobody paid.
11
+ // since a curve between two closes draws prices nobody paid. Events are markers
12
+ // on the point they fall in, so a spike carries the reason for it.
12
13
  import { useEffect, useMemo, useRef, useState } from 'react';
13
14
  const DAY = 86_400;
14
15
  const RANGE = {
@@ -49,6 +50,8 @@ function bucketStart(time, step) {
49
50
  * step, as a candle does its close: the first open, the highest high, the
50
51
  * lowest low. A count takes the mean of its days, so the week in progress does
51
52
  * not read as a collapse. Volume is summed.
53
+ *
54
+ * @internal
52
55
  */
53
56
  export function bucketChart(points, step, aggregate) {
54
57
  const out = [];
@@ -74,6 +77,31 @@ export function bucketChart(points, step, aggregate) {
74
77
  }
75
78
  return out;
76
79
  }
80
+ /**
81
+ * Markers on the points drawn: each on the last point at or before it, so one
82
+ * mid-week sits on its week. Markers on one point share it, labels joined; one
83
+ * before the first point or after the last point's step is left off.
84
+ *
85
+ * @internal
86
+ */
87
+ export function placeMarkers(markers, shown, step) {
88
+ const first = shown[0];
89
+ const last = shown.at(-1);
90
+ if (!first || !last)
91
+ return [];
92
+ const end = last.time + (step ? STEP[step].span : DAY);
93
+ const placed = new Map();
94
+ for (const m of [...markers].sort((a, b) => a.time - b.time)) {
95
+ if (m.time < first.time || m.time >= end)
96
+ continue;
97
+ let i = shown.length - 1;
98
+ while (i > 0 && shown[i].time > m.time)
99
+ i--;
100
+ const time = shown[i].time;
101
+ placed.set(time, [...(placed.get(time) ?? []), m.label]);
102
+ }
103
+ return [...placed].map(([time, labels]) => ({ time, label: labels.join(' · ') }));
104
+ }
77
105
  const isCandle = (p) => p.open !== undefined && p.high !== undefined && p.low !== undefined;
78
106
  const compact = new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 });
79
107
  const precise = new Intl.NumberFormat('en', { maximumSignificantDigits: 4 });
@@ -102,14 +130,16 @@ const fromZeroScale = (base) => {
102
130
  * `.chart-controls button[aria-pressed]`, and a site's stylesheet gives them
103
131
  * and the box their look; the line and fill are the canvas box's `color`.
104
132
  */
105
- export function TimeChart({ series, label, ranges = typeof series === 'function' ? CHART_RANGES : DAILY_RANGES, range: initial = ranges.includes('30d') ? '30d' : ranges[0], aggregate = 'last', fromZero = false, format = formatChartValue, candles: initialCandles = false, height, controls, className, }) {
133
+ export function TimeChart({ series, label, ranges = typeof series === 'function' ? CHART_RANGES : DAILY_RANGES, range: initial = ranges.includes('30d') ? '30d' : ranges[0], aggregate = 'last', fromZero = false, format = formatChartValue, candles: initialCandles = false, height, controls, markers, step: initialStep, onView, className, }) {
106
134
  const box = useRef(null);
107
135
  const chart = useRef(null);
108
136
  const formatRef = useRef(format);
109
137
  formatRef.current = format;
138
+ const onViewRef = useRef(onView);
139
+ onViewRef.current = onView;
110
140
  const [ready, setReady] = useState(false);
111
141
  const [range, setRange] = useState(initial);
112
- const [step, setStep] = useState(null);
142
+ const [step, setStep] = useState(initialStep ?? null);
113
143
  const [candles, setCandles] = useState(initialCandles);
114
144
  const [loaded, setLoaded] = useState(null);
115
145
  const resolution = RANGE[range].resolution;
@@ -118,7 +148,7 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
118
148
  if (!el)
119
149
  return;
120
150
  let disposed = false;
121
- import('lightweight-charts').then(({ createChart, AreaSeries, CandlestickSeries, HistogramSeries, ColorType, CrosshairMode, LineStyle, LineType }) => {
151
+ import('lightweight-charts').then(({ createChart, createSeriesMarkers, AreaSeries, CandlestickSeries, HistogramSeries, ColorType, CrosshairMode, LineStyle, LineType }) => {
122
152
  if (disposed)
123
153
  return;
124
154
  const style = getComputedStyle(el);
@@ -177,7 +207,11 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
177
207
  api.panes()[1]?.setStretchFactor(1);
178
208
  return volume;
179
209
  };
180
- chart.current = { api, area, candle, volume: null, addVolume, up, down };
210
+ chart.current = {
211
+ api, area, candle, volume: null, addVolume, up, down, ink,
212
+ areaMarkers: createSeriesMarkers(area, []),
213
+ candleMarkers: createSeriesMarkers(candle, []),
214
+ };
181
215
  setReady(true);
182
216
  });
183
217
  return () => {
@@ -259,8 +293,21 @@ export function TimeChart({ series, label, ranges = typeof series === 'function'
259
293
  const rising = p.value >= (p.open ?? shown[i - 1]?.value ?? p.value);
260
294
  return { time: time(p), value: p.volume ?? 0, color: (rising ? c.up : c.down).replace(/[\d.]+\)$/, '0.5)') };
261
295
  }));
296
+ const drawn = placeMarkers(markers ?? [], shown, view?.step ?? null).map(m => ({
297
+ time: time(m),
298
+ position: 'aboveBar',
299
+ shape: 'arrowDown',
300
+ color: c.ink,
301
+ text: m.label,
302
+ }));
303
+ c.areaMarkers.setMarkers(asCandles ? [] : drawn);
304
+ c.candleMarkers.setMarkers(asCandles ? drawn : []);
262
305
  c.api.timeScale().fitContent();
263
- }, [ready, source, view, candles]);
306
+ }, [ready, source, view, candles, markers]);
307
+ const shownStep = view?.step ?? null;
308
+ useEffect(() => {
309
+ onViewRef.current?.({ range, step: shownStep });
310
+ }, [range, shownStep]);
264
311
  // The steps always show where a range is drawn by day, so the buttons do not jump about; one
265
312
  // the range cannot make (any, under 24H and 7D) is disabled.
266
313
  const daily = ranges.some(r => RANGE[r].resolution === 'day');
@@ -43,10 +43,6 @@ export interface ComboboxAria {
43
43
  listProps: ComboboxListProps;
44
44
  optionProps: (index: number) => ComboboxOptionProps;
45
45
  }
46
- /** The id of one option row. Exported because a caller that scrolls the
47
- * highlighted row into view has to find it by the same id the input points at. */
48
- export declare function optionId(baseId: string, listKey: string | undefined, index: number): string;
49
- export declare function listId(baseId: string, listKey?: string): string;
50
46
  /**
51
47
  * Every attribute the three parts of a combobox need, derived from what the
52
48
  * typeahead already knows.
package/dist/combobox.js CHANGED
@@ -12,9 +12,11 @@
12
12
  // Enter does.
13
13
  /** The id of one option row. Exported because a caller that scrolls the
14
14
  * highlighted row into view has to find it by the same id the input points at. */
15
+ /** @internal */
15
16
  export function optionId(baseId, listKey, index) {
16
17
  return `${listId(baseId, listKey)}-opt-${index}`;
17
18
  }
19
+ /** @internal */
18
20
  export function listId(baseId, listKey) {
19
21
  return listKey ? `${baseId}-${listKey}` : `${baseId}-list`;
20
22
  }
@@ -12,15 +12,6 @@ export interface AiCrawler {
12
12
  */
13
13
  fetches: boolean;
14
14
  }
15
- /**
16
- * Order is significant: `detectAiBot` returns the first token the user agent
17
- * contains, so a token that is a substring of another must come after it.
18
- */
19
- export declare const AI_CRAWLERS: readonly AiCrawler[];
20
- /** The event label for a user agent, or null when it is not a known crawler. */
21
- export declare function detectAiBot(userAgent: string | null | undefined): string | null;
22
- /** Every token that needs its own robots.txt group — which is all of them. */
23
- export declare function aiCrawlerTokens(): string[];
24
15
  /**
25
16
  * Structurally what Next's `MetadataRoute.Robots['rules']` wants, without
26
17
  * importing Next: this package has no runtime dependencies and is not going to
@@ -31,21 +22,6 @@ export interface RobotsGroup {
31
22
  allow?: string | string[];
32
23
  disallow?: string | string[];
33
24
  }
34
- /**
35
- * The agent surface an S10 origin serves, allowed in every group. All three
36
- * `robots.ts` files listed these by hand, and a path missing from one list is
37
- * an endpoint the origin advertises and then closes to the callers it named.
38
- */
39
- export declare const AGENT_SURFACE_PATHS: readonly string[];
40
- /**
41
- * The React Server Components payload a `<Link>` prefetches. A crawler that
42
- * renders a page runs those prefetches too — 43% of Googlebot's requests on
43
- * one wiki, against 10% for HTML — and nothing that reads robots.txt can use
44
- * one. Every group gets it.
45
- */
46
- export declare const RSC_DISALLOW: readonly string[];
47
- /** The search engines that get the HTML site and none of its twins. */
48
- export declare const SEARCH_ENGINES: readonly string[];
49
25
  /**
50
26
  * Every group robots.txt needs: the wildcard, one per AI crawler, one per
51
27
  * search engine.
package/dist/crawlers.js CHANGED
@@ -14,6 +14,8 @@
14
14
  /**
15
15
  * Order is significant: `detectAiBot` returns the first token the user agent
16
16
  * contains, so a token that is a substring of another must come after it.
17
+ *
18
+ * @internal
17
19
  */
18
20
  export const AI_CRAWLERS = [
19
21
  { token: 'GPTBot', label: 'GPTBot', fetches: true },
@@ -39,7 +41,7 @@ export const AI_CRAWLERS = [
39
41
  { token: 'DuckAssistBot', label: 'DuckAssistBot', fetches: true },
40
42
  { token: 'Applebot-Extended', label: 'AppleExtended', fetches: false },
41
43
  ];
42
- /** The event label for a user agent, or null when it is not a known crawler. */
44
+ /** The event label for a user agent, or null when it is not a known crawler. @internal */
43
45
  export function detectAiBot(userAgent) {
44
46
  if (!userAgent)
45
47
  return null;
@@ -49,7 +51,7 @@ export function detectAiBot(userAgent) {
49
51
  }
50
52
  return null;
51
53
  }
52
- /** Every token that needs its own robots.txt group — which is all of them. */
54
+ /** Every token that needs its own robots.txt group — which is all of them. @internal */
53
55
  export function aiCrawlerTokens() {
54
56
  return AI_CRAWLERS.map(c => c.token);
55
57
  }
@@ -57,6 +59,8 @@ export function aiCrawlerTokens() {
57
59
  * The agent surface an S10 origin serves, allowed in every group. All three
58
60
  * `robots.ts` files listed these by hand, and a path missing from one list is
59
61
  * an endpoint the origin advertises and then closes to the callers it named.
62
+ *
63
+ * @internal
60
64
  */
61
65
  export const AGENT_SURFACE_PATHS = [
62
66
  '/api/mcp',
@@ -71,9 +75,11 @@ export const AGENT_SURFACE_PATHS = [
71
75
  * renders a page runs those prefetches too — 43% of Googlebot's requests on
72
76
  * one wiki, against 10% for HTML — and nothing that reads robots.txt can use
73
77
  * one. Every group gets it.
78
+ *
79
+ * @internal
74
80
  */
75
81
  export const RSC_DISALLOW = ['/*?_rsc=', '/*&_rsc='];
76
- /** The search engines that get the HTML site and none of its twins. */
82
+ /** The search engines that get the HTML site and none of its twins. @internal */
77
83
  export const SEARCH_ENGINES = ['Googlebot', 'Bingbot'];
78
84
  /**
79
85
  * Every group robots.txt needs: the wildcard, one per AI crawler, one per
package/dist/dom.d.ts CHANGED
@@ -17,11 +17,6 @@ export interface CopyButtonOptions {
17
17
  * Returns the number of buttons added, so a caller can skip work when zero.
18
18
  */
19
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.
23
- */
24
- export declare const TWITTER_ORIGIN = "https://platform.twitter.com";
25
20
  /** The embed iframe's src. `dnt=true` opts the embed out of Twitter's tracking. */
26
21
  export declare const tweetEmbedSrc: (tweetId: string | number) => string;
27
22
  /**
package/dist/dom.js CHANGED
@@ -56,6 +56,8 @@ export function addCopyButtons(root, options = {}) {
56
56
  /**
57
57
  * The one place this origin is written down. It is both the embed host and the
58
58
  * allow-list the resize listener checks.
59
+ *
60
+ * @internal
59
61
  */
60
62
  export const TWITTER_ORIGIN = 'https://platform.twitter.com';
61
63
  /** The embed iframe's src. `dnt=true` opts the embed out of Twitter's tracking. */
package/dist/editor.d.ts CHANGED
@@ -15,19 +15,6 @@ export type { StarterKitOptions } from '@tiptap/starter-kit';
15
15
  export type { LinkOptions } from '@tiptap/extension-link';
16
16
  export type { ImageOptions } from '@tiptap/extension-image';
17
17
  export type { TableOptions } from '@tiptap/extension-table';
18
- /**
19
- * Tiptap's Link, storing an href and the `link` class and nothing else.
20
- *
21
- * Stock Link writes `target="_blank" rel="noopener noreferrer nofollow"` onto
22
- * every link, internal ones included, and the schema then reads both back from
23
- * stored HTML, so a page edited once kept them for good. Nofollow on a site's
24
- * own links throws away its link equity, and a new tab for in-site navigation
25
- * is a bug. Whether a link is external depends on the host it is served from,
26
- * which the editor cannot know, so the render path decides: `normaliseLinks`
27
- * from `wiki-formant/links`. Dropping both attributes from the mark also cleans
28
- * a legacy link the next time its page is saved.
29
- */
30
- export declare const wikiLink: import("@tiptap/core").Mark<import("@tiptap/extension-link").LinkOptions, any>;
31
18
  export interface WikiEditorExtensionOptions {
32
19
  /** Empty-document prompt. */
33
20
  placeholder?: string;
@@ -41,20 +28,6 @@ export interface WikiEditorExtensionOptions {
41
28
  */
42
29
  nodes?: readonly AnyExtension[];
43
30
  }
44
- /**
45
- * Turn a pasted URL into the richest node that fits it, falling back to a bare
46
- * iframe. Order matters: a YouTube URL is also a valid iframe source, so the
47
- * specific cases have to be tried before the general one.
48
- *
49
- * A shortened map link inserts IMMEDIATELY with `about:blank` and swaps its
50
- * `src` once the redirect resolves. Pasting must not block on a network hop,
51
- * and the node has to already exist for the reader to see anything happen. The
52
- * swap re-finds the node by its `url` attribute rather than caching a position,
53
- * because every keystroke between the paste and the resolve moves it.
54
- */
55
- export declare function insertEmbed(editor: Editor, url: string, { resolveMapUrl }: {
56
- resolveMapUrl: (url: string) => Promise<string | null>;
57
- }): void;
58
31
  /** The table controls, shown only while the selection is inside a table. */
59
32
  export declare const TABLE_ACTIONS: ReadonlyArray<[command: string, label: string, danger?: boolean]>;
60
33
  /** `editor.isActive` as a toolbar button's `active` descriptor expresses it. */
@@ -71,13 +44,6 @@ export interface ToolbarAction {
71
44
  run?: (editor: Editor) => void;
72
45
  withUrl?: (editor: Editor, url: string) => void;
73
46
  }
74
- /**
75
- * The formatting commands, labelled. Three toolbars ran these identically and
76
- * two titled their buttons with the internal key — a screen reader said
77
- * "codeBlock", and inline code and code block shared one icon and one name.
78
- * Upload and embed stay with the caller: they are the parts that differ.
79
- */
80
- export declare const TOOLBAR_ACTIONS: readonly ToolbarAction[];
81
47
  /** The actions a toolbar shows, in the order it shows them. */
82
48
  export declare function toolbarActions(keys: readonly ToolbarKey[]): ToolbarAction[];
83
49
  /**
@@ -92,15 +58,6 @@ export declare function ToolbarButton({ label, pressed, onPress, className, chil
92
58
  children: ReactNode;
93
59
  }): import("react").JSX.Element;
94
60
  export type UrlPromptKind = 'link' | 'embed';
95
- /**
96
- * What a link or embed field does with its address. A link sets or replaces
97
- * the link the cursor is in, across the whole link rather than the collapsed
98
- * selection, and an emptied address takes it off while keeping the text. An
99
- * embed with no address does nothing.
100
- */
101
- export declare function applyUrlPrompt(editor: Editor, kind: UrlPromptKind, url: string, { resolveMapUrl }: {
102
- resolveMapUrl: (url: string) => Promise<string | null>;
103
- }): void;
104
61
  export interface UrlPromptState {
105
62
  /** The open field, or null. */
106
63
  kind: UrlPromptKind | null;