@byscott-io/pagelet 0.1.0 → 0.2.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 +38 -0
- package/README.md +20 -2
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +387 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +381 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var rsc = require('@puckeditor/core/rsc');
|
|
5
|
+
var react = require('react');
|
|
5
6
|
|
|
6
7
|
const DOCUMENT_VERSION = 1;
|
|
7
8
|
function emptyData() {
|
|
@@ -36,6 +37,10 @@ function needsPuckMigration(data) {
|
|
|
36
37
|
const root = data.root ?? {};
|
|
37
38
|
return Object.keys(root).some((key) => key !== "props" && key !== "readOnly");
|
|
38
39
|
}
|
|
40
|
+
function pageSettings(document) {
|
|
41
|
+
const props = upgradeDocument(document)?.data?.root?.props ?? {};
|
|
42
|
+
return { title: props.title || "", background: props.background ?? "site" };
|
|
43
|
+
}
|
|
39
44
|
|
|
40
45
|
function PageletPage({ document, palette, theme }) {
|
|
41
46
|
if (!palette) throw new Error("pagelet: <PageletPage> needs a palette (see createPalette)");
|
|
@@ -43,6 +48,63 @@ function PageletPage({ document, palette, theme }) {
|
|
|
43
48
|
return /* @__PURE__ */ jsxRuntime.jsx(rsc.Render, { config: palette.config, data, metadata: { pageletTheme: theme } });
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
const outline = { fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
|
|
52
|
+
const PATHS = {
|
|
53
|
+
location: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 22s7-6.2 7-12a7 7 0 1 0-14 0c0 5.8 7 12 7 12z" }),
|
|
55
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "10", r: "2.5" })
|
|
56
|
+
] }),
|
|
57
|
+
phone: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 3h3l1.5 4.5-2 1.5a12 12 0 0 0 6 6l1.5-2L21 14.5v3a2 2 0 0 1-2 2A16 16 0 0 1 4.5 5a2 2 0 0 1 2-2z" }),
|
|
58
|
+
email: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
59
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
|
|
60
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 7l9 6 9-6" })
|
|
61
|
+
] }),
|
|
62
|
+
video: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
63
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2.5", y: "6", width: "13", height: "12", rx: "2" }),
|
|
64
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.5 10.5 21.5 7v10l-6-3.5z" })
|
|
65
|
+
] }),
|
|
66
|
+
calendar: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
67
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "5", width: "18", height: "16", rx: "2" }),
|
|
68
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10h18M8 3v4M16 3v4" })
|
|
69
|
+
] }),
|
|
70
|
+
clock: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
71
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
72
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 7v5l3 2" })
|
|
73
|
+
] }),
|
|
74
|
+
link: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
75
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1" }),
|
|
76
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1" })
|
|
77
|
+
] }),
|
|
78
|
+
facebook: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
79
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" }),
|
|
80
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.5 8H14a2 2 0 0 0-2 2v11M9.5 13h5" })
|
|
81
|
+
] }),
|
|
82
|
+
youtube: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
83
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "4" }),
|
|
84
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.5 9.5v5l4-2.5z" })
|
|
85
|
+
] })
|
|
86
|
+
};
|
|
87
|
+
const iconNames = Object.keys(PATHS);
|
|
88
|
+
const iconOptions = [{ label: "None", value: "" }].concat(
|
|
89
|
+
iconNames.map((name) => ({ label: name[0].toUpperCase() + name.slice(1), value: name }))
|
|
90
|
+
);
|
|
91
|
+
function Icon({ name, size = "1.1em", style }) {
|
|
92
|
+
const glyph = PATHS[name];
|
|
93
|
+
if (!glyph) return null;
|
|
94
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
95
|
+
"svg",
|
|
96
|
+
{
|
|
97
|
+
viewBox: "0 0 24 24",
|
|
98
|
+
width: size,
|
|
99
|
+
height: size,
|
|
100
|
+
"aria-hidden": "true",
|
|
101
|
+
focusable: "false",
|
|
102
|
+
style: { ...outline, verticalAlign: "-0.15em", flexShrink: 0, ...style },
|
|
103
|
+
children: glyph
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
46
108
|
const SAFE_SCHEMES = ["http:", "https:", "mailto:", "tel:"];
|
|
47
109
|
function safeUrl(value) {
|
|
48
110
|
if (typeof value !== "string") return void 0;
|
|
@@ -94,8 +156,10 @@ function tokenVar(token) {
|
|
|
94
156
|
|
|
95
157
|
const alignOptions = [
|
|
96
158
|
{ label: "Left", value: "left" },
|
|
97
|
-
{ label: "Center", value: "center" }
|
|
159
|
+
{ label: "Center", value: "center" },
|
|
160
|
+
{ label: "Right", value: "right" }
|
|
98
161
|
];
|
|
162
|
+
const blockMargin = (align) => align === "center" ? { marginInline: "auto" } : align === "right" ? { marginLeft: "auto" } : {};
|
|
99
163
|
function paragraphs(text) {
|
|
100
164
|
return String(text ?? "").split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
101
165
|
}
|
|
@@ -157,7 +221,7 @@ const Text = {
|
|
|
157
221
|
align: { type: "radio", options: alignOptions }
|
|
158
222
|
},
|
|
159
223
|
defaultProps: { text: "Write something here.", align: "left" },
|
|
160
|
-
render: ({ text, align }) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: align, maxWidth: "68ch",
|
|
224
|
+
render: ({ text, align }) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: align, maxWidth: "68ch", ...blockMargin(align) }, children: /* @__PURE__ */ jsxRuntime.jsx(Paragraphs, { text }) })
|
|
161
225
|
};
|
|
162
226
|
const Button = {
|
|
163
227
|
label: "Button",
|
|
@@ -270,6 +334,79 @@ const Faq = {
|
|
|
270
334
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { paddingTop: "8px" }, children: /* @__PURE__ */ jsxRuntime.jsx(Paragraphs, { text: item.answer }) })
|
|
271
335
|
] }, index)) })
|
|
272
336
|
};
|
|
337
|
+
const Quote = {
|
|
338
|
+
label: "Quote",
|
|
339
|
+
category: "content",
|
|
340
|
+
fields: {
|
|
341
|
+
text: { type: "textarea" },
|
|
342
|
+
citation: { type: "text", label: "Citation (e.g. Matthew 28:19-20 ESV)" },
|
|
343
|
+
citationAlign: { type: "radio", label: "Citation alignment", options: alignOptions }
|
|
344
|
+
},
|
|
345
|
+
defaultProps: { text: "Quotation", citation: "", citationAlign: "right" },
|
|
346
|
+
render: ({ text, citation, citationAlign }) => /* @__PURE__ */ jsxRuntime.jsxs("figure", { style: { margin: "0 0 1em", maxWidth: "68ch" }, children: [
|
|
347
|
+
/* @__PURE__ */ jsxRuntime.jsx("blockquote", { style: { margin: 0, fontStyle: "italic" }, children: /* @__PURE__ */ jsxRuntime.jsx(Paragraphs, { text, style: { margin: "0 0 0.5em" } }) }),
|
|
348
|
+
citation && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { style: { textAlign: citationAlign ?? "right" }, children: citation })
|
|
349
|
+
] })
|
|
350
|
+
};
|
|
351
|
+
const IconList = {
|
|
352
|
+
label: "Icon list",
|
|
353
|
+
category: "content",
|
|
354
|
+
fields: {
|
|
355
|
+
items: {
|
|
356
|
+
type: "array",
|
|
357
|
+
getItemSummary: (item) => item.text?.split("\n")[0] || "Item",
|
|
358
|
+
arrayFields: {
|
|
359
|
+
icon: { type: "select", options: iconOptions },
|
|
360
|
+
text: { type: "textarea", label: "Text (line breaks are kept)" },
|
|
361
|
+
href: { type: "text", label: "Link (optional): https://, tel:, mailto:" }
|
|
362
|
+
},
|
|
363
|
+
defaultItemProps: { icon: "link", text: "", href: "" }
|
|
364
|
+
},
|
|
365
|
+
align: { type: "radio", options: alignOptions }
|
|
366
|
+
},
|
|
367
|
+
defaultProps: { items: [{ icon: "phone", text: "(555) 555-0100", href: "tel:+15555550100" }], align: "left" },
|
|
368
|
+
render: ({ items = [], align }) => /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", padding: 0, margin: "0 0 1em", textAlign: align }, children: items.map((item, index) => {
|
|
369
|
+
const href = safeUrl(item.href);
|
|
370
|
+
const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
371
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon, style: { marginRight: "0.4em" } }),
|
|
372
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { whiteSpace: "pre-line" }, children: item.text })
|
|
373
|
+
] });
|
|
374
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { style: { margin: "0.35em 0" }, children: href ? /* @__PURE__ */ jsxRuntime.jsx("a", { href, style: { color: tokenVar("colorAccent") }, children: content }) : content }, index);
|
|
375
|
+
}) })
|
|
376
|
+
};
|
|
377
|
+
const Schedule = {
|
|
378
|
+
label: "Schedule",
|
|
379
|
+
category: "content",
|
|
380
|
+
fields: {
|
|
381
|
+
rows: {
|
|
382
|
+
type: "array",
|
|
383
|
+
getItemSummary: (row) => [row.day, row.time, row.what].filter(Boolean).join(" ") || "Row",
|
|
384
|
+
arrayFields: { day: { type: "text" }, time: { type: "text" }, what: { type: "text" } },
|
|
385
|
+
defaultItemProps: { day: "Sunday", time: "10:00", what: "Worship" }
|
|
386
|
+
},
|
|
387
|
+
align: { type: "radio", options: alignOptions }
|
|
388
|
+
},
|
|
389
|
+
defaultProps: { rows: [{ day: "Sunday", time: "10:00", what: "Worship" }], align: "center" },
|
|
390
|
+
render: ({ rows = [], align }) => /* @__PURE__ */ jsxRuntime.jsx("table", { style: { borderCollapse: "collapse", margin: "0 0 1em", ...blockMargin(align ?? "center") }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, index) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "4px 12px", textAlign: "right", fontWeight: 600 }, children: row.day }),
|
|
392
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "4px 12px", textAlign: "right", fontVariantNumeric: "tabular-nums" }, children: row.time }),
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "4px 12px" }, children: row.what })
|
|
394
|
+
] }, index)) }) })
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
function overlayColor(hex, opacityPercent) {
|
|
398
|
+
let value = String(hex ?? "").trim().replace(/^#/, "");
|
|
399
|
+
if (/^[0-9a-f]{3}$/i.test(value)) value = value.split("").map((c) => c + c).join("");
|
|
400
|
+
if (!/^[0-9a-f]{6}$/i.test(value)) value = "ffffff";
|
|
401
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(value.slice(i, i + 2), 16));
|
|
402
|
+
const opacity = Math.min(100, Math.max(0, Number(opacityPercent) || 0)) / 100;
|
|
403
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
404
|
+
}
|
|
405
|
+
function cssImage(url) {
|
|
406
|
+
const safe = safeImageUrl(url);
|
|
407
|
+
return safe ? `url("${encodeURI(safe)}")` : void 0;
|
|
408
|
+
}
|
|
409
|
+
const percentField = (label) => ({ type: "number", label, min: 0, max: 100 });
|
|
273
410
|
|
|
274
411
|
const SPACING = { none: "0", small: "16px", medium: "40px", large: "80px" };
|
|
275
412
|
const spacingOptions = Object.keys(SPACING).map((value) => ({ label: value, value }));
|
|
@@ -290,21 +427,27 @@ const Section = {
|
|
|
290
427
|
{ label: "Accent", value: "accent" }
|
|
291
428
|
]
|
|
292
429
|
},
|
|
430
|
+
backgroundImage: { type: "text", label: "Background image URL (optional)" },
|
|
431
|
+
overlayColor: { type: "text", label: "Overlay color over the image (hex, e.g. #ffffff)" },
|
|
432
|
+
overlayOpacity: percentField("Overlay opacity %"),
|
|
293
433
|
padding: { type: "select", options: spacingOptions },
|
|
294
434
|
content: { type: "slot" }
|
|
295
435
|
},
|
|
296
|
-
defaultProps: { background: "none", padding: "medium" },
|
|
297
|
-
render: ({ background, padding, content: Content }) =>
|
|
298
|
-
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
436
|
+
defaultProps: { background: "none", backgroundImage: "", overlayColor: "#ffffff", overlayOpacity: 85, padding: "medium" },
|
|
437
|
+
render: ({ background, backgroundImage, overlayColor: color, overlayOpacity, padding, content: Content }) => {
|
|
438
|
+
const image = cssImage(backgroundImage);
|
|
439
|
+
const inner = /* @__PURE__ */ jsxRuntime.jsx("div", { style: { maxWidth: tokenVar("maxWidth"), margin: "0 auto", paddingInline: "16px" }, children: /* @__PURE__ */ jsxRuntime.jsx(Content, {}) });
|
|
440
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
441
|
+
"section",
|
|
442
|
+
{
|
|
443
|
+
style: {
|
|
444
|
+
background: image ? `${image} center / cover` : BACKGROUNDS[background] ?? BACKGROUNDS.none,
|
|
445
|
+
color: !image && background === "accent" ? tokenVar("colorAccentText") : void 0
|
|
446
|
+
},
|
|
447
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { paddingBlock: SPACING[padding] ?? SPACING.medium, background: image ? overlayColor(color, overlayOpacity ?? 85) : void 0 }, children: inner })
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
}
|
|
308
451
|
};
|
|
309
452
|
const Columns = {
|
|
310
453
|
label: "Columns",
|
|
@@ -357,15 +500,27 @@ const Image = {
|
|
|
357
500
|
fields: {
|
|
358
501
|
url: { type: "text", label: "Image URL" },
|
|
359
502
|
alt: { type: "text", label: "Description (for screen readers)" },
|
|
360
|
-
caption: { type: "text" }
|
|
503
|
+
caption: { type: "text" },
|
|
504
|
+
align: {
|
|
505
|
+
type: "radio",
|
|
506
|
+
options: [
|
|
507
|
+
{ label: "Left", value: "left" },
|
|
508
|
+
{ label: "Center", value: "center" },
|
|
509
|
+
{ label: "Right", value: "right" }
|
|
510
|
+
]
|
|
511
|
+
},
|
|
512
|
+
width: { ...percentField("Width (% of the column)"), min: 10 }
|
|
361
513
|
},
|
|
362
|
-
defaultProps: { url: "", alt: "", caption: "" },
|
|
363
|
-
render: ({ url, alt, caption }) => {
|
|
514
|
+
defaultProps: { url: "", alt: "", caption: "", align: "left", width: 100 },
|
|
515
|
+
render: ({ url, alt, caption, align, width }) => {
|
|
364
516
|
const src = safeImageUrl(url);
|
|
365
517
|
if (!src) return null;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
518
|
+
const requested = Number.isFinite(Number(width)) && width !== "" && width !== null ? Number(width) : 100;
|
|
519
|
+
const percent = Math.min(100, Math.max(10, requested));
|
|
520
|
+
const side = align === "center" ? { marginInline: "auto" } : align === "right" ? { marginLeft: "auto" } : {};
|
|
521
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("figure", { style: { margin: "0 0 1em", width: `${percent}%`, maxWidth: "100%", ...side }, children: [
|
|
522
|
+
/* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: alt ?? "", style: { width: "100%", height: "auto", display: "block", borderRadius: tokenVar("radius") } }),
|
|
523
|
+
caption && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { style: { fontSize: "0.9em", opacity: 0.75, marginTop: "6px", textAlign: align }, children: caption })
|
|
369
524
|
] });
|
|
370
525
|
}
|
|
371
526
|
};
|
|
@@ -447,6 +602,48 @@ const VideoEmbed = {
|
|
|
447
602
|
) });
|
|
448
603
|
}
|
|
449
604
|
};
|
|
605
|
+
function embedAllowed(value, hosts) {
|
|
606
|
+
let url;
|
|
607
|
+
try {
|
|
608
|
+
url = new URL(String(value ?? "").trim());
|
|
609
|
+
} catch {
|
|
610
|
+
return void 0;
|
|
611
|
+
}
|
|
612
|
+
if (url.protocol !== "https:") return void 0;
|
|
613
|
+
const host = url.hostname.toLowerCase();
|
|
614
|
+
const ok = hosts.some((allowed) => {
|
|
615
|
+
const name = String(allowed).toLowerCase();
|
|
616
|
+
return host === name || host.endsWith(`.${name}`);
|
|
617
|
+
});
|
|
618
|
+
return ok ? url.href : void 0;
|
|
619
|
+
}
|
|
620
|
+
function makeEmbed(hosts) {
|
|
621
|
+
return {
|
|
622
|
+
label: "Embedded page",
|
|
623
|
+
category: "media",
|
|
624
|
+
fields: {
|
|
625
|
+
url: { type: "text", label: `Page address (https, from: ${hosts.join(", ")})` },
|
|
626
|
+
title: { type: "text", label: "Title (for screen readers)" },
|
|
627
|
+
height: { type: "number", label: "Height (pixels)", min: 200, max: 4e3 }
|
|
628
|
+
},
|
|
629
|
+
defaultProps: { url: "", title: "Embedded page", height: 800 },
|
|
630
|
+
render: ({ url, title, height }) => {
|
|
631
|
+
const src = embedAllowed(url, hosts);
|
|
632
|
+
if (!src) return null;
|
|
633
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
634
|
+
"iframe",
|
|
635
|
+
{
|
|
636
|
+
src,
|
|
637
|
+
title: title || "Embedded page",
|
|
638
|
+
loading: "lazy",
|
|
639
|
+
sandbox: "allow-scripts allow-same-origin allow-popups",
|
|
640
|
+
referrerPolicy: "no-referrer",
|
|
641
|
+
style: { width: "100%", height: `${Math.min(4e3, Math.max(200, Number(height) || 800))}px`, border: 0, borderRadius: tokenVar("radius"), background: "#fff" }
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
450
647
|
|
|
451
648
|
const builtInBlocks = {
|
|
452
649
|
Section,
|
|
@@ -456,6 +653,9 @@ const builtInBlocks = {
|
|
|
456
653
|
Hero,
|
|
457
654
|
Heading,
|
|
458
655
|
Text,
|
|
656
|
+
Quote,
|
|
657
|
+
IconList,
|
|
658
|
+
Schedule,
|
|
459
659
|
Button,
|
|
460
660
|
CallToAction,
|
|
461
661
|
Cards,
|
|
@@ -470,18 +670,64 @@ const builtInCategories = {
|
|
|
470
670
|
media: { title: "Media" }
|
|
471
671
|
};
|
|
472
672
|
|
|
473
|
-
|
|
474
|
-
|
|
673
|
+
const SITE_BACKGROUND_DEFAULTS = { image: "", overlayColor: "#ffffff", overlayOpacity: 85, rounded: true, fit: "cover", frame: "none" };
|
|
674
|
+
function resolveBackground(theme = {}, page = {}) {
|
|
675
|
+
const site = { ...SITE_BACKGROUND_DEFAULTS, ...theme.background ?? {} };
|
|
676
|
+
const mode = page.background ?? "site";
|
|
677
|
+
if (mode === "none") return null;
|
|
678
|
+
const chosen = mode === "custom" ? {
|
|
679
|
+
image: page.backgroundImage,
|
|
680
|
+
overlayColor: page.overlayColor ?? site.overlayColor,
|
|
681
|
+
overlayOpacity: page.overlayOpacity ?? site.overlayOpacity,
|
|
682
|
+
rounded: site.rounded,
|
|
683
|
+
fit: site.fit,
|
|
684
|
+
frame: site.frame
|
|
685
|
+
} : {
|
|
686
|
+
...site,
|
|
687
|
+
// A page may keep the site's photo but change how much shows through.
|
|
688
|
+
overlayOpacity: page.overlayOpacity ?? site.overlayOpacity
|
|
689
|
+
};
|
|
690
|
+
const image = cssImage(chosen.image);
|
|
691
|
+
return image ? { ...chosen, image, imageUrl: safeImageUrl(chosen.image) } : null;
|
|
692
|
+
}
|
|
693
|
+
function ThemeRoot({ theme, page, children }) {
|
|
694
|
+
const background = resolveBackground(theme ?? {}, page ?? {});
|
|
695
|
+
if (!background) {
|
|
696
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet", style: themeToStyle(theme), children });
|
|
697
|
+
}
|
|
698
|
+
const radius = background.rounded ? "var(--pagelet-radius)" : 0;
|
|
699
|
+
const panel = { background: overlayColor(background.overlayColor, background.overlayOpacity), padding: "calc(var(--pagelet-space) * 2)" };
|
|
700
|
+
const area = background.fit === "photo" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pagelet-background", style: { display: "grid", borderRadius: radius, overflow: "hidden" }, children: [
|
|
701
|
+
/* @__PURE__ */ jsxRuntime.jsx("img", { src: background.imageUrl, alt: "", "aria-hidden": "true", style: { gridArea: "1 / 1", width: "100%", height: "100%", objectFit: "cover", display: "block" } }),
|
|
702
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet-panel", style: { gridArea: "1 / 1", ...panel }, children })
|
|
703
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet-background", style: { background: `${background.image} center / cover`, borderRadius: radius, overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet-panel", style: { ...panel, minHeight: "60vh" }, children }) });
|
|
704
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet", style: themeToStyle(theme), children: background.frame === "card" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet-frame", style: { border: "1px solid rgba(0, 0, 0, 0.15)", borderRadius: radius, padding: "var(--pagelet-space)", background: "var(--pagelet-color-background)" }, children: area }) : area });
|
|
475
705
|
}
|
|
476
706
|
|
|
477
|
-
|
|
707
|
+
const pageFields = {
|
|
708
|
+
title: { type: "text", label: "Page title (browser tab)" },
|
|
709
|
+
background: {
|
|
710
|
+
type: "radio",
|
|
711
|
+
label: "Background",
|
|
712
|
+
options: [
|
|
713
|
+
{ label: "Site's", value: "site" },
|
|
714
|
+
{ label: "Custom", value: "custom" },
|
|
715
|
+
{ label: "None", value: "none" }
|
|
716
|
+
]
|
|
717
|
+
},
|
|
718
|
+
backgroundImage: { type: "text", label: "Custom background image URL" },
|
|
719
|
+
overlayColor: { type: "text", label: "Custom overlay color (hex)" },
|
|
720
|
+
overlayOpacity: percentField("Overlay opacity % (blank uses the site setting)")
|
|
721
|
+
};
|
|
722
|
+
function createPalette({ blocks = {}, hide = [], categories = {}, root = {}, embedHosts = [] } = {}) {
|
|
723
|
+
const builtIns = embedHosts.length ? { ...builtInBlocks, Embed: makeEmbed(embedHosts) } : builtInBlocks;
|
|
478
724
|
const hidden = new Set(hide);
|
|
479
725
|
for (const name of hidden) {
|
|
480
|
-
if (!(name in
|
|
726
|
+
if (!(name in builtIns) && !(name in blocks)) {
|
|
481
727
|
throw new Error(`pagelet: cannot hide unknown block "${name}"`);
|
|
482
728
|
}
|
|
483
729
|
}
|
|
484
|
-
const all = { ...
|
|
730
|
+
const all = { ...builtIns, ...blocks };
|
|
485
731
|
const components = {};
|
|
486
732
|
const membership = {};
|
|
487
733
|
for (const [name, block] of Object.entries(all)) {
|
|
@@ -506,15 +752,28 @@ function createPalette({ blocks = {}, hide = [], categories = {}, root = {} } =
|
|
|
506
752
|
components,
|
|
507
753
|
categories: puckCategories,
|
|
508
754
|
root: {
|
|
755
|
+
defaultProps: { title: "", background: "site", backgroundImage: "", overlayColor: "#ffffff" },
|
|
509
756
|
...root,
|
|
510
|
-
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
|
|
757
|
+
fields: { ...pageFields, ...root.fields ?? {} },
|
|
758
|
+
// The page's root render applies the theme and the background. It is
|
|
759
|
+
// the root of the editor's preview iframe as well as of a rendered
|
|
760
|
+
// page, so both reach the editor. The site theme arrives through
|
|
761
|
+
// Puck's metadata; the page's own settings are this render's props.
|
|
762
|
+
render: ({ children, puck, title, background, backgroundImage, overlayColor, overlayOpacity }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
763
|
+
ThemeRoot,
|
|
764
|
+
{
|
|
765
|
+
theme: puck?.metadata?.pageletTheme,
|
|
766
|
+
page: { title, background, backgroundImage, overlayColor, overlayOpacity: blankToUndefined(overlayOpacity) },
|
|
767
|
+
children
|
|
768
|
+
}
|
|
769
|
+
)
|
|
514
770
|
}
|
|
515
771
|
}
|
|
516
772
|
};
|
|
517
773
|
}
|
|
774
|
+
function blankToUndefined(value) {
|
|
775
|
+
return value === "" || value === null || value === void 0 ? void 0 : value;
|
|
776
|
+
}
|
|
518
777
|
|
|
519
778
|
class PageletApiError extends Error {
|
|
520
779
|
constructor(message, { status, errors } = {}) {
|
|
@@ -557,31 +816,115 @@ function createPageletClient({ baseUrl = "/pagelet", csrfToken, headers = {}, fe
|
|
|
557
816
|
});
|
|
558
817
|
}
|
|
559
818
|
const pagePath = (id) => `/pages/${encodeURIComponent(id)}`;
|
|
819
|
+
async function getPublishedPage(path) {
|
|
820
|
+
try {
|
|
821
|
+
const data = await request("GET", `/published?path=${encodeURIComponent(path)}`);
|
|
822
|
+
return { document: data.document, pagePath: data.page_path, rest: data.rest ?? "" };
|
|
823
|
+
} catch (error) {
|
|
824
|
+
if (error instanceof PageletApiError && error.status === 404) return null;
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
560
828
|
return {
|
|
561
829
|
listPages: () => request("GET", "/pages"),
|
|
562
830
|
getPage: (id) => request("GET", pagePath(id)),
|
|
563
|
-
createPage: ({ path, title }) => request("POST", "/pages", { page: { path, title } }),
|
|
564
|
-
updatePage: (id, { path, title }) => request("PATCH", pagePath(id), { page: { path, title } }),
|
|
831
|
+
createPage: ({ path, title, matchesSubpaths }) => request("POST", "/pages", { page: { path, title, matches_subpaths: matchesSubpaths } }),
|
|
832
|
+
updatePage: (id, { path, title, matchesSubpaths }) => request("PATCH", pagePath(id), { page: { path, title, matches_subpaths: matchesSubpaths } }),
|
|
565
833
|
saveDraft: (id, document2) => request("PATCH", pagePath(id), { page: { draft_document: document2 } }),
|
|
566
834
|
deletePage: (id) => request("DELETE", pagePath(id)),
|
|
567
835
|
publish: (id, { publishedBy } = {}) => request("POST", `${pagePath(id)}/publish`, publishedBy ? { published_by: publishedBy } : {}),
|
|
568
836
|
listVersions: (id) => request("GET", `${pagePath(id)}/versions`),
|
|
569
837
|
restoreVersion: (id, number) => request("POST", `${pagePath(id)}/versions/${encodeURIComponent(number)}/restore`, {}),
|
|
570
838
|
// Public: the published document at a path, or null when there is none.
|
|
571
|
-
getPublished: async (path) =>
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
839
|
+
getPublished: async (path) => (await getPublishedPage(path))?.document ?? null,
|
|
840
|
+
// Public: { document, pagePath, rest } for a path, or null. rest is what
|
|
841
|
+
// follows a page that answers for sub-paths: /messages/43 -> "43".
|
|
842
|
+
getPublishedPage,
|
|
843
|
+
// Public: the site's theme and menu.
|
|
844
|
+
getSite: async () => (await request("GET", "/site")).settings,
|
|
845
|
+
// Needs publish permission: changes are live at once.
|
|
846
|
+
updateSite: async (settings) => (await request("PATCH", "/site", { settings })).settings
|
|
580
847
|
};
|
|
581
848
|
}
|
|
582
849
|
|
|
850
|
+
const CSS = `
|
|
851
|
+
.pagelet-menu{display:flex;align-items:center;flex-wrap:wrap;gap:4px 16px;padding:8px 16px;background:var(--pagelet-menu-background,#f8f9fa)}
|
|
852
|
+
.pagelet-menu--centered{flex-direction:column}
|
|
853
|
+
.pagelet-menu__groups{display:flex;flex:1;align-items:center;gap:4px 16px;flex-wrap:wrap}
|
|
854
|
+
.pagelet-menu--centered .pagelet-menu__groups{justify-content:center;flex:none}
|
|
855
|
+
.pagelet-menu__group{display:flex;gap:4px 16px;flex-wrap:wrap;align-items:center}
|
|
856
|
+
.pagelet-menu__group--right{margin-left:auto}
|
|
857
|
+
.pagelet-menu--centered .pagelet-menu__group--right{margin-left:0}
|
|
858
|
+
.pagelet-menu__link{text-decoration:none;color:rgba(0,0,0,.65);padding:6px 0;display:inline-flex;align-items:center;gap:6px}
|
|
859
|
+
.pagelet-menu__link:hover{color:rgba(0,0,0,.9)}
|
|
860
|
+
.pagelet-menu__link--current{color:rgba(0,0,0,.9)}
|
|
861
|
+
.pagelet-menu__toggle{display:none;margin-left:auto;background:none;border:1px solid rgba(0,0,0,.2);border-radius:6px;padding:6px 10px;cursor:pointer;font:inherit}
|
|
862
|
+
@media (max-width: 767px){
|
|
863
|
+
.pagelet-menu__toggle{display:inline-block}
|
|
864
|
+
.pagelet-menu__groups{display:none;flex-basis:100%;flex-direction:column;align-items:flex-start}
|
|
865
|
+
.pagelet-menu__groups--open{display:flex}
|
|
866
|
+
.pagelet-menu__group{flex-direction:column;align-items:flex-start}
|
|
867
|
+
.pagelet-menu__group--right{margin-left:0}
|
|
868
|
+
}`;
|
|
869
|
+
function isExternal(href) {
|
|
870
|
+
return /^https?:\/\//i.test(href);
|
|
871
|
+
}
|
|
872
|
+
function needsPlainAnchor(href, LinkComponent) {
|
|
873
|
+
return !LinkComponent || isExternal(href) || /^(mailto|tel):/i.test(href);
|
|
874
|
+
}
|
|
875
|
+
function MenuLink({ item, linkComponent: LinkComponent, currentPath, onNavigate }) {
|
|
876
|
+
const href = safeUrl(item.href);
|
|
877
|
+
if (!href) return null;
|
|
878
|
+
const current = !isExternal(href) && currentPath === href;
|
|
879
|
+
const className = `pagelet-menu__link${current ? " pagelet-menu__link--current" : ""}`;
|
|
880
|
+
const style = item.highlight ? { color: tokenVar("colorAccent") } : void 0;
|
|
881
|
+
const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
882
|
+
item.label,
|
|
883
|
+
item.icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: item.icon })
|
|
884
|
+
] });
|
|
885
|
+
if (needsPlainAnchor(href, LinkComponent)) {
|
|
886
|
+
const external = isExternal(href) ? { target: "_blank", rel: "noopener noreferrer" } : {};
|
|
887
|
+
return /* @__PURE__ */ jsxRuntime.jsx("a", { className, style, href, ...external, children: body });
|
|
888
|
+
}
|
|
889
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LinkComponent, { className, style, to: href, onClick: onNavigate, "aria-current": current ? "page" : void 0, children: body });
|
|
890
|
+
}
|
|
891
|
+
function PageletMenu({ menu = {}, linkComponent, currentPath }) {
|
|
892
|
+
const [open, setOpen] = react.useState(false);
|
|
893
|
+
const items = Array.isArray(menu.items) ? menu.items : [];
|
|
894
|
+
const brand = menu.brand ?? {};
|
|
895
|
+
const logo = safeImageUrl(brand.logo);
|
|
896
|
+
const brandHref = safeUrl(brand.href) ?? "/";
|
|
897
|
+
const close = () => setOpen(false);
|
|
898
|
+
const group = (name) => items.filter((item) => (item.group ?? "left") === name);
|
|
899
|
+
const linkProps = { linkComponent, currentPath, onNavigate: close };
|
|
900
|
+
const BrandLink = needsPlainAnchor(brandHref, linkComponent) ? "a" : linkComponent;
|
|
901
|
+
const brandTarget = BrandLink === "a" ? { href: brandHref } : { to: brandHref, onClick: close };
|
|
902
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `pagelet-menu${menu.style === "centered" ? " pagelet-menu--centered" : ""}`, "aria-label": "Site", children: [
|
|
903
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: CSS }),
|
|
904
|
+
(logo || brand.label) && /* @__PURE__ */ jsxRuntime.jsxs(BrandLink, { className: "pagelet-menu__brand", ...brandTarget, style: { display: "inline-flex", alignItems: "center", gap: "8px", textDecoration: "none", color: "inherit" }, children: [
|
|
905
|
+
logo && /* @__PURE__ */ jsxRuntime.jsx("img", { src: logo, alt: brand.label || "Home", style: { height: "40px", width: "auto", display: "block" } }),
|
|
906
|
+
!logo && brand.label
|
|
907
|
+
] }),
|
|
908
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pagelet-menu__toggle", "aria-expanded": open, onClick: () => setOpen((value) => !value), children: "Menu" }),
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `pagelet-menu__groups${open ? " pagelet-menu__groups--open" : ""}`, children: [
|
|
910
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet-menu__group", children: group("left").map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(MenuLink, { item, ...linkProps }, i)) }),
|
|
911
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pagelet-menu__group pagelet-menu__group--right", children: group("right").map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(MenuLink, { item, ...linkProps }, i)) })
|
|
912
|
+
] })
|
|
913
|
+
] });
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function PageletLayout({ site = {}, linkComponent, currentPath, children }) {
|
|
917
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pagelet-layout", style: { ...themeToStyle(site.theme), maxWidth: tokenVar("maxWidth"), margin: "0 auto" }, children: [
|
|
918
|
+
/* @__PURE__ */ jsxRuntime.jsx(PageletMenu, { menu: site.menu, linkComponent, currentPath }),
|
|
919
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { style: { marginTop: "var(--pagelet-space)" }, children })
|
|
920
|
+
] });
|
|
921
|
+
}
|
|
922
|
+
|
|
583
923
|
exports.DOCUMENT_VERSION = DOCUMENT_VERSION;
|
|
924
|
+
exports.Icon = Icon;
|
|
584
925
|
exports.PageletApiError = PageletApiError;
|
|
926
|
+
exports.PageletLayout = PageletLayout;
|
|
927
|
+
exports.PageletMenu = PageletMenu;
|
|
585
928
|
exports.PageletPage = PageletPage;
|
|
586
929
|
exports.builtInBlocks = builtInBlocks;
|
|
587
930
|
exports.builtInCategories = builtInCategories;
|
|
@@ -589,8 +932,12 @@ exports.createDocument = createDocument;
|
|
|
589
932
|
exports.createPageletClient = createPageletClient;
|
|
590
933
|
exports.createPalette = createPalette;
|
|
591
934
|
exports.defaultTheme = defaultTheme;
|
|
935
|
+
exports.embedAllowed = embedAllowed;
|
|
592
936
|
exports.embedUrl = embedUrl;
|
|
593
937
|
exports.emptyData = emptyData;
|
|
938
|
+
exports.iconNames = iconNames;
|
|
939
|
+
exports.overlayColor = overlayColor;
|
|
940
|
+
exports.pageSettings = pageSettings;
|
|
594
941
|
exports.safeImageUrl = safeImageUrl;
|
|
595
942
|
exports.safeUrl = safeUrl;
|
|
596
943
|
exports.themeToStyle = themeToStyle;
|