@escape-game-over/atlas 0.1.115 → 0.1.116
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/docs/NOT-BUILT.md +2 -2
- package/package.json +1 -1
- package/src/analytics/storage.ts +34 -39
- package/src/astro/images.ts +12 -26
- package/src/astro/index.ts +2 -9
- package/src/astro/public-files.ts +11 -52
- package/src/astro/site-routes.ts +11 -55
- package/src/config.ts +8 -17
- package/src/content/rich.ts +15 -18
- package/src/i18n/catalog.ts +20 -1
- package/src/i18n/define.ts +0 -5
- package/src/i18n/translate.ts +9 -48
- package/src/index.ts +23 -160
- package/src/jsonld/article.ts +7 -17
- package/src/jsonld/business.ts +27 -28
- package/src/jsonld/creative-work.ts +8 -20
- package/src/jsonld/node.ts +37 -7
- package/src/jsonld/organization.ts +21 -30
- package/src/jsonld/place.ts +2 -6
- package/src/jsonld/product.ts +16 -22
- package/src/jsonld/service.ts +21 -37
- package/src/jsonld/video.ts +4 -8
- package/src/jsonld/website.ts +3 -5
- package/src/meta/content.ts +14 -66
- package/src/meta/index.ts +10 -32
- package/src/project.ts +4 -18
- package/src/routes/define.ts +0 -13
- package/src/site/api.ts +1 -21
- package/src/site/create.ts +17 -26
- package/src/site/page.ts +1 -10
|
@@ -8,7 +8,7 @@ import type { ImageAsset } from "../image.ts";
|
|
|
8
8
|
import type { IsoDate } from "../types.ts";
|
|
9
9
|
import { absoluteUrl, type HttpsUrl, joinUrl, type UrlPath } from "../url.ts";
|
|
10
10
|
import { organizationId } from "./ids.ts";
|
|
11
|
-
import { alternateName, type JsonLdNode } from "./node.ts";
|
|
11
|
+
import { alternateName, type JsonLdNode, listed } from "./node.ts";
|
|
12
12
|
import { postalAddress } from "./place.ts";
|
|
13
13
|
|
|
14
14
|
export interface OrganizationInput {
|
|
@@ -172,36 +172,27 @@ export function organization(brand: OrganizationInput): JsonLdNode {
|
|
|
172
172
|
"@id": organizationId(brand.url),
|
|
173
173
|
name: brand.name,
|
|
174
174
|
url: brand.url,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
: {
|
|
188
|
-
image: brand.image.map((photo) =>
|
|
189
|
-
absoluteUrl(brand.url, photo.src, "organization photo")
|
|
190
|
-
),
|
|
191
|
-
}),
|
|
192
|
-
...(brand.legalName === undefined
|
|
193
|
-
? {}
|
|
194
|
-
: { legalName: brand.legalName }),
|
|
195
|
-
...(brand.vatID === undefined ? {} : { vatID: brand.vatID }),
|
|
175
|
+
alternateName: alternateName(brand.name, brand.alternateName),
|
|
176
|
+
description: brand.description,
|
|
177
|
+
foundingDate: brand.foundingDate,
|
|
178
|
+
logo:
|
|
179
|
+
brand.logo === undefined
|
|
180
|
+
? undefined
|
|
181
|
+
: joinUrl(brand.url, brand.logo),
|
|
182
|
+
image: listed(brand.image)?.map((photo) =>
|
|
183
|
+
absoluteUrl(brand.url, photo.src, "organization photo")
|
|
184
|
+
),
|
|
185
|
+
legalName: brand.legalName,
|
|
186
|
+
vatID: brand.vatID,
|
|
196
187
|
// E.164, as on the business: the only form that identifies a number
|
|
197
188
|
// unambiguously enough to match a company against a listing.
|
|
198
|
-
|
|
199
|
-
?
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
189
|
+
telephone:
|
|
190
|
+
brand.telephone === undefined ? undefined : e164(brand.telephone),
|
|
191
|
+
email: brand.email,
|
|
192
|
+
sameAs: brand.sameAs,
|
|
193
|
+
address:
|
|
194
|
+
brand.address === undefined
|
|
195
|
+
? undefined
|
|
196
|
+
: postalAddress(brand.address),
|
|
206
197
|
};
|
|
207
198
|
}
|
package/src/jsonld/place.ts
CHANGED
|
@@ -37,13 +37,9 @@ export function postalAddress(address: PostalAddress): JsonLdNode {
|
|
|
37
37
|
"@type": "PostalAddress",
|
|
38
38
|
streetAddress: address.street,
|
|
39
39
|
addressLocality: address.locality,
|
|
40
|
-
|
|
41
|
-
? {}
|
|
42
|
-
: { postalCode: address.postalCode }),
|
|
40
|
+
postalCode: address.postalCode,
|
|
43
41
|
addressCountry: address.country,
|
|
44
|
-
|
|
45
|
-
? {}
|
|
46
|
-
: { addressRegion: address.region }),
|
|
42
|
+
addressRegion: address.region,
|
|
47
43
|
};
|
|
48
44
|
}
|
|
49
45
|
|
package/src/jsonld/product.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import type { NonEmpty } from "../types.ts";
|
|
12
12
|
import type { HttpsUrl } from "../url.ts";
|
|
13
13
|
import type { BusinessId, OrganizationId } from "./ids.ts";
|
|
14
|
-
import { type JsonLdNode, schemaType } from "./node.ts";
|
|
14
|
+
import { type JsonLdNode, reference, schemaType } from "./node.ts";
|
|
15
15
|
import { quantitativeValue } from "./quantity.ts";
|
|
16
16
|
|
|
17
17
|
export interface ProductInput {
|
|
@@ -91,7 +91,7 @@ export function product(item: ProductInput): JsonLdNode {
|
|
|
91
91
|
name: item.name,
|
|
92
92
|
description: item.description,
|
|
93
93
|
url: item.url,
|
|
94
|
-
|
|
94
|
+
image: item.image,
|
|
95
95
|
offers: offersFor(item),
|
|
96
96
|
};
|
|
97
97
|
}
|
|
@@ -116,30 +116,24 @@ function offerFor(
|
|
|
116
116
|
"@type": "Offer",
|
|
117
117
|
// An `Offer` is a `Thing`, so it can carry a `name`; it is what tells
|
|
118
118
|
// the adults' price from the children's.
|
|
119
|
-
|
|
119
|
+
name,
|
|
120
120
|
price: amount,
|
|
121
121
|
priceCurrency: item.price.currency,
|
|
122
122
|
availability: "https://schema.org/InStock",
|
|
123
123
|
url: item.url,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
...(unit === undefined ? {} : { unitText: unit }),
|
|
138
|
-
},
|
|
139
|
-
}),
|
|
140
|
-
...(item.seller === undefined
|
|
141
|
-
? {}
|
|
142
|
-
: { seller: { "@id": item.seller } }),
|
|
124
|
+
// What makes two prices a table rather than a contradiction:
|
|
125
|
+
// each one says the quantity it applies at.
|
|
126
|
+
//
|
|
127
|
+
// The quantity is passed as written, not through `tierRange`:
|
|
128
|
+
// the two take the same `number | { min, max }`, and widening
|
|
129
|
+
// first would publish a band of one as `minValue: 2,
|
|
130
|
+
// maxValue: 2` — a range across a single size, where `value:
|
|
131
|
+
// 2` is the same claim said plainly.
|
|
132
|
+
eligibleQuantity:
|
|
133
|
+
band === undefined
|
|
134
|
+
? undefined
|
|
135
|
+
: { ...quantitativeValue(band.quantity), unitText: unit },
|
|
136
|
+
seller: reference(item.seller),
|
|
143
137
|
};
|
|
144
138
|
}
|
|
145
139
|
|
package/src/jsonld/service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { HttpsUrl } from "../url.ts";
|
|
2
2
|
import type { BusinessId, OrganizationId } from "./ids.ts";
|
|
3
3
|
import { serviceId } from "./ids.ts";
|
|
4
|
-
import type
|
|
4
|
+
import { type JsonLdNode, listed, reference } from "./node.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* What a business *does*, for the sites that sell work rather than tickets.
|
|
@@ -82,29 +82,23 @@ export interface ServiceInput {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/** An `Audience` node, or nothing. See `ServiceInput.audience`. */
|
|
85
|
-
function audienceFor(audienceType: string | undefined): {
|
|
86
|
-
readonly audience?: JsonLdNode;
|
|
87
|
-
} {
|
|
85
|
+
function audienceFor(audienceType: string | undefined): JsonLdNode | undefined {
|
|
88
86
|
return audienceType === undefined
|
|
89
|
-
?
|
|
90
|
-
: {
|
|
87
|
+
? undefined
|
|
88
|
+
: { "@type": "Audience", audienceType };
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
/** `areaServed`, as `Place` nodes. See `ServiceArea`. */
|
|
94
|
-
function areasFor(
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
function areasFor(
|
|
93
|
+
areas: readonly ServiceArea[] | undefined
|
|
94
|
+
): readonly JsonLdNode[] | undefined {
|
|
97
95
|
// Length rather than presence: a service that serves nowhere in particular
|
|
98
96
|
// says nothing, and an empty list is a claim to have been asked and had no
|
|
99
97
|
// answer.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"@type": area.kind ?? "AdministrativeArea",
|
|
105
|
-
name: area.name,
|
|
106
|
-
})),
|
|
107
|
-
};
|
|
98
|
+
return listed(areas)?.map((area) => ({
|
|
99
|
+
"@type": area.kind ?? "AdministrativeArea",
|
|
100
|
+
name: area.name,
|
|
101
|
+
}));
|
|
108
102
|
}
|
|
109
103
|
|
|
110
104
|
/**
|
|
@@ -118,17 +112,11 @@ export function service(input: ServiceInput): JsonLdNode {
|
|
|
118
112
|
"@id": serviceId(input.url),
|
|
119
113
|
name: input.name,
|
|
120
114
|
url: input.url,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
: { serviceType: input.serviceType }),
|
|
127
|
-
...(input.provider === undefined
|
|
128
|
-
? {}
|
|
129
|
-
: { provider: { "@id": input.provider } }),
|
|
130
|
-
...audienceFor(input.audience),
|
|
131
|
-
...areasFor(input.areaServed),
|
|
115
|
+
description: input.description,
|
|
116
|
+
serviceType: input.serviceType,
|
|
117
|
+
provider: reference(input.provider),
|
|
118
|
+
audience: audienceFor(input.audience),
|
|
119
|
+
areaServed: areasFor(input.areaServed),
|
|
132
120
|
};
|
|
133
121
|
}
|
|
134
122
|
|
|
@@ -212,9 +200,7 @@ export function offerCatalog(input: OfferCatalogInput): JsonLdNode {
|
|
|
212
200
|
return {
|
|
213
201
|
"@type": "OfferCatalog",
|
|
214
202
|
name: input.name,
|
|
215
|
-
|
|
216
|
-
? {}
|
|
217
|
-
: { description: input.description }),
|
|
203
|
+
description: input.description,
|
|
218
204
|
// Unconditional: this is the node's only link to anything.
|
|
219
205
|
provider: { "@id": input.provider },
|
|
220
206
|
itemListElement: input.entries.map((entry) => ({
|
|
@@ -224,13 +210,11 @@ export function offerCatalog(input: OfferCatalogInput): JsonLdNode {
|
|
|
224
210
|
// The id only where there is a page to hang it on. A fragment
|
|
225
211
|
// invented for an entry with no page would be an identity for
|
|
226
212
|
// something nothing else can ever refer to.
|
|
227
|
-
|
|
228
|
-
?
|
|
229
|
-
|
|
213
|
+
"@id":
|
|
214
|
+
entry.url === undefined ? undefined : serviceId(entry.url),
|
|
215
|
+
url: entry.url,
|
|
230
216
|
name: entry.name,
|
|
231
|
-
|
|
232
|
-
? {}
|
|
233
|
-
: { description: entry.description }),
|
|
217
|
+
description: entry.description,
|
|
234
218
|
},
|
|
235
219
|
})),
|
|
236
220
|
};
|
package/src/jsonld/video.ts
CHANGED
|
@@ -227,13 +227,9 @@ export function videoObject(video: VideoInput): JsonLdNode {
|
|
|
227
227
|
)
|
|
228
228
|
),
|
|
229
229
|
uploadDate: video.uploadDate,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
...(video.contentUrl === undefined
|
|
235
|
-
? {}
|
|
236
|
-
: { contentUrl: video.contentUrl }),
|
|
237
|
-
...(video.embedUrl === undefined ? {} : { embedUrl: video.embedUrl }),
|
|
230
|
+
description: video.description,
|
|
231
|
+
duration,
|
|
232
|
+
contentUrl: video.contentUrl,
|
|
233
|
+
embedUrl: video.embedUrl,
|
|
238
234
|
};
|
|
239
235
|
}
|
package/src/jsonld/website.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HttpsUrl } from "../url.ts";
|
|
2
2
|
import { type OrganizationId, websiteId } from "./ids.ts";
|
|
3
|
-
import { alternateName, type JsonLdNode } from "./node.ts";
|
|
3
|
+
import { alternateName, type JsonLdNode, reference } from "./node.ts";
|
|
4
4
|
|
|
5
5
|
export interface WebsiteInput {
|
|
6
6
|
/**
|
|
@@ -50,9 +50,7 @@ export function website(site: WebsiteInput): JsonLdNode {
|
|
|
50
50
|
"@id": websiteId(site.url),
|
|
51
51
|
name: site.name,
|
|
52
52
|
url: site.url,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
? {}
|
|
56
|
-
: { publisher: { "@id": site.publisher } }),
|
|
53
|
+
alternateName: alternateName(site.name, site.alternateName),
|
|
54
|
+
publisher: reference(site.publisher),
|
|
57
55
|
};
|
|
58
56
|
}
|
package/src/meta/content.ts
CHANGED
|
@@ -9,32 +9,9 @@ import type { RobotsPolicy } from "./robots.ts";
|
|
|
9
9
|
import type { ShareImage } from "./share-image.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* What only a consumer can supply: a page's words, and
|
|
12
|
+
* What only a consumer can supply: a page's words, and whether it is an article.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* What kind of thing a page represents, for `og:type`.
|
|
17
|
-
*
|
|
18
|
-
* The whole vocabulary Open Graph defines, and a union for the reason
|
|
19
|
-
* `RobotsDirective` is one: it is small and closed, and a value outside it is
|
|
20
|
-
* not an error anywhere — a scraper that does not recognise `og:type` falls
|
|
21
|
-
* back to `website` and renders a perfectly ordinary card. So `"artcile"` costs
|
|
22
|
-
* you the article treatment and looks exactly like success.
|
|
23
|
-
*
|
|
24
|
-
* `website` is the default and the right answer for most pages. `article` is
|
|
25
|
-
* the one that earns anything extra: it is what tells a scraper this is a dated
|
|
26
|
-
* piece of writing rather than a section of the site.
|
|
27
|
-
*
|
|
28
|
-
* Reference: <https://ogp.me/#types>
|
|
29
|
-
*/
|
|
30
|
-
export type OpenGraphType =
|
|
31
|
-
| "website"
|
|
32
|
-
| "article"
|
|
33
|
-
| "book"
|
|
34
|
-
| "profile"
|
|
35
|
-
| `music.${"song" | "album" | "playlist" | "radio_station"}`
|
|
36
|
-
| `video.${"movie" | "episode" | "tv_show" | "other"}`;
|
|
37
|
-
|
|
38
15
|
/**
|
|
39
16
|
* A page that is a dated piece of writing, said once.
|
|
40
17
|
*
|
|
@@ -48,10 +25,11 @@ export type OpenGraphType =
|
|
|
48
25
|
* Supplying this is what emits the `Article` node, `og:type: article` and the
|
|
49
26
|
* `article:*` properties together. They are three statements of one fact, and a
|
|
50
27
|
* page that could make them separately is a page that could make two of them.
|
|
51
|
-
* That is why `
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
28
|
+
* That is why there is no `type` to set: saying a page is an article without
|
|
29
|
+
* saying when it was published is the half-statement this exists to prevent.
|
|
30
|
+
* Open Graph's other kinds (`book`, `profile`, `video.*`) each come with fields
|
|
31
|
+
* of their own; one would be added the same way, as a field that is the
|
|
32
|
+
* description, with the two made exclusive then.
|
|
55
33
|
*/
|
|
56
34
|
export interface ArticleContent {
|
|
57
35
|
/**
|
|
@@ -99,34 +77,8 @@ export interface ArticleContent {
|
|
|
99
77
|
readonly images?: readonly ImageAsset[];
|
|
100
78
|
}
|
|
101
79
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
* A union rather than two optional fields, so the pair cannot both be set. They
|
|
106
|
-
* answer the same question — `article` decides `og:type` — and a page that
|
|
107
|
-
* supplied both would have one of them silently ignored, which is the shape of
|
|
108
|
-
* mistake this folder keeps removing rather than reporting.
|
|
109
|
-
*
|
|
110
|
-
* Both branches allow the fields to be absent, so an ordinary page writes
|
|
111
|
-
* neither and gets `website`.
|
|
112
|
-
*/
|
|
113
|
-
export type PageKind =
|
|
114
|
-
| {
|
|
115
|
-
readonly type?: Exclude<OpenGraphType, "article">;
|
|
116
|
-
readonly article?: never;
|
|
117
|
-
}
|
|
118
|
-
| {
|
|
119
|
-
readonly type?: never;
|
|
120
|
-
/**
|
|
121
|
-
* This page's publication details, if it is a dated piece of writing.
|
|
122
|
-
*
|
|
123
|
-
* One field, three outputs — see `ArticleContent`.
|
|
124
|
-
*/
|
|
125
|
-
readonly article: ArticleContent;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/** The fields every page has, whatever kind it is. */
|
|
129
|
-
export interface MetaContentBase {
|
|
80
|
+
/** What a page passes to `metaFor`: its words, its picture, and whether it is an article. */
|
|
81
|
+
export interface MetaContent {
|
|
130
82
|
readonly title: string;
|
|
131
83
|
/**
|
|
132
84
|
* Required: a page without a description leaves search engines and every
|
|
@@ -172,18 +124,14 @@ export interface MetaContentBase {
|
|
|
172
124
|
* and never sees the page they end up on.
|
|
173
125
|
*/
|
|
174
126
|
readonly jsonLd?: readonly JsonLdNode[];
|
|
127
|
+
/**
|
|
128
|
+
* This page's publication details, if it is a dated piece of writing:
|
|
129
|
+
* `og:type` becomes `article`, with its `article:*` properties and an
|
|
130
|
+
* `Article` node. Without it a page is a `website`. See `ArticleContent`.
|
|
131
|
+
*/
|
|
132
|
+
readonly article?: ArticleContent;
|
|
175
133
|
}
|
|
176
134
|
|
|
177
|
-
/**
|
|
178
|
-
* What a page passes to `metaFor`: its words, its picture, and what kind of
|
|
179
|
-
* thing it is.
|
|
180
|
-
*
|
|
181
|
-
* An intersection rather than one interface because `PageKind` is a union, and
|
|
182
|
-
* that union is the whole point — see it for why `type` and `article` cannot
|
|
183
|
-
* both be written.
|
|
184
|
-
*/
|
|
185
|
-
export type MetaContent = MetaContentBase & PageKind;
|
|
186
|
-
|
|
187
135
|
/** Browser-UI tint. A single colour, or one per `prefers-color-scheme`. */
|
|
188
136
|
export type ThemeColor =
|
|
189
137
|
| string
|
package/src/meta/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
serializeJsonLd,
|
|
38
38
|
} from "../jsonld/index.ts";
|
|
39
39
|
import type { HttpsUrl } from "../url.ts";
|
|
40
|
-
import type {
|
|
40
|
+
import type { MetaContent, ThemeColor } from "./content.ts";
|
|
41
41
|
import { robotsContent, warnAboutRobots } from "./robots.ts";
|
|
42
42
|
import {
|
|
43
43
|
type OgImage,
|
|
@@ -47,13 +47,7 @@ import {
|
|
|
47
47
|
import { link, type MetaTag, meta, preamble } from "./tag.ts";
|
|
48
48
|
import { type SiteVerification, verificationTags } from "./verification.ts";
|
|
49
49
|
|
|
50
|
-
export type {
|
|
51
|
-
ArticleContent,
|
|
52
|
-
MetaContent,
|
|
53
|
-
OpenGraphType,
|
|
54
|
-
PageKind,
|
|
55
|
-
ThemeColor,
|
|
56
|
-
} from "./content.ts";
|
|
50
|
+
export type { ArticleContent, MetaContent, ThemeColor } from "./content.ts";
|
|
57
51
|
export type { RobotsDirective, RobotsPolicy } from "./robots.ts";
|
|
58
52
|
export {
|
|
59
53
|
fitDescription,
|
|
@@ -66,15 +60,8 @@ export {
|
|
|
66
60
|
export type { MetaTag } from "./tag.ts";
|
|
67
61
|
export type { SiteVerification } from "./verification.ts";
|
|
68
62
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
*
|
|
72
|
-
* `PageKind` is intersected rather than inherited so that `Omit` applies to the
|
|
73
|
-
* base alone: omitting a key from a union would flatten it and lose the
|
|
74
|
-
* exclusivity that union exists for.
|
|
75
|
-
*/
|
|
76
|
-
export type MetaInput<L extends string> = Omit<MetaContentBase, "image"> &
|
|
77
|
-
PageKind &
|
|
63
|
+
/** Everything the head needs: what the page supplies, plus what lib derives. */
|
|
64
|
+
export type MetaInput<L extends string> = Omit<MetaContent, "image"> &
|
|
78
65
|
MetaInputDerived<L>;
|
|
79
66
|
|
|
80
67
|
interface MetaInputDerived<L extends string> extends ChromeInput {
|
|
@@ -90,14 +77,9 @@ interface MetaInputDerived<L extends string> extends ChromeInput {
|
|
|
90
77
|
/** The share image, already resolved against the site origin. */
|
|
91
78
|
readonly image: OgImage;
|
|
92
79
|
/**
|
|
93
|
-
* `ArticleContent.images`, already resolved against the site origin
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* is a union and `Omit` on a union flattens it — which is the whole reason
|
|
97
|
-
* `MetaInput` intersects that union separately instead of omitting through
|
|
98
|
-
* it. So the resolved form arrives beside the page's own, exactly as
|
|
99
|
-
* `image` does: a page states `ShareImage`, lib hands `buildMeta` an
|
|
100
|
-
* `OgImage`.
|
|
80
|
+
* `ArticleContent.images`, already resolved against the site origin: the
|
|
81
|
+
* page states its crops, as it states `image`, and lib hands `buildMeta`
|
|
82
|
+
* their absolute URLs beside them.
|
|
101
83
|
*/
|
|
102
84
|
readonly articleImages?: readonly HttpsUrl[];
|
|
103
85
|
/**
|
|
@@ -288,16 +270,12 @@ export function buildMeta<L extends string>(input: MetaInput<L>): DocumentTags {
|
|
|
288
270
|
|
|
289
271
|
// Open Graph
|
|
290
272
|
//
|
|
291
|
-
// The type follows the content rather than being stated beside it:
|
|
292
|
-
//
|
|
293
|
-
// about one thing, and only one of them could be right.
|
|
273
|
+
// The type follows the content rather than being stated beside it: an
|
|
274
|
+
// article is a page that describes one.
|
|
294
275
|
tags.push(
|
|
295
276
|
meta({
|
|
296
277
|
property: "og:type",
|
|
297
|
-
content:
|
|
298
|
-
input.article === undefined
|
|
299
|
-
? (input.type ?? "website")
|
|
300
|
-
: "article",
|
|
278
|
+
content: input.article === undefined ? "website" : "article",
|
|
301
279
|
})
|
|
302
280
|
);
|
|
303
281
|
tags.push(meta({ property: "og:title", content: input.title }));
|
package/src/project.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { AnalyticsSettings } from "./analytics/index.ts";
|
|
2
2
|
import type {
|
|
3
|
-
LocaleMetaOverride,
|
|
4
3
|
LocaleNotEnabled,
|
|
5
4
|
LocalesOf,
|
|
6
5
|
RoutingConfig,
|
|
@@ -131,10 +130,6 @@ export interface ProjectInput<L extends string, Routes> {
|
|
|
131
130
|
* reads like the whole truth when it is only a patch.
|
|
132
131
|
*/
|
|
133
132
|
readonly overrideRouting?: Partial<RoutingConfig<L>>;
|
|
134
|
-
/** Restates a locale's label. Its writing direction is not overridable. */
|
|
135
|
-
readonly overrideLocaleMeta?: Readonly<
|
|
136
|
-
Partial<Record<L, LocaleMetaOverride>>
|
|
137
|
-
>;
|
|
138
133
|
/** Adds to the site's `defaultFonts`, or replaces one by key. */
|
|
139
134
|
readonly overrideFonts?: Fonts;
|
|
140
135
|
/**
|
|
@@ -237,8 +232,9 @@ export type ProjectChecks<
|
|
|
237
232
|
// Against the published locales rather than the site's list: a
|
|
238
233
|
// segment spelled for a language this deployment does not
|
|
239
234
|
// publish is a word that appears in no URL it builds. Mapped
|
|
240
|
-
// over the keys written
|
|
241
|
-
//
|
|
235
|
+
// over the keys written: `T` is inferred from the literal, so a
|
|
236
|
+
// stray key just becomes part of `T` and there is no fresh object
|
|
237
|
+
// for excess-property checking to fire on.
|
|
242
238
|
readonly pageSegmentByLocale?: {
|
|
243
239
|
readonly [K in StringKeys<
|
|
244
240
|
NonNullable<T["overrideRouting"]>["pageSegmentByLocale"]
|
|
@@ -249,18 +245,8 @@ export type ProjectChecks<
|
|
|
249
245
|
T["overrideRoutes"],
|
|
250
246
|
StringKeys<Routes>
|
|
251
247
|
>;
|
|
252
|
-
// Nested, so that `dir` is rejected inside each locale's entry.
|
|
253
|
-
// Declaring the field as `Pick<LocaleMeta, "label">` is not enough:
|
|
254
|
-
// `T` is *inferred* from the literal, so a stray `dir` just becomes
|
|
255
|
-
// part of `T` and there is no fresh object for excess-property
|
|
256
|
-
// checking to fire on.
|
|
257
|
-
readonly overrideLocaleMeta?: {
|
|
258
|
-
readonly [K in StringKeys<T["overrideLocaleMeta"]>]: K extends Published
|
|
259
|
-
? NoExcessKeys<T["overrideLocaleMeta"][K], keyof LocaleMetaOverride>
|
|
260
|
-
: LocaleNotEnabled<K>;
|
|
261
|
-
};
|
|
262
248
|
// The clock's locales must be ones this project publishes, as for
|
|
263
|
-
// `
|
|
249
|
+
// `pageSegmentByLocale` above. That every one it publishes is there is
|
|
264
250
|
// checked in `ProjectOverrideChecks`: a conditional on `T` here would make
|
|
265
251
|
// the constraint circular.
|
|
266
252
|
readonly formats?: {
|
package/src/routes/define.ts
CHANGED
|
@@ -353,19 +353,6 @@ export type SectionPrefixOf<Routes, Overrides> = FirstSegment<
|
|
|
353
353
|
EnabledSlugs<Routes, Overrides>
|
|
354
354
|
>;
|
|
355
355
|
|
|
356
|
-
/**
|
|
357
|
-
* The sections with no page of their own — a prefix that heads a group but is
|
|
358
|
-
* not itself a slug anything is published at.
|
|
359
|
-
*
|
|
360
|
-
* These are the only sections whose link a consumer may choose. Where an index
|
|
361
|
-
* page exists it is the answer, and offering a second one would invite a
|
|
362
|
-
* heading that points somewhere other than the page it names.
|
|
363
|
-
*/
|
|
364
|
-
export type OrphanSectionPrefixOf<Routes, Overrides> = Exclude<
|
|
365
|
-
SectionPrefixOf<Routes, Overrides>,
|
|
366
|
-
EnabledSlugs<Routes, Overrides>
|
|
367
|
-
>;
|
|
368
|
-
|
|
369
356
|
/**
|
|
370
357
|
* The route ids that survive a given override map, computed from literal types.
|
|
371
358
|
*
|
package/src/site/api.ts
CHANGED
|
@@ -51,7 +51,6 @@ export interface LlmsOptions<
|
|
|
51
51
|
RouteId extends string,
|
|
52
52
|
L extends string,
|
|
53
53
|
Prefix extends string = string,
|
|
54
|
-
OrphanPrefix extends string = Prefix,
|
|
55
54
|
> {
|
|
56
55
|
/**
|
|
57
56
|
* This route's name and one-line summary, in the file's language.
|
|
@@ -88,21 +87,6 @@ export interface LlmsOptions<
|
|
|
88
87
|
* comparison that quietly never matches and leaves the default in place.
|
|
89
88
|
*/
|
|
90
89
|
sectionHeading?(prefix: Prefix, locale: L): string | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* Where a section with no index page should link.
|
|
93
|
-
*
|
|
94
|
-
* Separate from the heading because it is an enrichment rather than half of
|
|
95
|
-
* one decision: a section can be renamed without being linked, and linked
|
|
96
|
-
* without being renamed.
|
|
97
|
-
*
|
|
98
|
-
* Only sections that *have* no page of their own are offered. Where an index
|
|
99
|
-
* page exists it is already the link, and `OrphanPrefix` excludes it — so
|
|
100
|
-
* pointing a named family somewhere other than the page it is named after is
|
|
101
|
-
* a compile error rather than a choice. Returning `undefined` leaves the
|
|
102
|
-
* heading unlinked, which is the right answer when those pages are listed
|
|
103
|
-
* nowhere else.
|
|
104
|
-
*/
|
|
105
|
-
sectionLink?(prefix: OrphanPrefix, locale: L): RouteId | undefined;
|
|
106
90
|
/** The `>` block under the title: what this site is, in a sentence or two. */
|
|
107
91
|
readonly summary?: string;
|
|
108
92
|
/**
|
|
@@ -132,8 +116,6 @@ export interface Site<
|
|
|
132
116
|
* be, and no annotation should have to state it.
|
|
133
117
|
*/
|
|
134
118
|
Prefix extends string = string,
|
|
135
|
-
/** Of those, the ones with no index page of their own. */
|
|
136
|
-
OrphanPrefix extends string = Prefix,
|
|
137
119
|
> {
|
|
138
120
|
readonly locales: readonly L[];
|
|
139
121
|
readonly defaultLocale: L;
|
|
@@ -356,9 +338,7 @@ export interface Site<
|
|
|
356
338
|
* the first time a route is switched off or a slug is retranslated, and
|
|
357
339
|
* nothing reports it, because nothing reads the file during a build.
|
|
358
340
|
*/
|
|
359
|
-
llms(
|
|
360
|
-
options?: LlmsOptions<RouteId, L, Prefix, OrphanPrefix>
|
|
361
|
-
): GeneratedFile;
|
|
341
|
+
llms(options?: LlmsOptions<RouteId, L, Prefix>): GeneratedFile;
|
|
362
342
|
|
|
363
343
|
/**
|
|
364
344
|
* Old URLs that must keep working, resolved — for `siteRoutes()` only.
|