bearminion 1.5.0 → 1.6.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 +16 -0
- package/README.md +2 -2
- package/dist/astro.d.ts +2 -2
- package/dist/astro.js +230 -9
- package/dist/content.d.ts +1 -1
- package/dist/content.js +1 -1
- package/dist/index.d.ts +35 -3
- package/dist/index.js +3 -3
- package/dist/{migrate-B56wxCiC.js → migrate-b408EaLp.js} +3 -1
- package/dist/{options-DBfoi9D1.d.ts → options-DdCyRbPs.d.ts} +35 -2
- package/dist/{page-B5NoN74Q.d.ts → page-BFuPEq6Q.d.ts} +7 -7
- package/dist/rich-text.d.ts +17 -8
- package/dist/rich-text.js +45 -24
- package/dist/{theme-colors-CbCyVANH.js → theme-colors-CHROGZkI.js} +31 -1
- package/dist/ui/mount.js +689 -242
- package/package.json +2 -2
|
@@ -271,10 +271,10 @@ declare const textMarkSchema: z.ZodObject<{
|
|
|
271
271
|
start: z.ZodNumber;
|
|
272
272
|
end: z.ZodNumber;
|
|
273
273
|
weight: z.ZodOptional<z.ZodEnum<{
|
|
274
|
+
bold: "bold";
|
|
274
275
|
regular: "regular";
|
|
275
276
|
medium: "medium";
|
|
276
277
|
semibold: "semibold";
|
|
277
|
-
bold: "bold";
|
|
278
278
|
}>>;
|
|
279
279
|
italic: z.ZodOptional<z.ZodBoolean>;
|
|
280
280
|
font: z.ZodOptional<z.ZodEnum<{
|
|
@@ -315,10 +315,10 @@ declare function withEmphasis<T extends z.ZodRawShape>(shape: T): z.ZodPipe<z.Zo
|
|
|
315
315
|
start: z.ZodNumber;
|
|
316
316
|
end: z.ZodNumber;
|
|
317
317
|
weight: z.ZodOptional<z.ZodEnum<{
|
|
318
|
+
bold: "bold";
|
|
318
319
|
regular: "regular";
|
|
319
320
|
medium: "medium";
|
|
320
321
|
semibold: "semibold";
|
|
321
|
-
bold: "bold";
|
|
322
322
|
}>>;
|
|
323
323
|
italic: z.ZodOptional<z.ZodBoolean>;
|
|
324
324
|
font: z.ZodOptional<z.ZodEnum<{
|
|
@@ -333,10 +333,10 @@ declare function withEmphasis<T extends z.ZodRawShape>(shape: T): z.ZodPipe<z.Zo
|
|
|
333
333
|
start: z.ZodNumber;
|
|
334
334
|
end: z.ZodNumber;
|
|
335
335
|
weight: z.ZodOptional<z.ZodEnum<{
|
|
336
|
+
bold: "bold";
|
|
336
337
|
regular: "regular";
|
|
337
338
|
medium: "medium";
|
|
338
339
|
semibold: "semibold";
|
|
339
|
-
bold: "bold";
|
|
340
340
|
}>>;
|
|
341
341
|
italic: z.ZodOptional<z.ZodBoolean>;
|
|
342
342
|
font: z.ZodOptional<z.ZodEnum<{
|
|
@@ -353,10 +353,10 @@ declare function withEmphasis<T extends z.ZodRawShape>(shape: T): z.ZodPipe<z.Zo
|
|
|
353
353
|
start: z.ZodNumber;
|
|
354
354
|
end: z.ZodNumber;
|
|
355
355
|
weight: z.ZodOptional<z.ZodEnum<{
|
|
356
|
+
bold: "bold";
|
|
356
357
|
regular: "regular";
|
|
357
358
|
medium: "medium";
|
|
358
359
|
semibold: "semibold";
|
|
359
|
-
bold: "bold";
|
|
360
360
|
}>>;
|
|
361
361
|
italic: z.ZodOptional<z.ZodBoolean>;
|
|
362
362
|
font: z.ZodOptional<z.ZodEnum<{
|
|
@@ -613,10 +613,10 @@ declare function createSiteSchema(header: HeaderParts, footer: FooterParts): z.Z
|
|
|
613
613
|
start: z.ZodNumber;
|
|
614
614
|
end: z.ZodNumber;
|
|
615
615
|
weight: z.ZodOptional<z.ZodEnum<{
|
|
616
|
+
bold: "bold";
|
|
616
617
|
regular: "regular";
|
|
617
618
|
medium: "medium";
|
|
618
619
|
semibold: "semibold";
|
|
619
|
-
bold: "bold";
|
|
620
620
|
}>>;
|
|
621
621
|
italic: z.ZodOptional<z.ZodBoolean>;
|
|
622
622
|
font: z.ZodOptional<z.ZodEnum<{
|
|
@@ -848,7 +848,7 @@ declare function createSiteSchema(header: HeaderParts, footer: FooterParts): z.Z
|
|
|
848
848
|
nameMarks: {
|
|
849
849
|
start: number;
|
|
850
850
|
end: number;
|
|
851
|
-
weight?: "
|
|
851
|
+
weight?: "bold" | "regular" | "medium" | "semibold" | undefined;
|
|
852
852
|
italic?: boolean | undefined;
|
|
853
853
|
font?: "sans" | "serif" | "mono" | undefined;
|
|
854
854
|
}[] | undefined;
|
|
@@ -1106,7 +1106,7 @@ declare function createSiteSchema(header: HeaderParts, footer: FooterParts): z.Z
|
|
|
1106
1106
|
nameMarks?: {
|
|
1107
1107
|
start: number;
|
|
1108
1108
|
end: number;
|
|
1109
|
-
weight?: "
|
|
1109
|
+
weight?: "bold" | "regular" | "medium" | "semibold" | undefined;
|
|
1110
1110
|
italic?: boolean | undefined;
|
|
1111
1111
|
font?: "sans" | "serif" | "mono" | undefined;
|
|
1112
1112
|
}[] | undefined;
|
package/dist/rich-text.d.ts
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
//#region src/rich-text.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* The subset the editor toolbar writes: paragraphs, line breaks, bold, italic, inline code (backticks),
|
|
4
|
-
* links, and bulleted or numbered lists.
|
|
4
|
+
* links, and bulleted or numbered lists. Images are not supported. Headings are not either, unless a field asks
|
|
5
|
+
* for them (`{ headings: true }`): then `## ` and `### ` lines are h2 and h3, and nothing else is a heading.
|
|
5
6
|
* Stored as markdown. Rendered as a fixed set of tags, with text escaped.
|
|
6
7
|
*
|
|
7
|
-
* The output is always block content (`<p>`, `<ul>`, `<ol>`), so the element that
|
|
8
|
-
* a `div` or a `blockquote`, never a `<p>` or a heading.
|
|
8
|
+
* The output is always block content (`<p>`, `<ul>`, `<ol>`, with headings `<h2>`, `<h3>`), so the element that
|
|
9
|
+
* holds it must accept blocks: a `div` or a `blockquote`, never a `<p>` or a heading.
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
+
/** What a rich field allows beyond the base subset. */
|
|
12
|
+
export interface RichOptions {
|
|
13
|
+
/** Allow h2 and h3 as `## ` and `### ` lines. Page titles are h1, so nothing above h2 exists, and nothing below h3. */
|
|
14
|
+
headings?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function renderRichText(markdown: string, options?: RichOptions): string;
|
|
11
17
|
/**
|
|
12
18
|
* Pasted HTML, reduced to what a rich field can hold. The HTML is parsed (never inserted), read back as the
|
|
13
19
|
* markdown subset, and rendered again, so only the renderer's own tags survive: no attributes, handlers or
|
|
14
20
|
* elements from the clipboard reach the page. Headings and images are dropped. Browser only.
|
|
15
21
|
*/
|
|
16
|
-
export declare function sanitizeRichHtml(html: string): string;
|
|
17
|
-
/**
|
|
18
|
-
|
|
22
|
+
export declare function sanitizeRichHtml(html: string, options?: RichOptions): string;
|
|
23
|
+
/**
|
|
24
|
+
* Drop markdown images from pasted plain text, and headings too unless the field allows them. Then a heading of any
|
|
25
|
+
* level becomes the nearest one the field has: `#` is `##`, and `####` and deeper are `###`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function stripPlainRich(text: string, options?: RichOptions): string;
|
|
19
28
|
/** True when a link URL is safe to put in an href. */
|
|
20
29
|
export declare function isSafeHref(url: string): boolean;
|
|
21
30
|
export declare function escapeHtml(value: string): string;
|
|
@@ -23,7 +32,7 @@ export declare function escapeHtml(value: string): string;
|
|
|
23
32
|
* Turn the formatted DOM of a rich field back into the stored markdown subset. The HTML is parsed with
|
|
24
33
|
* `DOMParser`, which never runs scripts or loads resources. Browser only (the editor overlay).
|
|
25
34
|
*/
|
|
26
|
-
export declare function markdownFromHtml(html: string): string;
|
|
35
|
+
export declare function markdownFromHtml(html: string, options?: RichOptions): string;
|
|
27
36
|
export interface RichLinkSpan {
|
|
28
37
|
label: string;
|
|
29
38
|
href: string;
|
package/dist/rich-text.js
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
//#region src/rich-text.ts
|
|
2
|
-
|
|
3
|
-
* The subset the editor toolbar writes: paragraphs, line breaks, bold, italic, inline code (backticks),
|
|
4
|
-
* links, and bulleted or numbered lists. Headings and images are not supported.
|
|
5
|
-
* Stored as markdown. Rendered as a fixed set of tags, with text escaped.
|
|
6
|
-
*
|
|
7
|
-
* The output is always block content (`<p>`, `<ul>`, `<ol>`), so the element that holds it must accept blocks:
|
|
8
|
-
* a `div` or a `blockquote`, never a `<p>` or a heading.
|
|
9
|
-
*/
|
|
2
|
+
const HEADING_LINE = /^(#{2,3})[ \t]+(\S.*)$/;
|
|
10
3
|
const BLOCKED_PROTOCOL = /^(?:javascript|data|vbscript):/i;
|
|
11
|
-
function renderRichText(markdown) {
|
|
12
|
-
|
|
4
|
+
function renderRichText(markdown, options = {}) {
|
|
5
|
+
const headings = options.headings === true;
|
|
6
|
+
const text = markdown.replace(/\r\n/g, "\n");
|
|
7
|
+
return (headings ? text.split("\n").map((line) => HEADING_LINE.test(line.trim()) ? `\n\n${line.trim()}\n\n` : line).join("\n") : text).split(/\n{2,}/).filter((block) => block.trim() !== "").map((block) => {
|
|
8
|
+
const heading = headings ? HEADING_LINE.exec(block.trim()) : null;
|
|
9
|
+
if (heading) {
|
|
10
|
+
const tag = heading[1]?.length === 2 ? "h2" : "h3";
|
|
11
|
+
return `<${tag}>${renderInline((heading[2] ?? "").trim())}</${tag}>`;
|
|
12
|
+
}
|
|
13
|
+
const html = renderBlock(block);
|
|
14
|
+
return isListHtml(html) ? html : `<p>${html}</p>`;
|
|
15
|
+
}).join("");
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Pasted HTML, reduced to what a rich field can hold. The HTML is parsed (never inserted), read back as the
|
|
16
19
|
* markdown subset, and rendered again, so only the renderer's own tags survive: no attributes, handlers or
|
|
17
20
|
* elements from the clipboard reach the page. Headings and images are dropped. Browser only.
|
|
18
21
|
*/
|
|
19
|
-
function sanitizeRichHtml(html) {
|
|
20
|
-
return renderRichText(markdownFromHtml(html));
|
|
22
|
+
function sanitizeRichHtml(html, options = {}) {
|
|
23
|
+
return renderRichText(markdownFromHtml(html, options), options);
|
|
21
24
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Drop markdown images from pasted plain text, and headings too unless the field allows them. Then a heading of any
|
|
27
|
+
* level becomes the nearest one the field has: `#` is `##`, and `####` and deeper are `###`.
|
|
28
|
+
*/
|
|
29
|
+
function stripPlainRich(text, options = {}) {
|
|
30
|
+
const withoutImages = text.replace(/!\[[^\]]*\]\([^)\n]*\)/g, "");
|
|
31
|
+
if (options.headings !== true) return withoutImages.replace(/^#{1,6}[ \t]+.*$/gm, "");
|
|
32
|
+
return withoutImages.replace(/^#{4,6}([ \t]+)/gm, "###$1").replace(/^#([ \t]+)/gm, "##$1");
|
|
25
33
|
}
|
|
26
34
|
function renderBlock(block) {
|
|
27
35
|
const lines = block.split("\n");
|
|
@@ -155,8 +163,8 @@ function escapeAttr(value) {
|
|
|
155
163
|
* Turn the formatted DOM of a rich field back into the stored markdown subset. The HTML is parsed with
|
|
156
164
|
* `DOMParser`, which never runs scripts or loads resources. Browser only (the editor overlay).
|
|
157
165
|
*/
|
|
158
|
-
function markdownFromHtml(html) {
|
|
159
|
-
return cleanup(serialize(parseHtml(html)));
|
|
166
|
+
function markdownFromHtml(html, options = {}) {
|
|
167
|
+
return cleanup(serialize(parseHtml(html, options.headings === true)));
|
|
160
168
|
}
|
|
161
169
|
/** Elements whose content is dropped with them. */
|
|
162
170
|
const DROPPED = /* @__PURE__ */ new Set([
|
|
@@ -171,7 +179,10 @@ const DROPPED = /* @__PURE__ */ new Set([
|
|
|
171
179
|
"img",
|
|
172
180
|
"picture",
|
|
173
181
|
"video",
|
|
174
|
-
"audio"
|
|
182
|
+
"audio"
|
|
183
|
+
]);
|
|
184
|
+
/** Headings a field without them drops, with their text. */
|
|
185
|
+
const HEADING_TAGS = /* @__PURE__ */ new Set([
|
|
175
186
|
"h1",
|
|
176
187
|
"h2",
|
|
177
188
|
"h3",
|
|
@@ -179,16 +190,16 @@ const DROPPED = /* @__PURE__ */ new Set([
|
|
|
179
190
|
"h5",
|
|
180
191
|
"h6"
|
|
181
192
|
]);
|
|
182
|
-
function parseHtml(html) {
|
|
193
|
+
function parseHtml(html, headings) {
|
|
183
194
|
const root = {
|
|
184
195
|
mark: "root",
|
|
185
196
|
children: []
|
|
186
197
|
};
|
|
187
198
|
const body = new DOMParser().parseFromString(`<!doctype html><body>${html}`, "text/html").body;
|
|
188
|
-
appendChildren(body, root);
|
|
199
|
+
appendChildren(body, root, headings);
|
|
189
200
|
return root;
|
|
190
201
|
}
|
|
191
|
-
function appendChildren(parent, into) {
|
|
202
|
+
function appendChildren(parent, into, headings) {
|
|
192
203
|
for (const child of Array.from(parent.childNodes)) {
|
|
193
204
|
if (child.nodeType === TEXT_NODE) {
|
|
194
205
|
into.children.push({
|
|
@@ -202,6 +213,7 @@ function appendChildren(parent, into) {
|
|
|
202
213
|
const element = child;
|
|
203
214
|
const name = element.tagName.toLowerCase();
|
|
204
215
|
if (DROPPED.has(name)) continue;
|
|
216
|
+
if (HEADING_TAGS.has(name) && !headings) continue;
|
|
205
217
|
if (name === "br") {
|
|
206
218
|
into.children.push({
|
|
207
219
|
mark: "text",
|
|
@@ -212,7 +224,7 @@ function appendChildren(parent, into) {
|
|
|
212
224
|
}
|
|
213
225
|
const mark = blockOf(name) ?? markOf(name, element.getAttribute("style") ?? "");
|
|
214
226
|
if (!mark) {
|
|
215
|
-
appendChildren(element, into);
|
|
227
|
+
appendChildren(element, into, headings);
|
|
216
228
|
continue;
|
|
217
229
|
}
|
|
218
230
|
const tab = mark === "link" ? element.getAttribute("data-new-tab") : null;
|
|
@@ -223,12 +235,14 @@ function appendChildren(parent, into) {
|
|
|
223
235
|
newTab: tab === "true" ? "new" : tab === "false" ? "same" : void 0
|
|
224
236
|
};
|
|
225
237
|
into.children.push(node);
|
|
226
|
-
appendChildren(element, node);
|
|
238
|
+
appendChildren(element, node, headings);
|
|
227
239
|
}
|
|
228
240
|
}
|
|
229
241
|
const ELEMENT_NODE = 1;
|
|
230
242
|
const TEXT_NODE = 3;
|
|
231
243
|
function blockOf(name) {
|
|
244
|
+
if (name === "h1" || name === "h2") return "h2";
|
|
245
|
+
if (name === "h3" || name === "h4" || name === "h5" || name === "h6") return "h3";
|
|
232
246
|
if (name === "ul") return "ul";
|
|
233
247
|
if (name === "ol") return "ol";
|
|
234
248
|
if (name === "li") return "li";
|
|
@@ -260,6 +274,13 @@ function serializeFlow(nodes) {
|
|
|
260
274
|
out += `${list}\n\n`;
|
|
261
275
|
continue;
|
|
262
276
|
}
|
|
277
|
+
if (node.mark === "h2" || node.mark === "h3") {
|
|
278
|
+
const text = node.children.map((child) => serializeInline(child)).join("").replace(/\s*\n+\s*/g, " ").trim();
|
|
279
|
+
if (text === "") continue;
|
|
280
|
+
if (out.trim() !== "" && !out.endsWith("\n\n")) out = out.replace(/\n*$/, "\n\n");
|
|
281
|
+
out += `${node.mark === "h2" ? "##" : "###"} ${text}\n\n`;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
263
284
|
if (node.mark === "block") {
|
|
264
285
|
const inner = serializeFlow(node.children).replace(/\n+$/, "");
|
|
265
286
|
if (inner === "") continue;
|
|
@@ -291,7 +312,7 @@ function serializeInline(node) {
|
|
|
291
312
|
return text.trim() === "" ? text : `\`${text}\``;
|
|
292
313
|
}
|
|
293
314
|
if (node.mark === "ul" || node.mark === "ol") return serializeList(node);
|
|
294
|
-
if (node.mark === "li" || node.mark === "block" || node.mark === "root") return serializeFlow(node.children);
|
|
315
|
+
if (node.mark === "li" || node.mark === "block" || node.mark === "root" || node.mark === "h2" || node.mark === "h3") return serializeFlow(node.mark === "h2" || node.mark === "h3" ? [node] : node.children);
|
|
295
316
|
const inner = node.children.map((child) => serializeInline(child)).join("");
|
|
296
317
|
if (!inner) return "";
|
|
297
318
|
if (node.mark === "strong") return `**${inner}**`;
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
//#region src/media-source.ts
|
|
2
|
+
const YOUTUBE = /^https?:\/\/(?:(?:www|m)\.)?(?:youtube\.com\/(?:watch\?(?:[^#\s]*&)?v=|embed\/|shorts\/|live\/)|youtube-nocookie\.com\/embed\/|youtu\.be\/)([\w-]{6,})(?:[?&#][^\s]*)?$/i;
|
|
3
|
+
const VIMEO = /^https?:\/\/(?:(?:www|player)\.)?vimeo\.com\/(?:video\/)?(\d+)(?:[/?#][^\s]*)?$/i;
|
|
4
|
+
/** A path in `public/` (`/videos/demo.webm`) or an `https://` link to a video file. */
|
|
5
|
+
const VIDEO_FILE = /^(?:\/(?!\/)|https?:\/\/)\S+\.(?:mp4|webm|m4v|mov|ogv)(?:[?#]\S*)?$/i;
|
|
6
|
+
function parseMediaSource(value) {
|
|
7
|
+
const src = value.trim();
|
|
8
|
+
const youtube = YOUTUBE.exec(src);
|
|
9
|
+
if (youtube?.[1]) {
|
|
10
|
+
const id = youtube[1];
|
|
11
|
+
return {
|
|
12
|
+
kind: "youtube",
|
|
13
|
+
id,
|
|
14
|
+
embed: `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0`,
|
|
15
|
+
thumbnail: `https://i.ytimg.com/vi/${id}/hqdefault.jpg`
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const vimeo = VIMEO.exec(src);
|
|
19
|
+
if (vimeo?.[1]) return {
|
|
20
|
+
kind: "vimeo",
|
|
21
|
+
id: vimeo[1],
|
|
22
|
+
embed: `https://player.vimeo.com/video/${vimeo[1]}?autoplay=1&dnt=1`
|
|
23
|
+
};
|
|
24
|
+
if (VIDEO_FILE.test(src)) return {
|
|
25
|
+
kind: "file",
|
|
26
|
+
src
|
|
27
|
+
};
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
1
31
|
//#region src/consent.ts
|
|
2
32
|
/** Longest copy the consent banner takes, in characters. The editor shows the same limits. */
|
|
3
33
|
const CONSENT_LIMITS = {
|
|
@@ -478,4 +508,4 @@ function contrastChecks(colors) {
|
|
|
478
508
|
return checks;
|
|
479
509
|
}
|
|
480
510
|
//#endregion
|
|
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 };
|
|
511
|
+
export { consentCopy as _, contrastRatio as a, parseMediaSource as b, 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, VIDEO_FILE as y };
|