@goodfellow-cms/admin 0.7.0 → 0.8.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 CHANGED
@@ -1,11 +1,11 @@
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
- import { r as createChannel } from "./plugin-protocol-DkvBDqsF.js";
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";
1
+ import { J as editedMarkdown, U as storedData, W as storedEntryFields, Y as formattedHtml, g as mediaUrl, i as showMediaInPreview, n as createMediaPreviews, o as createChannel } from "./plugin-protocol-BgYL9WSm.js";
2
+ import { J as EditorTextContext, T as SETTINGS_SECTIONS, Y as useEditorText, a as AdminContext, b as MediaChoiceContext, d as setThemeChoice, g as ScheduleFields, i as usePreviewStyles, it as StringsProvider, l as useSiteContent, lt as findPage, p as useDarkMode, q as READY_MESSAGE, s as useAdmin, st as useStrings, t as SCHEDULE_PROP, x as MediaChooser } from "./entry-values-Bu8ljnoX.js";
3
+ import { COLOR_SCHEME_ATTRIBUTE, DEFAULT_THEME_COLORS, DRAWN_SLOT_ELEMENT, FONT_SIZES, MEDIA_DIR, MINIMUM_CONTRAST, RADII, SCREEN_SIZES, 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, isOwnersOnlyField, languageHome, languageLinks, mainLanguage, neverIndexed, pageLinks, pageView, pathLanguage, readBlockStyle, renderedProblems, shownOccurrence, sitePages, themeColorOf, themeToCss, todayIn, wallClock, weekdayName, weekdayOf, withBlockStyle } from "@goodfellow-cms/core";
4
+ import { Box } from "lucide-react";
4
5
  import { createContext, createElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
5
6
  import { createRoot } from "react-dom/client";
6
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
8
  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
9
  import { AutoField, Drawer, FieldLabel, Puck, Render, RichTextMenu, createUsePuck, legacySideBarPlugin, migrate, registerOverlayPortal, resolveAllData, useGetPuck } from "@puckeditor/core";
10
10
  import { createPortal, flushSync } from "react-dom";
11
11
  const CANVAS = `.gf-canvas`;
@@ -1354,17 +1354,24 @@ function FrameProviders({ config, client, init, site, children }) {
1354
1354
  kind,
1355
1355
  value
1356
1356
  }), [client]);
1357
+ const text = useMemo(() => ({
1358
+ puck: init.puck ?? {},
1359
+ labels: init.labels ?? {}
1360
+ }), [init.puck, init.labels]);
1357
1361
  return /* @__PURE__ */ jsx(StringsProvider, {
1358
1362
  strings: init.strings,
1359
- children: /* @__PURE__ */ jsx(AdminClientContext.Provider, {
1360
- value: client,
1361
- children: /* @__PURE__ */ jsx(FrameCodeContext.Provider, {
1362
- value: code,
1363
- children: /* @__PURE__ */ jsx(AdminContext.Provider, {
1364
- value: admin,
1365
- children: /* @__PURE__ */ jsx(MediaChoiceContext.Provider, {
1366
- value: chooseMedia,
1367
- children
1363
+ children: /* @__PURE__ */ jsx(EditorTextContext.Provider, {
1364
+ value: text,
1365
+ children: /* @__PURE__ */ jsx(AdminClientContext.Provider, {
1366
+ value: client,
1367
+ children: /* @__PURE__ */ jsx(FrameCodeContext.Provider, {
1368
+ value: code,
1369
+ children: /* @__PURE__ */ jsx(AdminContext.Provider, {
1370
+ value: admin,
1371
+ children: /* @__PURE__ */ jsx(MediaChoiceContext.Provider, {
1372
+ value: chooseMedia,
1373
+ children
1374
+ })
1368
1375
  })
1369
1376
  })
1370
1377
  })
@@ -1372,6 +1379,121 @@ function FrameProviders({ config, client, init, site, children }) {
1372
1379
  });
1373
1380
  }
1374
1381
  //#endregion
1382
+ //#region src/frame/labels.ts
1383
+ /** A label in the admin panel's language, or as it is where the table has none. */
1384
+ function translated(labels, text) {
1385
+ return text === void 0 ? text : labels[text] ?? text;
1386
+ }
1387
+ function withFieldLabels(field, labels) {
1388
+ return {
1389
+ ...field,
1390
+ ...field.label !== void 0 && { label: translated(labels, field.label) },
1391
+ ...typeof field.placeholder === "string" && { placeholder: translated(labels, field.placeholder) },
1392
+ ...Array.isArray(field.options) && { options: field.options.map((option) => ({
1393
+ ...option,
1394
+ label: translated(labels, option.label) ?? option.label
1395
+ })) },
1396
+ ...field.arrayFields && { arrayFields: withFieldsLabels(field.arrayFields, labels) },
1397
+ ...field.objectFields && { objectFields: withFieldsLabels(field.objectFields, labels) }
1398
+ };
1399
+ }
1400
+ function withFieldsLabels(fields, labels) {
1401
+ return Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, withFieldLabels(field, labels)]));
1402
+ }
1403
+ function withComponentLabels(component, labels) {
1404
+ if (!component) return component;
1405
+ const { resolveFields, label } = component;
1406
+ return {
1407
+ ...component,
1408
+ ...label !== void 0 && { label: translated(labels, label) },
1409
+ ...component.fields && { fields: withFieldsLabels(component.fields, labels) },
1410
+ ...resolveFields && { resolveFields: async (...args) => withFieldsLabels(await resolveFields(...args), labels) }
1411
+ };
1412
+ }
1413
+ /**
1414
+ * The config with blocks' names, groups and settings' labels, choices and
1415
+ * placeholders in the admin panel's language, looked up by their English
1416
+ * wording, so a site's own blocks that word theirs the same way are
1417
+ * translated too. Only what the editor shows changes: props, choices' values
1418
+ * and what blocks render stay as they are.
1419
+ */
1420
+ function withLabels(config, labels) {
1421
+ if (Object.keys(labels).length === 0) return config;
1422
+ const components = Object.fromEntries(Object.entries(config.components).map(([name, component]) => [name, withComponentLabels(component, labels)]));
1423
+ const categories = config.categories && Object.fromEntries(Object.entries(config.categories).map(([name, category]) => [name, {
1424
+ ...category,
1425
+ ...category.title !== void 0 && { title: translated(labels, category.title) }
1426
+ }]));
1427
+ return {
1428
+ ...config,
1429
+ components,
1430
+ ...categories && { categories },
1431
+ ...config.root && { root: withComponentLabels(config.root, labels) }
1432
+ };
1433
+ }
1434
+ //#endregion
1435
+ //#region src/frame/owners-only.tsx
1436
+ /**
1437
+ * Block settings only owners may change (`metadata: { owners: true }`, such as
1438
+ * Custom HTML used exactly as written), shown to editors on full-stack sites
1439
+ * as they are, without a way to change them. Their server refuses such a
1440
+ * change anyway (`ownersOnlyChanges()`); this only says so up front.
1441
+ */
1442
+ function valueLabel(field, value) {
1443
+ const option = ("options" in field && Array.isArray(field.options) ? field.options : []).find((choice) => JSON.stringify(choice.value) === JSON.stringify(value));
1444
+ if (option) return String(option.label);
1445
+ return value === void 0 || value === null ? "" : String(value);
1446
+ }
1447
+ function LockedField({ field, value }) {
1448
+ const t = useStrings();
1449
+ return /* @__PURE__ */ jsxs("div", {
1450
+ className: "gfa-locked-field",
1451
+ children: [
1452
+ field.label && /* @__PURE__ */ jsx("div", {
1453
+ className: "gfa-locked-field-label",
1454
+ children: field.label
1455
+ }),
1456
+ /* @__PURE__ */ jsx("div", {
1457
+ className: "gfa-locked-field-value",
1458
+ children: valueLabel(field, value)
1459
+ }),
1460
+ /* @__PURE__ */ jsx("p", {
1461
+ className: "gfa-locked-field-note",
1462
+ children: t("owners.only")
1463
+ })
1464
+ ]
1465
+ });
1466
+ }
1467
+ function locked(fields) {
1468
+ return Object.fromEntries(Object.entries(fields).map(([name, field]) => {
1469
+ if (!isOwnersOnlyField(field)) return [name, field];
1470
+ return [name, {
1471
+ type: "custom",
1472
+ ...field.label !== void 0 && { label: field.label },
1473
+ ...field.metadata !== void 0 && { metadata: field.metadata },
1474
+ render: ({ value }) => /* @__PURE__ */ jsx(LockedField, {
1475
+ field,
1476
+ value
1477
+ })
1478
+ }];
1479
+ }));
1480
+ }
1481
+ /** The config with owners-only settings shown but not changeable, for editors who aren't owners. */
1482
+ function withOwnersOnlyLocked(config) {
1483
+ const components = Object.fromEntries(Object.entries(config.components).map(([name, component]) => {
1484
+ const fields = component.fields ?? {};
1485
+ if (!Object.values(fields).some(isOwnersOnlyField)) return [name, component];
1486
+ return [name, {
1487
+ ...component,
1488
+ fields: locked(fields)
1489
+ }];
1490
+ }));
1491
+ return {
1492
+ ...config,
1493
+ components
1494
+ };
1495
+ }
1496
+ //#endregion
1375
1497
  //#region src/frame/page-settings.ts
1376
1498
  /** For pages and templates, whose settings include `noIndex`; `path` is the page's, for pages. */
1377
1499
  function shownSettings(original, path) {
@@ -1432,8 +1554,8 @@ function shownSettings(original, path) {
1432
1554
  /**
1433
1555
  * A block's Style field (`classNameField` in `@goodfellow-cms/react`), which
1434
1556
  * 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
1557
+ * text alignment, size and color, background, space inside and around it,
1558
+ * rounded corners and the sizes of screen it's hidden on, written into its `className` as Tailwind classes
1437
1559
  * (`withBlockStyle()` in `@goodfellow-cms/core`), with any other classes kept.
1438
1560
  */
1439
1561
  /** A color's value to show and compare: a theme color's, or the editor's own. */
@@ -1627,6 +1749,36 @@ function ColorChoice({ label, value, colors, readOnly, onChange }) {
1627
1749
  })]
1628
1750
  });
1629
1751
  }
1752
+ /** The sizes of screen a block is hidden on, a box for each. */
1753
+ function HiddenOnChoice({ hiddenOn, readOnly, onChange }) {
1754
+ const t = useStrings();
1755
+ const hidden = new Set(hiddenOn);
1756
+ return /* @__PURE__ */ jsxs("fieldset", {
1757
+ className: "gfa-style-group",
1758
+ children: [
1759
+ /* @__PURE__ */ jsx("legend", {
1760
+ className: "gfa-style-label",
1761
+ children: t("style.hiddenOn")
1762
+ }),
1763
+ SCREEN_SIZES.map((size) => /* @__PURE__ */ jsxs("label", {
1764
+ className: "gfa-style-check",
1765
+ children: [/* @__PURE__ */ jsx("input", {
1766
+ type: "checkbox",
1767
+ checked: hidden.has(size),
1768
+ disabled: readOnly,
1769
+ onChange: (event) => {
1770
+ const next = SCREEN_SIZES.filter((candidate) => candidate === size ? event.target.checked : hidden.has(candidate));
1771
+ onChange(next.length > 0 ? next : void 0);
1772
+ }
1773
+ }), t(`style.hiddenOn.${size}`)]
1774
+ }, size)),
1775
+ hidden.size > 0 && /* @__PURE__ */ jsx("p", {
1776
+ className: "gfa-hint",
1777
+ children: t(hidden.size === SCREEN_SIZES.length ? "style.hiddenOnAll" : "style.hiddenOnHint")
1778
+ })
1779
+ ]
1780
+ });
1781
+ }
1630
1782
  /** Other classes, kept as typed while the box has focus, so a class isn't taken into the controls halfway. */
1631
1783
  function OtherClasses({ other, readOnly, onChange }) {
1632
1784
  const t = useStrings();
@@ -1665,7 +1817,7 @@ function OtherClasses({ other, readOnly, onChange }) {
1665
1817
  ]
1666
1818
  });
1667
1819
  }
1668
- function StyleControls({ value, colors, readOnly, label, ownAlign, onChange }) {
1820
+ function StyleControls({ value, colors, readOnly, label, ownAlign, block, onChange }) {
1669
1821
  const t = useStrings();
1670
1822
  const { style, other } = readBlockStyle(value);
1671
1823
  const set = (next) => onChange(withBlockStyle(other, next));
@@ -1750,6 +1902,11 @@ function StyleControls({ value, colors, readOnly, label, ownAlign, onChange }) {
1750
1902
  readOnly,
1751
1903
  onChange: (radius) => change("radius", radius)
1752
1904
  }),
1905
+ (block || style.hiddenOn) && /* @__PURE__ */ jsx(HiddenOnChoice, {
1906
+ hiddenOn: style.hiddenOn,
1907
+ readOnly,
1908
+ onChange: (hiddenOn) => change("hiddenOn", hiddenOn)
1909
+ }),
1753
1910
  /* @__PURE__ */ jsx(OtherClasses, {
1754
1911
  other,
1755
1912
  readOnly,
@@ -1765,7 +1922,7 @@ function StyleControls({ value, colors, readOnly, label, ownAlign, onChange }) {
1765
1922
  ]
1766
1923
  });
1767
1924
  }
1768
- function withStyleField(field, colors, ownAlign) {
1925
+ function withStyleField(field, colors, ownAlign, block) {
1769
1926
  if (field.type !== "text" || !isStyleField(field)) return field;
1770
1927
  const render = ({ value, onChange, readOnly }) => /* @__PURE__ */ jsx(StyleControls, {
1771
1928
  value,
@@ -1773,6 +1930,7 @@ function withStyleField(field, colors, ownAlign) {
1773
1930
  readOnly,
1774
1931
  label: field.label,
1775
1932
  ownAlign,
1933
+ block,
1776
1934
  onChange
1777
1935
  });
1778
1936
  return {
@@ -1782,27 +1940,27 @@ function withStyleField(field, colors, ownAlign) {
1782
1940
  render
1783
1941
  };
1784
1942
  }
1785
- function withStyleFieldsIn(fields, colors) {
1943
+ function withStyleFieldsIn(fields, colors, block) {
1786
1944
  if (!fields) return fields;
1787
1945
  const ownAlign = "align" in fields;
1788
- return Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, withStyleField(field, colors, ownAlign)]));
1946
+ return Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, withStyleField(field, colors, ownAlign, block)]));
1789
1947
  }
1790
- function withStyleComponent(component, colors) {
1948
+ function withStyleComponent(component, colors, block) {
1791
1949
  if (!component) return component;
1792
1950
  const { resolveFields } = component;
1793
1951
  return {
1794
1952
  ...component,
1795
- ...component.fields && { fields: withStyleFieldsIn(component.fields, colors) },
1796
- ...resolveFields && { resolveFields: async (...args) => withStyleFieldsIn(await resolveFields(...args), colors) }
1953
+ ...component.fields && { fields: withStyleFieldsIn(component.fields, colors, block) },
1954
+ ...resolveFields && { resolveFields: async (...args) => withStyleFieldsIn(await resolveFields(...args), colors, block) }
1797
1955
  };
1798
1956
  }
1799
1957
  /** The config with every block's Style field, and the page's, drawn as a style's controls. */
1800
1958
  function withStyleFields(config, colors) {
1801
- const components = Object.fromEntries(Object.entries(config.components).map(([name, component]) => [name, withStyleComponent(component, colors)]));
1959
+ const components = Object.fromEntries(Object.entries(config.components).map(([name, component]) => [name, withStyleComponent(component, colors, true)]));
1802
1960
  return {
1803
1961
  ...config,
1804
1962
  components,
1805
- ...config.root && { root: withStyleComponent(config.root, colors) }
1963
+ ...config.root && { root: withStyleComponent(config.root, colors, false) }
1806
1964
  };
1807
1965
  }
1808
1966
  //#endregion
@@ -1932,7 +2090,8 @@ const ADMIN_PLACES = {
1932
2090
  "contact-settings": `#/settings/general?section=${SETTINGS_SECTIONS.contact}`,
1933
2091
  "forms-settings": `#/settings/general?section=${SETTINGS_SECTIONS.forms}`,
1934
2092
  "theme-settings": "#/settings/theme",
1935
- "language-settings": "#/settings/languages"
2093
+ "language-settings": "#/settings/languages",
2094
+ "comments-settings": "#/settings/comments"
1936
2095
  };
1937
2096
  /** Puck's text field, with the media library's chooser below it for fields that hold a media address. */
1938
2097
  function TextFieldWithMedia({ field, value, onChange, readOnly, children }) {
@@ -2226,15 +2385,16 @@ function FrameEditor({ open, onOpened }) {
2226
2385
  callPlugin
2227
2386
  ]);
2228
2387
  const entry = kind === "entry" && collection && open.entry ? findEntry(loaded, collection.id, open.entry) : void 0;
2388
+ const { puck: dictionary, labels } = useEditorText();
2229
2389
  const config = useMemo(() => {
2230
2390
  if (kind === "entry") {
2231
2391
  if (!collection || !entry) throw new Error("The item isn't in the site.");
2232
- return entryConfig(collection, entry.entry, content, {
2392
+ return withLabels(entryConfig(collection, entry.entry, content, {
2233
2393
  templateConfig,
2234
2394
  layoutConfig
2235
- });
2395
+ }), labels);
2236
2396
  }
2237
- return withTableFields(canvasConfig(kind, kind === "page" ? pageConfig : kind === "template" ? templateConfig : layoutConfig, layoutConfig, content.header.data, content.footer.data, site));
2397
+ return withLabels(withTableFields(canvasConfig(kind, kind === "page" ? pageConfig : kind === "template" ? templateConfig : layoutConfig, layoutConfig, content.header.data, content.footer.data, site)), labels);
2238
2398
  }, [
2239
2399
  kind,
2240
2400
  collection,
@@ -2243,13 +2403,18 @@ function FrameEditor({ open, onOpened }) {
2243
2403
  templateConfig,
2244
2404
  layoutConfig,
2245
2405
  content,
2246
- site
2406
+ site,
2407
+ labels
2247
2408
  ]);
2248
2409
  const described = useMemo(() => describeConfig(config), [config]);
2249
- const puckConfig = useMemo(() => kind === "entry" ? config : withStyleFields(config, content.settings.theme.colors), [
2410
+ const puckConfig = useMemo(() => {
2411
+ const styled = kind === "entry" ? config : withStyleFields(config, content.settings.theme.colors);
2412
+ return open.ownersOnlyLocked ? withOwnersOnlyLocked(styled) : styled;
2413
+ }, [
2250
2414
  kind,
2251
2415
  config,
2252
- content.settings.theme.colors
2416
+ content.settings.theme.colors,
2417
+ open.ownersOnlyLocked
2253
2418
  ]);
2254
2419
  const told = useRef(void 0);
2255
2420
  useEffect(() => {
@@ -2307,6 +2472,7 @@ function FrameEditor({ open, onOpened }) {
2307
2472
  data: initialData,
2308
2473
  metadata: siteMetadata(site),
2309
2474
  headerTitle: title,
2475
+ dictionary,
2310
2476
  height: "100%",
2311
2477
  iframe: { enabled: false },
2312
2478
  ui,
@@ -2429,7 +2595,8 @@ function editorKey(open) {
2429
2595
  open.path,
2430
2596
  open.collection,
2431
2597
  open.entry,
2432
- open.language
2598
+ open.language,
2599
+ open.reopened
2433
2600
  ]);
2434
2601
  }
2435
2602
  /** Opens an editor when the admin panel's page asks, and tells it Puck's config once it's open. */