wiki-formant 0.46.0 → 0.47.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 +7 -0
- package/dist/metadata.d.ts +21 -0
- package/dist/metadata.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -431,6 +431,13 @@ The schema.org nodes sit beside it, because they state the same facts to a diffe
|
|
|
431
431
|
<JsonLd data={articleLd({ headline: title, url, image, published, modified, publisher: { '@id': `${SITE_URL}/#organization` }, citation: citationsFromReferences(refs) })} />
|
|
432
432
|
```
|
|
433
433
|
|
|
434
|
+
`questionPairs(html)` reads a page's FAQ out of its own headings: every `h2`/`h3` ending in `?`, answered by the text beneath it. It never rewrites a statement heading as a question, so a page earns `FAQPage` by being written as one. `faqPageLd(pairs, url)` is that node, or null under two pairs, and takes an authored list just as well.
|
|
435
|
+
|
|
436
|
+
```tsx
|
|
437
|
+
const faq = faqPageLd(questionPairs(html), url);
|
|
438
|
+
{faq && <JsonLd data={faq} />}
|
|
439
|
+
```
|
|
440
|
+
|
|
434
441
|
`sitePageMetadata({ siteName, handle })` binds the three fields Next drops from every page that sets its own card, with `locale` defaulting to `en_US`. `siteGraphLd` is the Organization, WebSite (with its `SearchAction`) and `WebAPI` as one `@graph` for the root layout, every node carrying an `@id`, and `siteRefs(url)` hands out the same ids for a page's `publisher` and `isPartOf` — so no page writes an inline copy of the publisher that can disagree with the layout's.
|
|
435
442
|
|
|
436
443
|
```ts
|
package/dist/metadata.d.ts
CHANGED
|
@@ -121,6 +121,27 @@ export declare function collectionLd(o: CollectionLdOptions): LdNode;
|
|
|
121
121
|
* an authored `&` is not what a crawler reads.
|
|
122
122
|
*/
|
|
123
123
|
export declare function citationsFromReferences(items: readonly ReferenceItem[], max?: number): LdNode[];
|
|
124
|
+
export interface QaPair {
|
|
125
|
+
q: string;
|
|
126
|
+
a: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The question/answer pairs in a page's HTML: every `h2`/`h3` that ends in a
|
|
130
|
+
* question mark, with the text up to the next such heading as its answer.
|
|
131
|
+
*
|
|
132
|
+
* It never turns a statement heading into a question. Markup that rewrites
|
|
133
|
+
* "Protocol fees" as "What is Protocol fees?" claims a question the page never
|
|
134
|
+
* asked, so a page earns an FAQ by being written as one. An answer under 40
|
|
135
|
+
* characters is a heading with no section, and is skipped.
|
|
136
|
+
*/
|
|
137
|
+
export declare function questionPairs(html: string): QaPair[];
|
|
138
|
+
/**
|
|
139
|
+
* `FAQPage` over question/answer pairs, or null under two. One question is an
|
|
140
|
+
* article with a question for a heading, not an FAQ. Pairs come from
|
|
141
|
+
* `questionPairs` on stored HTML, or from a list the caller authors directly
|
|
142
|
+
* as plain text, so a home-page FAQ and a wiki article render the same node.
|
|
143
|
+
*/
|
|
144
|
+
export declare function faqPageLd(pairs: readonly QaPair[], url: string): LdNode | null;
|
|
124
145
|
/**
|
|
125
146
|
* Characters a document `<title>` has before a search result truncates it.
|
|
126
147
|
*
|
package/dist/metadata.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// with no image while their og:image named a card, one listed citations, one
|
|
20
20
|
// collection page had no ItemList at all.
|
|
21
21
|
import { decodeEntities } from './markdown.js';
|
|
22
|
+
import { stripHtml } from './text.js';
|
|
22
23
|
export function pageMetadata(o) {
|
|
23
24
|
const { title, description, url, type = 'website', image, imageSize = { width: 1200, height: 630 }, siteName, locale, handle, markdownTwin, article } = o;
|
|
24
25
|
const images = image ? [{ url: image, ...imageSize, alt: o.imageAlt ?? title }] : undefined;
|
|
@@ -107,6 +108,49 @@ export function citationsFromReferences(items, max = 50) {
|
|
|
107
108
|
})
|
|
108
109
|
.slice(0, max);
|
|
109
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The question/answer pairs in a page's HTML: every `h2`/`h3` that ends in a
|
|
113
|
+
* question mark, with the text up to the next such heading as its answer.
|
|
114
|
+
*
|
|
115
|
+
* It never turns a statement heading into a question. Markup that rewrites
|
|
116
|
+
* "Protocol fees" as "What is Protocol fees?" claims a question the page never
|
|
117
|
+
* asked, so a page earns an FAQ by being written as one. An answer under 40
|
|
118
|
+
* characters is a heading with no section, and is skipped.
|
|
119
|
+
*/
|
|
120
|
+
export function questionPairs(html) {
|
|
121
|
+
const pairs = [];
|
|
122
|
+
const sections = html.split(/<h[23][^>]*>/i);
|
|
123
|
+
for (const section of sections.slice(1)) {
|
|
124
|
+
const [heading = '', ...rest] = section.split(/<\/h[23]>/i);
|
|
125
|
+
const q = stripHtml(heading).replace(/\s+/g, ' ');
|
|
126
|
+
if (!q.endsWith('?'))
|
|
127
|
+
continue;
|
|
128
|
+
const a = stripHtml(rest.join('')).replace(/\s+/g, ' ');
|
|
129
|
+
if (a.length >= 40)
|
|
130
|
+
pairs.push({ q, a: a.slice(0, 1000) });
|
|
131
|
+
}
|
|
132
|
+
return pairs;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* `FAQPage` over question/answer pairs, or null under two. One question is an
|
|
136
|
+
* article with a question for a heading, not an FAQ. Pairs come from
|
|
137
|
+
* `questionPairs` on stored HTML, or from a list the caller authors directly
|
|
138
|
+
* as plain text, so a home-page FAQ and a wiki article render the same node.
|
|
139
|
+
*/
|
|
140
|
+
export function faqPageLd(pairs, url) {
|
|
141
|
+
if (pairs.length < 2)
|
|
142
|
+
return null;
|
|
143
|
+
return {
|
|
144
|
+
'@context': 'https://schema.org',
|
|
145
|
+
'@type': 'FAQPage',
|
|
146
|
+
url,
|
|
147
|
+
mainEntity: pairs.map(p => ({
|
|
148
|
+
'@type': 'Question',
|
|
149
|
+
name: p.q,
|
|
150
|
+
acceptedAnswer: { '@type': 'Answer', text: p.a },
|
|
151
|
+
})),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
110
154
|
/**
|
|
111
155
|
* Characters a document `<title>` has before a search result truncates it.
|
|
112
156
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.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",
|