bearminion 1.3.0 → 1.5.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/CHANGELOG.md +12 -0
- package/dist/astro.d.ts +2 -2
- package/dist/astro.js +4 -4
- package/dist/content.d.ts +1 -1
- package/dist/content.js +2 -2
- package/dist/index.d.ts +49 -21
- package/dist/index.js +28 -6
- package/dist/{options-BdPRHcqA.d.ts → options-DBfoi9D1.d.ts} +1 -1
- package/dist/{page-DJ6L8RmH.d.ts → page-B5NoN74Q.d.ts} +118 -10
- package/dist/{page-ON4Jkcm-.js → page-NOFVEqn1.js} +125 -3
- package/dist/{preview-CTVEBdEY.js → preview--S1QUrMM.js} +1 -1
- package/dist/{reload-Cq434rtI.js → reload-D0vwI0OT.js} +1 -1
- package/dist/{theme-DMpt3hcT.js → theme-DFjHjgFO.js} +1 -1
- package/dist/{theme-colors-B2kAO2n6.js → theme-colors-CbCyVANH.js} +52 -51
- package/dist/ui/mount.js +1094 -570
- package/package.json +4 -4
|
@@ -203,6 +203,130 @@ function addMarkIssues(text, marks, ctx, path = ["marks"]) {
|
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
//#endregion
|
|
206
|
+
//#region src/json-ld.ts
|
|
207
|
+
/** What generators hand out: the JSON wrapped in its own script tag. */
|
|
208
|
+
const SCRIPT_WRAPPER = /^\s*<script\b[^>]*\bapplication\/ld\+json\b[^>]*>([\s\S]*?)<\/script\s*>\s*$/i;
|
|
209
|
+
/**
|
|
210
|
+
* Parses what a person pastes into the JSON-LD box: an object, an array of objects, or either one inside a
|
|
211
|
+
* `<script type="application/ld+json">` tag. Empty input is valid and means "no custom JSON-LD".
|
|
212
|
+
*/
|
|
213
|
+
function parseJsonLdInput(raw) {
|
|
214
|
+
const body = (SCRIPT_WRAPPER.exec(raw)?.[1] ?? raw).trim();
|
|
215
|
+
if (body === "") return {
|
|
216
|
+
ok: true,
|
|
217
|
+
value: []
|
|
218
|
+
};
|
|
219
|
+
let data;
|
|
220
|
+
try {
|
|
221
|
+
data = JSON.parse(body);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
error: error instanceof Error ? error.message : "Invalid JSON"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const items = Array.isArray(data) ? data : [data];
|
|
229
|
+
if (items.length === 0) return {
|
|
230
|
+
ok: true,
|
|
231
|
+
value: []
|
|
232
|
+
};
|
|
233
|
+
if (!items.every(isBlock)) return {
|
|
234
|
+
ok: false,
|
|
235
|
+
error: "JSON-LD must be an object or an array of objects."
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
ok: true,
|
|
239
|
+
value: items
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/** The text shown in the editor box: one block as an object, several as an array, none as empty. */
|
|
243
|
+
function formatJsonLd(blocks) {
|
|
244
|
+
if (!blocks || blocks.length === 0) return "";
|
|
245
|
+
return JSON.stringify(blocks.length === 1 ? blocks[0] : blocks, null, 2);
|
|
246
|
+
}
|
|
247
|
+
/** Advice, not errors: a block without `@type` or `@context` is usually a paste mistake, but Google decides what it accepts. */
|
|
248
|
+
function jsonLdWarnings(blocks) {
|
|
249
|
+
const warnings = [];
|
|
250
|
+
blocks.forEach((block, index) => {
|
|
251
|
+
const name = blocks.length === 1 ? "The block" : `Block ${index + 1}`;
|
|
252
|
+
if (!("@context" in block)) warnings.push(`${name} has no @context. Search engines expect "https://schema.org".`);
|
|
253
|
+
if (!("@type" in block) && !("@graph" in block)) warnings.push(`${name} has no @type.`);
|
|
254
|
+
});
|
|
255
|
+
return warnings;
|
|
256
|
+
}
|
|
257
|
+
function isBlock(value) {
|
|
258
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
259
|
+
}
|
|
260
|
+
/** Custom structured data. At most 10 blocks and 50 KB, so a stray paste cannot bloat every page. */
|
|
261
|
+
const jsonLdSchema = z.array(z.record(z.string(), z.unknown())).max(10).refine((blocks) => JSON.stringify(blocks).length <= 5e4, "JSON-LD is limited to 50 KB");
|
|
262
|
+
/** `<site.url>/#organization` and `<site.url>/#website`, with trailing slashes on the site URL ignored. */
|
|
263
|
+
function siteJsonLdIds(siteUrl) {
|
|
264
|
+
const base = siteUrl.replace(/\/+$/, "");
|
|
265
|
+
return {
|
|
266
|
+
organization: `${base}/#organization`,
|
|
267
|
+
website: `${base}/#website`
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function blockId(block) {
|
|
271
|
+
const id = block["@id"];
|
|
272
|
+
return typeof id === "string" ? id : void 0;
|
|
273
|
+
}
|
|
274
|
+
/** Whether a block with this `@id` is in the list. */
|
|
275
|
+
function hasJsonLdId(blocks, id) {
|
|
276
|
+
return (blocks ?? []).some((block) => blockId(block) === id);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The blocks a page emits: every site block, then the page's own. A block with the same `@id` is
|
|
280
|
+
* emitted once and the page's copy wins, so a page can refine what the site says without repeating it.
|
|
281
|
+
*/
|
|
282
|
+
function mergeJsonLd(site, page) {
|
|
283
|
+
const taken = new Set(page.map(blockId).filter((id) => id !== void 0));
|
|
284
|
+
return [...(site ?? []).filter((block) => {
|
|
285
|
+
const id = blockId(block);
|
|
286
|
+
return id === void 0 || !taken.has(id);
|
|
287
|
+
}), ...page];
|
|
288
|
+
}
|
|
289
|
+
/** An `Organization` and a `WebSite` block for the site, in the `@id` convention of {@link siteJsonLdIds}. */
|
|
290
|
+
function generateSiteJsonLd(source) {
|
|
291
|
+
const ids = siteJsonLdIds(source.url);
|
|
292
|
+
const url = `${source.url.replace(/\/+$/, "")}/`;
|
|
293
|
+
return [{
|
|
294
|
+
"@context": "https://schema.org",
|
|
295
|
+
"@type": "Organization",
|
|
296
|
+
"@id": ids.organization,
|
|
297
|
+
name: source.name,
|
|
298
|
+
url,
|
|
299
|
+
...source.logo ? { logo: source.logo } : {}
|
|
300
|
+
}, {
|
|
301
|
+
"@context": "https://schema.org",
|
|
302
|
+
"@type": "WebSite",
|
|
303
|
+
"@id": ids.website,
|
|
304
|
+
name: source.name,
|
|
305
|
+
url,
|
|
306
|
+
inLanguage: source.lang || "en"
|
|
307
|
+
}];
|
|
308
|
+
}
|
|
309
|
+
/** Puts generated blocks into a list: a block with the same `@id` is replaced in place, the rest is kept. */
|
|
310
|
+
function upsertJsonLd(blocks, generated) {
|
|
311
|
+
const next = [...blocks];
|
|
312
|
+
for (const block of generated) {
|
|
313
|
+
const id = blockId(block);
|
|
314
|
+
const at = id === void 0 ? -1 : next.findIndex((item) => blockId(item) === id);
|
|
315
|
+
if (at >= 0) next[at] = block;
|
|
316
|
+
else next.push(block);
|
|
317
|
+
}
|
|
318
|
+
return next;
|
|
319
|
+
}
|
|
320
|
+
/** True while the site URL is still the template's `example.com` placeholder. */
|
|
321
|
+
function isPlaceholderSiteUrl(siteUrl) {
|
|
322
|
+
try {
|
|
323
|
+
const host = new URL(siteUrl).hostname.toLowerCase();
|
|
324
|
+
return host === "example.com" || host.endsWith(".example.com");
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
206
330
|
//#region src/link-ids.ts
|
|
207
331
|
/** Walk objects that look like links (`label` + `href`) and report each `id`. */
|
|
208
332
|
function walkLinkIds(value, path, visit) {
|
|
@@ -362,8 +486,6 @@ function addSectionIdentityIssues(sections, ctx) {
|
|
|
362
486
|
});
|
|
363
487
|
addLinkIdIssues(sections, ctx);
|
|
364
488
|
}
|
|
365
|
-
/** Custom structured data. At most 10 blocks and 50 KB, so a stray paste cannot bloat every page. */
|
|
366
|
-
const jsonLdSchema = z.array(z.record(z.string(), z.unknown())).max(10).refine((blocks) => JSON.stringify(blocks).length <= 5e4, "JSON-LD is limited to 50 KB");
|
|
367
489
|
const seoSchema = z.object({
|
|
368
490
|
title: textSchema,
|
|
369
491
|
description: z.string(),
|
|
@@ -425,4 +547,4 @@ function withoutOrphanSections(page, registry) {
|
|
|
425
547
|
};
|
|
426
548
|
}
|
|
427
549
|
//#endregion
|
|
428
|
-
export {
|
|
550
|
+
export { textSchema as A, withImageLayout as B, MAX_NAV_DEPTH as C, linkSchema as D, imageSchema as E, DEFAULT_IMAGE_POSITION as F, tones as G, motionOption as H, IMAGE_FITS as I, variantOption as K, IMAGE_POSITIONS as L, trackingIdSchema as M, withEmphasis as N, richTextSchema as O, DEFAULT_IMAGE_FIT as P, isImageFit as R, upsertJsonLd as S, htmlIdSchema as T, themeModes as U, buttonStyles as V, toneOption as W, jsonLdSchema as _, seoSchema as a, parseJsonLdInput as b, anchorSchema as c, sectionIdSchema as d, walkLinkIds as f, isPlaceholderSiteUrl as g, hasJsonLdId as h, isSystemPage as i, toneSchema as j, textMarkSchema as k, createSectionSchema as l, generateSiteJsonLd as m, createPageSchema as n, slugSchema as o, formatJsonLd as p, createTolerantPageSchema as r, withoutOrphanSections as s, SYSTEM_PAGES as t, isOrphanSection as u, jsonLdWarnings as v, addMarkIssues as w, siteJsonLdIds as x, mergeJsonLd as y, isImagePosition as z };
|
|
@@ -1,56 +1,57 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
/**
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
ok: true,
|
|
26
|
-
value: []
|
|
27
|
-
};
|
|
28
|
-
if (!items.every(isBlock)) return {
|
|
29
|
-
ok: false,
|
|
30
|
-
error: "JSON-LD must be an object or an array of objects."
|
|
31
|
-
};
|
|
1
|
+
//#region src/consent.ts
|
|
2
|
+
/** Longest copy the consent banner takes, in characters. The editor shows the same limits. */
|
|
3
|
+
const CONSENT_LIMITS = {
|
|
4
|
+
title: 80,
|
|
5
|
+
body: 400,
|
|
6
|
+
button: 30,
|
|
7
|
+
link: 40
|
|
8
|
+
};
|
|
9
|
+
/** Where visitors reopen the banner: a link in the footer, or a small fixed icon in the bottom-left corner. */
|
|
10
|
+
const CONSENT_REOPEN = ["footer", "icon"];
|
|
11
|
+
/** A policy link points to a path on this site, an anchor or an http(s) address, so it cannot run script. */
|
|
12
|
+
const CONSENT_HREF = /^(\/|#|https?:\/\/)/;
|
|
13
|
+
const CONSENT_HREF_MESSAGE = "Use a path on this site or an http(s) address";
|
|
14
|
+
/** The banner's words when `site.consent` does not set its own. The editor shows them, and themes render them. */
|
|
15
|
+
const CONSENT_COPY = {
|
|
16
|
+
title: "Cookies",
|
|
17
|
+
body: "This site counts visits with analytics cookies. Nothing is loaded until you accept.",
|
|
18
|
+
accept: "Accept",
|
|
19
|
+
reject: "Reject",
|
|
20
|
+
settings: "Cookie settings",
|
|
21
|
+
policyLabel: "Privacy policy"
|
|
22
|
+
};
|
|
23
|
+
/** `site.consent` laid over the defaults. */
|
|
24
|
+
function consentCopy(consent) {
|
|
32
25
|
return {
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
title: consent?.title ?? CONSENT_COPY.title,
|
|
27
|
+
body: consent?.body ?? CONSENT_COPY.body,
|
|
28
|
+
accept: consent?.accept ?? CONSENT_COPY.accept,
|
|
29
|
+
reject: consent?.reject ?? CONSENT_COPY.reject,
|
|
30
|
+
settings: consent?.settings ?? CONSENT_COPY.settings,
|
|
31
|
+
policyLabel: consent?.policyLabel ?? CONSENT_COPY.policyLabel,
|
|
32
|
+
policyHref: consent?.policyHref
|
|
35
33
|
};
|
|
36
34
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
35
|
+
const LIMIT_OF = {
|
|
36
|
+
title: CONSENT_LIMITS.title,
|
|
37
|
+
body: CONSENT_LIMITS.body,
|
|
38
|
+
accept: CONSENT_LIMITS.button,
|
|
39
|
+
reject: CONSENT_LIMITS.button,
|
|
40
|
+
settings: CONSENT_LIMITS.link,
|
|
41
|
+
policyLabel: CONSENT_LIMITS.link
|
|
42
|
+
};
|
|
43
|
+
/** What is wrong with a draft of the banner copy, per field. The same rules as `siteConsentSchema`, without zod. */
|
|
44
|
+
function consentTextIssues(copy) {
|
|
45
|
+
const issues = {};
|
|
46
|
+
for (const key of Object.keys(LIMIT_OF)) {
|
|
47
|
+
const value = copy[key];
|
|
48
|
+
if (value === void 0) continue;
|
|
49
|
+
if (value.trim() === "") issues[key] = "Cannot be empty";
|
|
50
|
+
else if (value.length > LIMIT_OF[key]) issues[key] = `At most ${LIMIT_OF[key]} characters`;
|
|
51
|
+
}
|
|
52
|
+
const href = copy.policyHref;
|
|
53
|
+
if (href !== void 0 && (href.length > 500 || !CONSENT_HREF.test(href))) issues.policyHref = CONSENT_HREF_MESSAGE;
|
|
54
|
+
return issues;
|
|
54
55
|
}
|
|
55
56
|
//#endregion
|
|
56
57
|
//#region src/theme-colors.ts
|
|
@@ -477,4 +478,4 @@ function contrastChecks(colors) {
|
|
|
477
478
|
return checks;
|
|
478
479
|
}
|
|
479
480
|
//#endregion
|
|
480
|
-
export { contrastRatio as a, hasThemeColors as c, tokenToHex as d,
|
|
481
|
+
export { consentCopy as _, contrastRatio as a, hasThemeColors as c, tokenToHex as d, CONSENT_COPY as f, CONSENT_REOPEN as g, CONSENT_LIMITS as h, contrastChecks as i, hexToToken as l, CONSENT_HREF_MESSAGE as m, HEX_COLOR as n, derivePalette as o, CONSENT_HREF as p, OVERRIDE_TOKENS as r, hasOverrides as s, DEFAULT_BRAND as t, themeCss as u, consentTextIssues as v };
|