@goodfellow-cms/admin 0.5.0 → 0.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/README.md +1 -1
- package/dist/dev.d.ts +4 -2
- package/dist/dev.js +21 -1
- package/dist/editor.js +215 -10
- package/dist/{entry-values-ocbalc9U.js → entry-values-BrY3L0Ai.js} +1623 -493
- package/dist/index.d.ts +236 -8
- package/dist/index.js +4797 -2878
- package/dist/plugin-protocol-DkvBDqsF.js +119 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.js +153 -0
- package/package.json +12 -6
- package/plugin.css +215 -0
- package/styles.css +142 -6
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The admin panel: the visual editor for a site's pages, collections, media, heade
|
|
|
4
4
|
|
|
5
5
|
Its editor is built with each site's own config, so it always has the site's blocks, and runs them in frames sandboxed away from the admin panel's page, which signs in and publishes: `goodfellow build` adds the page at `/admin` and the editor at `/admin/editor/`, and Next.js sites render `GoodfellowAdmin` and `GoodfellowEditor` from `@goodfellow-cms/next` there.
|
|
6
6
|
|
|
7
|
-
[Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub or
|
|
7
|
+
[Goodfellow](https://github.com/goodfellow-cms/goodfellow-cms) is a website builder for people who aren't developers, with no server or database: pages are files in your own GitHub, GitLab or Bitbucket repository, edited in a visual editor built on [Puck](https://puckeditor.com). To start a new site, run `npm create goodfellow@latest my-site`. See the [documentation](https://goodfellow-cms.github.io/goodfellow-cms/) for everything else.
|
|
8
8
|
|
|
9
9
|
More in the docs: [Edit pages](https://goodfellow-cms.github.io/goodfellow-cms/docs/edit-pages).
|
|
10
10
|
|
package/dist/dev.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { ContentStore } from "@goodfellow-cms/core";
|
|
1
|
+
import { ContentStore, PluginTasks } from "@goodfellow-cms/core";
|
|
2
2
|
//#region src/dev.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* A content store that reads and writes the site's files through
|
|
5
5
|
* `goodfellow dev`. For local development only: it is never part of a built site.
|
|
6
6
|
*/
|
|
7
|
-
export declare function localStore(apiBase?: string): ContentStore
|
|
7
|
+
export declare function localStore(apiBase?: string): ContentStore & {
|
|
8
|
+
tasks: PluginTasks;
|
|
9
|
+
};
|
|
8
10
|
//#endregion
|
package/dist/dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConflictError, decodeBase64, encodeBase64Bytes } from "@goodfellow-cms/core";
|
|
1
|
+
import { ConflictError, PluginTaskError, decodeBase64, encodeBase64Bytes } from "@goodfellow-cms/core";
|
|
2
2
|
//#region src/dev.ts
|
|
3
3
|
/**
|
|
4
4
|
* A content store that reads and writes the site's files through
|
|
@@ -53,6 +53,26 @@ function localStore(apiBase = "/__goodfellow/api") {
|
|
|
53
53
|
expectedRevision
|
|
54
54
|
})
|
|
55
55
|
})).json();
|
|
56
|
+
},
|
|
57
|
+
tasks: {
|
|
58
|
+
async supported() {
|
|
59
|
+
const response = await request("task", {}, true).catch(() => void 0);
|
|
60
|
+
return response?.ok === true && (await response.json()).supported === true;
|
|
61
|
+
},
|
|
62
|
+
async secretsPlace() {
|
|
63
|
+
const response = await request("task", {}, true).catch(() => void 0);
|
|
64
|
+
if (!response?.ok) return void 0;
|
|
65
|
+
return (await response.json()).place;
|
|
66
|
+
},
|
|
67
|
+
async run(task) {
|
|
68
|
+
return (await (await request("task", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "content-type": "application/json" },
|
|
71
|
+
body: JSON.stringify(task)
|
|
72
|
+
}).catch((error) => {
|
|
73
|
+
throw new PluginTaskError("failed", error.message);
|
|
74
|
+
})).json()).result;
|
|
75
|
+
}
|
|
56
76
|
}
|
|
57
77
|
};
|
|
58
78
|
}
|
package/dist/editor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Ft as storedEntryFields, G as READY_MESSAGE, Pt as storedData, R as createMediaPreviews, Rt as editedMarkdown, a as AdminContext, at as findPage, b as MediaChooser, ct as mediaUrl, et as StringsProvider, f as useDarkMode, h as ScheduleFields, i as usePreviewStyles, l as useSiteContent, rt as useStrings, s as useAdmin, t as SCHEDULE_PROP, u as setThemeChoice, w as SETTINGS_SECTIONS, y as MediaChoiceContext, z as showMediaInPreview, zt as formattedHtml } from "./entry-values-BrY3L0Ai.js";
|
|
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";
|
|
3
4
|
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
5
|
import { createRoot } from "react-dom/client";
|
|
5
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { PageBody, SiteProvider, adminSite, applyEntry, createPuckConfig, createPuckConfigs, cx, mediaFieldKind, preparePage, siteMetadata } from "@goodfellow-cms/react";
|
|
7
|
+
import { PageBody, PageContent, SiteProvider, adminSite, applyEntry, createPuckConfig, createPuckConfigs, cx, isTableField, mediaFieldKind, preparePage, siteMetadata, tableCells } from "@goodfellow-cms/react";
|
|
7
8
|
import { AutoField, FieldLabel, Puck, Render, RichTextMenu, createUsePuck, legacySideBarPlugin, migrate, registerOverlayPortal, resolveAllData, useGetPuck } from "@puckeditor/core";
|
|
8
9
|
import { createPortal, flushSync } from "react-dom";
|
|
9
10
|
const CANVAS = `.gf-canvas`;
|
|
@@ -907,6 +908,7 @@ function EntryPreview({ collection, entry, content, templateConfig, layoutConfig
|
|
|
907
908
|
menus: content.menus,
|
|
908
909
|
path: entry.path ?? "/",
|
|
909
910
|
collections: content.collections,
|
|
911
|
+
pages: pageLinks(content),
|
|
910
912
|
collection,
|
|
911
913
|
entry: current
|
|
912
914
|
}), [
|
|
@@ -999,9 +1001,76 @@ function entryConfig(collection, entry, content, configs) {
|
|
|
999
1001
|
* The site's code, run in the page that has loaded it: the frame that runs
|
|
1000
1002
|
* it for the admin panel, or tests. Never imported by the admin panel's own page.
|
|
1001
1003
|
*/
|
|
1004
|
+
/** The block drawn in each slot's place, which only drawing has: its name can't be one of a site's. */
|
|
1005
|
+
const DRAWN_SLOT_BLOCK = "__goodfellow_drawn_slot__";
|
|
1006
|
+
/** A block that draws nothing but an empty `DRAWN_SLOT_ELEMENT` with its slot's name. */
|
|
1007
|
+
const drawnSlot = {
|
|
1008
|
+
fields: { slot: { type: "text" } },
|
|
1009
|
+
render: ({ slot }) => createElement(DRAWN_SLOT_ELEMENT, { "data-slot": String(slot ?? "") })
|
|
1010
|
+
};
|
|
1011
|
+
/** The block as it's drawn: each of its slots holding only the block that marks its place. */
|
|
1012
|
+
function withSlotPlaces(block, config) {
|
|
1013
|
+
const fields = config?.fields ?? {};
|
|
1014
|
+
const places = Object.entries(fields).filter(([, field]) => field.type === "slot").map(([slot]) => [slot, [{
|
|
1015
|
+
type: DRAWN_SLOT_BLOCK,
|
|
1016
|
+
props: {
|
|
1017
|
+
id: `${String(block.props.id)}:${slot}`,
|
|
1018
|
+
slot
|
|
1019
|
+
}
|
|
1020
|
+
}]]);
|
|
1021
|
+
return {
|
|
1022
|
+
...block,
|
|
1023
|
+
props: {
|
|
1024
|
+
...block.props,
|
|
1025
|
+
...Object.fromEntries(places)
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
/** One block drawn as the site draws it, in the page at its address, as `drawBlock()` describes. */
|
|
1030
|
+
async function drawBlock(configs, content, { kind, path, block }) {
|
|
1031
|
+
const layout = kind === "header" || kind === "footer";
|
|
1032
|
+
const config = layout ? configs.layout : kind === "template" ? configs.template : configs.page;
|
|
1033
|
+
if (!config.components[block.type]) throw new Error(`The site has no block called ${block.type} here.`);
|
|
1034
|
+
const found = allPages(content).find((page) => page.path === path);
|
|
1035
|
+
const root = layout ? (kind === "header" ? content.header : content.footer).data.root : found?.content.data.root;
|
|
1036
|
+
const page = {
|
|
1037
|
+
path,
|
|
1038
|
+
file: found?.file ?? "",
|
|
1039
|
+
content: {
|
|
1040
|
+
...found?.content,
|
|
1041
|
+
version: found?.content.version ?? 1,
|
|
1042
|
+
data: {
|
|
1043
|
+
root: root ?? { props: {} },
|
|
1044
|
+
content: [withSlotPlaces(block, config.components[block.type])]
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
const prepared = await preparePage({
|
|
1049
|
+
...configs,
|
|
1050
|
+
page: config
|
|
1051
|
+
}, content, page);
|
|
1052
|
+
const container = document.createElement("div");
|
|
1053
|
+
const reactRoot = createRoot(container);
|
|
1054
|
+
try {
|
|
1055
|
+
flushSync(() => reactRoot.render(createElement(PageContent, {
|
|
1056
|
+
site: {
|
|
1057
|
+
...prepared.site,
|
|
1058
|
+
credits: []
|
|
1059
|
+
},
|
|
1060
|
+
pageConfig: prepared.pageConfig,
|
|
1061
|
+
page: prepared.data
|
|
1062
|
+
})));
|
|
1063
|
+
const main = container.querySelector("main");
|
|
1064
|
+
const zone = main?.children.length === 1 ? main.firstElementChild : null;
|
|
1065
|
+
return (zone?.tagName === "DIV" && zone.attributes.length === 0 ? zone : main)?.innerHTML ?? "";
|
|
1066
|
+
} finally {
|
|
1067
|
+
reactRoot.unmount();
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1002
1070
|
/** The site's code run here, in the page that has loaded it: the frame, or tests. */
|
|
1003
1071
|
function localSiteCode(config) {
|
|
1004
1072
|
const configs = createPuckConfigs(config);
|
|
1073
|
+
let drawing;
|
|
1005
1074
|
return {
|
|
1006
1075
|
async sitePages(content, today) {
|
|
1007
1076
|
return sitePages(content, today, config.blocks);
|
|
@@ -1039,6 +1108,17 @@ function localSiteCode(config) {
|
|
|
1039
1108
|
root.unmount();
|
|
1040
1109
|
}
|
|
1041
1110
|
return problems;
|
|
1111
|
+
},
|
|
1112
|
+
async drawBlock(content, target) {
|
|
1113
|
+
if (typeof document === "undefined") throw new Error("Blocks are drawn only in a browser.");
|
|
1114
|
+
drawing ??= createPuckConfigs({
|
|
1115
|
+
...config,
|
|
1116
|
+
blocks: {
|
|
1117
|
+
...config.blocks,
|
|
1118
|
+
[DRAWN_SLOT_BLOCK]: drawnSlot
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
return drawBlock(drawing, content, target);
|
|
1042
1122
|
}
|
|
1043
1123
|
};
|
|
1044
1124
|
}
|
|
@@ -1070,11 +1150,11 @@ function useAdminClient() {
|
|
|
1070
1150
|
return value;
|
|
1071
1151
|
}
|
|
1072
1152
|
/** Media from the admin panel's page, which reads files the live site doesn't have yet from the repository. */
|
|
1073
|
-
function frameMediaPreviews(client, siteUrl) {
|
|
1153
|
+
function frameMediaPreviews(client, siteUrl, storage = () => void 0) {
|
|
1074
1154
|
return createMediaPreviews({ readBytes: async (path) => {
|
|
1075
1155
|
if (!path.startsWith(`${MEDIA_DIR}/`)) return void 0;
|
|
1076
1156
|
return await client.call("media.bytes", mediaUrl(path)) ?? void 0;
|
|
1077
|
-
} }, siteUrl);
|
|
1157
|
+
} }, siteUrl, storage);
|
|
1078
1158
|
}
|
|
1079
1159
|
function refused() {
|
|
1080
1160
|
throw new Error("The site's frame can't do this.");
|
|
@@ -1089,7 +1169,9 @@ function FrameProviders({ config, client, init, site, children }) {
|
|
|
1089
1169
|
code: localSiteCode(config)
|
|
1090
1170
|
}), [config]);
|
|
1091
1171
|
const described = useMemo(() => adminSite(config), [config]);
|
|
1092
|
-
const
|
|
1172
|
+
const storage = useRef(site.content.mediaStorage);
|
|
1173
|
+
storage.current = site.content.mediaStorage;
|
|
1174
|
+
const mediaPreviews = useMemo(() => frameMediaPreviews(client, init.siteUrl, () => storage.current), [client, init.siteUrl]);
|
|
1093
1175
|
useEffect(() => client.channel.handle("media.remember", (file) => {
|
|
1094
1176
|
if (typeof file?.url === "string" && file.bytes instanceof Uint8Array) mediaPreviews.remember(file.url, file.bytes);
|
|
1095
1177
|
}), [client, mediaPreviews]);
|
|
@@ -1124,6 +1206,7 @@ function FrameProviders({ config, client, init, site, children }) {
|
|
|
1124
1206
|
},
|
|
1125
1207
|
readFile: async () => refused(),
|
|
1126
1208
|
listFiles: async () => refused(),
|
|
1209
|
+
readBytes: async () => refused(),
|
|
1127
1210
|
registries: {},
|
|
1128
1211
|
outOfReach: false,
|
|
1129
1212
|
contentOnly: false,
|
|
@@ -1221,6 +1304,119 @@ function shownSettings(original, path) {
|
|
|
1221
1304
|
};
|
|
1222
1305
|
}
|
|
1223
1306
|
//#endregion
|
|
1307
|
+
//#region src/frame/table-field.tsx
|
|
1308
|
+
/**
|
|
1309
|
+
* The grid of cells a table's field is edited in (`tableField()` in
|
|
1310
|
+
* `@goodfellow-cms/react`), which Puck's editor shows in place of the field's
|
|
1311
|
+
* own render, since blocks can't bring their own editing code.
|
|
1312
|
+
*/
|
|
1313
|
+
const MAX_ROWS = 100;
|
|
1314
|
+
const MAX_COLUMNS = 12;
|
|
1315
|
+
function TableCellsField({ value, onChange, readOnly, label }) {
|
|
1316
|
+
const t = useStrings();
|
|
1317
|
+
const rows = tableCells(value);
|
|
1318
|
+
const width = rows[0]?.length ?? 1;
|
|
1319
|
+
const set = (row, column, text) => onChange(rows.map((cells, r) => r === row ? cells.map((cell, c) => c === column ? text : cell) : cells));
|
|
1320
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1321
|
+
className: "gfa-table-field",
|
|
1322
|
+
children: [
|
|
1323
|
+
label && /* @__PURE__ */ jsx("div", {
|
|
1324
|
+
className: "gfa-table-field-label",
|
|
1325
|
+
children: label
|
|
1326
|
+
}),
|
|
1327
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1328
|
+
className: "gfa-table-field-grid",
|
|
1329
|
+
style: { gridTemplateColumns: `repeat(${width}, minmax(6rem, 1fr)) auto` },
|
|
1330
|
+
children: [rows.map((cells, row) => /* @__PURE__ */ jsxs("div", {
|
|
1331
|
+
className: "gfa-table-field-row",
|
|
1332
|
+
children: [cells.map((cell, column) => /* @__PURE__ */ jsx("input", {
|
|
1333
|
+
className: "gfa-input gfa-table-field-cell",
|
|
1334
|
+
value: cell,
|
|
1335
|
+
readOnly,
|
|
1336
|
+
"aria-label": t("table.cell", {
|
|
1337
|
+
row: String(row + 1),
|
|
1338
|
+
column: String(column + 1)
|
|
1339
|
+
}),
|
|
1340
|
+
onChange: (event) => set(row, column, event.target.value)
|
|
1341
|
+
}, column)), /* @__PURE__ */ jsx("button", {
|
|
1342
|
+
type: "button",
|
|
1343
|
+
className: "gfa-table-field-remove",
|
|
1344
|
+
disabled: readOnly || rows.length <= 1,
|
|
1345
|
+
title: t("table.removeRow"),
|
|
1346
|
+
"aria-label": t("table.removeRowNumber", { row: String(row + 1) }),
|
|
1347
|
+
onClick: () => onChange(rows.filter((_, r) => r !== row)),
|
|
1348
|
+
children: "×"
|
|
1349
|
+
})]
|
|
1350
|
+
}, row)), /* @__PURE__ */ jsx("div", {
|
|
1351
|
+
className: "gfa-table-field-row",
|
|
1352
|
+
children: Array.from({ length: width }, (_, column) => /* @__PURE__ */ jsx("button", {
|
|
1353
|
+
type: "button",
|
|
1354
|
+
className: "gfa-table-field-remove",
|
|
1355
|
+
disabled: readOnly || width <= 1,
|
|
1356
|
+
title: t("table.removeColumn"),
|
|
1357
|
+
"aria-label": t("table.removeColumnNumber", { column: String(column + 1) }),
|
|
1358
|
+
onClick: () => onChange(rows.map((cells) => cells.filter((_, c) => c !== column))),
|
|
1359
|
+
children: "×"
|
|
1360
|
+
}, column))
|
|
1361
|
+
})]
|
|
1362
|
+
}),
|
|
1363
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1364
|
+
className: "gfa-table-field-actions",
|
|
1365
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1366
|
+
type: "button",
|
|
1367
|
+
className: "gfa-button gfa-button-secondary",
|
|
1368
|
+
disabled: readOnly || rows.length >= MAX_ROWS,
|
|
1369
|
+
onClick: () => onChange([...rows, Array(width).fill("")]),
|
|
1370
|
+
children: t("table.addRow")
|
|
1371
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1372
|
+
type: "button",
|
|
1373
|
+
className: "gfa-button gfa-button-secondary",
|
|
1374
|
+
disabled: readOnly || width >= MAX_COLUMNS,
|
|
1375
|
+
onClick: () => onChange(rows.map((cells) => [...cells, ""])),
|
|
1376
|
+
children: t("table.addColumn")
|
|
1377
|
+
})]
|
|
1378
|
+
})
|
|
1379
|
+
]
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
function withTableField(field) {
|
|
1383
|
+
if (field.type === "custom" && isTableField(field)) {
|
|
1384
|
+
const render = ({ value, onChange, readOnly, field: own }) => /* @__PURE__ */ jsx(TableCellsField, {
|
|
1385
|
+
value,
|
|
1386
|
+
onChange,
|
|
1387
|
+
readOnly,
|
|
1388
|
+
label: own.label
|
|
1389
|
+
});
|
|
1390
|
+
return {
|
|
1391
|
+
...field,
|
|
1392
|
+
render
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
if (field.type === "array") return {
|
|
1396
|
+
...field,
|
|
1397
|
+
arrayFields: withTableFieldsIn(field.arrayFields)
|
|
1398
|
+
};
|
|
1399
|
+
if (field.type === "object") return {
|
|
1400
|
+
...field,
|
|
1401
|
+
objectFields: withTableFieldsIn(field.objectFields)
|
|
1402
|
+
};
|
|
1403
|
+
return field;
|
|
1404
|
+
}
|
|
1405
|
+
function withTableFieldsIn(fields) {
|
|
1406
|
+
return Object.fromEntries(Object.entries(fields ?? {}).map(([name, field]) => [name, withTableField(field)]));
|
|
1407
|
+
}
|
|
1408
|
+
/** The config with every table's field edited as a grid of cells. */
|
|
1409
|
+
function withTableFields(config) {
|
|
1410
|
+
const components = Object.fromEntries(Object.entries(config.components).map(([name, component]) => [name, component.fields ? {
|
|
1411
|
+
...component,
|
|
1412
|
+
fields: withTableFieldsIn(component.fields)
|
|
1413
|
+
} : component]));
|
|
1414
|
+
return {
|
|
1415
|
+
...config,
|
|
1416
|
+
components
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
//#endregion
|
|
1224
1420
|
//#region src/frame/editor.tsx
|
|
1225
1421
|
/**
|
|
1226
1422
|
* Puck in the site's frame, for one page, header, footer, template or item.
|
|
@@ -1489,18 +1685,25 @@ function FrameEditor({ open, onOpened }) {
|
|
|
1489
1685
|
}
|
|
1490
1686
|
};
|
|
1491
1687
|
}, [client]);
|
|
1688
|
+
const callPlugin = useCallback((plugin, method, ...args) => client.call("plugin.call", {
|
|
1689
|
+
plugin,
|
|
1690
|
+
method,
|
|
1691
|
+
args
|
|
1692
|
+
}), [client]);
|
|
1492
1693
|
const site = useMemo(() => ({
|
|
1493
1694
|
settings: content.settings,
|
|
1494
1695
|
menus: content.menus,
|
|
1495
1696
|
path,
|
|
1496
1697
|
collections: content.collections,
|
|
1698
|
+
pages: pageLinks(content),
|
|
1497
1699
|
collection,
|
|
1498
1700
|
...view && { view },
|
|
1499
1701
|
...languages && {
|
|
1500
1702
|
languages,
|
|
1501
1703
|
home: languageHome(loaded, language)
|
|
1502
1704
|
},
|
|
1503
|
-
adminLink
|
|
1705
|
+
adminLink,
|
|
1706
|
+
callPlugin
|
|
1504
1707
|
}), [
|
|
1505
1708
|
content,
|
|
1506
1709
|
loaded,
|
|
@@ -1509,7 +1712,8 @@ function FrameEditor({ open, onOpened }) {
|
|
|
1509
1712
|
collection,
|
|
1510
1713
|
view,
|
|
1511
1714
|
languages,
|
|
1512
|
-
adminLink
|
|
1715
|
+
adminLink,
|
|
1716
|
+
callPlugin
|
|
1513
1717
|
]);
|
|
1514
1718
|
const entry = kind === "entry" && collection && open.entry ? findEntry(loaded, collection.id, open.entry) : void 0;
|
|
1515
1719
|
const config = useMemo(() => {
|
|
@@ -1520,7 +1724,7 @@ function FrameEditor({ open, onOpened }) {
|
|
|
1520
1724
|
layoutConfig
|
|
1521
1725
|
});
|
|
1522
1726
|
}
|
|
1523
|
-
return canvasConfig(kind, kind === "page" ? pageConfig : kind === "template" ? templateConfig : layoutConfig, layoutConfig, content.header.data, content.footer.data, site);
|
|
1727
|
+
return withTableFields(canvasConfig(kind, kind === "page" ? pageConfig : kind === "template" ? templateConfig : layoutConfig, layoutConfig, content.header.data, content.footer.data, site));
|
|
1524
1728
|
}, [
|
|
1525
1729
|
kind,
|
|
1526
1730
|
collection,
|
|
@@ -1676,7 +1880,8 @@ function CodeService({ channel }) {
|
|
|
1676
1880
|
const stops = [
|
|
1677
1881
|
channel.handle("code.sitePages", ({ content, today }) => code.sitePages(content, today)),
|
|
1678
1882
|
channel.handle("code.pageView", async ({ page, content, today }) => await code.pageView(page, content, today) ?? null),
|
|
1679
|
-
channel.handle("code.renderedProblems", ({ content, targets, today }) => code.renderedProblems(content, targets, today))
|
|
1883
|
+
channel.handle("code.renderedProblems", ({ content, targets, today }) => code.renderedProblems(content, targets, today)),
|
|
1884
|
+
channel.handle("code.drawBlock", ({ content, target }) => code.drawBlock(content, target))
|
|
1680
1885
|
];
|
|
1681
1886
|
return () => {
|
|
1682
1887
|
for (const stop of stops) stop();
|