wiki-formant 0.62.0 → 0.63.1
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 +4 -2
- package/dist/block-views.d.ts +14 -1
- package/dist/block-views.js +7 -0
- package/dist/blocks.d.ts +19 -0
- package/dist/blocks.js +8 -1
- package/dist/text.js +4 -0
- package/dist/validation.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -686,7 +686,7 @@ leafBlocks(blocks, BLOCK_SHAPE.containers);
|
|
|
686
686
|
|
|
687
687
|
The dispatch over a repo's own block union stays in that repo; the bodies come from here. `coreAtomicText` has a prose body for every core leaf — the stats, link-grid and page-list bodies joined the originals when two of the three wikis turned out to extract nothing from them, so their MCP `get_page` answered a hub page as nearly empty.
|
|
688
688
|
|
|
689
|
-
The core leaves — `content`, `codeTabs`, `banner`, `references`, `stats`, `linkGrid`, `recentPages`, `pageList` — had the same case bodies in every wiki's text export, markdown twin, validator and new-block record. A repo's switch hands those cases over and keeps its own, so it stays exhaustive over its union:
|
|
689
|
+
The core leaves — `content`, `codeTabs`, `banner`, `references`, `stats`, `linkGrid`, `testimonial`, `recentPages`, `pageList` — had the same case bodies in every wiki's text export, markdown twin, validator and new-block record. A repo's switch hands those cases over and keeps its own, so it stays exhaustive over its union:
|
|
690
690
|
|
|
691
691
|
```ts
|
|
692
692
|
import { coreAtomicText, coreAtomicToMarkdown, coreBlockDefaults } from 'wiki-formant/blocks';
|
|
@@ -821,9 +821,11 @@ The dispatch stays a `switch` over the wiki's own union, so a new type is a comp
|
|
|
821
821
|
|
|
822
822
|
An editor that draws each block in place, with a list of its own (miow's), hosts the same fields in `<BlockFieldsKit kit={KIT}>`, without `<BlockEditor>`. Only the two container fields need the full editor, since they hold a block list.
|
|
823
823
|
|
|
824
|
+
A `testimonial` is `{ quote, author, role?, avatarUrl? }`, drawn by `TestimonialView` as `figure.testimonial`: the words in a `blockquote`, then a `figcaption` holding the photo and a `span` of `strong` (who) and `small` (their role). radix-wiki and miow each had one under its own field names; a site with sized images passes `photo`, the `<img>` attributes (`src`, `srcSet`, `sizes`) it made from `avatarUrl`. They are data, because a server component renders the view.
|
|
825
|
+
|
|
824
826
|
A banner with no text of its own shows `BANNER_MESSAGES` from `wiki-formant/text`, a sentence per variant that names no wiki; `BannerView` takes it unless `message` overrides it.
|
|
825
827
|
|
|
826
|
-
The core blocks are `ContentBlock`, `CodeTabsBlock`, `StatsBlock`, `LinkGridBlock`, `ReferencesBlock`, `BannerBlock`, `ColumnsBlock<A>` and `InfoboxBlock<A>`, `A` being the wiki's atomic union. A wiki builds its union from them beside its own `recentPages` and `pageList`, which hold its own page rows or a section, and its native types.
|
|
828
|
+
The core blocks are `ContentBlock`, `CodeTabsBlock`, `StatsBlock`, `LinkGridBlock`, `ReferencesBlock`, `BannerBlock`, `TestimonialBlock`, `ColumnsBlock<A>` and `InfoboxBlock<A>`, `A` being the wiki's atomic union. A wiki builds its union from them beside its own `recentPages` and `pageList`, which hold its own page rows or a section, and its native types.
|
|
827
829
|
|
|
828
830
|
## Analytics
|
|
829
831
|
|
package/dist/block-views.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
1
|
+
import { type ImgHTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import type { CodeTab, LinkGridGroup, ReferenceItem, ResolvedPageRef, StatItem } from './blocks.js';
|
|
3
3
|
import type { WikiLinkComponent } from './react-server.js';
|
|
4
4
|
export interface CodeTabsViewProps {
|
|
@@ -104,6 +104,19 @@ export declare function StatsView({ items, columns, }: {
|
|
|
104
104
|
})[];
|
|
105
105
|
columns?: number;
|
|
106
106
|
}): import("react").JSX.Element | null;
|
|
107
|
+
export interface TestimonialViewProps {
|
|
108
|
+
quote: string;
|
|
109
|
+
author: string;
|
|
110
|
+
role?: string;
|
|
111
|
+
avatarUrl?: string;
|
|
112
|
+
/**
|
|
113
|
+
* The photo's sized `<img>` attributes, for a site with an image pipeline.
|
|
114
|
+
* Data, not a function: a server component renders this client view.
|
|
115
|
+
*/
|
|
116
|
+
photo?: Pick<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'srcSet' | 'sizes'>;
|
|
117
|
+
}
|
|
118
|
+
/** Someone's words over who said them, with their photo when there is one. */
|
|
119
|
+
export declare function TestimonialView({ quote, author, role, avatarUrl, photo }: TestimonialViewProps): import("react").JSX.Element | null;
|
|
107
120
|
/**
|
|
108
121
|
* A maintenance notice.
|
|
109
122
|
*
|
package/dist/block-views.js
CHANGED
|
@@ -114,6 +114,13 @@ export function StatsView({ items, columns = 4, }) {
|
|
|
114
114
|
return null;
|
|
115
115
|
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))) }));
|
|
116
116
|
}
|
|
117
|
+
/** Someone's words over who said them, with their photo when there is one. */
|
|
118
|
+
export function TestimonialView({ quote, author, role, avatarUrl, photo }) {
|
|
119
|
+
if (!quote.trim())
|
|
120
|
+
return null;
|
|
121
|
+
const src = avatarUrl && safeLinkHref(avatarUrl);
|
|
122
|
+
return (_jsxs("figure", { className: "testimonial", children: [_jsx("blockquote", { children: quote }), _jsxs("figcaption", { children: [src && _jsx("img", { src: src, ...photo, alt: "", loading: "lazy" }), _jsxs("span", { children: [_jsx("strong", { children: author }), role && _jsx("small", { children: role })] })] })] }));
|
|
123
|
+
}
|
|
117
124
|
// ---- banner -----------------------------------------------------------------
|
|
118
125
|
/**
|
|
119
126
|
* A maintenance notice.
|
package/dist/blocks.d.ts
CHANGED
|
@@ -94,6 +94,15 @@ export interface ReferencesBlock {
|
|
|
94
94
|
title?: string;
|
|
95
95
|
items: EditableReferenceItem[];
|
|
96
96
|
}
|
|
97
|
+
/** Someone's words, as they said them, and who they are. */
|
|
98
|
+
export interface TestimonialBlock {
|
|
99
|
+
id: string;
|
|
100
|
+
type: 'testimonial';
|
|
101
|
+
quote: string;
|
|
102
|
+
author: string;
|
|
103
|
+
role?: string;
|
|
104
|
+
avatarUrl?: string;
|
|
105
|
+
}
|
|
97
106
|
/** A Wikipedia-style notice. `text` replaces the variant's standard message. */
|
|
98
107
|
export interface BannerBlock {
|
|
99
108
|
id: string;
|
|
@@ -247,6 +256,11 @@ export type CoreAtomicBlock<P = ResolvedPageRef> = {
|
|
|
247
256
|
type: 'linkGrid';
|
|
248
257
|
intro?: string | null;
|
|
249
258
|
groups: readonly LinkGridGroup[];
|
|
259
|
+
} | {
|
|
260
|
+
type: 'testimonial';
|
|
261
|
+
quote: string;
|
|
262
|
+
author: string;
|
|
263
|
+
role?: string | null;
|
|
250
264
|
} | {
|
|
251
265
|
type: 'recentPages' | 'pageList';
|
|
252
266
|
resolvedPages?: readonly P[] | null;
|
|
@@ -346,6 +360,11 @@ export interface CoreBlockDefaults {
|
|
|
346
360
|
type: 'banner';
|
|
347
361
|
variant: 'stub';
|
|
348
362
|
};
|
|
363
|
+
testimonial: () => {
|
|
364
|
+
type: 'testimonial';
|
|
365
|
+
quote: string;
|
|
366
|
+
author: string;
|
|
367
|
+
};
|
|
349
368
|
}
|
|
350
369
|
/**
|
|
351
370
|
* The new-block factories for the core types, which all three wikis had typed
|
package/dist/blocks.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// keeps this file free of any one repo's type union.
|
|
13
13
|
import { htmlToMarkdown, inlineToMarkdown } from './markdown.js';
|
|
14
14
|
import { relativeTime } from './freshness.js';
|
|
15
|
-
import { BANNER_LABELS, bannerToText, codeTabsToText, linkGridToText, pageListToText, referencesToText, statsToText, stripHtml, } from './text.js';
|
|
15
|
+
import { BANNER_LABELS, bannerToText, codeTabsToText, linkGridToText, pageListToText, referencesToText, statsToText, stripHtml, testimonialToText, } from './text.js';
|
|
16
16
|
// ---- the shapes the standard block types carry ------------------------------
|
|
17
17
|
/**
|
|
18
18
|
* The languages a code block or code tab offers, and the one a new one opens
|
|
@@ -188,6 +188,10 @@ export function statsToMarkdown(items) {
|
|
|
188
188
|
return '';
|
|
189
189
|
return items.map(s => `- **${s.value}${s.suffix ?? ''}** — ${s.label}`).join('\n');
|
|
190
190
|
}
|
|
191
|
+
/** A quotation over its attribution. @internal */
|
|
192
|
+
export function testimonialToMarkdown(quote, author, role) {
|
|
193
|
+
return `> ${quote.trim().replace(/\n+/g, '\n> ')}\n>\n> — ${[author, role].filter(Boolean).join(', ')}`;
|
|
194
|
+
}
|
|
191
195
|
/** Grouped links under their headings, with an optional lead paragraph. @internal */
|
|
192
196
|
export function linkGridToMarkdown(groups, intro) {
|
|
193
197
|
const lead = intro ? `${inlineToMarkdown(intro)}\n\n` : '';
|
|
@@ -216,6 +220,7 @@ export function coreAtomicText(block) {
|
|
|
216
220
|
case 'references': return referencesToText(block.items);
|
|
217
221
|
case 'stats': return statsToText(block.items);
|
|
218
222
|
case 'linkGrid': return linkGridToText(block.groups, block.intro);
|
|
223
|
+
case 'testimonial': return testimonialToText(block.quote, block.author, block.role);
|
|
219
224
|
case 'recentPages':
|
|
220
225
|
case 'pageList': return pageListToText(block.resolvedPages ?? []);
|
|
221
226
|
}
|
|
@@ -229,6 +234,7 @@ export function coreAtomicToMarkdown(block, opts) {
|
|
|
229
234
|
case 'references': return referencesToMarkdown(block.items, block.title || opts.referencesTitle || 'References');
|
|
230
235
|
case 'stats': return statsToMarkdown(block.items);
|
|
231
236
|
case 'linkGrid': return linkGridToMarkdown(block.groups, block.intro);
|
|
237
|
+
case 'testimonial': return testimonialToMarkdown(block.quote, block.author, block.role);
|
|
232
238
|
case 'recentPages':
|
|
233
239
|
case 'pageList':
|
|
234
240
|
return linkList((block.resolvedPages ?? []).map(p => ({
|
|
@@ -268,5 +274,6 @@ export function coreBlockDefaults(opts = {}) {
|
|
|
268
274
|
linkGrid: () => ({ type: 'linkGrid', groups: [{ id: newId(), heading: 'Group', links: [] }] }),
|
|
269
275
|
references: () => ({ type: 'references', title: referencesTitle, items: [] }),
|
|
270
276
|
banner: () => ({ type: 'banner', variant: 'stub' }),
|
|
277
|
+
testimonial: () => ({ type: 'testimonial', quote: '', author: '' }),
|
|
271
278
|
};
|
|
272
279
|
}
|
package/dist/text.js
CHANGED
|
@@ -107,6 +107,10 @@ export function bannerToText(label, text) {
|
|
|
107
107
|
export function codeTabsToText(tabs) {
|
|
108
108
|
return tabs.map(t => `[${t.label}]\n${t.code}`).join('\n');
|
|
109
109
|
}
|
|
110
|
+
/** `“Their words” — Name, role`. @internal */
|
|
111
|
+
export function testimonialToText(quote, author, role) {
|
|
112
|
+
return `“${quote.trim()}” — ${[author, role].filter(Boolean).join(', ')}`;
|
|
113
|
+
}
|
|
110
114
|
/** Metric cards, one per line: `99% Uptime`. @internal */
|
|
111
115
|
export function statsToText(items) {
|
|
112
116
|
return items.map(s => `${s.value}${s.suffix ?? ''} ${s.label}`).join('\n');
|
package/dist/validation.js
CHANGED
|
@@ -118,6 +118,8 @@ export function coreAtomicValidator(opts = {}) {
|
|
|
118
118
|
return validateReferenceItems(b.items);
|
|
119
119
|
case 'linkGrid':
|
|
120
120
|
return validateLinkGroups(b.groups);
|
|
121
|
+
case 'testimonial':
|
|
122
|
+
return typeof b.quote === 'string' && typeof b.author === 'string' && optionalString(b.role) && optionalString(b.avatarUrl);
|
|
121
123
|
default:
|
|
122
124
|
return false;
|
|
123
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.1",
|
|
4
4
|
"description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|