@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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { APP_API_HEADER, BROKER_AI_ADDRESS, BROKER_CONNECT_MESSAGE, BROKER_READY_MESSAGE, CODE_THEMES, COLLECTIONS_DIR, CONFIG_FILE, ConflictError, ContentError, DEFAULT_CODE_THEMES, DEFAULT_DARK_THEME_COLORS, DEFAULT_THEME_COLORS, DEMO_CONTENT_PATH, DomainError, EditorsError, FIELD_TYPES, FOOTER_FILE, GOODFELLOW_VERSION, HEADER_FILE, HeadCodeError, INSTALLED_PLUGINS_FILE, INSTALLED_RECORD_FILE, MEDIA_DIR, NIGHTLY_MODES, NPM_REGISTRY, PackageError, PluginInstallError, PluginTaskError, REDIRECT_LINK_PLACEHOLDER, REDIRECT_MESSAGE, RELEASE_AGE_DAYS, RegistryError, SetupError, SignInError, THEME_COLORS, THEME_FILE, THEME_RELEASE_AGE_DAYS, TITLE_FIELD, ThemeError, UPDATES_FILE, UpdatesError, VERSION_PACKAGE, addressPatternProblem, adminOrigin, allPages, availableBlocks, availableUpdates, blockUses, brokerAddressProblem, brokerOrigin, brokerReleaseUrl, checkAdminAddress, checkEditedData, checkRecords, chooseRelease, chooseThemeRelease, collectionFileSchema, compareReleases, configAdmin, configBroker, contentInLanguage, contrastProblems, credentialStorage, decodeBase64, demoContentStore, demoStore, describeSiteChanges, describedConfig, domainZone, encodeBase64Bytes, entryAddress, entryLanguage, entryPathIn, entrySourceHash, entryTitle, entryTranslations, eventValueProblem, fetchBrokerRelease, fetchPluginPackage, fetchTheme, findEntry, formTarget, getPageHead, hasLanguages, inRange, isAddressSegment, isEmptyValue, isLanguageCode, isLanguageHome, isReservedPagePath, languageLayoutFile, languageName, languagePrefix, loadSiteContent, mainLanguage, menusFileSchema, normalizeDomain, pageLanguage, pageLinks, pageSourceHash, pageTitle, pageTranslations, parseDemoContent, parseHeadCode, parseInstalledPlugins, parseInstalledRecord, parseThemeRecord, parseUpdateSettings, pathLanguage, permissionChanges, planBlockChanges, planBroker, planPluginInstall, planPluginRemove, planTheme, pluginReleases, pluginsOutOfRange, recordsInPlace, redirectFrom, redirectProblems, registrarName, releaseVersions, remoteBackend, reviewPluginPackages, scheduleOf, scheduleProblem, searchPlugins, searchThemes, serializeContent, siteChangesSince, siteHistory, siteLanguages, siteRestoreChanges, siteSettingsSchema, siteVersion, storedRedirects, themeFileUrl, themeLabel, themeLoads, themeReleases, themeToCss, themeUnknownBlocks, todayIn, translationState, trimChars, withAdminConfig, withBrokerConfig, withSchedule, writesLockfile } from "@goodfellow-cms/core";
|
|
1
|
+
import { $ as PublishedNotice, A as useRoute, At as pageFileChange, B as mayChange, C as AppLink, Ct as layoutFileChange, D as hasUnsavedChanges, Dt as movePageChanges, E as entryEditorHref, Et as moveEntryChanges, F as PluginScreenFrame, Ft as storedCss, G as useFrameServices, H as FrameServicesProvider, I as usePluginFrame, It as storedData, J as draftStarted, K as useFrameUpdates, L as usePluginHost, M as prepareUpload, Mt as redirectsFileChange, N as useOutsideHost, Nt as siteSettingsFileChange, O as navigate, Ot as newCollectionSettings, P as PublishFailure, Pt as slugify, Q as Field, R as createMediaPreviews, Rt as uniqueName, S as MediaScreen, St as entryFileChange$1, T as collectionHref, Tt as menusFileChange, U as reloadFrames, V as useMayChange, W as useFrame, X as Dialog, Y as Button, Z as ErrorMessage, _ as scheduleSettingsChanges, _t as collectionFileChange$1, at as useStrings, bt as deleteCollectionChanges, c as useCanChangeSettings, ct as allMedia, d as useApplyThemeChoice, dt as siteImages, et as TextField, f as useDarkMode, ft as uploadName, g as ScheduleStatus, gt as codeFileChanges, h as ScheduleFields, ht as checkPageAddress, it as useStringTable, j as useUnsavedChanges, jt as pageTitle$1, k as pageEditorHref, kt as newPageData, l as useSiteContent, lt as fileName, m as ComingUp, mt as checkEntrySlug, n as formValues, nt as StringsProvider, o as AdminProvider, ot as describeContrast, p as useThemeChoice$1, pt as addressPatternFor, r as PreviewFrame, rt as defaultStrings, s as useAdmin, t as SCHEDULE_PROP, tt as useDebounced, u as setThemeChoice, ut as mediaUrl, v as REGISTRY_VERSION, vt as collectionSettingsChanges, w as SETTINGS_SECTIONS, wt as mediaFileChange, x as MediaField, xt as duplicatePageData, yt as customCssFileChange, zt as updateSettingsFileChange } from "./entry-values-DO01xZ9L.js";
|
|
2
|
+
import { APP_API_HEADER, BROKER_AI_ADDRESS, BROKER_CONNECT_MESSAGE, BROKER_READY_MESSAGE, CODE_THEMES, COLLECTIONS_DIR, CONFIG_FILE, ConflictError, ContentError, DEFAULT_CODE_THEMES, DEFAULT_DARK_THEME_COLORS, DEFAULT_THEME_COLORS, DEMO_CONTENT_PATH, DomainError, EditorsError, FIELD_TYPES, FOOTER_FILE, GOODFELLOW_VERSION, HEADER_FILE, HeadCodeError, INSTALLED_PLUGINS_FILE, INSTALLED_RECORD_FILE, MEDIA_DIR, NIGHTLY_MODES, NPM_REGISTRY, PackageError, PluginInstallError, PluginTaskError, REDIRECT_LINK_PLACEHOLDER, REDIRECT_MESSAGE, RELEASE_AGE_DAYS, RegistryError, SetupError, SignInError, THEME_COLORS, THEME_FILE, THEME_RELEASE_AGE_DAYS, TITLE_FIELD, ThemeError, UPDATES_FILE, UpdatesError, VERSION_PACKAGE, addressPatternProblem, adminOrigin, allPages, availableBlocks, availableUpdates, blockUses, brokerAddressProblem, brokerOrigin, brokerReleaseUrl, checkAdminAddress, checkEditedData, checkRecords, chooseRelease, chooseThemeRelease, collectionFileSchema, compareReleases, configAdmin, configBroker, contentInLanguage, contrastProblems, credentialStorage, decodeBase64, demoContentStore, demoStore, describeSiteChanges, describedConfig, domainZone, encodeBase64Bytes, entryAddress, entryLanguage, entryPathIn, entrySourceHash, entryTitle, entryTranslations, eventValueProblem, fetchBrokerRelease, fetchPluginPackage, fetchTheme, findEntry, formTarget, getPageHead, googleFontsUrl, hasLanguages, inRange, isAddressSegment, isEmptyValue, isLanguageCode, isLanguageHome, isReservedPagePath, languageLayoutFile, languageName, languagePrefix, loadSiteContent, mainLanguage, menusFileSchema, normalizeDomain, pageLanguage, pageLinks, pageSourceHash, pageTitle, pageTranslations, parseDemoContent, parseHeadCode, parseInstalledPlugins, parseInstalledRecord, parseThemeRecord, parseUpdateSettings, pathLanguage, permissionChanges, permissionsFileChange, planBlockChanges, planBroker, planPluginInstall, planPluginRemove, planTheme, pluginReleases, pluginsOutOfRange, recordsInPlace, redirectFrom, redirectProblems, registrarName, releaseVersions, remoteBackend, reviewPluginPackages, scheduleOf, scheduleProblem, searchPlugins, searchThemes, serializeContent, siteChangesSince, siteHistory, siteLanguages, siteRestoreChanges, siteSettingsSchema, siteVersion, storedRedirects, themeFileUrl, themeLabel, themeLoads, themeReleases, themeToCss, themeUnknownBlocks, todayIn, translationState, trimChars, withAdminConfig, withBrokerConfig, withSchedule, writesLockfile } from "@goodfellow-cms/core";
|
|
3
3
|
import { Fragment, useCallback, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { createRoot } from "react-dom/client";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -2033,14 +2033,17 @@ function PluginEditorFrame({ plugin, target, data, onOpen, onChange }) {
|
|
|
2033
2033
|
themeCss
|
|
2034
2034
|
};
|
|
2035
2035
|
const opening = useRef(void 0);
|
|
2036
|
+
const [ready, setReady] = useState(false);
|
|
2036
2037
|
const hooks = useMemo(() => ({
|
|
2037
2038
|
open: () => {
|
|
2038
2039
|
const { target, data, content, description } = latest.current;
|
|
2039
2040
|
const opened = data();
|
|
2040
2041
|
opening.current = opened;
|
|
2042
|
+
setReady(false);
|
|
2041
2043
|
return editorOpen(target, opened, content, description);
|
|
2042
2044
|
},
|
|
2043
2045
|
onOpened: (opened, channel) => {
|
|
2046
|
+
setReady(true);
|
|
2044
2047
|
latest.current.onOpen(editorHandle(opened, channel), opening.current ?? latest.current.data());
|
|
2045
2048
|
},
|
|
2046
2049
|
onChange: ({ data, state }) => {
|
|
@@ -2064,19 +2067,31 @@ function PluginEditorFrame({ plugin, target, data, onOpen, onChange }) {
|
|
|
2064
2067
|
},
|
|
2065
2068
|
styles: async () => {
|
|
2066
2069
|
const { services, content, themeCss } = latest.current;
|
|
2070
|
+
const fonts = googleFontsUrl(content.settings.theme);
|
|
2067
2071
|
return {
|
|
2068
2072
|
stylesheets: [...await services.siteCss(), themeToCss(content.settings.theme)],
|
|
2069
|
-
tailwind: `${themeCss}\n${content.customCss}
|
|
2073
|
+
tailwind: `${themeCss}\n${content.customCss}`,
|
|
2074
|
+
...fonts && { fonts }
|
|
2070
2075
|
};
|
|
2071
2076
|
}
|
|
2072
2077
|
}), []);
|
|
2073
2078
|
const { failed, frameProps } = usePluginFrame(plugin, host, "editor", void 0, hooks);
|
|
2074
2079
|
const name = plugin.editor?.title ?? plugin.title;
|
|
2075
2080
|
if (failed) return /* @__PURE__ */ jsx(ErrorMessage, { message: t("editor.plugin.failed", { editor: name }) });
|
|
2076
|
-
return /* @__PURE__ */
|
|
2077
|
-
className: "gfa-editor-
|
|
2078
|
-
|
|
2079
|
-
|
|
2081
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2082
|
+
className: "gfa-plugin-editor-wrap",
|
|
2083
|
+
children: [/* @__PURE__ */ jsx("iframe", {
|
|
2084
|
+
className: "gfa-editor-frame",
|
|
2085
|
+
title: t("editor.frame", { title: target.title }),
|
|
2086
|
+
...frameProps
|
|
2087
|
+
}), !ready && /* @__PURE__ */ jsxs("div", {
|
|
2088
|
+
className: "gfa-editor-loading",
|
|
2089
|
+
role: "status",
|
|
2090
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2091
|
+
className: "gfa-spinner",
|
|
2092
|
+
"aria-hidden": "true"
|
|
2093
|
+
}), t("editor.plugin.loading", { editor: name })]
|
|
2094
|
+
})]
|
|
2080
2095
|
});
|
|
2081
2096
|
}
|
|
2082
2097
|
/** What the header asks the editor's frame for, as far as it said it can. */
|
|
@@ -2154,6 +2169,18 @@ function sharePreview(content, path, root) {
|
|
|
2154
2169
|
* can do, and the AI assistant, which keeps editors' keys out of the frame.
|
|
2155
2170
|
*/
|
|
2156
2171
|
/** Where the editors' sidebars' widths are remembered: items' fields have a wider one of their own. */
|
|
2172
|
+
/** One thing an editor plugin gave back that the site can't keep, in words: the block and setting, then what's wrong. */
|
|
2173
|
+
function problemLine(t, problem) {
|
|
2174
|
+
const where = problem.block ?? t("editor.plugin.pageSettings");
|
|
2175
|
+
return problem.field ? t("editor.plugin.problemField", {
|
|
2176
|
+
block: where,
|
|
2177
|
+
field: problem.field,
|
|
2178
|
+
message: problem.message
|
|
2179
|
+
}) : t("editor.plugin.problem", {
|
|
2180
|
+
block: where,
|
|
2181
|
+
message: problem.message
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2157
2184
|
const WIDTHS_KEY = "goodfellow-editor-sidebar-widths";
|
|
2158
2185
|
const ENTRY_WIDTHS_KEY = "goodfellow-entry-sidebar-widths";
|
|
2159
2186
|
function readWidths(key) {
|
|
@@ -2188,6 +2215,20 @@ const VIEWPORTS = [
|
|
|
2188
2215
|
function PuckEditor({ kind, path, title, data, collection, entry, validate, toChanges, actions, notice, language }) {
|
|
2189
2216
|
const t = useStrings();
|
|
2190
2217
|
const { site, publish, reload, drafts } = useAdmin();
|
|
2218
|
+
const readOnly = !useMayChange(kind === "page" ? {
|
|
2219
|
+
kind,
|
|
2220
|
+
path
|
|
2221
|
+
} : kind === "entry" ? {
|
|
2222
|
+
kind,
|
|
2223
|
+
collection: collection?.id ?? "",
|
|
2224
|
+
entry: entry ?? ""
|
|
2225
|
+
} : kind === "template" ? {
|
|
2226
|
+
kind,
|
|
2227
|
+
collection: collection?.id ?? ""
|
|
2228
|
+
} : {
|
|
2229
|
+
kind,
|
|
2230
|
+
...language && { language }
|
|
2231
|
+
});
|
|
2191
2232
|
const editors = useEditorChoice(site.description, kind);
|
|
2192
2233
|
const plugin = editors.chosen;
|
|
2193
2234
|
const editorName = plugin?.editor?.title ?? plugin?.title ?? "";
|
|
@@ -2211,7 +2252,8 @@ function PuckEditor({ kind, path, title, data, collection, entry, validate, toCh
|
|
|
2211
2252
|
data,
|
|
2212
2253
|
...collection && { collection: collection.id },
|
|
2213
2254
|
...entry && { entry },
|
|
2214
|
-
...language && { language }
|
|
2255
|
+
...language && { language },
|
|
2256
|
+
...readOnly && { readOnly }
|
|
2215
2257
|
}), [
|
|
2216
2258
|
kind,
|
|
2217
2259
|
path,
|
|
@@ -2219,7 +2261,8 @@ function PuckEditor({ kind, path, title, data, collection, entry, validate, toCh
|
|
|
2219
2261
|
data,
|
|
2220
2262
|
collection,
|
|
2221
2263
|
entry,
|
|
2222
|
-
language
|
|
2264
|
+
language,
|
|
2265
|
+
readOnly
|
|
2223
2266
|
]);
|
|
2224
2267
|
const onConnect = useCallback(async (client) => {
|
|
2225
2268
|
client.on("editor.state", (next) => {
|
|
@@ -2285,12 +2328,23 @@ function PuckEditor({ kind, path, title, data, collection, entry, validate, toCh
|
|
|
2285
2328
|
rightSideBarVisible: false
|
|
2286
2329
|
});
|
|
2287
2330
|
}, []);
|
|
2288
|
-
/**
|
|
2331
|
+
/**
|
|
2332
|
+
* Changes editor, keeping what's edited: from Puck, as it is when the choice is made, even if its frame hasn't
|
|
2333
|
+
* said so yet; from an editor plugin, only what the site can publish, and where some of it can't be, only once
|
|
2334
|
+
* the editor has agreed to leave those changes behind.
|
|
2335
|
+
*/
|
|
2289
2336
|
const chooseEditor = async (name) => {
|
|
2290
2337
|
if (plugin && pluginEditor) {
|
|
2291
2338
|
const checked = await pluginData(pluginEditor, latest.current.data);
|
|
2339
|
+
if (!checked.ok) {
|
|
2340
|
+
const problems = checked.problems.map((problem) => problemLine(t, problem)).join("\n");
|
|
2341
|
+
if (!window.confirm(t("editor.plugin.switchLoses", {
|
|
2342
|
+
editor: editorName,
|
|
2343
|
+
problems
|
|
2344
|
+
}))) return;
|
|
2345
|
+
}
|
|
2292
2346
|
latest.current.data = checked.ok ? checked.data : pluginEditor.opened;
|
|
2293
|
-
}
|
|
2347
|
+
} else if (!plugin && client) latest.current.data = await client.call("editor.data").catch(() => latest.current.data);
|
|
2294
2348
|
setPluginEditor(void 0);
|
|
2295
2349
|
editors.choose(name);
|
|
2296
2350
|
};
|
|
@@ -2305,7 +2359,7 @@ function PuckEditor({ kind, path, title, data, collection, entry, validate, toCh
|
|
|
2305
2359
|
setStatus({
|
|
2306
2360
|
type: "invalid",
|
|
2307
2361
|
message: t("editor.plugin.invalid", { editor: editorName }),
|
|
2308
|
-
details: checked.problems.map((problem) =>
|
|
2362
|
+
details: checked.problems.map((problem) => problemLine(t, problem)).join("\n")
|
|
2309
2363
|
});
|
|
2310
2364
|
return;
|
|
2311
2365
|
}
|
|
@@ -2381,6 +2435,10 @@ function PuckEditor({ kind, path, title, data, collection, entry, validate, toCh
|
|
|
2381
2435
|
className: "gfa-editor-notice",
|
|
2382
2436
|
children: notice
|
|
2383
2437
|
}),
|
|
2438
|
+
readOnly && /* @__PURE__ */ jsx("div", {
|
|
2439
|
+
className: "gfa-editor-notice",
|
|
2440
|
+
children: t("limits.readOnly")
|
|
2441
|
+
}),
|
|
2384
2442
|
/* @__PURE__ */ jsxs("div", {
|
|
2385
2443
|
className: "gfa-editor-header",
|
|
2386
2444
|
children: [
|
|
@@ -2461,13 +2519,13 @@ function PuckEditor({ kind, path, title, data, collection, entry, validate, toCh
|
|
|
2461
2519
|
onClick: () => setAiOpen(!aiOpen),
|
|
2462
2520
|
children: t("ai.label")
|
|
2463
2521
|
}),
|
|
2464
|
-
!drafts.current && /* @__PURE__ */ jsx(Button, {
|
|
2522
|
+
!drafts.current && !readOnly && /* @__PURE__ */ jsx(Button, {
|
|
2465
2523
|
title: t("draft.addHint"),
|
|
2466
2524
|
disabled: !state || status.type === "publishing",
|
|
2467
2525
|
onClick: () => void onPublish(true),
|
|
2468
2526
|
children: t("draft.add")
|
|
2469
2527
|
}),
|
|
2470
|
-
/* @__PURE__ */ jsx(Button, {
|
|
2528
|
+
!readOnly && /* @__PURE__ */ jsx(Button, {
|
|
2471
2529
|
variant: "primary",
|
|
2472
2530
|
disabled: !state || status.type === "publishing",
|
|
2473
2531
|
onClick: () => void onPublish(),
|
|
@@ -5013,7 +5071,7 @@ async function answers(address) {
|
|
|
5013
5071
|
/** Connects a custom domain to a site published with its git host's own Pages, and follows it until it works. */
|
|
5014
5072
|
function DomainScreen() {
|
|
5015
5073
|
const t = useStrings();
|
|
5016
|
-
const { pages, account, demo, publish } = useAdmin();
|
|
5074
|
+
const { pages, account, demo, publish, site: about } = useAdmin();
|
|
5017
5075
|
const { content } = useSiteContent();
|
|
5018
5076
|
const host = account?.hostName ?? "";
|
|
5019
5077
|
const outside = useOutsideHost();
|
|
@@ -5075,6 +5133,10 @@ function DomainScreen() {
|
|
|
5075
5133
|
className: "gfa-form",
|
|
5076
5134
|
children: [
|
|
5077
5135
|
/* @__PURE__ */ jsx("p", { children: t("domain.intro") }),
|
|
5136
|
+
about.copy?.url && /* @__PURE__ */ jsx("p", {
|
|
5137
|
+
className: "gfa-hint",
|
|
5138
|
+
children: t("domain.copy", { address: about.copy.url })
|
|
5139
|
+
}),
|
|
5078
5140
|
/* @__PURE__ */ jsx(OwnerTokenActive, { onForget: loadSite }),
|
|
5079
5141
|
body,
|
|
5080
5142
|
!demo && (pages || outside) && /* @__PURE__ */ jsx(AdminAddressSection, { outside })
|
|
@@ -5493,6 +5555,301 @@ function LayoutEditorScreen({ part, language }) {
|
|
|
5493
5555
|
});
|
|
5494
5556
|
}
|
|
5495
5557
|
//#endregion
|
|
5558
|
+
//#region src/permissions-section.tsx
|
|
5559
|
+
/** Limits a new person starts with: nothing, until the owner ticks what they may change. */
|
|
5560
|
+
const NO_LIMITS = {
|
|
5561
|
+
pages: [],
|
|
5562
|
+
collections: []
|
|
5563
|
+
};
|
|
5564
|
+
/** The page sections an owner can choose from: every page, then each page at the top of the main language's addresses. */
|
|
5565
|
+
function pageSections(content) {
|
|
5566
|
+
const main = mainLanguage(content.settings);
|
|
5567
|
+
const sections = /* @__PURE__ */ new Set();
|
|
5568
|
+
for (const page of content.pages) {
|
|
5569
|
+
if (pathLanguage(content.settings, page.path) !== main) continue;
|
|
5570
|
+
const first = page.path.split("/")[1];
|
|
5571
|
+
if (first) sections.add(`/${first}`);
|
|
5572
|
+
}
|
|
5573
|
+
return ["/", ...[...sections].sort()];
|
|
5574
|
+
}
|
|
5575
|
+
function toggled(list, value, on) {
|
|
5576
|
+
const without = (list ?? []).filter((item) => item !== value);
|
|
5577
|
+
return on ? [...without, value] : without;
|
|
5578
|
+
}
|
|
5579
|
+
/**
|
|
5580
|
+
* What each limited person may change, and who reviews the rest, written to
|
|
5581
|
+
* `content/permissions.json`. Anyone it doesn't list may change everything.
|
|
5582
|
+
*/
|
|
5583
|
+
function PermissionsSection() {
|
|
5584
|
+
const t = useStrings();
|
|
5585
|
+
const { publish, account } = useAdmin();
|
|
5586
|
+
const { content } = useSiteContent();
|
|
5587
|
+
const permissions = content.permissions ?? { people: {} };
|
|
5588
|
+
const [editing, setEditing] = useState();
|
|
5589
|
+
const [login, setLogin] = useState("");
|
|
5590
|
+
const [busy, setBusy] = useState(false);
|
|
5591
|
+
const [reviewers, setReviewers] = useState((permissions.reviewers ?? []).join(", "));
|
|
5592
|
+
const people = Object.entries(permissions.people).sort(([a], [b]) => a.localeCompare(b));
|
|
5593
|
+
const save = async (next, message) => {
|
|
5594
|
+
setBusy(true);
|
|
5595
|
+
const result = await publish([permissionsFileChange(next)], message, { checks: false });
|
|
5596
|
+
setBusy(false);
|
|
5597
|
+
return result.ok;
|
|
5598
|
+
};
|
|
5599
|
+
const remove = (person) => {
|
|
5600
|
+
const { [person]: _, ...rest } = permissions.people;
|
|
5601
|
+
save({
|
|
5602
|
+
...permissions,
|
|
5603
|
+
people: rest
|
|
5604
|
+
}, t("permissions.removeMessage", { name: person }));
|
|
5605
|
+
};
|
|
5606
|
+
const named = reviewers.split(/[\s,]+/).map((name) => name.trim()).filter(Boolean);
|
|
5607
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
5608
|
+
className: "gfa-form",
|
|
5609
|
+
"aria-labelledby": "gfa-permissions-title",
|
|
5610
|
+
children: [
|
|
5611
|
+
/* @__PURE__ */ jsx("h2", {
|
|
5612
|
+
id: "gfa-permissions-title",
|
|
5613
|
+
className: "gfa-section-title",
|
|
5614
|
+
children: t("permissions.title")
|
|
5615
|
+
}),
|
|
5616
|
+
/* @__PURE__ */ jsx("p", {
|
|
5617
|
+
className: "gfa-hint",
|
|
5618
|
+
children: t("permissions.intro")
|
|
5619
|
+
}),
|
|
5620
|
+
/* @__PURE__ */ jsx("p", {
|
|
5621
|
+
className: "gfa-hint",
|
|
5622
|
+
children: account?.role ? t("permissions.whereServer") : t("permissions.where", { host: account?.hostName || t("permissions.host") })
|
|
5623
|
+
}),
|
|
5624
|
+
people.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
5625
|
+
className: "gfa-check-list",
|
|
5626
|
+
children: people.map(([person, limits]) => /* @__PURE__ */ jsxs("li", { children: [
|
|
5627
|
+
/* @__PURE__ */ jsx("strong", { children: person }),
|
|
5628
|
+
" ",
|
|
5629
|
+
/* @__PURE__ */ jsx("span", {
|
|
5630
|
+
className: "gfa-hint",
|
|
5631
|
+
children: describeLimits(t, limits)
|
|
5632
|
+
}),
|
|
5633
|
+
" ",
|
|
5634
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5635
|
+
variant: "ghost",
|
|
5636
|
+
disabled: busy,
|
|
5637
|
+
onClick: () => setEditing({
|
|
5638
|
+
login: person,
|
|
5639
|
+
limits
|
|
5640
|
+
}),
|
|
5641
|
+
children: t("permissions.change")
|
|
5642
|
+
}),
|
|
5643
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5644
|
+
variant: "ghost",
|
|
5645
|
+
disabled: busy,
|
|
5646
|
+
onClick: () => remove(person),
|
|
5647
|
+
children: t("permissions.remove")
|
|
5648
|
+
})
|
|
5649
|
+
] }, person))
|
|
5650
|
+
}),
|
|
5651
|
+
/* @__PURE__ */ jsxs("form", {
|
|
5652
|
+
className: "gfa-form",
|
|
5653
|
+
onSubmit: (event) => {
|
|
5654
|
+
event.preventDefault();
|
|
5655
|
+
const name = login.trim();
|
|
5656
|
+
if (!name) return;
|
|
5657
|
+
setEditing({
|
|
5658
|
+
login: name,
|
|
5659
|
+
limits: permissions.people[name] ?? NO_LIMITS
|
|
5660
|
+
});
|
|
5661
|
+
},
|
|
5662
|
+
children: [/* @__PURE__ */ jsx(TextField, {
|
|
5663
|
+
label: t("permissions.person"),
|
|
5664
|
+
hint: t("permissions.personHint"),
|
|
5665
|
+
value: login,
|
|
5666
|
+
autoComplete: "off",
|
|
5667
|
+
onChange: setLogin
|
|
5668
|
+
}), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
5669
|
+
type: "submit",
|
|
5670
|
+
disabled: busy || !login.trim(),
|
|
5671
|
+
children: t("permissions.add")
|
|
5672
|
+
}) })]
|
|
5673
|
+
}),
|
|
5674
|
+
/* @__PURE__ */ jsxs("form", {
|
|
5675
|
+
className: "gfa-form",
|
|
5676
|
+
onSubmit: (event) => {
|
|
5677
|
+
event.preventDefault();
|
|
5678
|
+
const { reviewers: _, ...rest } = permissions;
|
|
5679
|
+
save(named.length > 0 ? {
|
|
5680
|
+
...rest,
|
|
5681
|
+
reviewers: named
|
|
5682
|
+
} : rest, t("permissions.reviewersMessage"));
|
|
5683
|
+
},
|
|
5684
|
+
children: [/* @__PURE__ */ jsx(TextField, {
|
|
5685
|
+
label: t("permissions.reviewers"),
|
|
5686
|
+
hint: t("permissions.reviewersHint"),
|
|
5687
|
+
value: reviewers,
|
|
5688
|
+
autoComplete: "off",
|
|
5689
|
+
onChange: setReviewers
|
|
5690
|
+
}), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
5691
|
+
type: "submit",
|
|
5692
|
+
disabled: busy || named.join(",") === (permissions.reviewers ?? []).join(","),
|
|
5693
|
+
children: t("permissions.saveReviewers")
|
|
5694
|
+
}) })]
|
|
5695
|
+
}),
|
|
5696
|
+
editing && /* @__PURE__ */ jsx(LimitsEditor, {
|
|
5697
|
+
login: editing.login,
|
|
5698
|
+
limits: editing.limits,
|
|
5699
|
+
busy,
|
|
5700
|
+
onClose: () => setEditing(void 0),
|
|
5701
|
+
onSave: async (limits) => {
|
|
5702
|
+
if (await save({
|
|
5703
|
+
...permissions,
|
|
5704
|
+
people: {
|
|
5705
|
+
...permissions.people,
|
|
5706
|
+
[editing.login]: limits
|
|
5707
|
+
}
|
|
5708
|
+
}, t("permissions.message", { name: editing.login }))) {
|
|
5709
|
+
setEditing(void 0);
|
|
5710
|
+
setLogin("");
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
})
|
|
5714
|
+
]
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
/** Someone's limits in a few words, for the list. */
|
|
5718
|
+
function describeLimits(t, limits) {
|
|
5719
|
+
const parts = [
|
|
5720
|
+
...(limits.pages ?? []).map((section) => section === "/" ? t("permissions.allPages") : section),
|
|
5721
|
+
...limits.collections ?? [],
|
|
5722
|
+
...limits.media ? [t("limits.media")] : [],
|
|
5723
|
+
...limits.layout ? [t("limits.layout")] : [],
|
|
5724
|
+
...limits.menus ? [t("limits.menus")] : []
|
|
5725
|
+
];
|
|
5726
|
+
const text = parts.length > 0 ? parts.join(", ") : t("permissions.nothing");
|
|
5727
|
+
return limits.languages ? `${text} (${limits.languages.join(", ")})` : text;
|
|
5728
|
+
}
|
|
5729
|
+
function Check({ label, checked, onChange }) {
|
|
5730
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
5731
|
+
className: "gfa-checkbox",
|
|
5732
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
5733
|
+
type: "checkbox",
|
|
5734
|
+
checked,
|
|
5735
|
+
onChange: (event) => onChange(event.target.checked)
|
|
5736
|
+
}), label]
|
|
5737
|
+
});
|
|
5738
|
+
}
|
|
5739
|
+
function LimitsEditor({ login, limits: initial, busy, onClose, onSave }) {
|
|
5740
|
+
const t = useStrings();
|
|
5741
|
+
const { content } = useSiteContent();
|
|
5742
|
+
const [limits, setLimits] = useState(initial);
|
|
5743
|
+
const id = useId();
|
|
5744
|
+
const sections = useMemo(() => {
|
|
5745
|
+
const found = pageSections(content);
|
|
5746
|
+
return [...found, ...(initial.pages ?? []).filter((section) => !found.includes(section))];
|
|
5747
|
+
}, [content, initial.pages]);
|
|
5748
|
+
const languages = [{
|
|
5749
|
+
code: mainLanguage(content.settings),
|
|
5750
|
+
name: void 0
|
|
5751
|
+
}, ...content.settings.languages ?? []];
|
|
5752
|
+
const set = (change) => setLimits((current) => ({
|
|
5753
|
+
...current,
|
|
5754
|
+
...change
|
|
5755
|
+
}));
|
|
5756
|
+
return /* @__PURE__ */ jsxs(Dialog, {
|
|
5757
|
+
title: t("permissions.editTitle", { name: login }),
|
|
5758
|
+
onClose,
|
|
5759
|
+
children: [
|
|
5760
|
+
/* @__PURE__ */ jsx("p", { children: t("permissions.editIntro") }),
|
|
5761
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
5762
|
+
className: "gfa-fieldset",
|
|
5763
|
+
"aria-describedby": `${id}-pages`,
|
|
5764
|
+
children: [
|
|
5765
|
+
/* @__PURE__ */ jsx("legend", { children: t("permissions.pages") }),
|
|
5766
|
+
/* @__PURE__ */ jsx("p", {
|
|
5767
|
+
id: `${id}-pages`,
|
|
5768
|
+
className: "gfa-hint",
|
|
5769
|
+
children: t("permissions.pagesHint")
|
|
5770
|
+
}),
|
|
5771
|
+
sections.map((section) => /* @__PURE__ */ jsx(Check, {
|
|
5772
|
+
label: section === "/" ? t("permissions.allPages") : section,
|
|
5773
|
+
checked: (limits.pages ?? []).includes(section),
|
|
5774
|
+
onChange: (on) => set({ pages: toggled(limits.pages, section, on) })
|
|
5775
|
+
}, section))
|
|
5776
|
+
]
|
|
5777
|
+
}),
|
|
5778
|
+
content.collections.length > 0 && /* @__PURE__ */ jsxs("fieldset", {
|
|
5779
|
+
className: "gfa-fieldset",
|
|
5780
|
+
children: [/* @__PURE__ */ jsx("legend", { children: t("permissions.collections") }), content.collections.map((collection) => /* @__PURE__ */ jsx(Check, {
|
|
5781
|
+
label: collection.settings.name,
|
|
5782
|
+
checked: (limits.collections ?? []).includes(collection.id),
|
|
5783
|
+
onChange: (on) => set({ collections: toggled(limits.collections, collection.id, on) })
|
|
5784
|
+
}, collection.id))]
|
|
5785
|
+
}),
|
|
5786
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
5787
|
+
className: "gfa-fieldset",
|
|
5788
|
+
children: [
|
|
5789
|
+
/* @__PURE__ */ jsx("legend", { children: t("permissions.more") }),
|
|
5790
|
+
/* @__PURE__ */ jsx(Check, {
|
|
5791
|
+
label: t("limits.media"),
|
|
5792
|
+
checked: limits.media === true,
|
|
5793
|
+
onChange: (on) => set({ media: on })
|
|
5794
|
+
}),
|
|
5795
|
+
/* @__PURE__ */ jsx(Check, {
|
|
5796
|
+
label: t("limits.layout"),
|
|
5797
|
+
checked: limits.layout === true,
|
|
5798
|
+
onChange: (on) => set({ layout: on })
|
|
5799
|
+
}),
|
|
5800
|
+
/* @__PURE__ */ jsx(Check, {
|
|
5801
|
+
label: t("limits.menus"),
|
|
5802
|
+
checked: limits.menus === true,
|
|
5803
|
+
onChange: (on) => set({ menus: on })
|
|
5804
|
+
})
|
|
5805
|
+
]
|
|
5806
|
+
}),
|
|
5807
|
+
languages.length > 1 && /* @__PURE__ */ jsxs("fieldset", {
|
|
5808
|
+
className: "gfa-fieldset",
|
|
5809
|
+
children: [
|
|
5810
|
+
/* @__PURE__ */ jsx("legend", { children: t("permissions.languages") }),
|
|
5811
|
+
/* @__PURE__ */ jsx(Check, {
|
|
5812
|
+
label: t("permissions.everyLanguage"),
|
|
5813
|
+
checked: !limits.languages,
|
|
5814
|
+
onChange: (on) => setLimits(({ languages: _, ...rest }) => on ? rest : {
|
|
5815
|
+
...rest,
|
|
5816
|
+
languages: []
|
|
5817
|
+
})
|
|
5818
|
+
}),
|
|
5819
|
+
limits.languages !== void 0 && languages.map(({ code, name }) => /* @__PURE__ */ jsx(Check, {
|
|
5820
|
+
label: name ? `${name} (${code})` : code,
|
|
5821
|
+
checked: limits.languages?.includes(code) === true,
|
|
5822
|
+
onChange: (on) => set({ languages: toggled(limits.languages, code, on) })
|
|
5823
|
+
}, code))
|
|
5824
|
+
]
|
|
5825
|
+
}),
|
|
5826
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5827
|
+
className: "gfa-dialog-actions",
|
|
5828
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
5829
|
+
onClick: onClose,
|
|
5830
|
+
children: t("action.cancel")
|
|
5831
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
5832
|
+
variant: "primary",
|
|
5833
|
+
disabled: busy,
|
|
5834
|
+
onClick: () => void onSave(cleaned(limits)),
|
|
5835
|
+
children: t(busy ? "publish.publishing" : "permissions.save")
|
|
5836
|
+
})]
|
|
5837
|
+
})
|
|
5838
|
+
]
|
|
5839
|
+
});
|
|
5840
|
+
}
|
|
5841
|
+
/** Limits as the file keeps them: no `false`, and no empty lists but pages and collections. */
|
|
5842
|
+
function cleaned(limits) {
|
|
5843
|
+
return {
|
|
5844
|
+
pages: limits.pages ?? [],
|
|
5845
|
+
collections: limits.collections ?? [],
|
|
5846
|
+
...limits.media && { media: true },
|
|
5847
|
+
...limits.layout && { layout: true },
|
|
5848
|
+
...limits.menus && { menus: true },
|
|
5849
|
+
...limits.languages && { languages: limits.languages }
|
|
5850
|
+
};
|
|
5851
|
+
}
|
|
5852
|
+
//#endregion
|
|
5496
5853
|
//#region src/editors-screen.tsx
|
|
5497
5854
|
const ROLE_LABELS = {
|
|
5498
5855
|
owner: "editors.role.owner",
|
|
@@ -5506,16 +5863,21 @@ function problemText(error) {
|
|
|
5506
5863
|
/** Who edits the site: inviting people, changing what they can do, and removing them. */
|
|
5507
5864
|
function EditorsScreen() {
|
|
5508
5865
|
const t = useStrings();
|
|
5509
|
-
const { editors, demo } = useAdmin();
|
|
5866
|
+
const { editors, demo, limits } = useAdmin();
|
|
5867
|
+
const owner = useCanChangeSettings() && !limits;
|
|
5510
5868
|
return /* @__PURE__ */ jsxs("div", {
|
|
5511
5869
|
className: "gfa-screen",
|
|
5512
|
-
children: [
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5870
|
+
children: [
|
|
5871
|
+
/* @__PURE__ */ jsx("div", {
|
|
5872
|
+
className: "gfa-screen-header",
|
|
5873
|
+
children: /* @__PURE__ */ jsx("h1", { children: t("settings.tab.editors") })
|
|
5874
|
+
}),
|
|
5875
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5876
|
+
className: "gfa-form",
|
|
5877
|
+
children: [/* @__PURE__ */ jsx("p", { children: t("editors.intro") }), demo ? /* @__PURE__ */ jsx("p", { children: t("editors.demo") }) : editors ? /* @__PURE__ */ jsx(EditorsPanel, {}) : /* @__PURE__ */ jsx("p", { children: t("editors.local") })]
|
|
5878
|
+
}),
|
|
5879
|
+
owner && /* @__PURE__ */ jsx(PermissionsSection, {})
|
|
5880
|
+
]
|
|
5519
5881
|
});
|
|
5520
5882
|
}
|
|
5521
5883
|
function EditorsPanel() {
|
|
@@ -6482,7 +6844,7 @@ function DeletePageDialog({ page, onClose }) {
|
|
|
6482
6844
|
}
|
|
6483
6845
|
function PagesScreen() {
|
|
6484
6846
|
const t = useStrings();
|
|
6485
|
-
const { siteUrl } = useAdmin();
|
|
6847
|
+
const { siteUrl, limits } = useAdmin();
|
|
6486
6848
|
const { content } = useSiteContent();
|
|
6487
6849
|
const [dialog, setDialog] = useState(null);
|
|
6488
6850
|
const close = () => setDialog(null);
|
|
@@ -6507,74 +6869,79 @@ function PagesScreen() {
|
|
|
6507
6869
|
className: "gfa-visually-hidden",
|
|
6508
6870
|
children: t("pages.edit")
|
|
6509
6871
|
}) })
|
|
6510
|
-
] }) }), /* @__PURE__ */ jsx("tbody", { children: content.pages.map((page) =>
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
/* @__PURE__ */ jsx(ScheduleStatus, {
|
|
6518
|
-
schedule: page.content,
|
|
6519
|
-
content
|
|
6520
|
-
}),
|
|
6521
|
-
/* @__PURE__ */ jsx(PageTranslations, { page })
|
|
6522
|
-
] }),
|
|
6523
|
-
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: page.path }) }),
|
|
6524
|
-
/* @__PURE__ */ jsxs("td", {
|
|
6525
|
-
className: "gfa-row-actions",
|
|
6526
|
-
children: [
|
|
6872
|
+
] }) }), /* @__PURE__ */ jsx("tbody", { children: content.pages.map((page) => {
|
|
6873
|
+
const changeable = mayChange(content, limits, {
|
|
6874
|
+
kind: "page",
|
|
6875
|
+
path: page.path
|
|
6876
|
+
});
|
|
6877
|
+
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
6878
|
+
/* @__PURE__ */ jsxs("td", { children: [
|
|
6527
6879
|
/* @__PURE__ */ jsx("a", {
|
|
6528
|
-
className: "gfa-button gfa-button-secondary",
|
|
6529
6880
|
href: pageEditorHref(page.path),
|
|
6530
|
-
|
|
6881
|
+
className: "gfa-link-strong",
|
|
6882
|
+
children: pageTitle$1(page)
|
|
6531
6883
|
}),
|
|
6532
|
-
/* @__PURE__ */ jsx(
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
target: "_blank",
|
|
6536
|
-
rel: "noreferrer",
|
|
6537
|
-
children: t("pages.view")
|
|
6884
|
+
/* @__PURE__ */ jsx(ScheduleStatus, {
|
|
6885
|
+
schedule: page.content,
|
|
6886
|
+
content
|
|
6538
6887
|
}),
|
|
6539
|
-
/* @__PURE__ */ jsx(
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6888
|
+
/* @__PURE__ */ jsx(PageTranslations, { page })
|
|
6889
|
+
] }),
|
|
6890
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: page.path }) }),
|
|
6891
|
+
/* @__PURE__ */ jsxs("td", {
|
|
6892
|
+
className: "gfa-row-actions",
|
|
6893
|
+
children: [
|
|
6894
|
+
/* @__PURE__ */ jsx("a", {
|
|
6895
|
+
className: "gfa-button gfa-button-secondary",
|
|
6896
|
+
href: pageEditorHref(page.path),
|
|
6897
|
+
children: t(changeable ? "pages.edit" : "pages.look")
|
|
6544
6898
|
}),
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
type: "duplicate",
|
|
6552
|
-
page
|
|
6899
|
+
/* @__PURE__ */ jsx("a", {
|
|
6900
|
+
className: "gfa-button gfa-button-ghost",
|
|
6901
|
+
href: `${siteUrl.replace(/\/$/, "")}${page.path}`,
|
|
6902
|
+
target: "_blank",
|
|
6903
|
+
rel: "noreferrer",
|
|
6904
|
+
children: t("pages.view")
|
|
6553
6905
|
}),
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
page
|
|
6906
|
+
changeable && /* @__PURE__ */ jsx(Button, {
|
|
6907
|
+
variant: "ghost",
|
|
6908
|
+
onClick: () => setDialog({
|
|
6909
|
+
type: "move",
|
|
6910
|
+
page
|
|
6911
|
+
}),
|
|
6912
|
+
disabled: page.path === "/",
|
|
6913
|
+
children: t("pages.move")
|
|
6561
6914
|
}),
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
page
|
|
6915
|
+
/* @__PURE__ */ jsx(Button, {
|
|
6916
|
+
variant: "ghost",
|
|
6917
|
+
onClick: () => setDialog({
|
|
6918
|
+
type: "duplicate",
|
|
6919
|
+
page
|
|
6920
|
+
}),
|
|
6921
|
+
children: t("pages.duplicate")
|
|
6570
6922
|
}),
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6923
|
+
changeable && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
6924
|
+
variant: "ghost",
|
|
6925
|
+
onClick: () => setDialog({
|
|
6926
|
+
type: "schedule",
|
|
6927
|
+
page
|
|
6928
|
+
}),
|
|
6929
|
+
disabled: page.path === "/",
|
|
6930
|
+
children: t("pages.schedule")
|
|
6931
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
6932
|
+
variant: "ghost",
|
|
6933
|
+
onClick: () => setDialog({
|
|
6934
|
+
type: "delete",
|
|
6935
|
+
page
|
|
6936
|
+
}),
|
|
6937
|
+
disabled: page.path === "/",
|
|
6938
|
+
title: page.path === "/" ? t("pages.homeCantDelete") : void 0,
|
|
6939
|
+
children: t("pages.delete")
|
|
6940
|
+
})] })
|
|
6941
|
+
]
|
|
6942
|
+
})
|
|
6943
|
+
] }, page.path);
|
|
6944
|
+
}) })]
|
|
6578
6945
|
}),
|
|
6579
6946
|
dialog?.type === "new" && /* @__PURE__ */ jsx(NewPageDialog, { onClose: close }),
|
|
6580
6947
|
dialog?.type === "duplicate" && /* @__PURE__ */ jsx(NewPageDialog, {
|
|
@@ -9151,6 +9518,16 @@ function validate(draft) {
|
|
|
9151
9518
|
};
|
|
9152
9519
|
return { errors: Object.fromEntries(result.error.issues.map((issue) => [issue.path.join("."), issue.message])) };
|
|
9153
9520
|
}
|
|
9521
|
+
/**
|
|
9522
|
+
* The loaded settings and redirects as the form would save them, serialized
|
|
9523
|
+
* to compare with. Settings the form can't save as they are stay as loaded.
|
|
9524
|
+
*/
|
|
9525
|
+
function loadedForm(content) {
|
|
9526
|
+
return {
|
|
9527
|
+
settings: serializeContent(validate(content.settings).settings ?? content.settings),
|
|
9528
|
+
redirects: serializeContent(storedRedirects(filledRedirects(content.redirects ?? [])))
|
|
9529
|
+
};
|
|
9530
|
+
}
|
|
9154
9531
|
/** The time zones this browser knows, such as `America/New_York`. */
|
|
9155
9532
|
function timeZones(current) {
|
|
9156
9533
|
const known = typeof Intl.supportedValuesOf === "function" ? Intl.supportedValuesOf("timeZone") : [];
|
|
@@ -9752,17 +10129,18 @@ function SettingsScreen({ tab, section }) {
|
|
|
9752
10129
|
const headError = useMemo(() => headCodeProblem(t, code.head), [t, code.head]);
|
|
9753
10130
|
const formsError = useMemo(() => formsProblem(t, settings.forms), [t, settings.forms]);
|
|
9754
10131
|
const savedRedirects = useMemo(() => storedRedirects(filledRedirects(redirects)), [redirects]);
|
|
10132
|
+
const loaded = useMemo(() => loadedForm(content), [content]);
|
|
9755
10133
|
const redirectsInvalid = useMemo(() => redirectProblems(savedRedirects).some(Boolean), [savedRedirects]);
|
|
9756
10134
|
const pages = useSitePages(content);
|
|
9757
10135
|
const taken = useMemo(() => pages.map((page) => page.path), [pages]);
|
|
9758
10136
|
const changes = useMemo(() => {
|
|
9759
10137
|
const result = [];
|
|
9760
|
-
if (validation.settings && serializeContent(validation.settings) !==
|
|
10138
|
+
if (validation.settings && serializeContent(validation.settings) !== loaded.settings) result.push(siteSettingsFileChange(validation.settings));
|
|
9761
10139
|
if (serializeContent(menus) !== serializeContent(content.menus) || serializeContent(languageMenus) !== serializeContent(content.languageMenus ?? {})) result.push(menusFileChange({
|
|
9762
10140
|
menus,
|
|
9763
10141
|
languageMenus
|
|
9764
10142
|
}));
|
|
9765
|
-
if (serializeContent(savedRedirects) !==
|
|
10143
|
+
if (serializeContent(savedRedirects) !== loaded.redirects || redirectMessage.trim() !== (content.redirectMessage ?? "").trim()) result.push(redirectsFileChange({
|
|
9766
10144
|
redirects: savedRedirects,
|
|
9767
10145
|
message: redirectMessage
|
|
9768
10146
|
}));
|
|
@@ -9771,6 +10149,7 @@ function SettingsScreen({ tab, section }) {
|
|
|
9771
10149
|
return result;
|
|
9772
10150
|
}, [
|
|
9773
10151
|
validation,
|
|
10152
|
+
loaded,
|
|
9774
10153
|
menus,
|
|
9775
10154
|
languageMenus,
|
|
9776
10155
|
savedRedirects,
|
|
@@ -11792,7 +12171,7 @@ function DemoBanner() {
|
|
|
11792
12171
|
*/
|
|
11793
12172
|
function DraftBanner() {
|
|
11794
12173
|
const t = useStrings();
|
|
11795
|
-
const { drafts, state } = useAdmin();
|
|
12174
|
+
const { drafts, state, limits } = useAdmin();
|
|
11796
12175
|
const [busy, setBusy] = useState(false);
|
|
11797
12176
|
const [confirming, setConfirming] = useState(false);
|
|
11798
12177
|
const [chosen, setChosen] = useState();
|
|
@@ -11850,14 +12229,29 @@ function DraftBanner() {
|
|
|
11850
12229
|
error: result.error
|
|
11851
12230
|
});
|
|
11852
12231
|
});
|
|
11853
|
-
const
|
|
12232
|
+
const reviewed = new Map(drafts.reviews.map((review) => [review.name, review]));
|
|
12233
|
+
const others = names.filter((name) => name !== current?.name);
|
|
12234
|
+
const forReview = limits ? [] : others.filter((name) => reviewed.has(name));
|
|
12235
|
+
const waiting = forReview.length > 0 ? forReview : others;
|
|
11854
12236
|
const selected = chosen && waiting.includes(chosen) ? chosen : waiting[0];
|
|
12237
|
+
const currentReview = current ? reviewed.get(current.name) : void 0;
|
|
12238
|
+
const selectedReview = selected ? reviewed.get(selected) : void 0;
|
|
11855
12239
|
return /* @__PURE__ */ jsxs("section", {
|
|
11856
12240
|
className: "gfa-draft-banner",
|
|
11857
12241
|
"aria-label": t("draft.label"),
|
|
11858
12242
|
children: [
|
|
11859
12243
|
current ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
11860
12244
|
/* @__PURE__ */ jsx("span", { children: t(kept ? "draft.bannerKept" : "draft.banner", { date: draftStarted(current.name) }) }),
|
|
12245
|
+
currentReview && /* @__PURE__ */ jsxs("span", { children: [
|
|
12246
|
+
t("draft.inReview"),
|
|
12247
|
+
" ",
|
|
12248
|
+
/* @__PURE__ */ jsx("a", {
|
|
12249
|
+
href: currentReview.url,
|
|
12250
|
+
target: "_blank",
|
|
12251
|
+
rel: "noreferrer",
|
|
12252
|
+
children: t("draft.seeReview")
|
|
12253
|
+
})
|
|
12254
|
+
] }),
|
|
11861
12255
|
current.paths.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
11862
12256
|
className: "gfa-draft-count",
|
|
11863
12257
|
children: current.paths.length === 1 ? t("draft.oneChange") : t("draft.changes", { count: String(current.paths.length) })
|
|
@@ -11886,20 +12280,26 @@ function DraftBanner() {
|
|
|
11886
12280
|
children: t("draft.leave")
|
|
11887
12281
|
})
|
|
11888
12282
|
] }) : waiting.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
11889
|
-
/* @__PURE__ */ jsx("span", { children: waiting.length === 1 ? t("draft.waiting", { date: draftStarted(waiting[0] ?? "") }) : t("draft.waitingMany", { count: String(waiting.length) }) }),
|
|
12283
|
+
/* @__PURE__ */ jsx("span", { children: forReview.length > 0 ? waiting.length === 1 ? t("draft.reviewWaiting", { title: selectedReview?.title ?? "" }) : t("draft.reviewWaitingMany", { count: String(waiting.length) }) : waiting.length === 1 ? t("draft.waiting", { date: draftStarted(waiting[0] ?? "") }) : t("draft.waitingMany", { count: String(waiting.length) }) }),
|
|
11890
12284
|
waiting.length > 1 && /* @__PURE__ */ jsx("select", {
|
|
11891
12285
|
"aria-label": t("draft.choose"),
|
|
11892
12286
|
value: selected,
|
|
11893
12287
|
onChange: (event) => setChosen(event.target.value),
|
|
11894
12288
|
children: waiting.map((name) => /* @__PURE__ */ jsx("option", {
|
|
11895
12289
|
value: name,
|
|
11896
|
-
children: t("draft.started", { date: draftStarted(name) })
|
|
12290
|
+
children: reviewed.get(name)?.title ?? t("draft.started", { date: draftStarted(name) })
|
|
11897
12291
|
}, name))
|
|
11898
12292
|
}),
|
|
11899
12293
|
/* @__PURE__ */ jsx(Button, {
|
|
11900
12294
|
disabled: busy,
|
|
11901
12295
|
onClick: () => void switchTo(selected),
|
|
11902
12296
|
children: t("draft.open")
|
|
12297
|
+
}),
|
|
12298
|
+
forReview.length > 0 && selectedReview && /* @__PURE__ */ jsx("a", {
|
|
12299
|
+
href: selectedReview.url,
|
|
12300
|
+
target: "_blank",
|
|
12301
|
+
rel: "noreferrer",
|
|
12302
|
+
children: t("draft.seeReview")
|
|
11903
12303
|
})
|
|
11904
12304
|
] }),
|
|
11905
12305
|
busy && notice === void 0 && current && /* @__PURE__ */ jsx("span", {
|