@goodfellow-cms/admin 0.6.0 → 0.7.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/dist/editor.js +540 -7
- package/dist/{entry-values-BrY3L0Ai.js → entry-values-DO01xZ9L.js} +376 -44
- package/dist/index.d.ts +104 -3
- package/dist/index.js +490 -90
- package/package.json +5 -5
- package/plugin.css +43 -39
- package/styles.css +243 -0
package/dist/editor.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Bt as editedMarkdown, It as storedData, Lt as storedEntryFields, R as createMediaPreviews, Vt as formattedHtml, a as AdminContext, at as useStrings, b as MediaChooser, f as useDarkMode, h as ScheduleFields, i as usePreviewStyles, l as useSiteContent, nt as StringsProvider, q as READY_MESSAGE, s as useAdmin, st as findPage, t as SCHEDULE_PROP, u as setThemeChoice, ut as mediaUrl, w as SETTINGS_SECTIONS, y as MediaChoiceContext, z as showMediaInPreview } from "./entry-values-DO01xZ9L.js";
|
|
2
2
|
import { r as createChannel } from "./plugin-protocol-DkvBDqsF.js";
|
|
3
|
-
import { COLOR_SCHEME_ATTRIBUTE, DRAWN_SLOT_ELEMENT, MEDIA_DIR, WEEKDAYS, allPages, canFormatMarkdown, cleanEventValue, contentInLanguage, datePart, escapeStyleText, findEntry, formatDate, formatOccurrence, getPageHead, googleFontsUrl, hasLanguages, isAllDay, isEventLike, languageHome, languageLinks, mainLanguage, neverIndexed, pageLinks, pageView, pathLanguage, renderedProblems, shownOccurrence, sitePages, themeToCss, todayIn, wallClock, weekdayName, weekdayOf } from "@goodfellow-cms/core";
|
|
4
|
-
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { COLOR_SCHEME_ATTRIBUTE, DEFAULT_THEME_COLORS, DRAWN_SLOT_ELEMENT, FONT_SIZES, MEDIA_DIR, MINIMUM_CONTRAST, RADII, SIDES, SPACING_STEPS, TEXT_ALIGNS, THEME_COLORS, WEEKDAYS, allPages, canFormatMarkdown, cleanEventValue, colorSlug, contentInLanguage, contrastRatio, customLength, datePart, escapeStyleText, findEntry, formatDate, formatOccurrence, getPageHead, googleFontsUrl, hasLanguages, isAllDay, isCustomColor, isEventLike, languageHome, languageLinks, mainLanguage, neverIndexed, pageLinks, pageView, pathLanguage, readBlockStyle, renderedProblems, shownOccurrence, sitePages, themeColorOf, themeToCss, todayIn, wallClock, weekdayName, weekdayOf, withBlockStyle } from "@goodfellow-cms/core";
|
|
4
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { createRoot } from "react-dom/client";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { PageBody, PageContent, SiteProvider, adminSite, applyEntry, createPuckConfig, createPuckConfigs, cx, isTableField, mediaFieldKind, preparePage, siteMetadata, tableCells } from "@goodfellow-cms/react";
|
|
8
|
-
import {
|
|
7
|
+
import { PageBody, PageContent, SiteProvider, adminSite, applyEntry, blockIcon, blockKeywords, createPuckConfig, createPuckConfigs, cx, isStyleField, isTableField, mediaFieldKind, preparePage, siteMetadata, tableCells } from "@goodfellow-cms/react";
|
|
8
|
+
import { Box } from "lucide-react";
|
|
9
|
+
import { AutoField, Drawer, FieldLabel, Puck, Render, RichTextMenu, createUsePuck, legacySideBarPlugin, migrate, registerOverlayPortal, resolveAllData, useGetPuck } from "@puckeditor/core";
|
|
9
10
|
import { createPortal, flushSync } from "react-dom";
|
|
10
11
|
const CANVAS = `.gf-canvas`;
|
|
11
12
|
const CANVAS_CONTAINER = "gf-canvas";
|
|
@@ -299,6 +300,128 @@ function SharePreview({ path }) {
|
|
|
299
300
|
});
|
|
300
301
|
}
|
|
301
302
|
//#endregion
|
|
303
|
+
//#region src/frame/block-icon.tsx
|
|
304
|
+
/** A block's icon, at the block list's size, in the text's color. */
|
|
305
|
+
function BlockIconSvg({ icon }) {
|
|
306
|
+
if (!icon) return /* @__PURE__ */ jsx(Box, {
|
|
307
|
+
className: "gfa-block-icon",
|
|
308
|
+
size: 16,
|
|
309
|
+
strokeWidth: 2,
|
|
310
|
+
"aria-hidden": "true"
|
|
311
|
+
});
|
|
312
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
313
|
+
className: "gfa-block-icon",
|
|
314
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
315
|
+
width: "16",
|
|
316
|
+
height: "16",
|
|
317
|
+
viewBox: "0 0 24 24",
|
|
318
|
+
fill: "none",
|
|
319
|
+
stroke: "currentColor",
|
|
320
|
+
strokeWidth: "2",
|
|
321
|
+
strokeLinecap: "round",
|
|
322
|
+
strokeLinejoin: "round",
|
|
323
|
+
"aria-hidden": "true",
|
|
324
|
+
children: icon.map(([shape, attributes], index) => createElement(shape, {
|
|
325
|
+
key: index,
|
|
326
|
+
...attributes
|
|
327
|
+
}))
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/** Puck's block list item, with the block's icon before its name. */
|
|
331
|
+
function drawerItemWithIcon(icons) {
|
|
332
|
+
return function DrawerItemWithIcon({ children, name }) {
|
|
333
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
334
|
+
className: "gfa-drawer-item",
|
|
335
|
+
children: [/* @__PURE__ */ jsx(BlockIconSvg, { icon: icons.get(name) }), children]
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/frame/block-search.tsx
|
|
341
|
+
/** The blocks of a Puck config, with their groups and the site's keywords for them, in the list's order. */
|
|
342
|
+
function searchableBlocks(config, siteConfig) {
|
|
343
|
+
const groups = /* @__PURE__ */ new Map();
|
|
344
|
+
const order = [];
|
|
345
|
+
for (const [key, category] of Object.entries(config.categories ?? {})) for (const name of category?.components ?? []) {
|
|
346
|
+
const block = String(name);
|
|
347
|
+
if (!config.components[block] || groups.has(block)) continue;
|
|
348
|
+
groups.set(block, category?.title ?? key);
|
|
349
|
+
order.push(block);
|
|
350
|
+
}
|
|
351
|
+
for (const name of Object.keys(config.components)) if (!groups.has(name)) order.push(name);
|
|
352
|
+
return order.map((name) => {
|
|
353
|
+
const block = siteConfig?.blocks[name];
|
|
354
|
+
return {
|
|
355
|
+
name,
|
|
356
|
+
label: config.components[name]?.label ?? name,
|
|
357
|
+
...groups.has(name) && { group: groups.get(name) },
|
|
358
|
+
keywords: (block && blockKeywords(block)) ?? []
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/** A block's name as words: `MediaText` is "media text". */
|
|
363
|
+
function nameWords(name) {
|
|
364
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ");
|
|
365
|
+
}
|
|
366
|
+
/** Whether every word typed starts a word of `text`. */
|
|
367
|
+
function startsWords(text, words) {
|
|
368
|
+
const lower = ` ${text.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ")}`;
|
|
369
|
+
return words.every((word) => lower.includes(` ${word}`));
|
|
370
|
+
}
|
|
371
|
+
/** The blocks `query` finds, best first: by label, then by name or keyword, then by group. */
|
|
372
|
+
function searchBlocks(query, blocks) {
|
|
373
|
+
const words = query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
374
|
+
if (words.length === 0) return [...blocks];
|
|
375
|
+
const rank = (block) => {
|
|
376
|
+
if (block.label.toLowerCase().startsWith(query.trim().toLowerCase())) return 0;
|
|
377
|
+
if (startsWords(block.label, words)) return 1;
|
|
378
|
+
if (startsWords(nameWords(block.name), words) || block.keywords.some((keyword) => startsWords(keyword, words))) return 2;
|
|
379
|
+
if (block.group && startsWords(`${block.group} ${block.label}`, words)) return 3;
|
|
380
|
+
};
|
|
381
|
+
return blocks.map((block, index) => ({
|
|
382
|
+
block,
|
|
383
|
+
index,
|
|
384
|
+
rank: rank(block)
|
|
385
|
+
})).filter((found) => found.rank !== void 0).sort((a, b) => a.rank - b.rank || a.index - b.index).map(({ block }) => block);
|
|
386
|
+
}
|
|
387
|
+
/** Puck's `drawer` override: the search box, then Puck's own list or what's found. */
|
|
388
|
+
function BlockSearch({ blocks, item, children }) {
|
|
389
|
+
const t = useStrings();
|
|
390
|
+
const [query, setQuery] = useState("");
|
|
391
|
+
const id = useId();
|
|
392
|
+
const found = query.trim() ? searchBlocks(query, blocks) : void 0;
|
|
393
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
394
|
+
className: "gfa-block-search",
|
|
395
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
396
|
+
className: "gfa-block-search-box",
|
|
397
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
398
|
+
id,
|
|
399
|
+
type: "search",
|
|
400
|
+
className: "gfa-block-search-input",
|
|
401
|
+
"aria-label": t("editor.blockSearch"),
|
|
402
|
+
placeholder: t("editor.blockSearch"),
|
|
403
|
+
value: query,
|
|
404
|
+
onChange: (event) => setQuery(event.target.value),
|
|
405
|
+
onKeyDown: (event) => {
|
|
406
|
+
if (event.key === "Escape") setQuery("");
|
|
407
|
+
}
|
|
408
|
+
})
|
|
409
|
+
}), found === void 0 ? children : found.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
410
|
+
className: "gfa-block-search-none",
|
|
411
|
+
role: "status",
|
|
412
|
+
children: t("editor.blockSearchNone")
|
|
413
|
+
}) : /* @__PURE__ */ jsx("section", {
|
|
414
|
+
className: "gfa-block-search-results",
|
|
415
|
+
"aria-label": t("editor.blockSearchFound"),
|
|
416
|
+
children: /* @__PURE__ */ jsx(Drawer, { children: found.map((block) => /* @__PURE__ */ jsx(Drawer.Item, {
|
|
417
|
+
name: block.name,
|
|
418
|
+
label: block.label,
|
|
419
|
+
children: item
|
|
420
|
+
}, block.name)) })
|
|
421
|
+
})]
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
302
425
|
//#region src/event-field.tsx
|
|
303
426
|
const EVERY_LABELS = {
|
|
304
427
|
none: "event.repeat.none",
|
|
@@ -1199,6 +1322,7 @@ function FrameProviders({ config, client, init, site, children }) {
|
|
|
1199
1322
|
drafts: {
|
|
1200
1323
|
kept: true,
|
|
1201
1324
|
names: [],
|
|
1325
|
+
reviews: [],
|
|
1202
1326
|
open: async () => refused(),
|
|
1203
1327
|
publishAll: async () => refused(),
|
|
1204
1328
|
submit: async () => refused(),
|
|
@@ -1304,6 +1428,384 @@ function shownSettings(original, path) {
|
|
|
1304
1428
|
};
|
|
1305
1429
|
}
|
|
1306
1430
|
//#endregion
|
|
1431
|
+
//#region src/frame/style-field.tsx
|
|
1432
|
+
/**
|
|
1433
|
+
* A block's Style field (`classNameField` in `@goodfellow-cms/react`), which
|
|
1434
|
+
* Puck's editor shows as controls for its look in place of a box of classes:
|
|
1435
|
+
* text alignment, size and color, background, space inside and around it and
|
|
1436
|
+
* rounded corners, written into its `className` as Tailwind classes
|
|
1437
|
+
* (`withBlockStyle()` in `@goodfellow-cms/core`), with any other classes kept.
|
|
1438
|
+
*/
|
|
1439
|
+
/** A color's value to show and compare: a theme color's, or the editor's own. */
|
|
1440
|
+
function shownColor(value, colors) {
|
|
1441
|
+
if (!value) return void 0;
|
|
1442
|
+
const theme = themeColorOf(value);
|
|
1443
|
+
return theme ? colors[theme] ?? DEFAULT_THEME_COLORS[theme] : value;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* A choice from a scale, or a size of the editor's own: the scale's steps,
|
|
1447
|
+
* "As the block has it" and "Your own…", which shows a box for a length.
|
|
1448
|
+
*/
|
|
1449
|
+
function ScaleChoice({ label, value, steps, stepLabel, readOnly, onChange }) {
|
|
1450
|
+
const t = useStrings();
|
|
1451
|
+
const id = useId();
|
|
1452
|
+
const custom = value !== void 0 && !steps.includes(value);
|
|
1453
|
+
const [ownChosen, setOwnChosen] = useState(custom);
|
|
1454
|
+
const [typed, setTyped] = useState(custom ? value : "");
|
|
1455
|
+
useEffect(() => {
|
|
1456
|
+
if (value !== void 0 && !steps.includes(value)) {
|
|
1457
|
+
setOwnChosen(true);
|
|
1458
|
+
setTyped(value);
|
|
1459
|
+
}
|
|
1460
|
+
}, [value, steps]);
|
|
1461
|
+
const own = custom || ownChosen;
|
|
1462
|
+
const invalid = own && typed.trim() !== "" && customLength(typed) === void 0;
|
|
1463
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1464
|
+
className: "gfa-style-choice",
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ jsx("label", {
|
|
1467
|
+
className: "gfa-style-label",
|
|
1468
|
+
htmlFor: id,
|
|
1469
|
+
children: label
|
|
1470
|
+
}),
|
|
1471
|
+
/* @__PURE__ */ jsxs("select", {
|
|
1472
|
+
id,
|
|
1473
|
+
className: "gfa-input",
|
|
1474
|
+
disabled: readOnly,
|
|
1475
|
+
value: own ? "custom" : value ?? "",
|
|
1476
|
+
onChange: (event) => {
|
|
1477
|
+
const chosen = event.target.value;
|
|
1478
|
+
if (chosen === "custom") {
|
|
1479
|
+
setOwnChosen(true);
|
|
1480
|
+
const length = customLength(typed);
|
|
1481
|
+
if (length) onChange(length);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
setOwnChosen(false);
|
|
1485
|
+
onChange(chosen === "" ? void 0 : chosen);
|
|
1486
|
+
},
|
|
1487
|
+
children: [
|
|
1488
|
+
/* @__PURE__ */ jsx("option", {
|
|
1489
|
+
value: "",
|
|
1490
|
+
children: t("style.default")
|
|
1491
|
+
}),
|
|
1492
|
+
steps.map((step) => /* @__PURE__ */ jsx("option", {
|
|
1493
|
+
value: step,
|
|
1494
|
+
children: stepLabel(step)
|
|
1495
|
+
}, step)),
|
|
1496
|
+
/* @__PURE__ */ jsx("option", {
|
|
1497
|
+
value: "custom",
|
|
1498
|
+
children: t("style.custom")
|
|
1499
|
+
})
|
|
1500
|
+
]
|
|
1501
|
+
}),
|
|
1502
|
+
own && /* @__PURE__ */ jsx("input", {
|
|
1503
|
+
className: "gfa-input",
|
|
1504
|
+
value: typed,
|
|
1505
|
+
readOnly,
|
|
1506
|
+
"aria-label": `${label}: ${t("style.customLength")}`,
|
|
1507
|
+
placeholder: t("style.customLength"),
|
|
1508
|
+
"aria-invalid": invalid,
|
|
1509
|
+
onChange: (event) => {
|
|
1510
|
+
setTyped(event.target.value);
|
|
1511
|
+
const length = customLength(event.target.value);
|
|
1512
|
+
if (length) onChange(length);
|
|
1513
|
+
}
|
|
1514
|
+
}),
|
|
1515
|
+
invalid && /* @__PURE__ */ jsx("p", {
|
|
1516
|
+
className: "gfa-error-text",
|
|
1517
|
+
children: t("style.customLengthInvalid")
|
|
1518
|
+
})
|
|
1519
|
+
]
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
/** Spacing steps as people read them: `4` is 16px. */
|
|
1523
|
+
function spacingLabel(t, step) {
|
|
1524
|
+
return step === "0" ? t("style.none") : `${Number(step) * 4}px`;
|
|
1525
|
+
}
|
|
1526
|
+
/** Space on every side at once, or on each side. */
|
|
1527
|
+
function SidesChoice({ label, sides, readOnly, onChange }) {
|
|
1528
|
+
const t = useStrings();
|
|
1529
|
+
const values = SIDES.map((side) => sides?.[side]);
|
|
1530
|
+
const same = values.every((value) => value === values[0]);
|
|
1531
|
+
const [each, setEach] = useState(!same);
|
|
1532
|
+
useEffect(() => {
|
|
1533
|
+
if (!same) setEach(true);
|
|
1534
|
+
}, [same]);
|
|
1535
|
+
const set = (next) => {
|
|
1536
|
+
const kept = Object.fromEntries(Object.entries(next).filter(([, value]) => value !== void 0));
|
|
1537
|
+
onChange(Object.keys(kept).length > 0 ? kept : void 0);
|
|
1538
|
+
};
|
|
1539
|
+
const stepLabel = (step) => spacingLabel(t, step);
|
|
1540
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1541
|
+
className: "gfa-style-group",
|
|
1542
|
+
children: [
|
|
1543
|
+
/* @__PURE__ */ jsx("legend", {
|
|
1544
|
+
className: "gfa-style-label",
|
|
1545
|
+
children: label
|
|
1546
|
+
}),
|
|
1547
|
+
each ? /* @__PURE__ */ jsx("div", {
|
|
1548
|
+
className: "gfa-style-sides",
|
|
1549
|
+
children: SIDES.map((side) => /* @__PURE__ */ jsx(ScaleChoice, {
|
|
1550
|
+
label: t(`style.side.${side}`),
|
|
1551
|
+
value: sides?.[side],
|
|
1552
|
+
steps: SPACING_STEPS,
|
|
1553
|
+
stepLabel,
|
|
1554
|
+
readOnly,
|
|
1555
|
+
onChange: (value) => set({
|
|
1556
|
+
...sides,
|
|
1557
|
+
[side]: value
|
|
1558
|
+
})
|
|
1559
|
+
}, side))
|
|
1560
|
+
}) : /* @__PURE__ */ jsx(ScaleChoice, {
|
|
1561
|
+
label: t("style.allSides"),
|
|
1562
|
+
value: values[0],
|
|
1563
|
+
steps: SPACING_STEPS,
|
|
1564
|
+
stepLabel,
|
|
1565
|
+
readOnly,
|
|
1566
|
+
onChange: (value) => set(Object.fromEntries(SIDES.map((side) => [side, value])))
|
|
1567
|
+
}),
|
|
1568
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1569
|
+
className: "gfa-style-check",
|
|
1570
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
1571
|
+
type: "checkbox",
|
|
1572
|
+
checked: each,
|
|
1573
|
+
disabled: readOnly || each && !same,
|
|
1574
|
+
onChange: (event) => setEach(event.target.checked)
|
|
1575
|
+
}), t("style.eachSide")]
|
|
1576
|
+
})
|
|
1577
|
+
]
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
/** The theme's colors as swatches, a color of the editor's own, and back to the block's. */
|
|
1581
|
+
function ColorChoice({ label, value, colors, readOnly, onChange }) {
|
|
1582
|
+
const t = useStrings();
|
|
1583
|
+
const own = value !== void 0 && isCustomColor(value);
|
|
1584
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1585
|
+
className: "gfa-style-group",
|
|
1586
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
1587
|
+
className: "gfa-style-label",
|
|
1588
|
+
children: label
|
|
1589
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1590
|
+
className: "gfa-style-swatches",
|
|
1591
|
+
children: [
|
|
1592
|
+
/* @__PURE__ */ jsx("button", {
|
|
1593
|
+
type: "button",
|
|
1594
|
+
className: "gfa-style-swatch gfa-style-swatch-none",
|
|
1595
|
+
"aria-pressed": value === void 0,
|
|
1596
|
+
disabled: readOnly,
|
|
1597
|
+
title: t("style.colorDefault"),
|
|
1598
|
+
"aria-label": t("style.colorDefault"),
|
|
1599
|
+
onClick: () => onChange(void 0)
|
|
1600
|
+
}),
|
|
1601
|
+
THEME_COLORS.map((color) => {
|
|
1602
|
+
const slug = colorSlug(color);
|
|
1603
|
+
const name = t(`theme.color.${color}`);
|
|
1604
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1605
|
+
type: "button",
|
|
1606
|
+
className: "gfa-style-swatch",
|
|
1607
|
+
style: { background: colors[color] ?? DEFAULT_THEME_COLORS[color] },
|
|
1608
|
+
"aria-pressed": value === slug,
|
|
1609
|
+
disabled: readOnly,
|
|
1610
|
+
title: name,
|
|
1611
|
+
"aria-label": name,
|
|
1612
|
+
onClick: () => onChange(slug)
|
|
1613
|
+
}, color);
|
|
1614
|
+
}),
|
|
1615
|
+
/* @__PURE__ */ jsx("label", {
|
|
1616
|
+
className: "gfa-style-swatch gfa-style-swatch-own",
|
|
1617
|
+
title: t("style.colorCustom"),
|
|
1618
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1619
|
+
type: "color",
|
|
1620
|
+
value: own ? value : "#000000",
|
|
1621
|
+
disabled: readOnly,
|
|
1622
|
+
"aria-label": `${label}: ${t("style.colorCustom")}`,
|
|
1623
|
+
onChange: (event) => onChange(event.target.value.toLowerCase())
|
|
1624
|
+
})
|
|
1625
|
+
})
|
|
1626
|
+
]
|
|
1627
|
+
})]
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
/** Other classes, kept as typed while the box has focus, so a class isn't taken into the controls halfway. */
|
|
1631
|
+
function OtherClasses({ other, readOnly, onChange }) {
|
|
1632
|
+
const t = useStrings();
|
|
1633
|
+
const id = useId();
|
|
1634
|
+
const joined = other.join(" ");
|
|
1635
|
+
const [typed, setTyped] = useState(joined);
|
|
1636
|
+
const [focused, setFocused] = useState(false);
|
|
1637
|
+
useEffect(() => {
|
|
1638
|
+
if (!focused) setTyped(joined);
|
|
1639
|
+
}, [joined, focused]);
|
|
1640
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1641
|
+
className: "gfa-style-choice",
|
|
1642
|
+
children: [
|
|
1643
|
+
/* @__PURE__ */ jsx("label", {
|
|
1644
|
+
className: "gfa-style-label",
|
|
1645
|
+
htmlFor: id,
|
|
1646
|
+
children: t("style.other")
|
|
1647
|
+
}),
|
|
1648
|
+
/* @__PURE__ */ jsx("input", {
|
|
1649
|
+
id,
|
|
1650
|
+
className: "gfa-input gfa-code",
|
|
1651
|
+
value: typed,
|
|
1652
|
+
readOnly,
|
|
1653
|
+
spellCheck: false,
|
|
1654
|
+
onFocus: () => setFocused(true),
|
|
1655
|
+
onBlur: () => setFocused(false),
|
|
1656
|
+
onChange: (event) => {
|
|
1657
|
+
setTyped(event.target.value);
|
|
1658
|
+
onChange(event.target.value.split(/\s+/).filter(Boolean));
|
|
1659
|
+
}
|
|
1660
|
+
}),
|
|
1661
|
+
/* @__PURE__ */ jsx("p", {
|
|
1662
|
+
className: "gfa-hint",
|
|
1663
|
+
children: t("style.otherHint")
|
|
1664
|
+
})
|
|
1665
|
+
]
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
function StyleControls({ value, colors, readOnly, label, ownAlign, onChange }) {
|
|
1669
|
+
const t = useStrings();
|
|
1670
|
+
const { style, other } = readBlockStyle(value);
|
|
1671
|
+
const set = (next) => onChange(withBlockStyle(other, next));
|
|
1672
|
+
const change = (key, next) => {
|
|
1673
|
+
const { [key]: _old, ...rest } = style;
|
|
1674
|
+
set(next === void 0 ? rest : {
|
|
1675
|
+
...rest,
|
|
1676
|
+
[key]: next
|
|
1677
|
+
});
|
|
1678
|
+
};
|
|
1679
|
+
const text = shownColor(style.color, colors);
|
|
1680
|
+
const background = shownColor(style.background, colors);
|
|
1681
|
+
const ratio = text && background ? contrastRatio(text, background) : void 0;
|
|
1682
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1683
|
+
className: "gfa-style-field",
|
|
1684
|
+
children: [
|
|
1685
|
+
label && /* @__PURE__ */ jsx("div", {
|
|
1686
|
+
className: "gfa-style-title",
|
|
1687
|
+
children: label
|
|
1688
|
+
}),
|
|
1689
|
+
!ownAlign && /* @__PURE__ */ jsxs("fieldset", {
|
|
1690
|
+
className: "gfa-style-group",
|
|
1691
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
1692
|
+
className: "gfa-style-label",
|
|
1693
|
+
children: t("style.align")
|
|
1694
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1695
|
+
className: "gfa-style-segments",
|
|
1696
|
+
children: [void 0, ...TEXT_ALIGNS].map((align) => /* @__PURE__ */ jsx("button", {
|
|
1697
|
+
type: "button",
|
|
1698
|
+
className: "gfa-style-segment",
|
|
1699
|
+
"aria-pressed": style.align === align,
|
|
1700
|
+
disabled: readOnly,
|
|
1701
|
+
onClick: () => change("align", align),
|
|
1702
|
+
children: t(`style.align.${align ?? "default"}`)
|
|
1703
|
+
}, align ?? "default"))
|
|
1704
|
+
})]
|
|
1705
|
+
}),
|
|
1706
|
+
/* @__PURE__ */ jsx(ScaleChoice, {
|
|
1707
|
+
label: t("style.fontSize"),
|
|
1708
|
+
value: style.fontSize,
|
|
1709
|
+
steps: FONT_SIZES,
|
|
1710
|
+
stepLabel: (step) => t(`style.fontSize.${step}`),
|
|
1711
|
+
readOnly,
|
|
1712
|
+
onChange: (size) => change("fontSize", size)
|
|
1713
|
+
}),
|
|
1714
|
+
/* @__PURE__ */ jsx(ColorChoice, {
|
|
1715
|
+
label: t("style.color"),
|
|
1716
|
+
value: style.color,
|
|
1717
|
+
colors,
|
|
1718
|
+
readOnly,
|
|
1719
|
+
onChange: (color) => change("color", color)
|
|
1720
|
+
}),
|
|
1721
|
+
/* @__PURE__ */ jsx(ColorChoice, {
|
|
1722
|
+
label: t("style.background"),
|
|
1723
|
+
value: style.background,
|
|
1724
|
+
colors,
|
|
1725
|
+
readOnly,
|
|
1726
|
+
onChange: (color) => change("background", color)
|
|
1727
|
+
}),
|
|
1728
|
+
ratio !== void 0 && ratio < MINIMUM_CONTRAST && /* @__PURE__ */ jsx("p", {
|
|
1729
|
+
className: "gfa-hint gfa-style-warning",
|
|
1730
|
+
role: "status",
|
|
1731
|
+
children: t("style.contrast")
|
|
1732
|
+
}),
|
|
1733
|
+
/* @__PURE__ */ jsx(SidesChoice, {
|
|
1734
|
+
label: t("style.padding"),
|
|
1735
|
+
sides: style.padding,
|
|
1736
|
+
readOnly,
|
|
1737
|
+
onChange: (sides) => change("padding", sides)
|
|
1738
|
+
}),
|
|
1739
|
+
/* @__PURE__ */ jsx(SidesChoice, {
|
|
1740
|
+
label: t("style.margin"),
|
|
1741
|
+
sides: style.margin,
|
|
1742
|
+
readOnly,
|
|
1743
|
+
onChange: (sides) => change("margin", sides)
|
|
1744
|
+
}),
|
|
1745
|
+
/* @__PURE__ */ jsx(ScaleChoice, {
|
|
1746
|
+
label: t("style.radius"),
|
|
1747
|
+
value: style.radius,
|
|
1748
|
+
steps: RADII,
|
|
1749
|
+
stepLabel: (step) => t(`style.radius.${step}`),
|
|
1750
|
+
readOnly,
|
|
1751
|
+
onChange: (radius) => change("radius", radius)
|
|
1752
|
+
}),
|
|
1753
|
+
/* @__PURE__ */ jsx(OtherClasses, {
|
|
1754
|
+
other,
|
|
1755
|
+
readOnly,
|
|
1756
|
+
onChange: (next) => onChange(withBlockStyle(next, style))
|
|
1757
|
+
}),
|
|
1758
|
+
/* @__PURE__ */ jsx("button", {
|
|
1759
|
+
type: "button",
|
|
1760
|
+
className: "gfa-button gfa-button-secondary",
|
|
1761
|
+
disabled: readOnly || Object.keys(style).length === 0 && other.length === 0,
|
|
1762
|
+
onClick: () => onChange(""),
|
|
1763
|
+
children: t("style.reset")
|
|
1764
|
+
})
|
|
1765
|
+
]
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
function withStyleField(field, colors, ownAlign) {
|
|
1769
|
+
if (field.type !== "text" || !isStyleField(field)) return field;
|
|
1770
|
+
const render = ({ value, onChange, readOnly }) => /* @__PURE__ */ jsx(StyleControls, {
|
|
1771
|
+
value,
|
|
1772
|
+
colors,
|
|
1773
|
+
readOnly,
|
|
1774
|
+
label: field.label,
|
|
1775
|
+
ownAlign,
|
|
1776
|
+
onChange
|
|
1777
|
+
});
|
|
1778
|
+
return {
|
|
1779
|
+
type: "custom",
|
|
1780
|
+
label: field.label,
|
|
1781
|
+
metadata: field.metadata,
|
|
1782
|
+
render
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
function withStyleFieldsIn(fields, colors) {
|
|
1786
|
+
if (!fields) return fields;
|
|
1787
|
+
const ownAlign = "align" in fields;
|
|
1788
|
+
return Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, withStyleField(field, colors, ownAlign)]));
|
|
1789
|
+
}
|
|
1790
|
+
function withStyleComponent(component, colors) {
|
|
1791
|
+
if (!component) return component;
|
|
1792
|
+
const { resolveFields } = component;
|
|
1793
|
+
return {
|
|
1794
|
+
...component,
|
|
1795
|
+
...component.fields && { fields: withStyleFieldsIn(component.fields, colors) },
|
|
1796
|
+
...resolveFields && { resolveFields: async (...args) => withStyleFieldsIn(await resolveFields(...args), colors) }
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
/** The config with every block's Style field, and the page's, drawn as a style's controls. */
|
|
1800
|
+
function withStyleFields(config, colors) {
|
|
1801
|
+
const components = Object.fromEntries(Object.entries(config.components).map(([name, component]) => [name, withStyleComponent(component, colors)]));
|
|
1802
|
+
return {
|
|
1803
|
+
...config,
|
|
1804
|
+
components,
|
|
1805
|
+
...config.root && { root: withStyleComponent(config.root, colors) }
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
//#endregion
|
|
1307
1809
|
//#region src/frame/table-field.tsx
|
|
1308
1810
|
/**
|
|
1309
1811
|
* The grid of cells a table's field is edited in (`tableField()` in
|
|
@@ -1608,6 +2110,14 @@ const ENTRY_UI = {
|
|
|
1608
2110
|
};
|
|
1609
2111
|
const entryPlugins = [legacySideBarPlugin()];
|
|
1610
2112
|
/** Puck, set up for one page, header, footer, template or item: site context, previews and the bridge. */
|
|
2113
|
+
/** Puck's permissions for something shown without editing: nothing can be changed, added, moved or removed. */
|
|
2114
|
+
const READ_ONLY = {
|
|
2115
|
+
drag: false,
|
|
2116
|
+
duplicate: false,
|
|
2117
|
+
delete: false,
|
|
2118
|
+
edit: false,
|
|
2119
|
+
insert: false
|
|
2120
|
+
};
|
|
1611
2121
|
function FrameEditor({ open, onOpened }) {
|
|
1612
2122
|
const client = useAdminClient();
|
|
1613
2123
|
const { config: siteConfig, pageConfig, layoutConfig, templateConfig } = useFrameCode();
|
|
@@ -1736,6 +2246,11 @@ function FrameEditor({ open, onOpened }) {
|
|
|
1736
2246
|
site
|
|
1737
2247
|
]);
|
|
1738
2248
|
const described = useMemo(() => describeConfig(config), [config]);
|
|
2249
|
+
const puckConfig = useMemo(() => kind === "entry" ? config : withStyleFields(config, content.settings.theme.colors), [
|
|
2250
|
+
kind,
|
|
2251
|
+
config,
|
|
2252
|
+
content.settings.theme.colors
|
|
2253
|
+
]);
|
|
1739
2254
|
const told = useRef(void 0);
|
|
1740
2255
|
useEffect(() => {
|
|
1741
2256
|
if (told.current === described) return;
|
|
@@ -1753,25 +2268,42 @@ function FrameEditor({ open, onOpened }) {
|
|
|
1753
2268
|
...kind === "entry" ? ENTRY_UI : {},
|
|
1754
2269
|
...open.ui
|
|
1755
2270
|
}), [kind, open.ui]);
|
|
2271
|
+
const searchable = useMemo(() => searchableBlocks(config, siteConfig), [config, siteConfig]);
|
|
2272
|
+
const drawerItem = useMemo(() => {
|
|
2273
|
+
const icons = /* @__PURE__ */ new Map();
|
|
2274
|
+
for (const [name, block] of Object.entries(siteConfig.blocks)) {
|
|
2275
|
+
const icon = blockIcon(block);
|
|
2276
|
+
if (icon) icons.set(name, icon);
|
|
2277
|
+
}
|
|
2278
|
+
return drawerItemWithIcon(icons);
|
|
2279
|
+
}, [siteConfig]);
|
|
1756
2280
|
const overrides = useMemo(() => ({
|
|
1757
2281
|
header: () => /* @__PURE__ */ jsx(Bridge, {
|
|
1758
2282
|
channel: client.channel,
|
|
1759
2283
|
settings
|
|
1760
2284
|
}),
|
|
2285
|
+
drawer: ({ children }) => /* @__PURE__ */ jsx(BlockSearch, {
|
|
2286
|
+
blocks: searchable,
|
|
2287
|
+
item: drawerItem,
|
|
2288
|
+
children
|
|
2289
|
+
}),
|
|
2290
|
+
drawerItem,
|
|
1761
2291
|
fieldTypes: { text: TextFieldWithMedia },
|
|
1762
2292
|
...kind === "page" && path && { fields: ({ children, itemSelector }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [children, !itemSelector && /* @__PURE__ */ jsx(SharePreview, { path })] }) }
|
|
1763
2293
|
}), [
|
|
1764
2294
|
client,
|
|
1765
2295
|
settings,
|
|
1766
2296
|
kind,
|
|
1767
|
-
path
|
|
2297
|
+
path,
|
|
2298
|
+
searchable,
|
|
2299
|
+
drawerItem
|
|
1768
2300
|
]);
|
|
1769
2301
|
return /* @__PURE__ */ jsx("div", {
|
|
1770
2302
|
className: `gfa-editor-puck gfa-editor-for-${kind}`,
|
|
1771
2303
|
children: /* @__PURE__ */ jsx(SiteProvider, {
|
|
1772
2304
|
value: site,
|
|
1773
2305
|
children: /* @__PURE__ */ jsx(Puck, {
|
|
1774
|
-
config,
|
|
2306
|
+
config: puckConfig,
|
|
1775
2307
|
data: initialData,
|
|
1776
2308
|
metadata: siteMetadata(site),
|
|
1777
2309
|
headerTitle: title,
|
|
@@ -1779,6 +2311,7 @@ function FrameEditor({ open, onOpened }) {
|
|
|
1779
2311
|
iframe: { enabled: false },
|
|
1780
2312
|
ui,
|
|
1781
2313
|
...kind === "entry" && { plugins: entryPlugins },
|
|
2314
|
+
...open.readOnly && { permissions: READ_ONLY },
|
|
1782
2315
|
overrides,
|
|
1783
2316
|
onChange: updateView
|
|
1784
2317
|
})
|