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.
- package/README.md +85 -27
- package/bin/maintenance-log.mjs +198 -0
- package/bin/mark-verified.mjs +75 -0
- package/bin/prisma-models.mjs +53 -0
- package/dist/analytics.d.ts +0 -25
- package/dist/analytics.js +10 -49
- package/dist/base.css +23 -2
- package/dist/blocks.d.ts +7 -6
- package/dist/blocks.js +11 -3
- package/dist/chart.d.ts +0 -13
- package/dist/chart.js +4 -0
- package/dist/combobox.d.ts +0 -4
- package/dist/combobox.js +2 -0
- package/dist/crawlers.d.ts +0 -24
- package/dist/crawlers.js +9 -3
- package/dist/dom.d.ts +0 -5
- package/dist/dom.js +2 -0
- package/dist/editor.d.ts +0 -43
- package/dist/editor.js +7 -1
- package/dist/feed.d.ts +0 -14
- package/dist/feed.js +4 -0
- package/dist/freshness.d.ts +0 -7
- package/dist/freshness.js +3 -0
- package/dist/headers.d.ts +0 -8
- package/dist/headers.js +3 -1
- package/dist/headings.d.ts +0 -8
- package/dist/headings.js +2 -0
- package/dist/history.d.ts +0 -6
- package/dist/history.js +2 -0
- package/dist/http.d.ts +0 -11
- package/dist/http.js +5 -0
- package/dist/license.d.ts +0 -8
- package/dist/license.js +2 -0
- package/dist/link-check.d.ts +0 -21
- package/dist/link-check.js +6 -2
- package/dist/links.d.ts +0 -5
- package/dist/links.js +2 -0
- package/dist/maintenance.d.ts +61 -0
- package/dist/maintenance.js +137 -0
- package/dist/maps.d.ts +0 -6
- package/dist/maps.js +2 -0
- package/dist/markdown.d.ts +0 -2
- package/dist/markdown.js +1 -1
- package/dist/mcp.d.ts +0 -14
- package/dist/mcp.js +1 -1
- package/dist/metadata.d.ts +14 -11
- package/dist/metadata.js +33 -0
- package/dist/pagination.d.ts +0 -5
- package/dist/pagination.js +0 -4
- package/dist/passkey.js +2 -15
- package/dist/rate-limit.d.ts +5 -16
- package/dist/rate-limit.js +10 -1
- package/dist/react-server.d.ts +0 -12
- package/dist/react-server.js +2 -0
- package/dist/revisions.d.ts +0 -7
- package/dist/revisions.js +2 -0
- package/dist/rola.d.ts +74 -0
- package/dist/rola.js +97 -0
- package/dist/search.d.ts +0 -12
- package/dist/search.js +4 -0
- package/dist/sidebar.d.ts +0 -16
- package/dist/sidebar.js +2 -0
- package/dist/stats.d.ts +0 -4
- package/dist/stats.js +2 -2
- package/dist/taxonomy.d.ts +0 -4
- package/dist/taxonomy.js +2 -2
- package/dist/text.d.ts +16 -22
- package/dist/text.js +39 -5
- package/dist/tiptap.d.ts +5 -4
- package/dist/tiptap.js +2 -1
- package/dist/upload.d.ts +45 -0
- package/dist/upload.js +106 -0
- package/dist/validation.d.ts +0 -6
- package/dist/validation.js +3 -3
- package/dist/well-known.d.ts +0 -20
- package/dist/well-known.js +3 -0
- package/package.json +37 -8
- package/prisma/analytics.prisma +57 -0
- package/prisma/passkey.prisma +21 -0
package/dist/analytics.d.ts
CHANGED
|
@@ -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
|
|
18
|
-
//
|
|
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];
|
package/dist/combobox.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/crawlers.d.ts
CHANGED
|
@@ -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
|
-
|
|
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, ']]>')}]]>`;
|
|
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);
|
package/dist/freshness.d.ts
CHANGED
|
@@ -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.
|