wiki-formant 0.13.0 → 0.15.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 +12 -25
- package/bin/check-classes.mjs +2 -3
- package/dist/analytics.d.ts +0 -1
- package/dist/analytics.js +0 -1
- package/dist/block-views.d.ts +97 -0
- package/dist/block-views.js +119 -0
- package/dist/blocks.d.ts +0 -1
- package/dist/blocks.js +1 -2
- package/dist/combobox.d.ts +0 -1
- package/dist/combobox.js +0 -1
- package/dist/conformance.d.ts +0 -3
- package/dist/conformance.js +40 -38
- package/dist/crawlers.d.ts +0 -1
- package/dist/crawlers.js +0 -1
- package/dist/dom.d.ts +0 -1
- package/dist/dom.js +0 -1
- package/dist/editor.d.ts +101 -0
- package/dist/editor.js +218 -0
- package/dist/feed.d.ts +0 -1
- package/dist/feed.js +0 -1
- package/dist/freshness.d.ts +0 -1
- package/dist/freshness.js +2 -2
- package/dist/headings.d.ts +0 -1
- package/dist/headings.js +1 -3
- package/dist/html.d.ts +16 -0
- package/dist/html.js +23 -0
- package/dist/http.d.ts +20 -1
- package/dist/http.js +23 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/license.d.ts +0 -1
- package/dist/license.js +0 -1
- package/dist/link-check.d.ts +92 -0
- package/dist/link-check.js +249 -0
- package/dist/links.d.ts +0 -1
- package/dist/links.js +1 -6
- package/dist/maps.d.ts +0 -1
- package/dist/maps.js +2 -2
- package/dist/markdown.d.ts +1 -1
- package/dist/markdown.js +34 -3
- package/dist/mcp.d.ts +17 -7
- package/dist/mcp.js +8 -8
- package/dist/pagination.d.ts +19 -1
- package/dist/pagination.js +21 -1
- package/dist/rate-limit.d.ts +18 -1
- package/dist/rate-limit.js +22 -1
- package/dist/react-server.d.ts +91 -9
- package/dist/react-server.js +68 -16
- package/dist/react.d.ts +37 -48
- package/dist/react.js +44 -23
- package/dist/revisions.d.ts +0 -1
- package/dist/revisions.js +0 -1
- package/dist/rola.d.ts +0 -1
- package/dist/rola.js +0 -1
- package/dist/seeded.d.ts +11 -0
- package/dist/seeded.js +32 -0
- package/dist/sidebar.d.ts +0 -1
- package/dist/sidebar.js +0 -1
- package/dist/taxonomy.d.ts +0 -1
- package/dist/taxonomy.js +0 -1
- package/dist/text.d.ts +14 -1
- package/dist/text.js +15 -2
- package/dist/tiptap.d.ts +1 -1
- package/dist/tiptap.js +1 -2
- package/dist/validation.d.ts +2 -3
- package/dist/validation.js +4 -5
- package/dist/versioning.d.ts +0 -1
- package/dist/versioning.js +0 -1
- package/dist/well-known.d.ts +19 -2
- package/dist/well-known.js +28 -2
- package/dist/x402.d.ts +2 -11
- package/dist/x402.js +4 -16
- package/package.json +52 -27
- package/dist/analytics.d.ts.map +0 -1
- package/dist/analytics.js.map +0 -1
- package/dist/blocks.d.ts.map +0 -1
- package/dist/blocks.js.map +0 -1
- package/dist/combobox.d.ts.map +0 -1
- package/dist/combobox.js.map +0 -1
- package/dist/conformance.d.ts.map +0 -1
- package/dist/conformance.js.map +0 -1
- package/dist/crawlers.d.ts.map +0 -1
- package/dist/crawlers.js.map +0 -1
- package/dist/dom.d.ts.map +0 -1
- package/dist/dom.js.map +0 -1
- package/dist/entities.d.ts +0 -3
- package/dist/entities.d.ts.map +0 -1
- package/dist/entities.js +0 -30
- package/dist/entities.js.map +0 -1
- package/dist/feed.d.ts.map +0 -1
- package/dist/feed.js.map +0 -1
- package/dist/freshness.d.ts.map +0 -1
- package/dist/freshness.js.map +0 -1
- package/dist/headings.d.ts.map +0 -1
- package/dist/headings.js.map +0 -1
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/license.d.ts.map +0 -1
- package/dist/license.js.map +0 -1
- package/dist/links.d.ts.map +0 -1
- package/dist/links.js.map +0 -1
- package/dist/maps.d.ts.map +0 -1
- package/dist/maps.js.map +0 -1
- package/dist/markdown.d.ts.map +0 -1
- package/dist/markdown.js.map +0 -1
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/pagination.d.ts.map +0 -1
- package/dist/pagination.js.map +0 -1
- package/dist/rate-limit.d.ts.map +0 -1
- package/dist/rate-limit.js.map +0 -1
- package/dist/react-server.d.ts.map +0 -1
- package/dist/react-server.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/revisions.d.ts.map +0 -1
- package/dist/revisions.js.map +0 -1
- package/dist/rola.d.ts.map +0 -1
- package/dist/rola.js.map +0 -1
- package/dist/sidebar.d.ts.map +0 -1
- package/dist/sidebar.js.map +0 -1
- package/dist/taxonomy.d.ts.map +0 -1
- package/dist/taxonomy.js.map +0 -1
- package/dist/text.d.ts.map +0 -1
- package/dist/text.js.map +0 -1
- package/dist/tiptap.d.ts.map +0 -1
- package/dist/tiptap.js.map +0 -1
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/versioning.d.ts.map +0 -1
- package/dist/versioning.js.map +0 -1
- package/dist/well-known.d.ts.map +0 -1
- package/dist/well-known.js.map +0 -1
- package/dist/x402.d.ts.map +0 -1
- package/dist/x402.js.map +0 -1
package/README.md
CHANGED
|
@@ -264,7 +264,9 @@ are unit-tested without a DOM.
|
|
|
264
264
|
|
|
265
265
|
`FacetBar` renders the rows `createTaxonomy` already produces. This is why the taxonomy exports a rows model rather than markup: the rows could always cross the boundary and, until this subpath existed, the markup could not, so all three wikis hand-rendered it and two put `aria-pressed` on an `<a>`. `Breadcrumbs` renders the trail and its `BreadcrumbList` JSON-LD together, because a trail whose structured data is written somewhere else is a trail that will one day disagree with its own markup — which is the case Google penalises. It takes a `base` origin: structured-data URLs must be absolute and a package cannot know the site.
|
|
266
266
|
|
|
267
|
-
`
|
|
267
|
+
`PageNav` is the previous/next pair at the foot of an article — the sequential read the infobox rail's lateral links do not cover. Ordering is the caller's, because it is the one part that is never portable: a wiki's sequence is its section's configured sort, a knowledge base's is a taxonomy walk. Pair it with `adjacentPages` from `wiki-formant/pagination` over a list you already hold — neither wiki needs a query for it, and the two indexed lookups the neighbours used to cost were the reason one of them dropped the control.
|
|
268
|
+
|
|
269
|
+
`RailShell` stays in `wiki-formant/react`, because it calls `useSidebar` and genuinely is a client component. It renders the rail's landmark, its scroll wrapper and the three collapse states — including the `--instant` class that keeps a remembered-closed rail from animating shut on first paint, and the close-on-tap that a mobile rail needs. Compose your own rail inside it and mark the active link with `isRailLinkActive`; all three wikis do, and the pre-composed component that used to sit here had one consumer and had already lost both of those behaviours.
|
|
268
270
|
|
|
269
271
|
All three take the router's link component as a prop:
|
|
270
272
|
|
|
@@ -419,30 +421,15 @@ they name a gap in the corpus in the reader's own words.
|
|
|
419
421
|
|
|
420
422
|
## API
|
|
421
423
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
| `parseVersion`, `formatVersion`, `incrementVersion`, `bump`, `compareVersions` | `wiki-formant/versioning` |
|
|
432
|
-
| `plausibleEvent`, `mcpCallProps`, `searchQueryProps`, `plausibleDomain` | `wiki-formant/analytics` |
|
|
433
|
-
| `comboboxAria`, `listId`, `optionId` | `wiki-formant/combobox` |
|
|
434
|
-
| `computeRevisionDiff`, `diffBlocks`, `extractBlocks`, `classifyChanges`, `changeSummary` | `wiki-formant/revisions` |
|
|
435
|
-
| `renderFeed`, `renderItem`, `escXml`, `cdata`, `clampWords`, `absolutise`, `FEED_HEADERS` | `wiki-formant/feed` |
|
|
436
|
-
| `ccBy40`, `licenseBlock`, `licenseLines`, `licenseNote` | `wiki-formant/license` |
|
|
437
|
-
| `AI_CRAWLERS`, `detectAiBot`, `aiCrawlerTokens`, `aiCrawlerRules` | `wiki-formant/crawlers` |
|
|
438
|
-
| `useCollapsibleSidebar`, `SidebarProvider`, `useSidebar`, `TableOfContents`, `useTypeahead`, `useLinkPreview`, `useClickOutside`, `useTableSort`, `useCopy`, `ErrorBoundary`, `WikiRail` | `wiki-formant/react` |
|
|
439
|
-
| `FacetBar`, `Breadcrumbs` | `wiki-formant/react-server` |
|
|
440
|
-
| `gatePaidCalls` | `wiki-formant/x402` |
|
|
441
|
-
| `resolveSidebarOpen`, `sidebarBootScript`, `SIDEBAR_ATTRIBUTE` | `wiki-formant/sidebar` |
|
|
442
|
-
| `addCopyButtons`, `activateTabGroups`, `tweetEmbedSrc`, `onTweetResize`, `hydrateTweetEmbeds`, `sizeTweetEmbeds`, `TWITTER_ORIGIN` | `wiki-formant/dom` |
|
|
443
|
-
| `Iframe`, `YouTube`, `TwitterEmbed`, `createMapEmbed`, `createCodeBlock`, `createTabs` | `wiki-formant/tiptap` |
|
|
444
|
-
|
|
445
|
-
Everything above `wiki-formant/react` is also re-exported from the package root. The React, sidebar, DOM, tiptap, react-server and x402 subpaths are not: they carry `'use client'`, reach for a browser global, or need a peer the root must not assume, and the root has to stay importable from a route handler.
|
|
424
|
+
Every module has a subpath — `wiki-formant/taxonomy`, `wiki-formant/mcp`, and so on.
|
|
425
|
+
The emitted `.d.ts` files are the reference. There is no hand-maintained symbol list
|
|
426
|
+
here, because the one that used to be here drifted from them.
|
|
427
|
+
|
|
428
|
+
The package root re-exports the runtime modules that need no peer dependency and no
|
|
429
|
+
client boundary, so `import … from 'wiki-formant'` stays importable from a route
|
|
430
|
+
handler with nothing else installed. Everything that reaches for React, a browser
|
|
431
|
+
global, tiptap or a wallet — and the tooling modules, which no route imports — is
|
|
432
|
+
subpath-only.
|
|
446
433
|
|
|
447
434
|
## A bin
|
|
448
435
|
|
package/bin/check-classes.mjs
CHANGED
|
@@ -113,7 +113,6 @@ for (const [tok, at] of [...dead].sort((a, b) => b[1].length - a[1].length)) {
|
|
|
113
113
|
console.error(` ${tok.padEnd(24)} ${String(at.length).padStart(3)}x ${at.slice(0, 4).join(', ')}${at.length > 4 ? ', …' : ''}`);
|
|
114
114
|
}
|
|
115
115
|
// Derived, not hardcoded: this runs in every sibling project and their
|
|
116
|
-
// globals.css does not sit at the same path.
|
|
117
|
-
|
|
118
|
-
console.error(`\nEither define it in ${globals} or delete the usage.`);
|
|
116
|
+
// globals.css does not sit at the same path. Found once, above.
|
|
117
|
+
console.error(`\nEither define it in ${globalsFile ?? 'your globals.css'} or delete the usage.`);
|
|
119
118
|
process.exit(WARN_ONLY ? 0 : 1);
|
package/dist/analytics.d.ts
CHANGED
|
@@ -62,4 +62,3 @@ export interface SearchQueryInput {
|
|
|
62
62
|
export declare function searchQueryProps({ query, results, surface, }: SearchQueryInput): Record<string, string> | null;
|
|
63
63
|
/** The hostname of a site URL, for the `domain` a Plausible property is filed under. */
|
|
64
64
|
export declare function plausibleDomain(siteUrl: string | undefined, fallback: string): string;
|
|
65
|
-
//# sourceMappingURL=analytics.d.ts.map
|
package/dist/analytics.js
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { CodeTab, LinkGridGroup, ReferenceItem, StatItem } from './blocks.js';
|
|
3
|
+
import type { WikiLinkComponent } from './react-server.js';
|
|
4
|
+
/**
|
|
5
|
+
* Tabbed code samples. Every tab's body stays mounted and the inactive ones are
|
|
6
|
+
* hidden rather than unmounted: the panels carry pre-highlighted markup, and
|
|
7
|
+
* remounting one would re-run whatever the consumer's highlighter attached to
|
|
8
|
+
* it on every tab press.
|
|
9
|
+
*/
|
|
10
|
+
export declare function CodeTabsView({ tabs }: {
|
|
11
|
+
tabs: readonly CodeTab[];
|
|
12
|
+
}): import("react").JSX.Element | null;
|
|
13
|
+
export interface ColumnsViewProps<B extends {
|
|
14
|
+
id: string;
|
|
15
|
+
}> {
|
|
16
|
+
columns: ReadonlyArray<{
|
|
17
|
+
id: string;
|
|
18
|
+
blocks?: readonly B[];
|
|
19
|
+
}>;
|
|
20
|
+
gap?: 'sm' | 'md' | 'lg';
|
|
21
|
+
align?: 'start' | 'center' | 'end' | 'stretch';
|
|
22
|
+
/** The caller's own dispatch, because the block union is the caller's. */
|
|
23
|
+
render: (block: B) => ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A row of columns, each holding blocks the caller renders.
|
|
27
|
+
*
|
|
28
|
+
* Children are wrapped in a `Fragment`, not a `<div>`. One repo used a div and
|
|
29
|
+
* it was a layout bug rather than a style: `.column-view` is the flow context
|
|
30
|
+
* its stylesheet spaces children in, and an extra element between them means
|
|
31
|
+
* every gap rule inside a column silently matches nothing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ColumnsView<B extends {
|
|
34
|
+
id: string;
|
|
35
|
+
}>({ columns, gap, align, render, }: ColumnsViewProps<B>): import("react").JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* Banded groups of link pills.
|
|
38
|
+
*
|
|
39
|
+
* A link whose href fails `safeLinkHref` is DROPPED, not rendered inert. These
|
|
40
|
+
* hrefs are author-supplied, and a `javascript:` URL fails the http test above
|
|
41
|
+
* — so without the guard it falls through to the router's link component, which
|
|
42
|
+
* renders it looking like an ordinary pill and executes it on click.
|
|
43
|
+
*/
|
|
44
|
+
export declare function LinkGridView({ groups, intro, link: Link, }: {
|
|
45
|
+
groups?: readonly (LinkGridGroup & {
|
|
46
|
+
id?: string;
|
|
47
|
+
})[];
|
|
48
|
+
intro?: string;
|
|
49
|
+
link?: WikiLinkComponent;
|
|
50
|
+
}): import("react").JSX.Element;
|
|
51
|
+
/**
|
|
52
|
+
* The numbered citation list, with the `#cite-n` back-links the inline markers
|
|
53
|
+
* point at. `processHtml` is a prop because only one wiki runs reference text
|
|
54
|
+
* through its HTML pipeline, and running it in the other would rewrite anchors
|
|
55
|
+
* that were minted under different rules.
|
|
56
|
+
*/
|
|
57
|
+
export declare function ReferencesView({ items, title, processHtml, }: {
|
|
58
|
+
items?: readonly (ReferenceItem & {
|
|
59
|
+
id?: string;
|
|
60
|
+
})[];
|
|
61
|
+
title?: string;
|
|
62
|
+
processHtml?: (html: string) => string;
|
|
63
|
+
}): import("react").JSX.Element | null;
|
|
64
|
+
/**
|
|
65
|
+
* A row of measured figures.
|
|
66
|
+
*
|
|
67
|
+
* The column count is a class (`stat-grid-4`), never an inline
|
|
68
|
+
* `gridTemplateColumns`. One repo did it inline, which put a layout decision
|
|
69
|
+
* outside the stylesheet that owns every other one — invisible to the dead-class
|
|
70
|
+
* check, unreachable from a media query, and so a four-up row that could not
|
|
71
|
+
* become a two-up row on a phone.
|
|
72
|
+
*/
|
|
73
|
+
export declare function StatsView({ items, columns, }: {
|
|
74
|
+
items?: readonly (StatItem & {
|
|
75
|
+
id?: string;
|
|
76
|
+
})[];
|
|
77
|
+
columns?: number;
|
|
78
|
+
}): import("react").JSX.Element | null;
|
|
79
|
+
/**
|
|
80
|
+
* A maintenance notice.
|
|
81
|
+
*
|
|
82
|
+
* The label and the fallback message arrive as `meta` because they are this
|
|
83
|
+
* wiki's editorial voice — "You can help RADIX Wiki by expanding it" has a name
|
|
84
|
+
* in it. The markup, the `role="note"` and the variant class are shared, and
|
|
85
|
+
* were identical in both repos. `icon` is optional: one wiki sets one, and a
|
|
86
|
+
* component library that hardcoded an icon set would make its consumers install
|
|
87
|
+
* that icon set.
|
|
88
|
+
*/
|
|
89
|
+
export declare function BannerView({ variant, text, meta, icon, }: {
|
|
90
|
+
variant: string;
|
|
91
|
+
text?: string | null;
|
|
92
|
+
meta: {
|
|
93
|
+
label: string;
|
|
94
|
+
message: string;
|
|
95
|
+
};
|
|
96
|
+
icon?: ReactNode;
|
|
97
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// block-views.tsx — the block renderers both wikis had written twice.
|
|
4
|
+
//
|
|
5
|
+
// Behind its own subpath for the reason `react.tsx` is: React is an OPTIONAL
|
|
6
|
+
// peer, so a consumer that only wants the taxonomy or the MCP transport still
|
|
7
|
+
// installs a package with no runtime dependencies.
|
|
8
|
+
//
|
|
9
|
+
// WHY THESE FIVE AND NOT THE SWITCH. The block union is per-repo and staying
|
|
10
|
+
// that way (see the Block Model note in the workspace CLAUDE.md) — a closed
|
|
11
|
+
// union is what makes `switch (block.type)` exhaustive, so a new block type is
|
|
12
|
+
// a compile error rather than a silent blank. What is NOT per-repo is what a
|
|
13
|
+
// codeTabs or a linkGrid LOOKS like once you have dispatched to it: those were
|
|
14
|
+
// byte-identical in both repos, down to the class names. So the dispatch stays
|
|
15
|
+
// with the caller and the leaves move here.
|
|
16
|
+
//
|
|
17
|
+
// The class names are NOT props. Like the rail's `wiki-rail__*` tree they are
|
|
18
|
+
// the shared convention both stylesheets already implement, and making them
|
|
19
|
+
// configurable would only let that convention fork again. Everything that
|
|
20
|
+
// genuinely differs — the prose of a banner, whether references run through an
|
|
21
|
+
// HTML processor, the router's link — arrives as a prop.
|
|
22
|
+
import { Fragment, useState } from 'react';
|
|
23
|
+
import { Anchor } from './react-server.js';
|
|
24
|
+
import { safeLinkHref } from './validation.js';
|
|
25
|
+
import { cx } from './html.js';
|
|
26
|
+
// ---- codeTabs ---------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Tabbed code samples. Every tab's body stays mounted and the inactive ones are
|
|
29
|
+
* hidden rather than unmounted: the panels carry pre-highlighted markup, and
|
|
30
|
+
* remounting one would re-run whatever the consumer's highlighter attached to
|
|
31
|
+
* it on every tab press.
|
|
32
|
+
*/
|
|
33
|
+
export function CodeTabsView({ tabs }) {
|
|
34
|
+
const [activeTab, setActiveTab] = useState(0);
|
|
35
|
+
if (!tabs.length)
|
|
36
|
+
return null;
|
|
37
|
+
return (_jsxs("div", { className: "code-tabs", children: [_jsx("div", { className: "code-tabs-list", children: tabs.map((tab, i) => (_jsx("button", { type: "button", className: cx('code-tabs-btn', i === activeTab && 'code-tabs-btn-active'), onClick: () => setActiveTab(i), children: tab.label }, i))) }), tabs.map((tab, i) => (_jsx("div", { className: i === activeTab ? 'block' : 'hidden', dangerouslySetInnerHTML: { __html: tab.code } }, i)))] }));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A row of columns, each holding blocks the caller renders.
|
|
41
|
+
*
|
|
42
|
+
* Children are wrapped in a `Fragment`, not a `<div>`. One repo used a div and
|
|
43
|
+
* it was a layout bug rather than a style: `.column-view` is the flow context
|
|
44
|
+
* its stylesheet spaces children in, and an extra element between them means
|
|
45
|
+
* every gap rule inside a column silently matches nothing.
|
|
46
|
+
*/
|
|
47
|
+
export function ColumnsView({ columns, gap = 'md', align = 'start', render, }) {
|
|
48
|
+
const gapClass = { sm: 'gap-2', md: 'gap-4', lg: 'gap-6' }[gap];
|
|
49
|
+
const alignClass = {
|
|
50
|
+
start: 'items-start',
|
|
51
|
+
center: 'items-center',
|
|
52
|
+
end: 'items-end',
|
|
53
|
+
stretch: 'items-stretch',
|
|
54
|
+
}[align];
|
|
55
|
+
return (_jsx("div", { className: cx('columns-layout', gapClass, alignClass), children: columns.map(col => (_jsx("div", { className: "column-view", children: (col.blocks ?? []).map(bl => (_jsx(Fragment, { children: render(bl) }, bl.id))) }, col.id))) }));
|
|
56
|
+
}
|
|
57
|
+
// ---- linkGrid ---------------------------------------------------------------
|
|
58
|
+
/**
|
|
59
|
+
* Banded groups of link pills.
|
|
60
|
+
*
|
|
61
|
+
* A link whose href fails `safeLinkHref` is DROPPED, not rendered inert. These
|
|
62
|
+
* hrefs are author-supplied, and a `javascript:` URL fails the http test above
|
|
63
|
+
* — so without the guard it falls through to the router's link component, which
|
|
64
|
+
* renders it looking like an ordinary pill and executes it on click.
|
|
65
|
+
*/
|
|
66
|
+
export function LinkGridView({ groups, intro, link: Link = Anchor, }) {
|
|
67
|
+
return (_jsxs("div", { className: "link-grid", children: [intro && _jsx("p", { children: intro }), (groups ?? []).map((group, gi) => (_jsxs("section", { className: "link-grid-group", children: [_jsx("h3", { children: group.heading }), group.description && (_jsx("div", { className: "link-grid-group-description", dangerouslySetInnerHTML: { __html: group.description } })), _jsx("div", { className: "link-grid-pills", children: (group.links ?? []).map((item, i) => {
|
|
68
|
+
const href = safeLinkHref(item.href);
|
|
69
|
+
if (!href)
|
|
70
|
+
return null;
|
|
71
|
+
return /^https?:\/\//.test(href) ? (_jsx("a", { href: href, target: "_blank", rel: "noopener", children: item.label }, i)) : (_jsx(Link, { href: href, children: item.label }, i));
|
|
72
|
+
}) })] }, group.id ?? gi)))] }));
|
|
73
|
+
}
|
|
74
|
+
// ---- references -------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* The numbered citation list, with the `#cite-n` back-links the inline markers
|
|
77
|
+
* point at. `processHtml` is a prop because only one wiki runs reference text
|
|
78
|
+
* through its HTML pipeline, and running it in the other would rewrite anchors
|
|
79
|
+
* that were minted under different rules.
|
|
80
|
+
*/
|
|
81
|
+
export function ReferencesView({ items, title = 'References', processHtml = (html) => html, }) {
|
|
82
|
+
const rows = items ?? [];
|
|
83
|
+
if (!rows.length)
|
|
84
|
+
return null;
|
|
85
|
+
return (_jsxs("section", { className: "references-block", "aria-labelledby": "references-heading", children: [_jsx("h2", { id: "references-heading", children: title }), _jsx("ol", { className: "references-list", children: rows.map((item, i) => {
|
|
86
|
+
const url = safeLinkHref(item.url);
|
|
87
|
+
return (_jsxs("li", { id: `ref-${i + 1}`, className: "reference-item", children: [_jsx("a", { href: `#cite-${i + 1}`, className: "ref-backlink", "aria-label": "Back to citation", children: "\u2191" }), ' ', _jsx("span", { dangerouslySetInnerHTML: { __html: processHtml(item.text) } }), url && (_jsxs(_Fragment, { children: [' ', _jsx("a", { href: url, target: "_blank", rel: "noopener", className: "reference-link", "aria-label": "Open source", children: "\u2197" })] }))] }, item.id ?? i));
|
|
88
|
+
}) })] }));
|
|
89
|
+
}
|
|
90
|
+
// ---- stats ------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* A row of measured figures.
|
|
93
|
+
*
|
|
94
|
+
* The column count is a class (`stat-grid-4`), never an inline
|
|
95
|
+
* `gridTemplateColumns`. One repo did it inline, which put a layout decision
|
|
96
|
+
* outside the stylesheet that owns every other one — invisible to the dead-class
|
|
97
|
+
* check, unreachable from a media query, and so a four-up row that could not
|
|
98
|
+
* become a two-up row on a phone.
|
|
99
|
+
*/
|
|
100
|
+
export function StatsView({ items, columns = 4, }) {
|
|
101
|
+
const rows = items ?? [];
|
|
102
|
+
if (!rows.length)
|
|
103
|
+
return null;
|
|
104
|
+
return (_jsx("div", { className: cx('stat-grid', `stat-grid-${columns}`), children: rows.map((item, i) => (_jsxs("div", { className: "stat-card", children: [_jsxs("span", { className: "stat-value", children: [item.value, item.suffix && _jsx("span", { className: "stat-suffix", children: item.suffix })] }), _jsx("span", { className: "stat-label", children: item.label })] }, item.id ?? i))) }));
|
|
105
|
+
}
|
|
106
|
+
// ---- banner -----------------------------------------------------------------
|
|
107
|
+
/**
|
|
108
|
+
* A maintenance notice.
|
|
109
|
+
*
|
|
110
|
+
* The label and the fallback message arrive as `meta` because they are this
|
|
111
|
+
* wiki's editorial voice — "You can help RADIX Wiki by expanding it" has a name
|
|
112
|
+
* in it. The markup, the `role="note"` and the variant class are shared, and
|
|
113
|
+
* were identical in both repos. `icon` is optional: one wiki sets one, and a
|
|
114
|
+
* component library that hardcoded an icon set would make its consumers install
|
|
115
|
+
* that icon set.
|
|
116
|
+
*/
|
|
117
|
+
export function BannerView({ variant, text, meta, icon, }) {
|
|
118
|
+
return (_jsxs("div", { className: cx('editorial-banner', `editorial-banner-${variant}`), role: "note", children: [icon, _jsxs("p", { className: "editorial-banner-body", children: [_jsxs("strong", { children: [meta.label, "."] }), " ", text?.trim() || meta.message] })] }));
|
|
119
|
+
}
|
package/dist/blocks.d.ts
CHANGED
|
@@ -94,4 +94,3 @@ export declare function statsToMarkdown(items: readonly StatItem[]): string;
|
|
|
94
94
|
export declare function linkGridToMarkdown(groups: readonly LinkGridGroup[], intro?: string | null): string;
|
|
95
95
|
/** A flat bullet list of links — resolved page lists, feed items, link rails. */
|
|
96
96
|
export declare function linkList(items: readonly LinkGridLink[]): string;
|
|
97
|
-
//# sourceMappingURL=blocks.d.ts.map
|
package/dist/blocks.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// new block type is a compile error until it is handled) and the bodies come
|
|
11
11
|
// from here. Each renderer takes the block's DATA rather than the block, which
|
|
12
12
|
// keeps this file free of any one repo's type union.
|
|
13
|
-
import { decodeEntities } from './
|
|
13
|
+
import { decodeEntities } from './markdown.js';
|
|
14
14
|
import { htmlToMarkdown, inlineToMarkdown } from './markdown.js';
|
|
15
15
|
/**
|
|
16
16
|
* The whole tree as markdown. Containers flatten in document order, because a
|
|
@@ -121,4 +121,3 @@ export function linkGridToMarkdown(groups, intro) {
|
|
|
121
121
|
export function linkList(items) {
|
|
122
122
|
return items.map(l => `- [${l.label}](${l.href})`).join('\n');
|
|
123
123
|
}
|
|
124
|
-
//# sourceMappingURL=blocks.js.map
|
package/dist/combobox.d.ts
CHANGED
package/dist/combobox.js
CHANGED
package/dist/conformance.d.ts
CHANGED
|
@@ -91,8 +91,6 @@ export declare function transportChecks(t: Tester, clientName: string,
|
|
|
91
91
|
* business, so it is a parameter rather than one surface's set hardcoded here.
|
|
92
92
|
*/
|
|
93
93
|
expectedCapabilities?: readonly string[]): Promise<Rpc>;
|
|
94
|
-
/** The newest protocol revision `wiki-formant/mcp` speaks. */
|
|
95
|
-
export declare const CURRENT_PROTOCOL = "2025-06-18";
|
|
96
94
|
/**
|
|
97
95
|
* One service, many descriptors — server.json, the two agent-card paths, the
|
|
98
96
|
* OpenAPI document, the MCP server card, and `initialize` — should never
|
|
@@ -161,4 +159,3 @@ export declare function payloadBudget(t: Tester, calls: ReadonlyArray<{
|
|
|
161
159
|
* every unnamed crawler is telling two different stories to the same caller.
|
|
162
160
|
*/
|
|
163
161
|
export declare function robotsChecks(t: Tester, paths: readonly string[]): Promise<void>;
|
|
164
|
-
//# sourceMappingURL=conformance.d.ts.map
|
package/dist/conformance.js
CHANGED
|
@@ -11,6 +11,31 @@
|
|
|
11
11
|
// same package — so the suite that checks it belongs next to it. What each app
|
|
12
12
|
// keeps is its own fixtures: which tools it expects, what a good answer from each
|
|
13
13
|
// looks like, and which text surfaces it publishes.
|
|
14
|
+
//
|
|
15
|
+
// Every value the transport assertions compare against is imported from `mcp.ts`,
|
|
16
|
+
// not restated here. A suite that carries its own copy of the contract stops
|
|
17
|
+
// testing the boundary the moment the contract moves and says nothing about it.
|
|
18
|
+
import { DEFAULT_MAX_BATCH, MCP_CORS, MCP_PROTOCOL_VERSION } from './mcp.js';
|
|
19
|
+
/**
|
|
20
|
+
* Does the live preflight carry every token `MCP_CORS` declares for this header?
|
|
21
|
+
*
|
|
22
|
+
* Derived rather than listed: the assertion used to name three headers by hand,
|
|
23
|
+
* so a fourth added to `MCP_CORS` was tested by nobody.
|
|
24
|
+
*/
|
|
25
|
+
const corsCovers = (res, header) => {
|
|
26
|
+
const live = res.headers.get(header) ?? '';
|
|
27
|
+
return (MCP_CORS[header] ?? '').split(',').every(tok => live.includes(tok.trim()));
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A POST that `Tester.rpc` cannot make: one whose raw status and headers are the
|
|
31
|
+
* thing under test, and which must not take `rpc`'s 429 retry. A string body is
|
|
32
|
+
* sent as-is, so the malformed-JSON case uses the same door as the rest.
|
|
33
|
+
*/
|
|
34
|
+
const rawPost = (endpoint, body, headers = {}) => fetch(endpoint, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
37
|
+
body: typeof body === 'string' ? body : JSON.stringify(body),
|
|
38
|
+
});
|
|
14
39
|
export function createTester(opts) {
|
|
15
40
|
const base = opts.base.replace(/\/$/, '');
|
|
16
41
|
const endpoint = opts.endpoint ?? `${base}/api/mcp`;
|
|
@@ -87,30 +112,26 @@ expectedCapabilities = ['tools']) {
|
|
|
87
112
|
// 2025-03-26 — forfeiting structured output, tool titles and `_meta` — while
|
|
88
113
|
// every suite reported green.
|
|
89
114
|
const echoed = await t.rpc('initialize', {
|
|
90
|
-
protocolVersion:
|
|
115
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
91
116
|
capabilities: {},
|
|
92
117
|
clientInfo: { name: clientName, version: '1' },
|
|
93
118
|
});
|
|
94
|
-
t.check('protocol negotiated', echoed.result?.protocolVersion ===
|
|
119
|
+
t.check('protocol negotiated', echoed.result?.protocolVersion === MCP_PROTOCOL_VERSION, `asked ${MCP_PROTOCOL_VERSION}, got ${echoed.result?.protocolVersion}`);
|
|
95
120
|
t.check('downgrades gracefully', (init.result?.protocolVersion ?? '').length > 0, `asked 2024-11-05, got ${init.result?.protocolVersion}`);
|
|
96
121
|
console.log(`\n=== transport ===`);
|
|
97
122
|
const opt = await fetch(t.endpoint, { method: 'OPTIONS' });
|
|
98
123
|
t.check('OPTIONS preflight', opt.status === 204 &&
|
|
99
124
|
opt.headers.get('access-control-allow-origin') === '*' &&
|
|
100
|
-
(opt
|
|
125
|
+
corsCovers(opt, 'Access-Control-Allow-Headers'), `${opt.status} ACAO=${opt.headers.get('access-control-allow-origin')}`);
|
|
101
126
|
// Allow-Headers governs what a browser may send; Expose-Headers what it may
|
|
102
127
|
// read. Without the second, a browser client cannot see `Retry-After` on a
|
|
103
128
|
// 429 and a rate limit reads to it as a hang.
|
|
104
|
-
t.check('CORS exposes response headers',
|
|
129
|
+
t.check('CORS exposes response headers', corsCovers(opt, 'Access-Control-Expose-Headers'), `expose=${opt.headers.get('access-control-expose-headers')}`);
|
|
105
130
|
// A budget stated only in the 429 can be discovered only by exceeding it —
|
|
106
131
|
// the one moment an agent is least able to act on it. This suite is the
|
|
107
132
|
// proof: with no header to read, its own client blind-sleeps five seconds on
|
|
108
133
|
// a 429 and hopes.
|
|
109
|
-
const headroom = await
|
|
110
|
-
method: 'POST',
|
|
111
|
-
headers: { 'Content-Type': 'application/json' },
|
|
112
|
-
body: JSON.stringify({ jsonrpc: '2.0', id: 'headroom', method: 'ping' }),
|
|
113
|
-
});
|
|
134
|
+
const headroom = await rawPost(t.endpoint, { jsonrpc: '2.0', id: 'headroom', method: 'ping' });
|
|
114
135
|
t.recordCall();
|
|
115
136
|
const remaining = headroom.headers.get('ratelimit-remaining');
|
|
116
137
|
t.check('rate limit states headroom', !!headroom.headers.get('ratelimit-limit') && remaining !== null, `RateLimit-Limit=${headroom.headers.get('ratelimit-limit')} Remaining=${remaining} Reset=${headroom.headers.get('ratelimit-reset')}`);
|
|
@@ -118,23 +139,15 @@ expectedCapabilities = ['tools']) {
|
|
|
118
139
|
t.check('GET→405', get.status === 405 && get.headers.get('access-control-allow-origin') === '*', `${get.status} Allow=${get.headers.get('allow')} ACAO=${get.headers.get('access-control-allow-origin')}`);
|
|
119
140
|
// A notification has no id, so it must be acknowledged with no body at all —
|
|
120
141
|
// a JSON-RPC response to one is a protocol error.
|
|
121
|
-
const notif = await
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers: { 'Content-Type': 'application/json' },
|
|
124
|
-
body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),
|
|
125
|
-
});
|
|
142
|
+
const notif = await rawPost(t.endpoint, { jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
126
143
|
const notifBody = await notif.text();
|
|
127
144
|
t.check('notification→202', notif.status === 202 && notifBody === '', `${notif.status} body="${notifBody}"`);
|
|
128
|
-
const bad = await
|
|
145
|
+
const bad = await rawPost(t.endpoint, 'not json');
|
|
129
146
|
const badJson = (await bad.json());
|
|
130
147
|
t.check('parse error→400', bad.status === 400 && badJson.error?.code === -32700, `${bad.status} code=${badJson.error?.code}`);
|
|
131
|
-
const over = await
|
|
132
|
-
method: 'POST',
|
|
133
|
-
headers: { 'Content-Type': 'application/json' },
|
|
134
|
-
body: JSON.stringify(Array.from({ length: 21 }, (_, i) => ({ jsonrpc: '2.0', id: i, method: 'ping' }))),
|
|
135
|
-
});
|
|
148
|
+
const over = await rawPost(t.endpoint, Array.from({ length: DEFAULT_MAX_BATCH + 1 }, (_, i) => ({ jsonrpc: '2.0', id: i, method: 'ping' })));
|
|
136
149
|
const overJson = (await over.json());
|
|
137
|
-
t.check(
|
|
150
|
+
t.check(`batch cap (${DEFAULT_MAX_BATCH + 1})`, overJson.error?.code === -32600, `code=${overJson.error?.code}: ${(overJson.error?.message ?? '').slice(0, 80)}`);
|
|
138
151
|
const caps = init.result?.capabilities ?? {};
|
|
139
152
|
t.check('capabilities honest', expectedCapabilities.every(k => k in caps), `${JSON.stringify(caps)} expected=[${expectedCapabilities.join(', ')}]`);
|
|
140
153
|
// Declaring `resources` makes a client ask for templates. -32601 to a method
|
|
@@ -143,26 +156,20 @@ expectedCapabilities = ['tools']) {
|
|
|
143
156
|
const templates = await t.rpc('resources/templates/list');
|
|
144
157
|
t.check('resources/templates/list', Array.isArray(templates.result?.resourceTemplates), templates.error ? `-${templates.error.code}` : `${templates.result?.resourceTemplates?.length} templates`);
|
|
145
158
|
}
|
|
146
|
-
const versioned = await
|
|
147
|
-
|
|
148
|
-
headers: { 'Content-Type': 'application/json', 'MCP-Protocol-Version': CURRENT_PROTOCOL },
|
|
149
|
-
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }),
|
|
159
|
+
const versioned = await rawPost(t.endpoint, { jsonrpc: '2.0', id: 1, method: 'ping' }, {
|
|
160
|
+
'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
|
|
150
161
|
});
|
|
151
162
|
t.recordCall();
|
|
152
|
-
t.check('MCP-Protocol-Version echoed', versioned.headers.get('mcp-protocol-version') ===
|
|
163
|
+
t.check('MCP-Protocol-Version echoed', versioned.headers.get('mcp-protocol-version') === MCP_PROTOCOL_VERSION, `sent ${MCP_PROTOCOL_VERSION}, got ${versioned.headers.get('mcp-protocol-version')}`);
|
|
153
164
|
// Batching was removed in 2025-06-18. A server that keeps honouring it under
|
|
154
165
|
// a version that forbids it is telling the client something untrue.
|
|
155
|
-
const batched = await
|
|
156
|
-
|
|
157
|
-
headers: { 'Content-Type': 'application/json', 'MCP-Protocol-Version': CURRENT_PROTOCOL },
|
|
158
|
-
body: JSON.stringify([{ jsonrpc: '2.0', id: 1, method: 'ping' }]),
|
|
166
|
+
const batched = await rawPost(t.endpoint, [{ jsonrpc: '2.0', id: 1, method: 'ping' }], {
|
|
167
|
+
'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
|
|
159
168
|
});
|
|
160
169
|
t.recordCall();
|
|
161
170
|
t.check('batch refused at 2025-06-18', batched.status === 400, `${batched.status}`);
|
|
162
171
|
return init;
|
|
163
172
|
}
|
|
164
|
-
/** The newest protocol revision `wiki-formant/mcp` speaks. */
|
|
165
|
-
export const CURRENT_PROTOCOL = '2025-06-18';
|
|
166
173
|
/**
|
|
167
174
|
* One service, many descriptors — server.json, the two agent-card paths, the
|
|
168
175
|
* OpenAPI document, the MCP server card, and `initialize` — should never
|
|
@@ -212,11 +219,7 @@ export async function agentCardParity(t) {
|
|
|
212
219
|
// `url` is where a client sends its first call. Pointed at the homepage it
|
|
213
220
|
// gets HTML back, which is the failure that looks like a broken agent.
|
|
214
221
|
const endpoint = String(a.url ?? '');
|
|
215
|
-
const probe = await
|
|
216
|
-
method: 'POST',
|
|
217
|
-
headers: { 'Content-Type': 'application/json' },
|
|
218
|
-
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }),
|
|
219
|
-
});
|
|
222
|
+
const probe = await rawPost(endpoint, { jsonrpc: '2.0', id: 1, method: 'ping' });
|
|
220
223
|
t.recordCall();
|
|
221
224
|
t.check('agent card url is callable', (probe.headers.get('content-type') ?? '').includes('json'), `POST ${endpoint} -> ${probe.status} ${probe.headers.get('content-type')}`);
|
|
222
225
|
}
|
|
@@ -372,4 +375,3 @@ export async function robotsChecks(t, paths) {
|
|
|
372
375
|
t.check(`robots allows ${path}`, !match || match.allow, match ? `${match.allow ? 'Allow' : 'Disallow'}: ${match.path}` : 'no matching rule');
|
|
373
376
|
}
|
|
374
377
|
}
|
|
375
|
-
//# sourceMappingURL=conformance.js.map
|
package/dist/crawlers.d.ts
CHANGED
package/dist/crawlers.js
CHANGED
package/dist/dom.d.ts
CHANGED
package/dist/dom.js
CHANGED
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { AnyExtension, Editor } from '@tiptap/core';
|
|
2
|
+
import { type ChangeEvent, type RefObject } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Re-exported so the COMMAND AUGMENTATIONS reach consumers.
|
|
5
|
+
*
|
|
6
|
+
* Each of these packages ships a `declare module '@tiptap/core'` block that adds
|
|
7
|
+
* its commands to `ChainedCommands` — `toggleBold`, `insertTable`, `setLink`,
|
|
8
|
+
* `setImage`. A consumer that imported the extensions itself picked those up as
|
|
9
|
+
* a side effect of the import; now that this module owns them, it has to carry
|
|
10
|
+
* the augmentation across the package boundary, and a re-exported type is what
|
|
11
|
+
* makes the emitted `.d.ts` load the module that declares it. Without these
|
|
12
|
+
* four lines every `editor.chain().focus().toggleBold()` in every consumer
|
|
13
|
+
* stops type-checking.
|
|
14
|
+
*/
|
|
15
|
+
export type { StarterKitOptions } from '@tiptap/starter-kit';
|
|
16
|
+
export type { LinkOptions } from '@tiptap/extension-link';
|
|
17
|
+
export type { ImageOptions } from '@tiptap/extension-image';
|
|
18
|
+
export type { TableOptions } from '@tiptap/extension-table';
|
|
19
|
+
/**
|
|
20
|
+
* Reduce pasted HTML to structure plus the three attributes that carry meaning.
|
|
21
|
+
*
|
|
22
|
+
* A paste from a word processor or a web page arrives carrying its whole
|
|
23
|
+
* stylesheet inline. Keeping any of it means the wiki's own typography loses to
|
|
24
|
+
* whatever the author copied from, per paragraph, invisibly — and `style` on a
|
|
25
|
+
* pasted node is also the cheapest way to smuggle a full-bleed overlay into a
|
|
26
|
+
* page body.
|
|
27
|
+
*
|
|
28
|
+
* Browser-only: it parses with `DOMParser`. Called from `transformPastedHTML`,
|
|
29
|
+
* which only ever runs in response to a paste.
|
|
30
|
+
*/
|
|
31
|
+
export declare function cleanPastedHtml(html: string): string;
|
|
32
|
+
export interface WikiEditorExtensionOptions {
|
|
33
|
+
/** Empty-document prompt. */
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The custom nodes, built by the consumer.
|
|
37
|
+
*
|
|
38
|
+
* They stay the consumer's because `createCodeBlock`, `createTabs` and
|
|
39
|
+
* `createMapEmbed` all take that design system's class names and icons — and
|
|
40
|
+
* `createMapEmbed` needs its API route to follow a shortened link. What is
|
|
41
|
+
* shared is the twelve entries around them and how each is configured.
|
|
42
|
+
*/
|
|
43
|
+
nodes?: readonly AnyExtension[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The editor's extension set.
|
|
47
|
+
*
|
|
48
|
+
* `codeBlock: false` on StarterKit is load-bearing: the consumer registers its
|
|
49
|
+
* own via `createCodeBlock`, and leaving StarterKit's in place would give the
|
|
50
|
+
* schema two nodes claiming the same name. Headings stop at h2 — the page title
|
|
51
|
+
* is the only h1 a wiki page has.
|
|
52
|
+
*/
|
|
53
|
+
export declare function wikiEditorExtensions({ placeholder, nodes, }?: WikiEditorExtensionOptions): AnyExtension[];
|
|
54
|
+
/**
|
|
55
|
+
* Turn a pasted URL into the richest node that fits it, falling back to a bare
|
|
56
|
+
* iframe. Order matters: a YouTube URL is also a valid iframe source, so the
|
|
57
|
+
* specific cases have to be tried before the general one.
|
|
58
|
+
*
|
|
59
|
+
* A shortened map link inserts IMMEDIATELY with `about:blank` and swaps its
|
|
60
|
+
* `src` once the redirect resolves. Pasting must not block on a network hop,
|
|
61
|
+
* and the node has to already exist for the reader to see anything happen. The
|
|
62
|
+
* swap re-finds the node by its `url` attribute rather than caching a position,
|
|
63
|
+
* because every keystroke between the paste and the resolve moves it.
|
|
64
|
+
*/
|
|
65
|
+
export declare function insertEmbed(editor: Editor, url: string, { resolveMapUrl }: {
|
|
66
|
+
resolveMapUrl: (url: string) => Promise<string | null>;
|
|
67
|
+
}): void;
|
|
68
|
+
/** The table controls, shown only while the selection is inside a table. */
|
|
69
|
+
export declare const TABLE_ACTIONS: ReadonlyArray<[command: string, label: string, danger?: boolean]>;
|
|
70
|
+
/** `editor.isActive` as a toolbar button's `active` descriptor expresses it. */
|
|
71
|
+
export type ActiveDescriptor = string | [string, Record<string, unknown>];
|
|
72
|
+
export interface WikiEditorOptions {
|
|
73
|
+
value: string;
|
|
74
|
+
onChange: (html: string) => void;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
nodes?: readonly AnyExtension[];
|
|
77
|
+
/** The prose class the editor body renders under. */
|
|
78
|
+
proseClass: string;
|
|
79
|
+
/** Upload a chosen file, returning its URL or null. The route is the app's. */
|
|
80
|
+
uploadImage: (file: File) => Promise<string | null>;
|
|
81
|
+
/** Debounce before `onChange` fires, in ms. */
|
|
82
|
+
debounceMs?: number;
|
|
83
|
+
}
|
|
84
|
+
export interface WikiEditorState {
|
|
85
|
+
editor: Editor | null;
|
|
86
|
+
fileInputRef: RefObject<HTMLInputElement | null>;
|
|
87
|
+
isUploading: boolean;
|
|
88
|
+
handleFileChange: (e: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
89
|
+
/** Open the file picker — what an upload toolbar button calls. */
|
|
90
|
+
triggerUpload: () => void;
|
|
91
|
+
isActive: (a?: ActiveDescriptor) => boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The editor, its upload plumbing and the state a toolbar reads.
|
|
95
|
+
*
|
|
96
|
+
* The toolbar itself stays with the consumer: the two wikis differ on which
|
|
97
|
+
* buttons exist, which icon set draws them, and whether a link is entered in a
|
|
98
|
+
* `window.prompt` or an inline field. Those are real differences. What they had
|
|
99
|
+
* no business differing on is everything below.
|
|
100
|
+
*/
|
|
101
|
+
export declare function useWikiEditor({ value, onChange, placeholder, nodes, proseClass, uploadImage, debounceMs, }: WikiEditorOptions): WikiEditorState;
|