wiki-formant 0.48.0 → 0.50.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 +85 -27
  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 +0 -13
  11. package/dist/chart.js +4 -0
  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 +45 -0
  72. package/dist/upload.js +106 -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
@@ -35,19 +35,6 @@ export declare function chartSteps(span: number): {
35
35
  steps: ChartStep[];
36
36
  fallback: ChartStep;
37
37
  };
38
- /**
39
- * Points, oldest first, one per step. A price keeps the last value in each
40
- * step, as a candle does its close: the first open, the highest high, the
41
- * lowest low. A count takes the mean of its days, so the week in progress does
42
- * not read as a collapse. Volume is summed.
43
- */
44
- export declare function bucketChart(points: readonly ChartPoint[], step: ChartStep, aggregate: 'last' | 'mean'): ChartPoint[];
45
- /**
46
- * Markers on the points drawn: each on the last point at or before it, so one
47
- * mid-week sits on its week. Markers on one point share it, labels joined; one
48
- * before the first point or after the last point's step is left off.
49
- */
50
- export declare function placeMarkers(markers: readonly ChartMarker[], shown: readonly ChartPoint[], step: ChartStep | null): ChartMarker[];
51
38
  /** Compact from a thousand up, four significant figures below. */
52
39
  export declare const formatChartValue: (n: number) => string;
53
40
  export interface TimeChartProps {
package/dist/chart.js CHANGED
@@ -50,6 +50,8 @@ function bucketStart(time, step) {
50
50
  * step, as a candle does its close: the first open, the highest high, the
51
51
  * lowest low. A count takes the mean of its days, so the week in progress does
52
52
  * not read as a collapse. Volume is summed.
53
+ *
54
+ * @internal
53
55
  */
54
56
  export function bucketChart(points, step, aggregate) {
55
57
  const out = [];
@@ -79,6 +81,8 @@ export function bucketChart(points, step, aggregate) {
79
81
  * Markers on the points drawn: each on the last point at or before it, so one
80
82
  * mid-week sits on its week. Markers on one point share it, labels joined; one
81
83
  * before the first point or after the last point's step is left off.
84
+ *
85
+ * @internal
82
86
  */
83
87
  export function placeMarkers(markers, shown, step) {
84
88
  const first = shown[0];
@@ -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;
package/dist/editor.js CHANGED
@@ -71,6 +71,8 @@ function cleanPastedHtml(html) {
71
71
  * which the editor cannot know, so the render path decides: `normaliseLinks`
72
72
  * from `wiki-formant/links`. Dropping both attributes from the mark also cleans
73
73
  * a legacy link the next time its page is saved.
74
+ *
75
+ * @internal
74
76
  */
75
77
  export const wikiLink = TiptapLink.extend({
76
78
  addAttributes() {
@@ -117,7 +119,7 @@ const SHORT_MAP = /maps\.app\.goo\.gl|goo\.gl\/maps/;
117
119
  * swap re-finds the node by its `url` attribute rather than caching a position,
118
120
  * because every keystroke between the paste and the resolve moves it.
119
121
  */
120
- export function insertEmbed(editor, url, { resolveMapUrl }) {
122
+ function insertEmbed(editor, url, { resolveMapUrl }) {
121
123
  const yt = url.match(YOUTUBE);
122
124
  if (yt) {
123
125
  editor.chain().focus().setYoutubeVideo({ src: url }).run();
@@ -168,6 +170,8 @@ export function runTableAction(editor, command) {
168
170
  * two titled their buttons with the internal key — a screen reader said
169
171
  * "codeBlock", and inline code and code block shared one icon and one name.
170
172
  * Upload and embed stay with the caller: they are the parts that differ.
173
+ *
174
+ * @internal
171
175
  */
172
176
  export const TOOLBAR_ACTIONS = [
173
177
  { key: 'bold', label: 'Bold', active: 'bold', run: e => e.chain().focus().toggleBold().run() },
@@ -220,6 +224,8 @@ export function ToolbarButton({ label, pressed, onPress, className, children, })
220
224
  * the link the cursor is in, across the whole link rather than the collapsed
221
225
  * selection, and an emptied address takes it off while keeping the text. An
222
226
  * embed with no address does nothing.
227
+ *
228
+ * @internal
223
229
  */
224
230
  export function applyUrlPrompt(editor, kind, url, { resolveMapUrl }) {
225
231
  const href = url.trim();
package/dist/feed.d.ts CHANGED
@@ -1,10 +1,4 @@
1
1
  export declare const escXml: (s: string) => string;
2
- /**
3
- * `content:encoded` carries raw HTML, so it travels inside CDATA. A literal
4
- * `]]>` in the body would close the section early and corrupt every item after
5
- * it — not just the one that contained it.
6
- */
7
- export declare const cdata: (html: string) => string;
8
2
  /**
9
3
  * Aggregators hard-truncate a description around 150 characters. Trimming on a
10
4
  * word boundary first means the cut lands between words rather than inside one.
@@ -45,14 +39,6 @@ export interface FeedChannel {
45
39
  */
46
40
  lastBuild?: Date | null;
47
41
  }
48
- /**
49
- * `lastBuildDate` comes from the newest item, not from the clock.
50
- *
51
- * A feed whose build date moves on every request tells every poller it changed
52
- * when it did not, which is exactly the recrawl the conditional-GET helpers
53
- * exist to prevent. An empty feed has no build date rather than a fictional one.
54
- */
55
- export declare function renderFeed(channel: FeedChannel, items: readonly FeedItem[]): string;
56
42
  /**
57
43
  * The feed as a response, with the validators a poller revalidates against.
58
44
  *
package/dist/feed.js CHANGED
@@ -28,6 +28,8 @@ export const escXml = (s) => s.replace(/[&<>"']/g, c => XML_ESCAPES[c] ?? c);
28
28
  * `content:encoded` carries raw HTML, so it travels inside CDATA. A literal
29
29
  * `]]>` in the body would close the section early and corrupt every item after
30
30
  * it — not just the one that contained it.
31
+ *
32
+ * @internal
31
33
  */
32
34
  export const cdata = (html) => `<![CDATA[${html.replace(/]]>/g, ']]&gt;')}]]>`;
33
35
  /**
@@ -69,6 +71,8 @@ export function renderItem(item) {
69
71
  * A feed whose build date moves on every request tells every poller it changed
70
72
  * when it did not, which is exactly the recrawl the conditional-GET helpers
71
73
  * exist to prevent. An empty feed has no build date rather than a fictional one.
74
+ *
75
+ * @internal
72
76
  */
73
77
  export function renderFeed(channel, items) {
74
78
  const newest = lastBuild(channel, items);
@@ -4,7 +4,6 @@ export interface FreshnessInput {
4
4
  lastVerifiedAt?: Date | string | null;
5
5
  updatedAt?: Date | string | null;
6
6
  }
7
- export declare const DEFAULT_MAX_AGE_DAYS = 180;
8
7
  /** Whole days between `date` and `now`, or null when there is no usable date. */
9
8
  export declare function daysSince(date: Date | string | null | undefined, now: number): number | null;
10
9
  /**
@@ -13,12 +12,6 @@ export declare function daysSince(date: Date | string | null | undefined, now: n
13
12
  * edit is a weaker signal than a verification, but it is a signal.
14
13
  */
15
14
  export declare function isStale(page: FreshnessInput, now: number, maxAgeDays?: number): boolean;
16
- /**
17
- * The sentence the notice carries. Both wikis shipped this string identically,
18
- * down to the ISO date and the closing request, so it lives here rather than
19
- * being re-typed either side of the extraction.
20
- */
21
- export declare function freshnessNotice(page: FreshnessInput): string;
22
15
  /**
23
16
  * A synthetic `outdated` banner block for a stale page, or null when it is
24
17
  * fresh — the shape every wiki's `banner` type already stores, so it drops
package/dist/freshness.js CHANGED
@@ -10,6 +10,7 @@
10
10
  // disagree with the one the server just sent.
11
11
  import { isoDate } from './html.js';
12
12
  export { isoDate };
13
+ /** @internal */
13
14
  export const DEFAULT_MAX_AGE_DAYS = 180;
14
15
  const DAY_MS = 86_400_000;
15
16
  /** Whole days between `date` and `now`, or null when there is no usable date. */
@@ -34,6 +35,8 @@ export function isStale(page, now, maxAgeDays = DEFAULT_MAX_AGE_DAYS) {
34
35
  * The sentence the notice carries. Both wikis shipped this string identically,
35
36
  * down to the ISO date and the closing request, so it lives here rather than
36
37
  * being re-typed either side of the extraction.
38
+ *
39
+ * @internal
37
40
  */
38
41
  export function freshnessNotice(page) {
39
42
  const when = page.lastVerifiedAt
package/dist/headers.d.ts CHANGED
@@ -9,16 +9,8 @@ export interface HeaderEntry {
9
9
  * embed that clears one clears the other.
10
10
  */
11
11
  export declare const DEFAULT_IFRAME_HOSTS: readonly string[];
12
- /** `frame-src` sources for a host list — the one a sanitiser was given. */
13
- export declare const frameSources: (hosts?: readonly string[]) => string[];
14
12
  /** CSP directive names this builder writes, in the order it writes them. */
15
13
  export type CspDirective = 'default-src' | 'base-uri' | 'object-src' | 'frame-ancestors' | 'form-action' | 'script-src' | 'style-src' | 'font-src' | 'img-src' | 'media-src' | 'connect-src' | 'frame-src' | 'worker-src' | 'manifest-src';
16
- /**
17
- * The policy all four origins shared line for line. Everything a site adds is
18
- * a source list it hands `contentSecurityPolicy`, which replaces the default
19
- * for that directive rather than appending, so a reader sees the whole list.
20
- */
21
- export declare const BASE_CSP: Readonly<Partial<Record<CspDirective, readonly string[]>>>;
22
14
  /**
23
15
  * The policy string: `BASE_CSP` with each directive in `directives` replacing
24
16
  * its default. An empty list drops the directive.