@nakedcreativity/cee-ms-node 2.0.0-beta.1 → 2.0.0-beta.10
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 +202 -10
- package/lib/draft.d.ts +7 -4
- package/lib/draft.js +47 -25
- package/lib/draft.js.map +1 -1
- package/lib/index.d.ts +31 -4
- package/lib/index.js +131 -23
- package/lib/index.js.map +1 -1
- package/lib/interfaces.d.ts +295 -10
- package/lib/interfaces.js +1 -0
- package/lib/interfaces.js.map +1 -1
- package/lib/links.d.ts +67 -2
- package/lib/links.js +201 -7
- package/lib/links.js.map +1 -1
- package/lib/preview-token.d.ts +13 -4
- package/lib/preview-token.js +6 -2
- package/lib/preview-token.js.map +1 -1
- package/lib/social-meta.d.ts +15 -0
- package/lib/social-meta.js +74 -0
- package/lib/social-meta.js.map +1 -0
- package/lib/storage.d.ts +1 -0
- package/lib/storage.js +3 -1
- package/lib/storage.js.map +1 -1
- package/lib/structured-data.d.ts +35 -0
- package/lib/structured-data.js +591 -0
- package/lib/structured-data.js.map +1 -0
- package/lib/tags/blog.d.ts +3 -2
- package/lib/tags/blog.js +14 -5
- package/lib/tags/blog.js.map +1 -1
- package/lib/tags/index.d.ts +2 -0
- package/lib/tags/index.js +2 -0
- package/lib/tags/index.js.map +1 -1
- package/lib/tags/social-meta.d.ts +6 -0
- package/lib/tags/social-meta.js +19 -0
- package/lib/tags/social-meta.js.map +1 -0
- package/lib/tags/structured-data.d.ts +8 -0
- package/lib/tags/structured-data.js +36 -0
- package/lib/tags/structured-data.js.map +1 -0
- package/lib/views.d.ts +76 -0
- package/lib/views.js +426 -0
- package/lib/views.js.map +1 -0
- package/package.json +8 -1
package/lib/links.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LinkDrop = void 0;
|
|
3
|
+
exports.RowDrop = exports.DocumentDrop = exports.LinkDrop = void 0;
|
|
4
|
+
exports.resolveRichText = resolveRichText;
|
|
4
5
|
exports.wrapLinks = wrapLinks;
|
|
6
|
+
exports.referencedRows = referencedRows;
|
|
7
|
+
exports.linkContextFor = linkContextFor;
|
|
8
|
+
exports.bylineAuthors = bylineAuthors;
|
|
5
9
|
const liquidjs_1 = require("liquidjs");
|
|
6
10
|
/** The kinds of link Cee.ms stores: `{ kind, url, pageId?, documentId?, anchor?, email? }`. */
|
|
7
11
|
const LINK_KINDS = ['url', 'page', 'document', 'anchor', 'email'];
|
|
@@ -10,6 +14,8 @@ const LINK_KINDS = ['url', 'page', 'document', 'anchor', 'email'];
|
|
|
10
14
|
* link field still works where templates print it (`{{ item.url }}`), filter it (`| escape`, `| append`),
|
|
11
15
|
* compare it (`== '/about'`, `!= blank`) or measure it (`| size`); and its parts are there too
|
|
12
16
|
* (`{{ item.url.kind }}`, `.pageId`, `.anchor`). `| json` shows the whole link.
|
|
17
|
+
*
|
|
18
|
+
* A page link whose page isn't published reads as `''`, with `broken` true, so `{% if link %}` leaves it out.
|
|
13
19
|
*/
|
|
14
20
|
class LinkDrop extends liquidjs_1.Drop {
|
|
15
21
|
kind;
|
|
@@ -18,6 +24,7 @@ class LinkDrop extends liquidjs_1.Drop {
|
|
|
18
24
|
documentId;
|
|
19
25
|
anchor;
|
|
20
26
|
email;
|
|
27
|
+
broken;
|
|
21
28
|
constructor(link) {
|
|
22
29
|
super();
|
|
23
30
|
Object.assign(this, link);
|
|
@@ -41,21 +48,208 @@ class LinkDrop extends liquidjs_1.Drop {
|
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
exports.LinkDrop = LinkDrop;
|
|
51
|
+
/**
|
|
52
|
+
* A document value (`{ documentId, url, thumbnail, info }`) in templates, read from the published documents
|
|
53
|
+
* so a replaced file is right everywhere. It prints as its address, so `{{ download }}` and
|
|
54
|
+
* `{{ download.url }}` both work.
|
|
55
|
+
*/
|
|
56
|
+
class DocumentDrop extends liquidjs_1.Drop {
|
|
57
|
+
documentId;
|
|
58
|
+
url;
|
|
59
|
+
thumbnail;
|
|
60
|
+
info;
|
|
61
|
+
constructor(value) {
|
|
62
|
+
super();
|
|
63
|
+
Object.assign(this, value);
|
|
64
|
+
}
|
|
65
|
+
valueOf() {
|
|
66
|
+
return this.url;
|
|
67
|
+
}
|
|
68
|
+
toString() {
|
|
69
|
+
return this.url;
|
|
70
|
+
}
|
|
71
|
+
toJSON() {
|
|
72
|
+
return { ...this };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.DocumentDrop = DocumentDrop;
|
|
76
|
+
/**
|
|
77
|
+
* A row of a data collection, from a `row` field: its columns by key (`{{ author.name }}`,
|
|
78
|
+
* `{% image person.photo 'square' %}`), plus `_id`. It prints as its `name`, else its first text value.
|
|
79
|
+
*/
|
|
80
|
+
class RowDrop extends liquidjs_1.Drop {
|
|
81
|
+
_id;
|
|
82
|
+
constructor(row) {
|
|
83
|
+
super();
|
|
84
|
+
Object.assign(this, row);
|
|
85
|
+
}
|
|
86
|
+
toString() {
|
|
87
|
+
if (typeof this.name === 'string')
|
|
88
|
+
return this.name;
|
|
89
|
+
const first = Object.entries(this).find(([key, v]) => key !== '_id' && typeof v === 'string');
|
|
90
|
+
return first ? String(first[1]) : '';
|
|
91
|
+
}
|
|
92
|
+
valueOf() {
|
|
93
|
+
return this.toString();
|
|
94
|
+
}
|
|
95
|
+
toJSON() {
|
|
96
|
+
return { ...this };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.RowDrop = RowDrop;
|
|
44
100
|
const isLink = (value) => typeof value.kind === 'string' && LINK_KINDS.includes(value.kind) && typeof value.url === 'string';
|
|
45
|
-
|
|
46
|
-
|
|
101
|
+
const isRowRef = (value) => value.kind === 'row' && value.rowId !== undefined && value.collectionId !== undefined;
|
|
102
|
+
const isDocumentValue = (value) => value.documentId !== undefined && typeof value.url === 'string' && value.kind === undefined;
|
|
103
|
+
const RICH_LINK = /<a\b([^>]*)>/gi;
|
|
104
|
+
/**
|
|
105
|
+
* Rich text links carry the page they point at (`data-page-id`); their `href` comes from the published page,
|
|
106
|
+
* keeping any `#anchor`, and is dropped when that page isn't published. Documents' come from `documents`.
|
|
107
|
+
*/
|
|
108
|
+
function resolveRichText(html, context) {
|
|
109
|
+
if (!context.pageIndex && !context.documents)
|
|
110
|
+
return html;
|
|
111
|
+
return html.replace(RICH_LINK, (tag, attrs) => {
|
|
112
|
+
const pageId = /\bdata-page-id="([0-9a-f]{24})"/i.exec(attrs)?.[1];
|
|
113
|
+
const documentId = /\bdata-document-id="([0-9a-f]{24})"/i.exec(attrs)?.[1];
|
|
114
|
+
let href;
|
|
115
|
+
if (pageId && context.pageIndex) {
|
|
116
|
+
const anchor = /\bhref="[^"#]*#([^"]*)"/i.exec(attrs)?.[1];
|
|
117
|
+
const entry = context.pageIndex[pageId];
|
|
118
|
+
href = entry ? entry.path + (anchor ? `#${anchor}` : '') : null;
|
|
119
|
+
}
|
|
120
|
+
else if (documentId && context.documents) {
|
|
121
|
+
href = context.documents[documentId]?.url;
|
|
122
|
+
}
|
|
123
|
+
if (href === undefined)
|
|
124
|
+
return tag;
|
|
125
|
+
if (href === null)
|
|
126
|
+
return tag.replace(/\s*\bhref="[^"]*"/i, '');
|
|
127
|
+
const value = `href="${href.replace(/&/g, '&').replace(/"/g, '"')}"`;
|
|
128
|
+
return /\bhref="[^"]*"/i.test(attrs) ? tag.replace(/\bhref="[^"]*"/i, value) : `<a ${value}${attrs}>`;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function linkDrop(record, context) {
|
|
132
|
+
if (record.kind === 'page' && record.pageId && context.pageIndex) {
|
|
133
|
+
const entry = context.pageIndex[String(record.pageId)];
|
|
134
|
+
const anchor = record.anchor ? `#${record.anchor}` : '';
|
|
135
|
+
return new LinkDrop(entry ? { ...record, url: entry.path + anchor, broken: false } : { ...record, url: '', broken: true });
|
|
136
|
+
}
|
|
137
|
+
if (record.kind === 'document' && record.documentId && context.documents) {
|
|
138
|
+
const document = context.documents[String(record.documentId)];
|
|
139
|
+
if (document)
|
|
140
|
+
return new LinkDrop({ ...record, url: document.url });
|
|
141
|
+
}
|
|
142
|
+
return new LinkDrop(record);
|
|
143
|
+
}
|
|
144
|
+
/** Rows inside rows resolve this deep, so rows that point at each other can't loop. */
|
|
145
|
+
const ROW_DEPTH = 2;
|
|
146
|
+
/**
|
|
147
|
+
* Wraps every stored link in a published page, data file or draft in a `LinkDrop`, every document value in a
|
|
148
|
+
* `DocumentDrop` and every row reference in a `RowDrop`, in place, resolving each against `context`. A row
|
|
149
|
+
* that isn't published reads as nothing, and is left out of lists.
|
|
150
|
+
*/
|
|
151
|
+
function wrapLinks(value, context = {}, depth = 0) {
|
|
47
152
|
if (Array.isArray(value)) {
|
|
48
|
-
|
|
49
|
-
|
|
153
|
+
const wrapped = value.map((item) => wrapLinks(item, context, depth));
|
|
154
|
+
// a list of rows skips the ones that are gone
|
|
155
|
+
const kept = wrapped.filter((item, i) => item !== undefined || !isRowLike(value[i]));
|
|
156
|
+
value.splice(0, value.length, ...kept);
|
|
50
157
|
return value;
|
|
51
158
|
}
|
|
159
|
+
if (typeof value === 'string')
|
|
160
|
+
return (value.includes('<a') ? resolveRichText(value, context) : value);
|
|
52
161
|
if (!value || typeof value !== 'object' || value instanceof liquidjs_1.Drop)
|
|
53
162
|
return value;
|
|
54
163
|
const record = value;
|
|
55
164
|
if (isLink(record))
|
|
56
|
-
return
|
|
165
|
+
return linkDrop(record, context);
|
|
166
|
+
if (isRowRef(record)) {
|
|
167
|
+
if (!context.row || depth >= ROW_DEPTH)
|
|
168
|
+
return (context.row ? undefined : value);
|
|
169
|
+
const row = context.row(String(record.collectionId), String(record.rowId));
|
|
170
|
+
// a copy: rows come from data files cached across renders
|
|
171
|
+
return (row ? new RowDrop(wrapLinks(structuredClone(row), context, depth + 1)) : undefined);
|
|
172
|
+
}
|
|
173
|
+
if (isDocumentValue(record) && context.documents) {
|
|
174
|
+
const document = context.documents[String(record.documentId)];
|
|
175
|
+
return new DocumentDrop(document ? { ...record, ...document } : record);
|
|
176
|
+
}
|
|
57
177
|
for (const key of Object.keys(record))
|
|
58
|
-
record[key] = wrapLinks(record[key]);
|
|
178
|
+
record[key] = wrapLinks(record[key], context, depth);
|
|
59
179
|
return value;
|
|
60
180
|
}
|
|
181
|
+
const isRowLike = (value) => !!value && typeof value === 'object' && isRowRef(value);
|
|
182
|
+
/** The rows a page's row references and byline authors point at, by collection id. */
|
|
183
|
+
function referencedRows(value, found = new Map()) {
|
|
184
|
+
if (Array.isArray(value)) {
|
|
185
|
+
for (const item of value)
|
|
186
|
+
referencedRows(item, found);
|
|
187
|
+
}
|
|
188
|
+
else if (value && typeof value === 'object' && !(value instanceof liquidjs_1.Drop)) {
|
|
189
|
+
const record = value;
|
|
190
|
+
if (isRowRef(record) || (record.kind === 'person' && record.rowId !== undefined)) {
|
|
191
|
+
const collection = String(record.collectionId);
|
|
192
|
+
found.set(collection, (found.get(collection) ?? new Set()).add(String(record.rowId)));
|
|
193
|
+
}
|
|
194
|
+
else
|
|
195
|
+
for (const child of Object.values(record))
|
|
196
|
+
referencedRows(child, found);
|
|
197
|
+
}
|
|
198
|
+
return found;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The context for a site and the data files its page points at. People come from `site.json` `people` (with
|
|
202
|
+
* `sameAs`), so a person reads the same wherever they are referenced.
|
|
203
|
+
*/
|
|
204
|
+
function linkContextFor(site, files = []) {
|
|
205
|
+
const rows = new Map();
|
|
206
|
+
for (const file of files) {
|
|
207
|
+
if (!file?.rows)
|
|
208
|
+
continue;
|
|
209
|
+
rows.set(String(file._id), new Map(file.rows.map((row) => [String(row._id), row])));
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
pageIndex: site?.pageIndex,
|
|
213
|
+
documents: site?.documents,
|
|
214
|
+
row: (collectionId, rowId) => {
|
|
215
|
+
const person = site?.people?.[rowId];
|
|
216
|
+
return person ? { _id: rowId, ...person } : rows.get(collectionId)?.get(rowId);
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* An article's byline as templates and structured data read it, in order: `page.authors`. A person is their
|
|
222
|
+
* People row (`{{ author.name }}`, `{{ author.profile }}`); a guest is what was typed; the organisation is the
|
|
223
|
+
* site's. Each has `kind`, `name` and `url` (a person's profile page, else their first profile link).
|
|
224
|
+
*/
|
|
225
|
+
function bylineAuthors(page, site, context) {
|
|
226
|
+
const authors = [];
|
|
227
|
+
for (const author of page.byline?.authors ?? []) {
|
|
228
|
+
if (author?.kind === 'person') {
|
|
229
|
+
const row = context.row?.(String(author.collectionId), String(author.rowId));
|
|
230
|
+
if (!row)
|
|
231
|
+
continue;
|
|
232
|
+
const person = new RowDrop(wrapLinks(structuredClone(row), context, 1));
|
|
233
|
+
const profile = person.profile;
|
|
234
|
+
const sameAs = Array.isArray(person.sameAs) ? person.sameAs : [];
|
|
235
|
+
person.kind = 'person';
|
|
236
|
+
person.url = profile && String(profile) ? String(profile) : sameAs[0];
|
|
237
|
+
authors.push(person);
|
|
238
|
+
}
|
|
239
|
+
else if (author?.kind === 'guest') {
|
|
240
|
+
authors.push({ kind: 'guest', name: author.name, url: author.url, jobTitle: author.jobTitle });
|
|
241
|
+
}
|
|
242
|
+
else if (author?.kind === 'organisation') {
|
|
243
|
+
const org = site?.site?.organisation;
|
|
244
|
+
if (org?.name)
|
|
245
|
+
authors.push({
|
|
246
|
+
kind: 'organisation',
|
|
247
|
+
name: org.name,
|
|
248
|
+
url: org.url || site?.site?.url,
|
|
249
|
+
photo: org.logo,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return authors;
|
|
254
|
+
}
|
|
61
255
|
//# sourceMappingURL=links.js.map
|
package/lib/links.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.js","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"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"}
|
package/lib/preview-token.d.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Preview tokens, issued by the Cee.ms API and verified here. Format: `v1.<payload>.<signature>`, where
|
|
3
|
-
* `payload` is base64url JSON `{ p: pageId, e: expiresAtUnixSeconds }` and
|
|
4
|
-
* HMAC-SHA256 of `v1.<payload>` with the site's preview secret. The secret is per
|
|
5
|
-
* also proves the token is for this site.
|
|
3
|
+
* `payload` is base64url JSON `{ p: pageId, e: expiresAtUnixSeconds, a: apiUrl, o: editorOrigin }` and
|
|
4
|
+
* `signature` is base64url HMAC-SHA256 of `v1.<payload>` with the site's preview secret. The secret is per
|
|
5
|
+
* site, so a valid signature also proves the token is for this site, and that `a` and `o` came from Cee.ms.
|
|
6
6
|
*/
|
|
7
7
|
export interface PreviewTokenPayload {
|
|
8
8
|
/** Page id. */
|
|
9
9
|
p: string;
|
|
10
10
|
/** Expiry, Unix seconds. */
|
|
11
11
|
e: number;
|
|
12
|
+
/** The API that issued the token and serves its draft. */
|
|
13
|
+
a?: string;
|
|
14
|
+
/** The editor the token was issued for: the only page allowed to frame the draft. */
|
|
15
|
+
o?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Where a token was issued, for `signPreviewToken`. */
|
|
18
|
+
export interface PreviewIssuer {
|
|
19
|
+
apiUrl: string;
|
|
20
|
+
editorOrigin: string;
|
|
12
21
|
}
|
|
13
22
|
export type VerifyResult = {
|
|
14
23
|
ok: true;
|
|
@@ -17,5 +26,5 @@ export type VerifyResult = {
|
|
|
17
26
|
ok: false;
|
|
18
27
|
reason: string;
|
|
19
28
|
};
|
|
20
|
-
export declare function signPreviewToken(secret: string, pageId: string, expiresAt: Date): string;
|
|
29
|
+
export declare function signPreviewToken(secret: string, pageId: string, expiresAt: Date, issuer?: PreviewIssuer): string;
|
|
21
30
|
export declare function verifyPreviewToken(secret: string, token: string, pageId: string, now?: Date): VerifyResult;
|
package/lib/preview-token.js
CHANGED
|
@@ -4,8 +4,12 @@ exports.signPreviewToken = signPreviewToken;
|
|
|
4
4
|
exports.verifyPreviewToken = verifyPreviewToken;
|
|
5
5
|
const crypto_1 = require("crypto");
|
|
6
6
|
const sign = (secret, data) => (0, crypto_1.createHmac)('sha256', secret).update(data).digest('base64url');
|
|
7
|
-
function signPreviewToken(secret, pageId, expiresAt) {
|
|
8
|
-
const payload = {
|
|
7
|
+
function signPreviewToken(secret, pageId, expiresAt, issuer) {
|
|
8
|
+
const payload = {
|
|
9
|
+
p: pageId,
|
|
10
|
+
e: Math.floor(expiresAt.getTime() / 1000),
|
|
11
|
+
...(issuer ? { a: issuer.apiUrl, o: issuer.editorOrigin } : {}),
|
|
12
|
+
};
|
|
9
13
|
const body = 'v1.' + Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
10
14
|
return body + '.' + sign(secret, body);
|
|
11
15
|
}
|
package/lib/preview-token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-token.js","sourceRoot":"","sources":["../src/preview-token.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"preview-token.js","sourceRoot":"","sources":["../src/preview-token.ts"],"names":[],"mappings":";;AA8BA,4CAQC;AAED,gDAqBC;AA7DD,mCAAqD;AA4BrD,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,EAAE,CAAC,IAAA,mBAAU,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAE7G,SAAgB,gBAAgB,CAAC,MAAc,EAAE,MAAc,EAAE,SAAe,EAAE,MAAsB;IACpG,MAAM,OAAO,GAAwB;QACjC,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;QACzC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChF,OAAO,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,SAAgB,kBAAkB,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IAC9F,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAEvF,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,CAAC,IAAA,wBAAe,EAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,OAA4B,CAAC;IACjC,IAAI,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC9G,IAAI,OAAO,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACrE,IAAI,OAAO,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CeemsPage, CeemsSite } from './interfaces';
|
|
2
|
+
/** One `<meta>`: Open Graph tags take `property`, X's take `name`. */
|
|
3
|
+
export interface SocialMetaTag {
|
|
4
|
+
attribute: 'property' | 'name';
|
|
5
|
+
key: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The page's link-preview tags: Open Graph, read by Facebook, LinkedIn, WhatsApp, Slack and others, and X's
|
|
10
|
+
* card. Built from what the page already publishes for search (title, description, social image, dates), so
|
|
11
|
+
* a template needs no more than `{% socialmeta %}`. X reads the title, description and image from Open Graph.
|
|
12
|
+
*/
|
|
13
|
+
export declare function socialMeta(site: CeemsSite, page: CeemsPage): SocialMetaTag[];
|
|
14
|
+
/** The tags as HTML, their content escaped for an attribute. */
|
|
15
|
+
export declare function renderSocialMeta(tags: SocialMetaTag[]): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.socialMeta = socialMeta;
|
|
4
|
+
exports.renderSocialMeta = renderSocialMeta;
|
|
5
|
+
const structured_data_1 = require("./structured-data");
|
|
6
|
+
/**
|
|
7
|
+
* The page's link-preview tags: Open Graph, read by Facebook, LinkedIn, WhatsApp, Slack and others, and X's
|
|
8
|
+
* card. Built from what the page already publishes for search (title, description, social image, dates), so
|
|
9
|
+
* a template needs no more than `{% socialmeta %}`. X reads the title, description and image from Open Graph.
|
|
10
|
+
*/
|
|
11
|
+
function socialMeta(site, page) {
|
|
12
|
+
const root = String(site.site?.url ?? '').replace(/\/+$/, '');
|
|
13
|
+
const path = page.fullpath && page.fullpath !== 'home' ? `/${page.fullpath}` : '/';
|
|
14
|
+
const absolute = (href) => /^https?:\/\//i.test(href) ? href : `${root}${href.startsWith('/') ? '' : '/'}${href}`;
|
|
15
|
+
const entity = (0, structured_data_1.entityOf)(page);
|
|
16
|
+
const image = (0, structured_data_1.imageUrl)(page.social);
|
|
17
|
+
const tags = [];
|
|
18
|
+
const og = (key, value) => {
|
|
19
|
+
const content = (0, structured_data_1.text)(value);
|
|
20
|
+
if (content)
|
|
21
|
+
tags.push({ attribute: 'property', key, content });
|
|
22
|
+
};
|
|
23
|
+
const x = (key, value) => {
|
|
24
|
+
const content = (0, structured_data_1.text)(value);
|
|
25
|
+
if (content)
|
|
26
|
+
tags.push({ attribute: 'name', key, content });
|
|
27
|
+
};
|
|
28
|
+
og('og:type', entity === 'ARTICLE' ? 'article' : entity === 'PERSON' ? 'profile' : 'website');
|
|
29
|
+
og('og:title', (0, structured_data_1.text)(page.title) ?? (0, structured_data_1.text)(page.name) ?? site.site?.name);
|
|
30
|
+
og('og:description', (0, structured_data_1.plain)(page.description));
|
|
31
|
+
if (root)
|
|
32
|
+
og('og:url', root + path);
|
|
33
|
+
og('og:site_name', site.site?.name);
|
|
34
|
+
if (image) {
|
|
35
|
+
og('og:image', absolute(image));
|
|
36
|
+
if (page.social?.width && page.social?.height) {
|
|
37
|
+
og('og:image:width', page.social.width);
|
|
38
|
+
og('og:image:height', page.social.height);
|
|
39
|
+
}
|
|
40
|
+
og('og:image:alt', page.social?.alt);
|
|
41
|
+
}
|
|
42
|
+
if (entity === 'ARTICLE') {
|
|
43
|
+
const published = (0, structured_data_1.isoDate)(page.date) ?? (0, structured_data_1.isoDate)(page.publishedAt);
|
|
44
|
+
og('article:published_time', published);
|
|
45
|
+
og('article:modified_time', (0, structured_data_1.isoDate)(page.updatedAt) ?? published);
|
|
46
|
+
for (const author of page.authors ?? []) {
|
|
47
|
+
const profile = (0, structured_data_1.text)(author.profile);
|
|
48
|
+
if (profile)
|
|
49
|
+
og('article:author', absolute(profile));
|
|
50
|
+
}
|
|
51
|
+
for (const tag of page.tags ?? [])
|
|
52
|
+
og('article:tag', tag);
|
|
53
|
+
}
|
|
54
|
+
x('twitter:card', image ? 'summary_large_image' : 'summary');
|
|
55
|
+
const org = site.site?.organisation;
|
|
56
|
+
x('twitter:site', xHandle(org?.socialProfiles?.x ? [org.socialProfiles.x] : org?.sameAs));
|
|
57
|
+
return tags;
|
|
58
|
+
}
|
|
59
|
+
/** The organisation's X account, `@name`, from its profile links. */
|
|
60
|
+
function xHandle(sameAs) {
|
|
61
|
+
for (const url of sameAs ?? []) {
|
|
62
|
+
const match = /^https?:\/\/(?:www\.)?(?:x|twitter)\.com\/@?([A-Za-z0-9_]{1,15})\/?$/i.exec(url.trim());
|
|
63
|
+
if (match)
|
|
64
|
+
return `@${match[1]}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** The tags as HTML, their content escaped for an attribute. */
|
|
68
|
+
function renderSocialMeta(tags) {
|
|
69
|
+
return tags.map((t) => `<meta ${t.attribute}="${t.key}" content="${escapeAttribute(t.content)}">`).join('');
|
|
70
|
+
}
|
|
71
|
+
function escapeAttribute(value) {
|
|
72
|
+
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=social-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social-meta.js","sourceRoot":"","sources":["../src/social-meta.ts"],"names":[],"mappings":"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"}
|
package/lib/storage.d.ts
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
export interface Storage {
|
|
3
3
|
read(key: string): Promise<string>;
|
|
4
4
|
}
|
|
5
|
+
/** Files under `folder`, with or without its trailing separator. */
|
|
5
6
|
export declare function localStorage(folder: string): Storage;
|
|
6
7
|
export declare function s3Storage(bucket: string, region?: string): Storage;
|
package/lib/storage.js
CHANGED
|
@@ -6,9 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.localStorage = localStorage;
|
|
7
7
|
exports.s3Storage = s3Storage;
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
11
|
+
/** Files under `folder`, with or without its trailing separator. */
|
|
10
12
|
function localStorage(folder) {
|
|
11
|
-
return { read: (key) => fs_1.default.promises.readFile(folder
|
|
13
|
+
return { read: (key) => fs_1.default.promises.readFile(path_1.default.join(folder, key), 'utf-8') };
|
|
12
14
|
}
|
|
13
15
|
function s3Storage(bucket, region) {
|
|
14
16
|
const client = new client_s3_1.S3Client(region ? { region } : {});
|
package/lib/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":";;;;;AAUA,oCAEC;AAED,8BASC;AAvBD,4CAAoB;AACpB,gDAAwB;AACxB,kDAAgE;AAOhE,oEAAoE;AACpE,SAAgB,YAAY,CAAC,MAAc;IACvC,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC;AACpF,CAAC;AAED,SAAgB,SAAS,CAAC,MAAc,EAAE,MAAe;IACrD,MAAM,MAAM,GAAG,IAAI,oBAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtD,OAAO;QACH,KAAK,CAAC,IAAI,CAAC,GAAG;YACV,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,4BAAgB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YACrF,IAAI,CAAC,MAAM,CAAC,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;YAC1D,OAAO,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;KACJ,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CeemsImage, CeemsPage, CeemsSite, PageEntity, StructuredDataDefaults, StructuredDataType } from './interfaces';
|
|
2
|
+
/** The types a page or the site's defaults can switch on, in the order the tag outputs them. */
|
|
3
|
+
export declare const STRUCTURED_DATA_TYPES: readonly StructuredDataType[];
|
|
4
|
+
/** What `{% structureddata %}` can be limited to: the types, and the page's entity (its product, event…). */
|
|
5
|
+
export type StructuredDataPart = StructuredDataType | 'ENTITY';
|
|
6
|
+
export declare const STRUCTURED_DATA_PARTS: readonly StructuredDataPart[];
|
|
7
|
+
/**
|
|
8
|
+
* Used when the site has published no defaults. The site and organisation are described once, on the home
|
|
9
|
+
* page; other pages refer to them by `@id` with a short copy.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_STRUCTURED_DATA: StructuredDataDefaults;
|
|
12
|
+
type Node = Record<string, unknown>;
|
|
13
|
+
/** The page's entity, defaulting as Cee.ms publishes it, for pages published before entities. */
|
|
14
|
+
export declare function entityOf(page: CeemsPage): PageEntity;
|
|
15
|
+
/** The types a page outputs: its own choice, else the site's defaults for its kind. */
|
|
16
|
+
export declare function enabledTypes(site: CeemsSite, page: CeemsPage): StructuredDataType[];
|
|
17
|
+
/**
|
|
18
|
+
* The page's schema.org graph: one node for each enabled type that has the data it needs, and one for its
|
|
19
|
+
* entity. A node missing a required property (an event with no start date, a product with neither price nor
|
|
20
|
+
* reviews) is left out rather than output invalid. `only` limits the graph to those parts.
|
|
21
|
+
*/
|
|
22
|
+
export declare function structuredDataGraph(site: CeemsSite, page: CeemsPage, only?: StructuredDataPart[]): {
|
|
23
|
+
'@context': string;
|
|
24
|
+
'@graph': Node[];
|
|
25
|
+
};
|
|
26
|
+
/** JSON for a `<script>`: `<`, `>` and `&` are escaped so content can't end the script. */
|
|
27
|
+
export declare function serialiseJsonLd(value: unknown): string;
|
|
28
|
+
export declare function imageUrl(image?: CeemsImage | null): string | undefined;
|
|
29
|
+
/** A date published as an ISO string (or a `Date`, for custom pages) as an ISO string. */
|
|
30
|
+
export declare function isoDate(value: unknown): string | undefined;
|
|
31
|
+
/** A non-empty trimmed string, else `undefined`. Stored links (`LinkDrop`) read as their address. */
|
|
32
|
+
export declare function text(value: unknown): string | undefined;
|
|
33
|
+
/** Rich text as plain text, for properties that take no markup. */
|
|
34
|
+
export declare function plain(value: unknown): unknown;
|
|
35
|
+
export {};
|