wiki-formant 0.87.0 → 0.88.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 CHANGED
@@ -883,7 +883,7 @@ const SCHEMA: BlockSchema<Block> = {
883
883
  <BlockEditor blocks={blocks} onChange={setBlocks} schema={SCHEMA} kit={KIT} />
884
884
  ```
885
885
 
886
- The dispatch stays a `switch` over the wiki's own union, so a new type is a compile error there. `CodeTabsFields`, `StatsFields`, `EventsFields`, `ReferencesFields`, `LinkGridFields`, `BannerFields`, `PageListFields`, `ListFields`, `ColumnsFields` and `InfoboxFields` take the core blocks `wiki-formant/blocks` declares; the two containers hold the same list, offering `nestedTypes`. `Field`, `Choice` and `LimitField` are exported for a wiki's own editors, so its native types wear the same kit. A card is marked `data-selected` while it is being edited and `data-nested` inside a container; a type the build does not know is shown by its raw type and carried through untouched. Keep the kit and the schema stable — a module constant or a `useMemo` — since both reach every card through context, and a fresh one re-renders every card on every keystroke.
886
+ The dispatch stays a `switch` over the wiki's own union, so a new type is a compile error there. `CodeTabsFields`, `StatsFields`, `TestimonialFields`, `EventsFields`, `ReferencesFields`, `LinkGridFields`, `BannerFields`, `PageListFields`, `ListFields`, `ColumnsFields` and `InfoboxFields` take the core blocks `wiki-formant/blocks` declares; the two containers hold the same list, offering `nestedTypes`. `Field`, `Choice` and `LimitField` are exported for a wiki's own editors, so its native types wear the same kit. A card is marked `data-selected` while it is being edited and `data-nested` inside a container; a type the build does not know is shown by its raw type and carried through untouched. Keep the kit and the schema stable — a module constant or a `useMemo` — since both reach every card through context, and a fresh one re-renders every card on every keystroke.
887
887
 
888
888
  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.
889
889
 
@@ -893,7 +893,7 @@ An `events` block is `{ upcoming?, past?, items: [{ id, date, end?, text }] }`:
893
893
 
894
894
  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.
895
895
 
896
- The core blocks are `ContentBlock`, `CodeTabsBlock`, `StatsBlock`, `LinkGridBlock`, `ReferencesBlock`, `BannerBlock`, `TestimonialBlock`, `EventsBlock`, `ListBlock`, `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.
896
+ The core blocks are `ContentBlock`, `CodeTabsBlock`, `StatsBlock`, `LinkGridBlock`, `ReferencesBlock`, `BannerBlock`, `TestimonialBlock`, `EventsBlock`, `ListBlock`, `ColumnsBlock<A>` and `InfoboxBlock<A>`, `A` being the wiki's atomic union. `CoreLeafBlock` is every one of them but the list and the containers, as one union, and `CoreLeafType` its types. A wiki builds its atomic union from it, `CoreLeafBlock | ListBlock | its native types`, with its own `list` holding its own page rows, and derives its `BlockType` from the result. A leaf added here then reaches every wiki with the release, and fails to compile there until its label, its view and its fields are wired: a `Record` keyed by the block types is missing a key, and a switch closed with `block satisfies never` has a case left over. miow, whose union is zod's, asserts that it carries every `CoreLeafType`.
897
897
 
898
898
  `ListBlock` is one list of the wiki's pages where those two were two: the pages in a section (`source: 'under'`), the newest, in a section or across the wiki (`'newest'`, `count`), or pages picked by hand (`'chosen'`, `pages`, keyed as the wiki keys a page, an id or a path), shown as links with their age or as cards (`show`). A wiki resolves it to `items` as it does a page list. `ListFields` edits it, with `pages` to pick from and `sections` to name; a wiki with a source of its own, a shop's products say, passes it in `sources` and draws its fields as `children`.
899
899
 
package/dist/base.css CHANGED
@@ -155,7 +155,8 @@ html[data-sidebar='closed'] .wiki-sidebar { width: 0; }
155
155
  The structure three or four sites had each restated around this markup;
156
156
  padding, gaps, radii and colour stay theirs. A stats block is two columns,
157
157
  three or four from 40rem; a code-tab strip is a row of buttons; a banner sets
158
- its icon beside its text; a long address in an infobox wraps; an events
158
+ its icon beside its text; a long address in an infobox wraps; a testimonial
159
+ sets its photo, round, beside who said it; an events
159
160
  block sets every row's dates in one column, shared by Upcoming and Past
160
161
  through a subgrid, and stacks them over the words wherever it is narrower
161
162
  than 30rem: a phone, or a narrow column. */
@@ -174,6 +175,9 @@ html[data-sidebar='closed'] .wiki-sidebar { width: 0; }
174
175
  sits outside the article's prose box, and on radix.wiki ran 709px. */
175
176
  .link-grid-group-description { max-width: var(--measure); }
176
177
  .infobox-series { display: flex; flex-direction: column; }
178
+ .testimonial figcaption { display: flex; align-items: center; gap: 0.75rem; }
179
+ .testimonial figcaption img { width: 2.5rem; height: 2.5rem; border-radius: 50%; object-fit: cover; }
180
+ .testimonial figcaption > span { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 0.5rem; }
177
181
  .events { display: grid; grid-template-columns: auto 1fr; container-type: inline-size; }
178
182
  .events > *, .event-list li { grid-column: 1 / -1; }
179
183
  .event-list, .event-list li { display: grid; grid-template-columns: subgrid; }
@@ -308,6 +312,10 @@ html[data-sidebar='closed'] .wiki-sidebar { width: 0; }
308
312
 
309
313
  .facet-summary { margin: 0; font-size: var(--ui-small); color: var(--ui-muted); }
310
314
 
315
+ .testimonial { margin: 1.5rem 0; }
316
+ .testimonial > blockquote { margin: 0 0 0.75rem; font-size: 1.125rem; }
317
+ .testimonial figcaption small { color: var(--ui-muted); }
318
+
311
319
  .events { column-gap: 1rem; margin-block: 1rem; }
312
320
  .events h2 { margin: 0 0 1rem; }
313
321
  .events ol ~ h2 { margin-top: 2.5rem; }
@@ -1,5 +1,5 @@
1
1
  import { type ComponentType, type ReactElement, type ReactNode } from 'react';
2
- import { type BannerBlock, type CodeTabsBlock, type ColumnsBlock, type EventsBlock, type InfoboxBlock, type LinkGridBlock, type ReferencesBlock, type StatsBlock } from './blocks.js';
2
+ import { type BannerBlock, type CodeTabsBlock, type ColumnsBlock, type EventsBlock, type InfoboxBlock, type LinkGridBlock, type ReferencesBlock, type StatsBlock, type TestimonialBlock } from './blocks.js';
3
3
  export interface BlockEditorClassNames {
4
4
  /** A page's block list. */
5
5
  list?: string;
@@ -167,6 +167,11 @@ export declare function StatsFields({ block, onUpdate, hint }: {
167
167
  onUpdate: (block: StatsBlock) => void;
168
168
  hint?: string;
169
169
  }): import("react").JSX.Element;
170
+ /** Someone's words exactly, and who they are: their name, and optionally their role and photo. */
171
+ export declare function TestimonialFields({ block, onUpdate }: {
172
+ block: TestimonialBlock;
173
+ onUpdate: (block: TestimonialBlock) => void;
174
+ }): import("react").JSX.Element;
170
175
  /**
171
176
  * Events in the order they were written, each with how its dates read and
172
177
  * where the page sets it today: the page sorts them itself, and moves each on
@@ -163,6 +163,11 @@ export function StatsFields({ block, onUpdate, hint }) {
163
163
  const set = (next) => onUpdate({ ...block, items: next });
164
164
  return (_jsxs("div", { className: c.stack, children: [_jsx(Choice, { label: "Per row", value: String(block.columns), options: ['2', '3', '4'].map(n => ({ value: n, label: n })), onChange: n => onUpdate({ ...block, columns: Number(n) }) }), items.map((item, i) => (_jsxs("div", { className: c.row, children: [_jsx(TextInput, { label: "Figure", className: c.figure, value: item.value, onChange: value => set(replaceAt(items, i, { ...item, value: value ?? '' })) }), _jsx(TextInput, { label: "What it counts", value: item.label, onChange: label => set(replaceAt(items, i, { ...item, label: label ?? '' })) }), _jsx(Remove, { label: `Remove figure ${i + 1}`, onPress: () => set(removeAt(items, i)) })] }, item.id))), _jsx(Add, { onPress: () => set([...items, { id: newId(), value: '', label: '' }]), children: "Add figure" }), hint && _jsx("small", { className: c.muted, children: hint })] }));
165
165
  }
166
+ /** Someone's words exactly, and who they are: their name, and optionally their role and photo. */
167
+ export function TestimonialFields({ block, onUpdate }) {
168
+ const c = useEditorContext().kit.classNames;
169
+ return (_jsxs("div", { className: c.stack, children: [_jsx(Field, { label: "Their words", children: _jsx("textarea", { "aria-label": "Their words", placeholder: "Their words, exactly as they said them", rows: 3, className: c.input, value: block.quote, onChange: e => onUpdate({ ...block, quote: e.target.value }) }) }), _jsxs("div", { className: c.row, children: [_jsx(TextInput, { label: "Who said them", value: block.author, onChange: author => onUpdate({ ...block, author: author ?? '' }) }), _jsx(TextInput, { label: "Their role (optional)", optional: true, value: block.role, onChange: role => onUpdate({ ...block, role }) })] }), _jsx(TextInput, { label: "Photo address (optional)", type: "url", placeholder: "https://", optional: true, value: block.avatarUrl, onChange: avatarUrl => onUpdate({ ...block, avatarUrl }) })] }));
170
+ }
166
171
  /**
167
172
  * Events in the order they were written, each with how its dates read and
168
173
  * where the page sets it today: the page sorts them itself, and moves each on
package/dist/blocks.d.ts CHANGED
@@ -139,6 +139,16 @@ export interface BannerBlock {
139
139
  variant: BannerVariant;
140
140
  text?: string;
141
141
  }
142
+ /**
143
+ * The leaves every wiki here stores alike, as one union: a repo builds its own
144
+ * atomic union from it, `CoreLeafBlock | ListBlock | its native types`, so a
145
+ * leaf added here reaches every repo with the release, and every record keyed
146
+ * by its block types and every exhaustive switch over them fails to compile
147
+ * there until it is drawn, edited and labelled. The list is left out because
148
+ * each repo resolves its `items` to its own rows.
149
+ */
150
+ export type CoreLeafBlock = ContentBlock | CodeTabsBlock | StatsBlock | LinkGridBlock | ReferencesBlock | BannerBlock | TestimonialBlock | EventsBlock;
151
+ export type CoreLeafType = CoreLeafBlock['type'];
142
152
  /** `A` is the wiki's atomic union: a container never holds another container. */
143
153
  export interface ColumnsBlock<A> {
144
154
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-formant",
3
- "version": "0.87.0",
3
+ "version": "0.88.0",
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",