@goodfellow-cms/admin 0.9.0 → 0.10.1
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/{admin-text-sources-BTZJPlVK.js → admin-text-sources-B1CoPEu6.js} +1 -1
- package/dist/{custom-font-faces-DJ2AF2oN.js → custom-font-faces-DCan2jgW.js} +2 -2
- package/dist/editor.js +3 -3
- package/dist/{entry-values-FcVgwQkJ.js → entry-values-DWZqC5ZZ.js} +8 -8
- package/dist/{es-ChxY0KVH.js → es-Z-QY8sdc.js} +144 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1332 -227
- package/dist/plugin.js +1 -1
- package/dist/{strings-CWaRN_0v.d.ts → strings-Bd9r6qQn.d.ts} +144 -1
- package/dist/{strings-D94noZrv.js → strings-CgC6pd6k.js} +144 -1
- package/dist/text.d.ts +1 -1
- package/dist/text.js +2 -2
- package/package.json +6 -6
- package/styles.css +23 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { A as duplicatePageData, B as pageFileChange, C as checkPageAddress, D as customCssFileChange, E as collectionSettingsChanges, F as menusFileChange, G as storedCss, H as redirectsFileChange, I as moveEntryChanges, J as syncedBlockFileChange, K as storedData, L as movePageChanges, M as itemLinkRemovalChanges, N as layoutFileChange, O as deleteCollectionChanges, P as mediaFileChange, R as newCollectionSettings, S as checkEntrySlug, T as collectionFileChange$1, U as siteSettingsFileChange, V as pageTitle$1, W as slugify, X as updateSettingsFileChange, Y as uniqueName, _ as mediaUrl, b as uploadName, f as fileName$1, j as entryFileChange$1, k as deleteSyncedBlockChanges, r as createMediaPreviews, t as installCustomFonts, u as allMedia, w as codeFileChanges, x as addressPatternFor, y as siteImages, z as newPageData } from "./custom-font-faces-
|
|
2
|
-
import { $ as hasUnsavedChanges, A as useOutsideHost, B as useFrameServices, E as MediaScreen, F as mayChange, I as useMayChange, J as shortcutFor, K as isTyping, L as FrameServicesProvider, M as usePluginFrame, N as usePluginHost, O as AppLink, P as PublishFailure, Q as entryEditorHref, R as reloadFrames, S as REGISTRY_VERSION, T as MediaField, U as findPlaces, V as useFrameUpdates, W as goToPlaces, X as SETTINGS_SECTIONS, Y as usesCommandKey, Z as collectionHref, _ as useThemeChoice$1, _t as useDebounced, a as applyEditorChange, at as useUnsavedChanges, b as ScheduleStatus, ct as draftStarted, d as useCanChangeSettings, dt as ErrorMessage, et as navigate, f as useSiteContent, ft as Field, g as useDarkMode, gt as docsLink, h as useApplyThemeChoice, ht as TextField, it as useRoute, j as PluginScreenFrame, k as prepareUpload, l as AdminProvider, lt as Button, mt as PublishedNotice, n as formValues, nt as sectionId, o as findBlock, ot as EditorTextContext, p as setDarkMode, pt as InfoBubble, q as onShortcut, r as PreviewFrame, rt as syncedBlockEditorHref, t as SCHEDULE_PROP, tt as pageEditorHref, u as useAdmin, ut as Dialog, v as ComingUp, vt as describeContrast, x as scheduleSettingsChanges, y as ScheduleFields, z as useFrame } from "./entry-values-
|
|
3
|
-
import { i as useStrings, n as defaultStrings, r as useStringTable, t as StringsProvider } from "./strings-
|
|
4
|
-
import { a as languageStatus, c as sourcesToTranslate, d as PUCK_ENGLISH, i as labelSources, l as withEdits, n as entryOf, o as sourceKey, r as filterSources, s as sourceState, t as adminTextSources, u as withTranslations } from "./admin-text-sources-
|
|
5
|
-
import { ADMIN_TEXT_INDEX, APP_API_HEADER, BROKER_AI_ADDRESS, BROKER_CONNECT_MESSAGE, BROKER_READY_MESSAGE, CODE_PURPOSES, CODE_THEMES, COLLECTIONS_DIR, CONFIG_FILE, CONSENT_DAYS, CONSENT_KINDS, CONTENT_DIR, CommentsError, ConflictError, ContentError, DEFAULT_CODE_THEMES, DEFAULT_DARK_THEME_COLORS, DEFAULT_THEME_COLORS, DEMO_CONTENT_PATH, DISQUS_CREATE_URL, DomainError, EditorsError, FIELD_TYPES, FONT_CATEGORIES, FOOTER_FILE, GARRUL_URL, GISCUS_APP_URL, GOODFELLOW_VERSION, GROUP_FIELD_TYPES, HEADER_FILE, HIDE_REASONS, HeadCodeError, INSTALLED_PLUGINS_FILE, INSTALLED_RECORD_FILE, MAX_CONSENT_DAYS, MAX_CUSTOM_FONTS, MAX_FONT_BYTES, MAX_FONT_FILES, MAX_GROUP_FIELDS, MEDIA_DIR, NIGHTLY_MODES, NPM_REGISTRY, PUBLISHED_ADMIN_TEXT_DIR, PackageError, PluginInstallError, PluginTaskError, REDIRECT_LINK_PLACEHOLDER, REDIRECT_MESSAGE, RELEASE_AGE_DAYS, RegistryError, STATS_SERVICES, SYNCED_BLOCK, SYNCED_BLOCKS_DIR, SYNCED_BLOCK_PROP, SetupError, SignInError, THEME_COLORS, THEME_FILE, THEME_RELEASE_AGE_DAYS, TITLE_FIELD, ThemeError, UPDATES_FILE, UpdatesError, VERSION_PACKAGE, addressPatternProblem, adminOrigin, adminTextFile, adminTextFileChange, adminTextOverlay, adminTextProblem, adminTextTable, allPages, availableBlocks, availableUpdates, blockUses, brokerAddressProblem, brokerOrigin, brokerReleaseUrl, checkAdminAddress, checkEditedData, checkPuckData, checkRecords, chooseLanguagePackRelease, chooseRelease, chooseThemeRelease, collectionFileSchema, collectionsLinkingTo, compareReleases, configAdmin, configBroker, consentDefaults, consentInLanguage, consentKinds, consentSchema, contentInLanguage, contrastProblems, credentialStorage, customFont, customFontFileChanges, decodeBase64, demoContentStore, demoStore, describeSiteChanges, describedConfig, disqusModerationUrl, disqusShortname, domainZone, encodeBase64Bytes, endsHeldTemplate, entryAddress, entryLanguage, entryPathIn, entrySourceHash, entryTitle, entryTranslations, eventValueProblem, expandSyncedBlocks, fetchBrokerRelease, fetchLanguagePack, fetchPluginPackage, fetchTheme, findEntry, findSyncedBlock, fontFileName, formTarget, garrulAddress, garrulModerationUrl, getPageHead, googleFontsUrl, guessFontFamily, guessFontFile, hasLanguages, inRange, isAddressSegment, isDisqus, isEmptyValue, isGarrul, isLanguageCode, isLanguageHome, isReservedPagePath, isWoff2, itemsLinkingTo, languageLayoutFile, languageName, languagePackReleases, languagePrefix, loadSiteContent, mainLanguage, mayInstallEarly, mediaDetailsOf, menusFileSchema, normalizeDomain, pageLanguage, pageLinks, pageSourceHash, pageTitle, pageTranslations, parseAdminTextFile, parseAdminTextIndex, parseDemoContent, parseHeadCode, parseInstalledPlugins, parseInstalledRecord, parseThemeRecord, parseUpdateSettings, pathLanguage, permissionChanges, permissionsFileChange, planBlockChanges, planBroker, planLanguagePack, planPluginInstall, planPluginRemove, planTheme, pluginReleases, pluginsOutOfRange, readAdminText, readProvenanceSource, readStatsCode, recordsInPlace, redirectFrom, redirectProblems, registrarName, releaseVersions, remoteBackend, reviewPluginPackages, scheduleOf, scheduleProblem, searchLanguagePacks, searchPlugins, searchThemes, serializeContent, siteChangesSince, siteHistory, siteLanguages, siteRestoreChanges, siteSettingsSchema, siteVersion, statsDashboard, statsElements, storedRedirects, syncedBlockUses, themeFileUrl, themeLabel, themeLoads, themeReleases, themeToCss, themeUnknownBlocks, todayIn, translationState, trimChars, withAdminConfig, withBrokerConfig, withSchedule, writesEarlyLockfile, writesLockfile } from "@goodfellow-cms/core";
|
|
1
|
+
import { A as duplicatePageData, B as pageFileChange, C as checkPageAddress, D as customCssFileChange, E as collectionSettingsChanges, F as menusFileChange, G as storedCss, H as redirectsFileChange, I as moveEntryChanges, J as syncedBlockFileChange, K as storedData, L as movePageChanges, M as itemLinkRemovalChanges, N as layoutFileChange, O as deleteCollectionChanges, P as mediaFileChange, R as newCollectionSettings, S as checkEntrySlug, T as collectionFileChange$1, U as siteSettingsFileChange, V as pageTitle$1, W as slugify, X as updateSettingsFileChange, Y as uniqueName, _ as mediaUrl, b as uploadName, f as fileName$1, j as entryFileChange$1, k as deleteSyncedBlockChanges, r as createMediaPreviews, t as installCustomFonts, u as allMedia, w as codeFileChanges, x as addressPatternFor, y as siteImages, z as newPageData } from "./custom-font-faces-DCan2jgW.js";
|
|
2
|
+
import { $ as hasUnsavedChanges, A as useOutsideHost, B as useFrameServices, E as MediaScreen, F as mayChange, I as useMayChange, J as shortcutFor, K as isTyping, L as FrameServicesProvider, M as usePluginFrame, N as usePluginHost, O as AppLink, P as PublishFailure, Q as entryEditorHref, R as reloadFrames, S as REGISTRY_VERSION, T as MediaField, U as findPlaces, V as useFrameUpdates, W as goToPlaces, X as SETTINGS_SECTIONS, Y as usesCommandKey, Z as collectionHref, _ as useThemeChoice$1, _t as useDebounced, a as applyEditorChange, at as useUnsavedChanges, b as ScheduleStatus, ct as draftStarted, d as useCanChangeSettings, dt as ErrorMessage, et as navigate, f as useSiteContent, ft as Field, g as useDarkMode, gt as docsLink, h as useApplyThemeChoice, ht as TextField, it as useRoute, j as PluginScreenFrame, k as prepareUpload, l as AdminProvider, lt as Button, mt as PublishedNotice, n as formValues, nt as sectionId, o as findBlock, ot as EditorTextContext, p as setDarkMode, pt as InfoBubble, q as onShortcut, r as PreviewFrame, rt as syncedBlockEditorHref, t as SCHEDULE_PROP, tt as pageEditorHref, u as useAdmin, ut as Dialog, v as ComingUp, vt as describeContrast, x as scheduleSettingsChanges, y as ScheduleFields, z as useFrame } from "./entry-values-DWZqC5ZZ.js";
|
|
3
|
+
import { i as useStrings, n as defaultStrings, r as useStringTable, t as StringsProvider } from "./strings-CgC6pd6k.js";
|
|
4
|
+
import { a as languageStatus, c as sourcesToTranslate, d as PUCK_ENGLISH, i as labelSources, l as withEdits, n as entryOf, o as sourceKey, r as filterSources, s as sourceState, t as adminTextSources, u as withTranslations } from "./admin-text-sources-B1CoPEu6.js";
|
|
5
|
+
import { ADMIN_TEXT_INDEX, APP_API_HEADER, BROKER_AI_ADDRESS, BROKER_CONNECT_MESSAGE, BROKER_READY_MESSAGE, CODE_PURPOSES, CODE_THEMES, COLLECTIONS_DIR, CONFIG_FILE, CONSENT_DAYS, CONSENT_KINDS, CONTENT_DIR, CommentsError, ConflictError, ContentError, DEFAULT_CODE_THEMES, DEFAULT_DARK_THEME_COLORS, DEFAULT_THEME_COLORS, DEMO_CONTENT_PATH, DISQUS_CREATE_URL, DomainError, EditorsError, FIELD_TYPES, FONT_CATEGORIES, FOOTER_FILE, GARRUL_URL, GISCUS_APP_URL, GOODFELLOW_REGISTRY, GOODFELLOW_VERSION, GROUP_FIELD_TYPES, HEADER_FILE, HIDE_REASONS, HeadCodeError, INSTALLED_PLUGINS_FILE, INSTALLED_RECORD_FILE, MAX_CONSENT_DAYS, MAX_CUSTOM_FONTS, MAX_FONT_BYTES, MAX_FONT_FILES, MAX_GROUP_FIELDS, MAX_UPLOAD_BYTES, MEDIA_DIR, NIGHTLY_MODES, NPM_REGISTRY, PUBLISHED_ADMIN_TEXT_DIR, PackageError, PluginInstallError, PluginTaskError, REDIRECT_LINK_PLACEHOLDER, REDIRECT_MESSAGE, RELEASE_AGE_DAYS, RegistryError, STATS_SERVICES, SYNCED_BLOCK, SYNCED_BLOCKS_DIR, SYNCED_BLOCK_PROP, SetupError, SignInError, SiteImportError, THEME_COLORS, THEME_FILE, THEME_RELEASE_AGE_DAYS, TITLE_FIELD, TRANSFER_FOLDERS, TRANSFER_RECORDS, ThemeError, UPDATES_FILE, UpdatesError, VERSION_PACKAGE, addressPatternProblem, adminOrigin, adminTextFile, adminTextFileChange, adminTextOverlay, adminTextProblem, adminTextTable, allPages, availableBlocks, availableUpdates, blockUses, brokerAddressProblem, brokerOrigin, brokerReleaseUrl, checkAdminAddress, checkEditedData, checkPuckData, checkRecords, chooseLanguagePackRelease, chooseRelease, chooseThemeRelease, collectionFileSchema, collectionsLinkingTo, compareReleases, configAdmin, configBackendHost, configBackendOption, configBroker, consentDefaults, consentInLanguage, consentKinds, consentSchema, contentInLanguage, contrastProblems, credentialStorage, customFont, customFontFileChanges, decodeBase64, demoContentStore, demoStore, describeSiteChanges, describedConfig, disqusModerationUrl, disqusShortname, domainZone, encodeBase64Bytes, endsHeldTemplate, entryAddress, entryLanguage, entryPathIn, entrySourceHash, entryTitle, entryTranslations, eventValueProblem, expandSyncedBlocks, fetchBrokerRelease, fetchLanguagePack, fetchPluginPackage, fetchTheme, findEntry, findSyncedBlock, fontFileName, formTarget, garrulAddress, garrulModerationUrl, getPageHead, googleFontsUrl, guessFontFamily, guessFontFile, hasLanguages, inRange, isAddressSegment, isDisqus, isEmptyValue, isGarrul, isLanguageCode, isLanguageHome, isReservedPagePath, isWoff2, itemsLinkingTo, languageLayoutFile, languageName, languagePackReleases, languagePrefix, loadSiteContent, mainLanguage, mayInstallEarly, mediaDetailsOf, mediaKind, menusFileSchema, normalizeDomain, pageLanguage, pageLinks, pageSourceHash, pageTitle, pageTranslations, parseAdminTextFile, parseAdminTextIndex, parseDemoContent, parseHeadCode, parseInstalledPlugins, parseInstalledRecord, parseThemeRecord, parseUpdateSettings, pathLanguage, permissionChanges, permissionsFileChange, planBlockChanges, planBroker, planLanguagePack, planPluginInstall, planPluginRemove, planSiteImport, planTheme, pluginReleases, pluginsOutOfRange, readAdminText, readProvenanceSource, readStatsCode, recordsInPlace, redirectFrom, redirectProblems, registrarName, releaseVersions, remoteBackend, reviewPluginPackages, scheduleOf, scheduleProblem, searchLanguagePacks, searchPlugins, searchThemes, serializeContent, signInClientIdProblem, signInRedirectAddresses, signInServiceProblem, siteChangesSince, siteHistory, siteLanguages, siteRestoreChanges, siteSettingsSchema, siteVersion, statsDashboard, statsElements, storedRedirects, syncedBlockUses, themeFileUrl, themeLabel, themeLoads, themeReleases, themeToCss, themeUnknownBlocks, todayIn, transferManifestFile, transferName, transferPaths, translationState, trimChars, withAdminConfig, withBackendOption, withBrokerConfig, withSchedule, writesEarlyLockfile, writesLockfile } from "@goodfellow-cms/core";
|
|
6
6
|
import { Braces, ChartColumn, CodeXml, FileInput, Globe, History, Languages, Menu, MessageSquare, MessageSquareText, Moon, Palette, PanelLeftClose, PanelLeftOpen, Plug, Puzzle, RefreshCw, Settings, ShieldCheck, Signpost, Sun, SwatchBook, Users, X } from "lucide-react";
|
|
7
7
|
import { Fragment, createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
8
8
|
import { createRoot } from "react-dom/client";
|
|
@@ -24,7 +24,7 @@ const ENGLISH = {
|
|
|
24
24
|
labels: {}
|
|
25
25
|
};
|
|
26
26
|
/** Each language Goodfellow ships but English, loaded only when someone chooses it, so the admin panel stays small for everyone else. */
|
|
27
|
-
const LOADERS = { es: () => import("./es-
|
|
27
|
+
const LOADERS = { es: () => import("./es-Z-QY8sdc.js").then((module) => module.spanish) };
|
|
28
28
|
const builtIn = (code) => ADMIN_LANGUAGES.some((language) => language.code === code);
|
|
29
29
|
const listeners = /* @__PURE__ */ new Set();
|
|
30
30
|
let siteText = {
|
|
@@ -3805,7 +3805,7 @@ function problemKey(error) {
|
|
|
3805
3805
|
if (error instanceof SyntaxError) return "ai.error.answer";
|
|
3806
3806
|
return "ai.error.service";
|
|
3807
3807
|
}
|
|
3808
|
-
function ServiceSettings({ providers, settings, onChange, siteAi }) {
|
|
3808
|
+
function ServiceSettings$1({ providers, settings, onChange, siteAi }) {
|
|
3809
3809
|
const t = useStrings();
|
|
3810
3810
|
const info = PROVIDERS[settings.provider];
|
|
3811
3811
|
const sitePays = siteAi?.services.includes(settings.provider) ?? false;
|
|
@@ -4339,7 +4339,7 @@ function AiPanel({ context, editor }) {
|
|
|
4339
4339
|
className: "gfa-ai-service",
|
|
4340
4340
|
open: settingsOpen,
|
|
4341
4341
|
onToggle: (event) => setSettingsOpen(event.currentTarget.open),
|
|
4342
|
-
children: [/* @__PURE__ */ jsx("summary", { children: t("ai.settings", { service }) }), /* @__PURE__ */ jsx(ServiceSettings, {
|
|
4342
|
+
children: [/* @__PURE__ */ jsx("summary", { children: t("ai.settings", { service }) }), /* @__PURE__ */ jsx(ServiceSettings$1, {
|
|
4343
4343
|
providers,
|
|
4344
4344
|
settings: chosen,
|
|
4345
4345
|
onChange: updateSettings,
|
|
@@ -4794,7 +4794,7 @@ function useDefaults(code) {
|
|
|
4794
4794
|
useEffect(() => {
|
|
4795
4795
|
if (code !== "es" || spanish) return;
|
|
4796
4796
|
let live = true;
|
|
4797
|
-
import("./es-
|
|
4797
|
+
import("./es-Z-QY8sdc.js").then((module) => live && setSpanish(module.spanish));
|
|
4798
4798
|
return () => {
|
|
4799
4799
|
live = false;
|
|
4800
4800
|
};
|
|
@@ -5081,7 +5081,7 @@ function AdminLanguagesScreen() {
|
|
|
5081
5081
|
}),
|
|
5082
5082
|
!ai.ready && /* @__PURE__ */ jsx("p", { children: t("adminText.noService") }),
|
|
5083
5083
|
ai.settings.provider === "manual" && /* @__PURE__ */ jsx("p", { children: t("adminText.manual") }),
|
|
5084
|
-
/* @__PURE__ */ jsx(ServiceSettings, {
|
|
5084
|
+
/* @__PURE__ */ jsx(ServiceSettings$1, {
|
|
5085
5085
|
providers: ai.providers,
|
|
5086
5086
|
settings: ai.chosen,
|
|
5087
5087
|
onChange: ai.updateSettings,
|
|
@@ -5420,13 +5420,13 @@ function AppSignInGate({ api, children }) {
|
|
|
5420
5420
|
}
|
|
5421
5421
|
//#endregion
|
|
5422
5422
|
//#region src/blocks-screen.tsx
|
|
5423
|
-
const fetchJson = async (url) => {
|
|
5423
|
+
const fetchJson$1 = async (url) => {
|
|
5424
5424
|
const response = await fetch(url);
|
|
5425
5425
|
if (!response.ok) throw new Error(`${url} answered ${response.status}.`);
|
|
5426
5426
|
return response.json();
|
|
5427
5427
|
};
|
|
5428
5428
|
/** What went wrong with a block registry, in plain words. */
|
|
5429
|
-
function problemText$
|
|
5429
|
+
function problemText$2(t, problem, name) {
|
|
5430
5430
|
const key = `blocks.error.${problem.code}`;
|
|
5431
5431
|
if (problem.code === "packages") return t(key, { packages: problem.packages.join(", ") });
|
|
5432
5432
|
if (problem.code === "file-exists" || problem.code === "unsupported-file") return t(key, { path: problem.path });
|
|
@@ -5546,7 +5546,7 @@ function BlocksScreen() {
|
|
|
5546
5546
|
}, [readFile, revision]);
|
|
5547
5547
|
const loadAvailable = useCallback(() => {
|
|
5548
5548
|
setLoadError(void 0);
|
|
5549
|
-
availableBlocks(registries, fetchJson).then(setAvailable, setLoadError);
|
|
5549
|
+
availableBlocks(registries, fetchJson$1).then(setAvailable, setLoadError);
|
|
5550
5550
|
}, [registries]);
|
|
5551
5551
|
useEffect(loadAvailable, [loadAvailable]);
|
|
5552
5552
|
const isPendingAdd = (block) => pending.some((change) => change.kind === "add" && change.block.ref === block.ref);
|
|
@@ -5583,7 +5583,7 @@ function BlocksScreen() {
|
|
|
5583
5583
|
const plan = await planBlockChanges({
|
|
5584
5584
|
changes: pending.map((item) => item.kind === "add" ? { add: item.block.ref } : { remove: item.name }),
|
|
5585
5585
|
registries,
|
|
5586
|
-
fetchJson,
|
|
5586
|
+
fetchJson: fetchJson$1,
|
|
5587
5587
|
readFile,
|
|
5588
5588
|
record,
|
|
5589
5589
|
blocks: Object.keys(blocks),
|
|
@@ -5616,7 +5616,7 @@ function BlocksScreen() {
|
|
|
5616
5616
|
} catch (error) {
|
|
5617
5617
|
setNotice({
|
|
5618
5618
|
type: "failed",
|
|
5619
|
-
message: error instanceof RegistryError ? problemText$
|
|
5619
|
+
message: error instanceof RegistryError ? problemText$2(t, error.problem, failedTitle(error)) : t("publish.error"),
|
|
5620
5620
|
error
|
|
5621
5621
|
});
|
|
5622
5622
|
}
|
|
@@ -5735,7 +5735,7 @@ function BlocksScreen() {
|
|
|
5735
5735
|
error: recordError
|
|
5736
5736
|
}),
|
|
5737
5737
|
loadError !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
5738
|
-
message: loadError instanceof RegistryError ? problemText$
|
|
5738
|
+
message: loadError instanceof RegistryError ? problemText$2(t, loadError.problem, "") : t("blocks.error.unreachable"),
|
|
5739
5739
|
error: loadError,
|
|
5740
5740
|
action: /* @__PURE__ */ jsx(Button, {
|
|
5741
5741
|
onClick: loadAvailable,
|
|
@@ -9014,12 +9014,19 @@ function ConnectedDomain({ domain, onRemoved, admin = false }) {
|
|
|
9014
9014
|
className: "gfa-form",
|
|
9015
9015
|
children: [
|
|
9016
9016
|
/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: t(admin ? "domain.admin.connected" : "domain.connected", { domain }) }) }),
|
|
9017
|
-
account?.redirectAddressesListed && /* @__PURE__ */
|
|
9017
|
+
account?.redirectAddressesListed && /* @__PURE__ */ jsxs("p", {
|
|
9018
9018
|
className: "gfa-notice",
|
|
9019
|
-
children:
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9019
|
+
children: [
|
|
9020
|
+
t("domain.signIn", {
|
|
9021
|
+
host,
|
|
9022
|
+
address: `${address}/admin/`
|
|
9023
|
+
}),
|
|
9024
|
+
" ",
|
|
9025
|
+
/* @__PURE__ */ jsx(AppLink, {
|
|
9026
|
+
href: "#/settings/security",
|
|
9027
|
+
children: t("domain.signInSettings")
|
|
9028
|
+
})
|
|
9029
|
+
]
|
|
9023
9030
|
}),
|
|
9024
9031
|
done && /* @__PURE__ */ jsx("p", {
|
|
9025
9032
|
className: "gfa-notice gfa-notice-success",
|
|
@@ -9558,7 +9565,7 @@ const ROLE_LABELS = {
|
|
|
9558
9565
|
viewer: "editors.role.viewer"
|
|
9559
9566
|
};
|
|
9560
9567
|
/** Plain words for why a change to the editors didn't work. */
|
|
9561
|
-
function problemText(error) {
|
|
9568
|
+
function problemText$1(error) {
|
|
9562
9569
|
return error instanceof EditorsError ? `editors.error.${error.problem}` : "editors.error.other";
|
|
9563
9570
|
}
|
|
9564
9571
|
/** Who edits the site: inviting people, changing what they can do, and removing them. */
|
|
@@ -9648,7 +9655,7 @@ function EditorsPanel() {
|
|
|
9648
9655
|
})]
|
|
9649
9656
|
}),
|
|
9650
9657
|
problem !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
9651
|
-
message: t(problemText(problem), { host }),
|
|
9658
|
+
message: t(problemText$1(problem), { host }),
|
|
9652
9659
|
error: problem
|
|
9653
9660
|
}),
|
|
9654
9661
|
/* @__PURE__ */ jsxs("table", {
|
|
@@ -9841,6 +9848,271 @@ function EntryEditorScreen({ collectionId, slug }) {
|
|
|
9841
9848
|
});
|
|
9842
9849
|
}
|
|
9843
9850
|
//#endregion
|
|
9851
|
+
//#region src/site-download.ts
|
|
9852
|
+
/**
|
|
9853
|
+
* A copy of the site to keep: its content, its media library and the records
|
|
9854
|
+
* of the blocks and plugins it installed, as one zip file, read at the
|
|
9855
|
+
* revision the admin panel loaded. fflate is loaded only when a copy is made.
|
|
9856
|
+
* No React or DOM, so it's tested in Node.
|
|
9857
|
+
*/
|
|
9858
|
+
/** The folders a copy holds whole. */
|
|
9859
|
+
const DOWNLOAD_FOLDERS = TRANSFER_FOLDERS;
|
|
9860
|
+
/** Single files a copy holds where the site has them: which blocks and plugins it installed, to add them again. */
|
|
9861
|
+
const DOWNLOAD_RECORDS = TRANSFER_RECORDS;
|
|
9862
|
+
/** How many files are read at once. */
|
|
9863
|
+
const READ_AT_ONCE = 6;
|
|
9864
|
+
/** Files that are already compressed are stored as they are, which is as small and much quicker. */
|
|
9865
|
+
const COMPRESSED = /\.(jpe?g|png|webp|gif|avif|mp4|webm|mov|m4v|mp3|m4a|ogg|oga|wav|pdf|woff2?|zip|gz)$/i;
|
|
9866
|
+
/** Every file a copy holds, sorted, from the folders and records the site has. */
|
|
9867
|
+
async function downloadPaths(list) {
|
|
9868
|
+
const folders = await Promise.all(DOWNLOAD_FOLDERS.map((dir) => list(dir)));
|
|
9869
|
+
const records = await Promise.all(DOWNLOAD_RECORDS.map(async (path) => {
|
|
9870
|
+
return (await list(path.slice(0, path.lastIndexOf("/")))).includes(path) ? [path] : [];
|
|
9871
|
+
}));
|
|
9872
|
+
return [.../* @__PURE__ */ new Set([...folders.flat(), ...records.flat()])].sort();
|
|
9873
|
+
}
|
|
9874
|
+
/**
|
|
9875
|
+
* The copy's file name: the site's title in plain letters, then the day,
|
|
9876
|
+
* `st-anne-parish-2026-11-01.zip`, or `site-…` for a title with none.
|
|
9877
|
+
*/
|
|
9878
|
+
const downloadName = transferName;
|
|
9879
|
+
/**
|
|
9880
|
+
* Zips the files, read with `read`, in the order given, a few at a time, and
|
|
9881
|
+
* returns the zip in pieces, for a `Blob`. A file that's gone since it was
|
|
9882
|
+
* listed is left out. Throws what reading throws.
|
|
9883
|
+
*/
|
|
9884
|
+
async function siteArchive(paths, read, onProgress) {
|
|
9885
|
+
const { Zip, ZipDeflate, ZipPassThrough } = await import("fflate");
|
|
9886
|
+
const chunks = [];
|
|
9887
|
+
let finish = () => {};
|
|
9888
|
+
let fail = () => {};
|
|
9889
|
+
const finished = new Promise((resolve, reject) => {
|
|
9890
|
+
finish = resolve;
|
|
9891
|
+
fail = reject;
|
|
9892
|
+
});
|
|
9893
|
+
const zip = new Zip((error, chunk, final) => {
|
|
9894
|
+
if (error) return fail(error);
|
|
9895
|
+
chunks.push(chunk);
|
|
9896
|
+
if (final) finish();
|
|
9897
|
+
});
|
|
9898
|
+
let done = 0;
|
|
9899
|
+
onProgress?.({
|
|
9900
|
+
done,
|
|
9901
|
+
total: paths.length
|
|
9902
|
+
});
|
|
9903
|
+
try {
|
|
9904
|
+
for (let start = 0; start < paths.length; start += READ_AT_ONCE) {
|
|
9905
|
+
const batch = paths.slice(start, start + READ_AT_ONCE);
|
|
9906
|
+
const files = await Promise.all(batch.map((path) => read(path)));
|
|
9907
|
+
batch.forEach((path, index) => {
|
|
9908
|
+
const bytes = files[index];
|
|
9909
|
+
if (bytes) {
|
|
9910
|
+
const entry = COMPRESSED.test(path) ? new ZipPassThrough(path) : new ZipDeflate(path, { level: 6 });
|
|
9911
|
+
zip.add(entry);
|
|
9912
|
+
entry.push(bytes, true);
|
|
9913
|
+
}
|
|
9914
|
+
done += 1;
|
|
9915
|
+
});
|
|
9916
|
+
onProgress?.({
|
|
9917
|
+
done,
|
|
9918
|
+
total: paths.length
|
|
9919
|
+
});
|
|
9920
|
+
}
|
|
9921
|
+
} catch (error) {
|
|
9922
|
+
zip.terminate();
|
|
9923
|
+
throw error;
|
|
9924
|
+
}
|
|
9925
|
+
zip.end();
|
|
9926
|
+
await finished;
|
|
9927
|
+
return chunks;
|
|
9928
|
+
}
|
|
9929
|
+
/**
|
|
9930
|
+
* A copy of the site as one zip, with the manifest that lets another
|
|
9931
|
+
* Goodfellow site import it (`TRANSFER_MANIFEST`), in pieces for a `Blob`.
|
|
9932
|
+
*/
|
|
9933
|
+
async function siteCopyArchive(list, readBytes, content, onProgress) {
|
|
9934
|
+
const paths = await downloadPaths(list);
|
|
9935
|
+
const manifest = transferManifestFile(content);
|
|
9936
|
+
const bytes = new TextEncoder().encode(manifest.content);
|
|
9937
|
+
return siteArchive([...paths, manifest.path], async (path) => path === manifest.path ? bytes : readBytes(path), onProgress);
|
|
9938
|
+
}
|
|
9939
|
+
//#endregion
|
|
9940
|
+
//#region src/markdown-export.tsx
|
|
9941
|
+
/** What a Markdown export couldn't bring across as it was, in words. */
|
|
9942
|
+
function noteText$3(t, note, blockName) {
|
|
9943
|
+
const listed = (items) => {
|
|
9944
|
+
const shown = items.slice(0, 3).join(", ");
|
|
9945
|
+
return items.length > 3 ? t("import.note.more", {
|
|
9946
|
+
items: shown,
|
|
9947
|
+
count: String(items.length - 3)
|
|
9948
|
+
}) : shown;
|
|
9949
|
+
};
|
|
9950
|
+
switch (note.kind) {
|
|
9951
|
+
case "left-out-block": return t("markdownExport.note.leftOutBlock", {
|
|
9952
|
+
name: blockName(note.name),
|
|
9953
|
+
items: listed(note.items)
|
|
9954
|
+
});
|
|
9955
|
+
case "scheduled": return t(note.included ? "markdownExport.note.scheduledIncluded" : "markdownExport.note.scheduledLeftOut", { items: listed(note.items) });
|
|
9956
|
+
case "no-pages": return t("markdownExport.note.noPages", { collections: listed(note.collections) });
|
|
9957
|
+
case "templates": return t("markdownExport.note.templates", { collections: listed(note.collections) });
|
|
9958
|
+
case "redirects": return t("markdownExport.note.redirects", { count: String(note.count) });
|
|
9959
|
+
case "layout": return t("markdownExport.note.layout");
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
/**
|
|
9963
|
+
* Writes the site as a folder of Markdown files (`markdownExport()`), zipped
|
|
9964
|
+
* in the browser with the media library, for other static sites, notes apps
|
|
9965
|
+
* and AI tools, on Site Settings' Import tab beside the WordPress export.
|
|
9966
|
+
*/
|
|
9967
|
+
function MarkdownExportSection() {
|
|
9968
|
+
const t = useStrings();
|
|
9969
|
+
const { blocks, readBytes } = useAdmin();
|
|
9970
|
+
const { content, media } = useSiteContent();
|
|
9971
|
+
const [scheduled, setScheduled] = useState(false);
|
|
9972
|
+
const [links, setLinks] = useState("site");
|
|
9973
|
+
const [result, setResult] = useState();
|
|
9974
|
+
const [progress, setProgress] = useState();
|
|
9975
|
+
const [problem, setProblem] = useState();
|
|
9976
|
+
const busy = progress !== void 0;
|
|
9977
|
+
const blockName = (name) => blocks[name]?.label ?? name;
|
|
9978
|
+
const stored = Object.keys(content.mediaStorage?.files ?? {}).length > 0;
|
|
9979
|
+
const exportSite = async () => {
|
|
9980
|
+
setProblem(void 0);
|
|
9981
|
+
setResult(void 0);
|
|
9982
|
+
setProgress({
|
|
9983
|
+
done: 0,
|
|
9984
|
+
total: 0
|
|
9985
|
+
});
|
|
9986
|
+
try {
|
|
9987
|
+
const { markdownExport, markdownExportName } = await import("@goodfellow-cms/core/wordpress");
|
|
9988
|
+
const written = markdownExport(content, {
|
|
9989
|
+
mediaFiles: media,
|
|
9990
|
+
scheduled,
|
|
9991
|
+
links
|
|
9992
|
+
});
|
|
9993
|
+
const folder = markdownExportName(content);
|
|
9994
|
+
const texts = new Map(written.files.map((file) => [`${folder}/${file.path}`, file.content]));
|
|
9995
|
+
const copies = new Map(written.media.map((file) => [`${folder}/${file.path}`, file.from]));
|
|
9996
|
+
const encoder = new TextEncoder();
|
|
9997
|
+
const chunks = await siteArchive([...texts.keys(), ...copies.keys()], async (path) => {
|
|
9998
|
+
const text = texts.get(path);
|
|
9999
|
+
if (text !== void 0) return encoder.encode(text);
|
|
10000
|
+
const from = copies.get(path);
|
|
10001
|
+
return from ? readBytes(from) : void 0;
|
|
10002
|
+
}, setProgress);
|
|
10003
|
+
const url = URL.createObjectURL(new Blob(chunks, { type: "application/zip" }));
|
|
10004
|
+
const link = document.createElement("a");
|
|
10005
|
+
link.href = url;
|
|
10006
|
+
link.download = `${folder}.zip`;
|
|
10007
|
+
document.body.append(link);
|
|
10008
|
+
link.click();
|
|
10009
|
+
link.remove();
|
|
10010
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
10011
|
+
setResult(written);
|
|
10012
|
+
} catch (error) {
|
|
10013
|
+
setProblem(error ?? /* @__PURE__ */ new Error("Export failed"));
|
|
10014
|
+
} finally {
|
|
10015
|
+
setProgress(void 0);
|
|
10016
|
+
}
|
|
10017
|
+
};
|
|
10018
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
10019
|
+
"aria-labelledby": "gfa-markdown-export-title",
|
|
10020
|
+
children: [
|
|
10021
|
+
/* @__PURE__ */ jsx("h2", {
|
|
10022
|
+
id: "gfa-markdown-export-title",
|
|
10023
|
+
className: "gfa-section-title",
|
|
10024
|
+
children: t("markdownExport.title")
|
|
10025
|
+
}),
|
|
10026
|
+
/* @__PURE__ */ jsx("p", { children: t("markdownExport.intro") }),
|
|
10027
|
+
/* @__PURE__ */ jsx("p", {
|
|
10028
|
+
className: "gfa-hint",
|
|
10029
|
+
children: t("markdownExport.limits")
|
|
10030
|
+
}),
|
|
10031
|
+
stored && /* @__PURE__ */ jsx("p", {
|
|
10032
|
+
className: "gfa-hint",
|
|
10033
|
+
children: t("markdownExport.stored")
|
|
10034
|
+
}),
|
|
10035
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
10036
|
+
className: "gfa-fieldset",
|
|
10037
|
+
children: [
|
|
10038
|
+
/* @__PURE__ */ jsx("legend", { children: t("markdownExport.links") }),
|
|
10039
|
+
/* @__PURE__ */ jsxs("label", {
|
|
10040
|
+
className: "gfa-checkbox",
|
|
10041
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10042
|
+
type: "radio",
|
|
10043
|
+
name: "gfa-markdown-links",
|
|
10044
|
+
checked: links === "site",
|
|
10045
|
+
disabled: busy,
|
|
10046
|
+
onChange: () => setLinks("site")
|
|
10047
|
+
}), t("markdownExport.links.site")]
|
|
10048
|
+
}),
|
|
10049
|
+
/* @__PURE__ */ jsxs("label", {
|
|
10050
|
+
className: "gfa-checkbox",
|
|
10051
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10052
|
+
type: "radio",
|
|
10053
|
+
name: "gfa-markdown-links",
|
|
10054
|
+
checked: links === "files",
|
|
10055
|
+
disabled: busy,
|
|
10056
|
+
onChange: () => setLinks("files")
|
|
10057
|
+
}), t("markdownExport.links.files")]
|
|
10058
|
+
})
|
|
10059
|
+
]
|
|
10060
|
+
}),
|
|
10061
|
+
/* @__PURE__ */ jsxs("label", {
|
|
10062
|
+
className: "gfa-checkbox",
|
|
10063
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10064
|
+
type: "checkbox",
|
|
10065
|
+
checked: scheduled,
|
|
10066
|
+
disabled: busy,
|
|
10067
|
+
onChange: (event) => setScheduled(event.target.checked)
|
|
10068
|
+
}), t("markdownExport.scheduled")]
|
|
10069
|
+
}),
|
|
10070
|
+
problem !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
10071
|
+
message: t("markdownExport.error"),
|
|
10072
|
+
error: problem
|
|
10073
|
+
}),
|
|
10074
|
+
/* @__PURE__ */ jsx("p", {
|
|
10075
|
+
className: "gfa-import-actions",
|
|
10076
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
10077
|
+
onClick: () => void exportSite(),
|
|
10078
|
+
disabled: busy,
|
|
10079
|
+
children: t("markdownExport.download")
|
|
10080
|
+
})
|
|
10081
|
+
}),
|
|
10082
|
+
progress && progress.total > 0 && /* @__PURE__ */ jsx("p", {
|
|
10083
|
+
role: "status",
|
|
10084
|
+
children: t("markdownExport.working", {
|
|
10085
|
+
done: String(progress.done),
|
|
10086
|
+
total: String(progress.total)
|
|
10087
|
+
})
|
|
10088
|
+
}),
|
|
10089
|
+
result && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
10090
|
+
/* @__PURE__ */ jsx("p", {
|
|
10091
|
+
role: "status",
|
|
10092
|
+
className: "gfa-notice gfa-notice-success",
|
|
10093
|
+
children: t("markdownExport.done")
|
|
10094
|
+
}),
|
|
10095
|
+
/* @__PURE__ */ jsxs("ul", {
|
|
10096
|
+
className: "gfa-import-summary",
|
|
10097
|
+
children: [
|
|
10098
|
+
/* @__PURE__ */ jsx("li", { children: t("export.summary.pages", { count: String(result.summary.pages) }) }),
|
|
10099
|
+
/* @__PURE__ */ jsx("li", { children: t("markdownExport.summary.items", { count: String(result.summary.items) }) }),
|
|
10100
|
+
/* @__PURE__ */ jsx("li", { children: t("import.summary.menus", { count: String(result.summary.menus) }) }),
|
|
10101
|
+
/* @__PURE__ */ jsx("li", { children: t("markdownExport.summary.media", { count: String(result.summary.media) }) })
|
|
10102
|
+
]
|
|
10103
|
+
}),
|
|
10104
|
+
result.notes.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("h3", {
|
|
10105
|
+
className: "gfa-section-title",
|
|
10106
|
+
children: t("import.check")
|
|
10107
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
10108
|
+
className: "gfa-import-notes",
|
|
10109
|
+
children: result.notes.map((note) => /* @__PURE__ */ jsx("li", { children: noteText$3(t, note, blockName) }, JSON.stringify(note)))
|
|
10110
|
+
})] })
|
|
10111
|
+
] })
|
|
10112
|
+
]
|
|
10113
|
+
});
|
|
10114
|
+
}
|
|
10115
|
+
//#endregion
|
|
9844
10116
|
//#region src/markdown-import.tsx
|
|
9845
10117
|
const NOTES = {
|
|
9846
10118
|
drafts: "markdownImport.note.drafts",
|
|
@@ -9852,7 +10124,7 @@ const NOTES = {
|
|
|
9852
10124
|
"template-code": "markdownImport.note.templateCode",
|
|
9853
10125
|
html: "markdownImport.note.html"
|
|
9854
10126
|
};
|
|
9855
|
-
function listed(t, items) {
|
|
10127
|
+
function listed$1(t, items) {
|
|
9856
10128
|
const shown = items.slice(0, 3).join(", ");
|
|
9857
10129
|
return items.length > 3 ? t("import.note.more", {
|
|
9858
10130
|
items: shown,
|
|
@@ -9860,12 +10132,12 @@ function listed(t, items) {
|
|
|
9860
10132
|
}) : shown;
|
|
9861
10133
|
}
|
|
9862
10134
|
/** What the import couldn't bring across as it was, in words. */
|
|
9863
|
-
function noteText$
|
|
10135
|
+
function noteText$2(t, note) {
|
|
9864
10136
|
switch (note.kind) {
|
|
9865
|
-
case "fields": return t("markdownImport.note.fields", { names: listed(t, note.names) });
|
|
9866
|
-
case "converted": return t("markdownImport.note.converted", { items: listed(t, note.files) });
|
|
10137
|
+
case "fields": return t("markdownImport.note.fields", { names: listed$1(t, note.names) });
|
|
10138
|
+
case "converted": return t("markdownImport.note.converted", { items: listed$1(t, note.files) });
|
|
9867
10139
|
default: return t(NOTES[note.kind], {
|
|
9868
|
-
items: listed(t, note.files),
|
|
10140
|
+
items: listed$1(t, note.files),
|
|
9869
10141
|
count: String(note.files.length)
|
|
9870
10142
|
});
|
|
9871
10143
|
}
|
|
@@ -10206,45 +10478,578 @@ function Notes$1({ plan }) {
|
|
|
10206
10478
|
children: t("import.check")
|
|
10207
10479
|
}), /* @__PURE__ */ jsx("ul", {
|
|
10208
10480
|
className: "gfa-import-notes",
|
|
10209
|
-
children: plan.notes.map((note) => /* @__PURE__ */ jsx("li", { children: noteText$
|
|
10481
|
+
children: plan.notes.map((note) => /* @__PURE__ */ jsx("li", { children: noteText$2(t, note) }, JSON.stringify(note)))
|
|
10210
10482
|
})] });
|
|
10211
10483
|
}
|
|
10212
10484
|
//#endregion
|
|
10213
|
-
//#region src/
|
|
10214
|
-
|
|
10215
|
-
const
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
dropped: "import.note.dropped",
|
|
10219
|
-
shortcode: "import.note.shortcode",
|
|
10220
|
-
embed: "import.note.embed",
|
|
10221
|
-
form: "import.note.form",
|
|
10222
|
-
script: "import.note.script",
|
|
10223
|
-
table: "import.note.table",
|
|
10224
|
-
posts: "import.note.posts",
|
|
10225
|
-
video: "import.note.video"
|
|
10226
|
-
};
|
|
10227
|
-
const LEFT_OUT = {
|
|
10228
|
-
drafts: "import.note.leftOut.drafts",
|
|
10229
|
-
private: "import.note.leftOut.private",
|
|
10230
|
-
trash: "import.note.leftOut.trash",
|
|
10231
|
-
comments: "import.note.leftOut.comments"
|
|
10485
|
+
//#region src/site-transfer.tsx
|
|
10486
|
+
const fetchJson = async (url) => {
|
|
10487
|
+
const response = await fetch(url);
|
|
10488
|
+
if (!response.ok) throw new Error(`${url} answered ${response.status}.`);
|
|
10489
|
+
return response.json();
|
|
10232
10490
|
};
|
|
10233
|
-
/**
|
|
10234
|
-
function
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10491
|
+
/** Saves zip pieces as a download. */
|
|
10492
|
+
function saveZip(chunks, name) {
|
|
10493
|
+
const url = URL.createObjectURL(new Blob(chunks, { type: "application/zip" }));
|
|
10494
|
+
const link = document.createElement("a");
|
|
10495
|
+
link.href = url;
|
|
10496
|
+
link.download = name;
|
|
10497
|
+
document.body.append(link);
|
|
10498
|
+
link.click();
|
|
10499
|
+
link.remove();
|
|
10500
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
10501
|
+
}
|
|
10502
|
+
/** Downloads the site's content and media as one zip, with its manifest, for another Goodfellow site to import. */
|
|
10503
|
+
function SiteExportSection() {
|
|
10504
|
+
const t = useStrings();
|
|
10505
|
+
const { listFiles, readBytes } = useAdmin();
|
|
10506
|
+
const { content } = useSiteContent();
|
|
10507
|
+
const [progress, setProgress] = useState();
|
|
10508
|
+
const [error, setError] = useState();
|
|
10509
|
+
const stored = Object.keys(content.mediaStorage?.files ?? {}).length > 0;
|
|
10510
|
+
const download = async () => {
|
|
10511
|
+
setError(void 0);
|
|
10512
|
+
setProgress({
|
|
10513
|
+
done: 0,
|
|
10514
|
+
total: 0
|
|
10239
10515
|
});
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10516
|
+
try {
|
|
10517
|
+
saveZip(await siteCopyArchive(listFiles, readBytes, content, setProgress), downloadName(content.settings.title, /* @__PURE__ */ new Date()));
|
|
10518
|
+
} catch (caught) {
|
|
10519
|
+
setError(caught);
|
|
10520
|
+
} finally {
|
|
10521
|
+
setProgress(void 0);
|
|
10522
|
+
}
|
|
10523
|
+
};
|
|
10524
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
10525
|
+
"aria-labelledby": "gfa-site-export-title",
|
|
10526
|
+
children: [
|
|
10527
|
+
/* @__PURE__ */ jsx("h2", {
|
|
10528
|
+
id: "gfa-site-export-title",
|
|
10529
|
+
className: "gfa-section-title",
|
|
10530
|
+
children: t("siteTransfer.export.title")
|
|
10531
|
+
}),
|
|
10532
|
+
/* @__PURE__ */ jsx("p", { children: t("siteTransfer.export.intro") }),
|
|
10533
|
+
stored && /* @__PURE__ */ jsx("p", {
|
|
10534
|
+
className: "gfa-hint",
|
|
10535
|
+
children: t("siteTransfer.export.stored")
|
|
10536
|
+
}),
|
|
10537
|
+
/* @__PURE__ */ jsx("p", {
|
|
10538
|
+
className: "gfa-import-actions",
|
|
10539
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
10540
|
+
onClick: () => void download(),
|
|
10541
|
+
disabled: progress !== void 0,
|
|
10542
|
+
children: t("siteTransfer.export.button")
|
|
10543
|
+
})
|
|
10544
|
+
}),
|
|
10545
|
+
progress && progress.total > 0 && /* @__PURE__ */ jsx("p", {
|
|
10546
|
+
role: "status",
|
|
10547
|
+
children: t("siteTransfer.export.working", {
|
|
10548
|
+
done: String(progress.done),
|
|
10549
|
+
total: String(progress.total)
|
|
10550
|
+
})
|
|
10551
|
+
}),
|
|
10552
|
+
error !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
10553
|
+
message: t("siteTransfer.export.error"),
|
|
10554
|
+
error
|
|
10555
|
+
})
|
|
10556
|
+
]
|
|
10557
|
+
});
|
|
10558
|
+
}
|
|
10559
|
+
const NO_CHOICES = {
|
|
10560
|
+
replace: false,
|
|
10561
|
+
menus: false,
|
|
10562
|
+
settings: false,
|
|
10563
|
+
layout: false,
|
|
10564
|
+
redirects: false,
|
|
10565
|
+
withoutMissingBlocks: false,
|
|
10566
|
+
take: /* @__PURE__ */ new Set(),
|
|
10567
|
+
install: /* @__PURE__ */ new Set()
|
|
10568
|
+
};
|
|
10569
|
+
const decoder = new TextDecoder();
|
|
10570
|
+
/** The export's files, as `planSiteImport()` reads them. */
|
|
10571
|
+
function transferFiles(files) {
|
|
10572
|
+
return {
|
|
10573
|
+
paths: [...files.keys()],
|
|
10574
|
+
read: (path) => {
|
|
10575
|
+
const bytes = files.get(path);
|
|
10576
|
+
return bytes && decoder.decode(bytes);
|
|
10577
|
+
}
|
|
10578
|
+
};
|
|
10579
|
+
}
|
|
10580
|
+
function listed(t, items) {
|
|
10581
|
+
const shown = items.slice(0, 3).join(", ");
|
|
10582
|
+
return items.length > 3 ? t("import.note.more", {
|
|
10583
|
+
items: shown,
|
|
10584
|
+
count: String(items.length - 3)
|
|
10585
|
+
}) : shown;
|
|
10586
|
+
}
|
|
10587
|
+
function noteText$1(t, note, blockName) {
|
|
10588
|
+
switch (note.kind) {
|
|
10589
|
+
case "missing-block": return t("siteTransfer.note.missingBlock", {
|
|
10590
|
+
name: blockName(note.name),
|
|
10591
|
+
items: listed(t, note.items)
|
|
10592
|
+
});
|
|
10593
|
+
case "left-out": return t("siteTransfer.note.leftOut", { items: listed(t, note.items) });
|
|
10594
|
+
case "without-blocks": return t("siteTransfer.note.withoutBlocks", { items: listed(t, note.items) });
|
|
10595
|
+
case "taken": return t("siteTransfer.note.taken", { items: listed(t, note.items) });
|
|
10596
|
+
case "collection-differs": return t("siteTransfer.note.collectionDiffers", { collections: listed(t, note.collections) });
|
|
10597
|
+
case "uncopied": return t("siteTransfer.note.uncopied", { items: listed(t, note.files) });
|
|
10598
|
+
case "stored-media": return t(note.merged ? "siteTransfer.note.storedMerged" : "siteTransfer.note.stored", { count: String(note.count) });
|
|
10599
|
+
case "languages": return t("siteTransfer.note.languages", { codes: note.codes.join(", ") });
|
|
10600
|
+
case "plugins": return t("siteTransfer.note.plugins", { names: listed(t, note.names) });
|
|
10601
|
+
case "code": return t("siteTransfer.note.code");
|
|
10602
|
+
case "kept": return t("siteTransfer.note.kept", { things: note.things.map((thing) => t(`siteTransfer.kept.${thing}`)).join(", ") });
|
|
10603
|
+
}
|
|
10604
|
+
}
|
|
10605
|
+
function conflictText(t, conflict) {
|
|
10606
|
+
const values = {
|
|
10607
|
+
name: conflict.name,
|
|
10608
|
+
detail: conflict.detail ?? ""
|
|
10609
|
+
};
|
|
10610
|
+
switch (conflict.kind) {
|
|
10611
|
+
case "page": return t("siteTransfer.conflict.page", values);
|
|
10612
|
+
case "item": return t("siteTransfer.conflict.item", values);
|
|
10613
|
+
case "collection": return t("siteTransfer.conflict.collection", values);
|
|
10614
|
+
case "synced-block": return t("siteTransfer.conflict.syncedBlock", values);
|
|
10615
|
+
case "media": return t("siteTransfer.conflict.media", values);
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
function problemText(t, error) {
|
|
10619
|
+
if (error instanceof SiteImportError) {
|
|
10620
|
+
if (error.problem === "not-a-site") return t("siteTransfer.error.notASite");
|
|
10621
|
+
if (error.problem === "newer") return t("siteTransfer.error.newer");
|
|
10622
|
+
return t("siteTransfer.error.invalid");
|
|
10623
|
+
}
|
|
10624
|
+
return t("siteTransfer.error.other");
|
|
10625
|
+
}
|
|
10626
|
+
/**
|
|
10627
|
+
* Brings another Goodfellow site's export into this one with
|
|
10628
|
+
* `planSiteImport()`: added to its content, with a choice for each thing both
|
|
10629
|
+
* have differently, or in place of it, then published in one go, with blocks
|
|
10630
|
+
* from Goodfellow's registry it lacks added in the same publish where the
|
|
10631
|
+
* site's code can be changed from here.
|
|
10632
|
+
*/
|
|
10633
|
+
function SiteImportSection() {
|
|
10634
|
+
const t = useStrings();
|
|
10635
|
+
const { blocks, publish, mediaPreviews, listFiles, readBytes, readFile, registries } = useAdmin();
|
|
10636
|
+
const { contentOnly, demo, account, site } = useAdmin();
|
|
10637
|
+
const { content } = useSiteContent();
|
|
10638
|
+
const [loaded, setLoaded] = useState();
|
|
10639
|
+
const [choices, setChoices] = useState(NO_CHOICES);
|
|
10640
|
+
const [plan, setPlan] = useState();
|
|
10641
|
+
const [reading, setReading] = useState(false);
|
|
10642
|
+
const [publishing, setPublishing] = useState(false);
|
|
10643
|
+
const [done, setDone] = useState();
|
|
10644
|
+
const [problem, setProblem] = useState();
|
|
10645
|
+
const [failure, setFailure] = useState(null);
|
|
10646
|
+
const mayInstall = !contentOnly && !demo && !account?.codeReview;
|
|
10647
|
+
const blockName = (name) => blocks[name]?.label ?? name;
|
|
10648
|
+
useEffect(() => {
|
|
10649
|
+
if (!loaded) return;
|
|
10650
|
+
let current = true;
|
|
10651
|
+
planSiteImport(transferFiles(loaded.files), {
|
|
10652
|
+
content,
|
|
10653
|
+
files: loaded.siteFiles,
|
|
10654
|
+
read: readFile
|
|
10655
|
+
}, {
|
|
10656
|
+
...choices,
|
|
10657
|
+
blocks: Object.keys(blocks),
|
|
10658
|
+
installing: [...choices.install],
|
|
10659
|
+
plugins: loaded.plugins,
|
|
10660
|
+
sameMedia: loaded.same,
|
|
10661
|
+
uncopied: loaded.uncopied
|
|
10662
|
+
}).then((next) => current && setPlan(next), (error) => {
|
|
10663
|
+
if (!current) return;
|
|
10664
|
+
setProblem({
|
|
10665
|
+
message: problemText(t, error),
|
|
10666
|
+
error
|
|
10667
|
+
});
|
|
10668
|
+
setLoaded(void 0);
|
|
10669
|
+
setPlan(void 0);
|
|
10670
|
+
});
|
|
10671
|
+
return () => {
|
|
10672
|
+
current = false;
|
|
10673
|
+
};
|
|
10674
|
+
}, [
|
|
10675
|
+
loaded,
|
|
10676
|
+
choices,
|
|
10677
|
+
content,
|
|
10678
|
+
blocks,
|
|
10679
|
+
readFile,
|
|
10680
|
+
t
|
|
10681
|
+
]);
|
|
10682
|
+
const choose = async (file) => {
|
|
10683
|
+
setProblem(void 0);
|
|
10684
|
+
setFailure(null);
|
|
10685
|
+
setDone(void 0);
|
|
10686
|
+
setPlan(void 0);
|
|
10687
|
+
setReading(true);
|
|
10688
|
+
try {
|
|
10689
|
+
const { unzipSync } = await import("fflate");
|
|
10690
|
+
const archive = unzipSync(new Uint8Array(await file.arrayBuffer()));
|
|
10691
|
+
const paths = transferPaths(Object.keys(archive));
|
|
10692
|
+
if (paths.size === 0) throw new SiteImportError("not-a-site");
|
|
10693
|
+
const files = /* @__PURE__ */ new Map();
|
|
10694
|
+
for (const [path, at] of paths) {
|
|
10695
|
+
const bytes = archive[at];
|
|
10696
|
+
if (bytes) files.set(path, bytes);
|
|
10697
|
+
}
|
|
10698
|
+
const siteFiles = [...await listFiles(CONTENT_DIR), ...await listFiles(MEDIA_DIR)];
|
|
10699
|
+
const same = /* @__PURE__ */ new Set();
|
|
10700
|
+
const uncopied = /* @__PURE__ */ new Set();
|
|
10701
|
+
const mine = new Set(siteFiles);
|
|
10702
|
+
for (const [path, bytes] of files) {
|
|
10703
|
+
if (!path.startsWith(`${MEDIA_DIR}/`)) continue;
|
|
10704
|
+
if (bytes.length > MAX_UPLOAD_BYTES || !mediaKind(fileName$1(path))) uncopied.add(path);
|
|
10705
|
+
else if (mine.has(path)) {
|
|
10706
|
+
const own = await readBytes(path);
|
|
10707
|
+
if (own && own.length === bytes.length && own.every((byte, index) => byte === bytes[index])) same.add(path);
|
|
10708
|
+
}
|
|
10709
|
+
}
|
|
10710
|
+
let plugins = (site.description.plugins ?? []).map((plugin) => plugin.name);
|
|
10711
|
+
try {
|
|
10712
|
+
const text = await readFile(INSTALLED_PLUGINS_FILE);
|
|
10713
|
+
if (text) plugins = [...plugins, ...Object.keys(JSON.parse(text).plugins ?? {})];
|
|
10714
|
+
} catch {}
|
|
10715
|
+
setChoices(NO_CHOICES);
|
|
10716
|
+
setLoaded({
|
|
10717
|
+
name: file.name,
|
|
10718
|
+
files,
|
|
10719
|
+
siteFiles,
|
|
10720
|
+
same,
|
|
10721
|
+
uncopied,
|
|
10722
|
+
plugins
|
|
10723
|
+
});
|
|
10724
|
+
} catch (error) {
|
|
10725
|
+
setProblem({
|
|
10726
|
+
message: problemText(t, error),
|
|
10727
|
+
error
|
|
10728
|
+
});
|
|
10729
|
+
} finally {
|
|
10730
|
+
setReading(false);
|
|
10731
|
+
}
|
|
10732
|
+
};
|
|
10733
|
+
const importNow = async () => {
|
|
10734
|
+
if (!loaded || !plan) return;
|
|
10735
|
+
setPublishing(true);
|
|
10736
|
+
setFailure(null);
|
|
10737
|
+
setProblem(void 0);
|
|
10738
|
+
try {
|
|
10739
|
+
let blockChanges = [];
|
|
10740
|
+
const install = plan.installable.filter((block) => choices.install.has(block.name));
|
|
10741
|
+
if (install.length > 0) try {
|
|
10742
|
+
const record = parseInstalledRecord(await readFile(INSTALLED_RECORD_FILE));
|
|
10743
|
+
blockChanges = (await planBlockChanges({
|
|
10744
|
+
changes: install.map((block) => ({ add: block.source })),
|
|
10745
|
+
registries: { [GOODFELLOW_REGISTRY]: registries[GOODFELLOW_REGISTRY] },
|
|
10746
|
+
fetchJson,
|
|
10747
|
+
readFile,
|
|
10748
|
+
record,
|
|
10749
|
+
blocks: Object.keys(blocks),
|
|
10750
|
+
content
|
|
10751
|
+
})).changes;
|
|
10752
|
+
} catch (error) {
|
|
10753
|
+
setProblem({
|
|
10754
|
+
message: t("siteTransfer.error.blocks"),
|
|
10755
|
+
error
|
|
10756
|
+
});
|
|
10757
|
+
setPublishing(false);
|
|
10758
|
+
return;
|
|
10759
|
+
}
|
|
10760
|
+
const media = plan.media.flatMap((path) => {
|
|
10761
|
+
const bytes = loaded.files.get(path);
|
|
10762
|
+
return bytes ? [{
|
|
10763
|
+
path,
|
|
10764
|
+
bytes
|
|
10765
|
+
}] : [];
|
|
10766
|
+
});
|
|
10767
|
+
for (const file of media) if ("bytes" in file) mediaPreviews.remember(mediaUrl(file.path), file.bytes);
|
|
10768
|
+
const title = plan.summary.title || loaded.name;
|
|
10769
|
+
const result = await publish([
|
|
10770
|
+
...plan.changes,
|
|
10771
|
+
...media,
|
|
10772
|
+
...blockChanges
|
|
10773
|
+
], t("siteTransfer.import.message", { title }));
|
|
10774
|
+
if (result.ok) {
|
|
10775
|
+
setDone(title);
|
|
10776
|
+
setLoaded(void 0);
|
|
10777
|
+
setPlan(void 0);
|
|
10778
|
+
} else setFailure(result);
|
|
10779
|
+
} finally {
|
|
10780
|
+
setPublishing(false);
|
|
10781
|
+
}
|
|
10782
|
+
};
|
|
10783
|
+
const toggle = (key, label) => /* @__PURE__ */ jsxs("label", {
|
|
10784
|
+
className: "gfa-checkbox",
|
|
10785
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10786
|
+
type: "checkbox",
|
|
10787
|
+
checked: choices[key],
|
|
10788
|
+
disabled: publishing,
|
|
10789
|
+
onChange: (event) => setChoices({
|
|
10790
|
+
...choices,
|
|
10791
|
+
[key]: event.target.checked
|
|
10792
|
+
})
|
|
10793
|
+
}), t(label)]
|
|
10794
|
+
});
|
|
10795
|
+
const setTake = (keys) => setChoices({
|
|
10796
|
+
...choices,
|
|
10797
|
+
take: new Set(keys)
|
|
10798
|
+
});
|
|
10799
|
+
const leftOut = plan?.notes.some((note) => note.kind === "left-out" || note.kind === "without-blocks");
|
|
10800
|
+
const nothing = plan !== void 0 && plan.changes.length === 0 && plan.media.length === 0 && choices.install.size === 0;
|
|
10801
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
10802
|
+
"aria-labelledby": "gfa-site-import-title",
|
|
10803
|
+
children: [
|
|
10804
|
+
/* @__PURE__ */ jsx("h2", {
|
|
10805
|
+
id: "gfa-site-import-title",
|
|
10806
|
+
className: "gfa-section-title",
|
|
10807
|
+
children: t("siteTransfer.import.title")
|
|
10808
|
+
}),
|
|
10809
|
+
done && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
|
|
10810
|
+
role: "status",
|
|
10811
|
+
className: "gfa-notice gfa-notice-success",
|
|
10812
|
+
children: t("siteTransfer.import.done", { title: done })
|
|
10813
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
10814
|
+
className: "gfa-import-actions",
|
|
10815
|
+
children: /* @__PURE__ */ jsx(AppLink, {
|
|
10816
|
+
href: "#/pages",
|
|
10817
|
+
className: "gfa-button gfa-button-primary",
|
|
10818
|
+
children: t("import.viewPages")
|
|
10819
|
+
})
|
|
10820
|
+
})] }),
|
|
10821
|
+
!loaded ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
10822
|
+
/* @__PURE__ */ jsx("p", { children: t("siteTransfer.import.intro") }),
|
|
10823
|
+
problem && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
10824
|
+
message: problem.message,
|
|
10825
|
+
error: problem.error
|
|
10826
|
+
}),
|
|
10827
|
+
/* @__PURE__ */ jsxs("label", {
|
|
10828
|
+
className: "gfa-field",
|
|
10829
|
+
children: [
|
|
10830
|
+
/* @__PURE__ */ jsx("span", {
|
|
10831
|
+
className: "gfa-label",
|
|
10832
|
+
children: t("siteTransfer.import.file")
|
|
10833
|
+
}),
|
|
10834
|
+
/* @__PURE__ */ jsx("input", {
|
|
10835
|
+
type: "file",
|
|
10836
|
+
className: "gfa-input",
|
|
10837
|
+
accept: ".zip,application/zip",
|
|
10838
|
+
disabled: reading,
|
|
10839
|
+
onChange: (event) => {
|
|
10840
|
+
const chosen = event.target.files?.[0];
|
|
10841
|
+
event.target.value = "";
|
|
10842
|
+
if (chosen) choose(chosen);
|
|
10843
|
+
}
|
|
10844
|
+
}),
|
|
10845
|
+
/* @__PURE__ */ jsx("span", {
|
|
10846
|
+
className: "gfa-hint",
|
|
10847
|
+
children: t("siteTransfer.import.fileHint")
|
|
10848
|
+
})
|
|
10849
|
+
]
|
|
10850
|
+
}),
|
|
10851
|
+
reading && /* @__PURE__ */ jsx("p", {
|
|
10852
|
+
role: "status",
|
|
10853
|
+
children: t("siteTransfer.import.reading")
|
|
10854
|
+
})
|
|
10855
|
+
] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
10856
|
+
/* @__PURE__ */ jsx("p", { children: t("siteTransfer.import.ready", { title: plan?.summary.title || loaded.name }) }),
|
|
10857
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
10858
|
+
className: "gfa-fieldset",
|
|
10859
|
+
children: [
|
|
10860
|
+
/* @__PURE__ */ jsx("legend", { children: t("siteTransfer.import.mode") }),
|
|
10861
|
+
/* @__PURE__ */ jsxs("label", {
|
|
10862
|
+
className: "gfa-checkbox",
|
|
10863
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10864
|
+
type: "radio",
|
|
10865
|
+
name: "gfa-site-import-mode",
|
|
10866
|
+
checked: !choices.replace,
|
|
10867
|
+
disabled: publishing,
|
|
10868
|
+
onChange: () => setChoices({
|
|
10869
|
+
...choices,
|
|
10870
|
+
replace: false
|
|
10871
|
+
})
|
|
10872
|
+
}), t("siteTransfer.import.mode.add")]
|
|
10873
|
+
}),
|
|
10874
|
+
/* @__PURE__ */ jsxs("label", {
|
|
10875
|
+
className: "gfa-checkbox",
|
|
10876
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10877
|
+
type: "radio",
|
|
10878
|
+
name: "gfa-site-import-mode",
|
|
10879
|
+
checked: choices.replace,
|
|
10880
|
+
disabled: publishing,
|
|
10881
|
+
onChange: () => setChoices({
|
|
10882
|
+
...choices,
|
|
10883
|
+
replace: true
|
|
10884
|
+
})
|
|
10885
|
+
}), t("siteTransfer.import.mode.replace")]
|
|
10886
|
+
})
|
|
10887
|
+
]
|
|
10888
|
+
}),
|
|
10889
|
+
choices.replace ? /* @__PURE__ */ jsx("p", {
|
|
10890
|
+
className: "gfa-notice gfa-notice-warning",
|
|
10891
|
+
children: t("siteTransfer.import.replaceWarning")
|
|
10892
|
+
}) : /* @__PURE__ */ jsxs("fieldset", {
|
|
10893
|
+
className: "gfa-fieldset",
|
|
10894
|
+
children: [
|
|
10895
|
+
/* @__PURE__ */ jsx("legend", { children: t("siteTransfer.import.also") }),
|
|
10896
|
+
toggle("menus", "siteTransfer.import.menus"),
|
|
10897
|
+
toggle("settings", "siteTransfer.import.settings"),
|
|
10898
|
+
toggle("layout", "siteTransfer.import.layout"),
|
|
10899
|
+
toggle("redirects", "siteTransfer.import.redirects")
|
|
10900
|
+
]
|
|
10901
|
+
}),
|
|
10902
|
+
plan && mayInstall && plan.installable.length > 0 && /* @__PURE__ */ jsxs("fieldset", {
|
|
10903
|
+
className: "gfa-fieldset",
|
|
10904
|
+
children: [
|
|
10905
|
+
/* @__PURE__ */ jsx("legend", { children: t("siteTransfer.import.blocks") }),
|
|
10906
|
+
plan.installable.map((block) => /* @__PURE__ */ jsxs("label", {
|
|
10907
|
+
className: "gfa-checkbox",
|
|
10908
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10909
|
+
type: "checkbox",
|
|
10910
|
+
checked: choices.install.has(block.name),
|
|
10911
|
+
disabled: publishing,
|
|
10912
|
+
onChange: (event) => {
|
|
10913
|
+
const install = new Set(choices.install);
|
|
10914
|
+
if (event.target.checked) install.add(block.name);
|
|
10915
|
+
else install.delete(block.name);
|
|
10916
|
+
setChoices({
|
|
10917
|
+
...choices,
|
|
10918
|
+
install
|
|
10919
|
+
});
|
|
10920
|
+
}
|
|
10921
|
+
}), t("siteTransfer.import.install", { title: block.title })]
|
|
10922
|
+
}, block.name)),
|
|
10923
|
+
/* @__PURE__ */ jsx("p", {
|
|
10924
|
+
className: "gfa-hint",
|
|
10925
|
+
children: t("siteTransfer.import.installHint")
|
|
10926
|
+
})
|
|
10927
|
+
]
|
|
10928
|
+
}),
|
|
10929
|
+
leftOut && toggle("withoutMissingBlocks", "siteTransfer.import.withoutBlocks"),
|
|
10930
|
+
plan && plan.conflicts.length > 0 && /* @__PURE__ */ jsxs("fieldset", {
|
|
10931
|
+
className: "gfa-fieldset",
|
|
10932
|
+
children: [
|
|
10933
|
+
/* @__PURE__ */ jsx("legend", { children: t("siteTransfer.import.conflicts") }),
|
|
10934
|
+
/* @__PURE__ */ jsx("p", {
|
|
10935
|
+
className: "gfa-hint",
|
|
10936
|
+
children: t("siteTransfer.import.conflictsHint")
|
|
10937
|
+
}),
|
|
10938
|
+
/* @__PURE__ */ jsxs("p", {
|
|
10939
|
+
className: "gfa-import-actions",
|
|
10940
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
10941
|
+
onClick: () => setTake(plan.conflicts.map((conflict) => conflict.key)),
|
|
10942
|
+
disabled: publishing,
|
|
10943
|
+
children: t("siteTransfer.import.useAllTheirs")
|
|
10944
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
10945
|
+
onClick: () => setTake([]),
|
|
10946
|
+
disabled: publishing,
|
|
10947
|
+
children: t("siteTransfer.import.useAllMine")
|
|
10948
|
+
})]
|
|
10949
|
+
}),
|
|
10950
|
+
plan.conflicts.map((conflict) => /* @__PURE__ */ jsxs("label", {
|
|
10951
|
+
className: "gfa-checkbox",
|
|
10952
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10953
|
+
type: "checkbox",
|
|
10954
|
+
checked: choices.take.has(conflict.key),
|
|
10955
|
+
disabled: publishing,
|
|
10956
|
+
onChange: (event) => {
|
|
10957
|
+
const take = new Set(choices.take);
|
|
10958
|
+
if (event.target.checked) take.add(conflict.key);
|
|
10959
|
+
else take.delete(conflict.key);
|
|
10960
|
+
setTake(take);
|
|
10961
|
+
}
|
|
10962
|
+
}), t("siteTransfer.import.useTheirs", { what: conflictText(t, conflict) })]
|
|
10963
|
+
}, conflict.key))
|
|
10964
|
+
]
|
|
10965
|
+
}),
|
|
10966
|
+
plan && /* @__PURE__ */ jsxs(Fragment$1, { children: [nothing ? /* @__PURE__ */ jsx("p", {
|
|
10967
|
+
className: "gfa-notice",
|
|
10968
|
+
children: t("siteTransfer.summary.nothing")
|
|
10969
|
+
}) : /* @__PURE__ */ jsxs("ul", {
|
|
10970
|
+
className: "gfa-import-summary",
|
|
10971
|
+
children: [
|
|
10972
|
+
/* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.pages", { count: String(plan.summary.pages) }) }),
|
|
10973
|
+
/* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.items", { count: String(plan.summary.items) }) }),
|
|
10974
|
+
plan.summary.collections > 0 && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.collections", { count: String(plan.summary.collections) }) }),
|
|
10975
|
+
plan.summary.syncedBlocks > 0 && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.syncedBlocks", { count: String(plan.summary.syncedBlocks) }) }),
|
|
10976
|
+
/* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.media", { count: String(plan.summary.media) }) }),
|
|
10977
|
+
plan.summary.menus && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.menus") }),
|
|
10978
|
+
plan.summary.settings && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.settings") }),
|
|
10979
|
+
plan.summary.layout && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.layout") }),
|
|
10980
|
+
plan.summary.redirects && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.redirects") }),
|
|
10981
|
+
plan.summary.removed > 0 && /* @__PURE__ */ jsx("li", { children: t("siteTransfer.summary.removed", { count: String(plan.summary.removed) }) })
|
|
10982
|
+
]
|
|
10983
|
+
}), plan.notes.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("h3", {
|
|
10984
|
+
className: "gfa-section-title",
|
|
10985
|
+
children: t("import.check")
|
|
10986
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
10987
|
+
className: "gfa-import-notes",
|
|
10988
|
+
children: plan.notes.map((note) => /* @__PURE__ */ jsx("li", { children: noteText$1(t, note, blockName) }, JSON.stringify(note)))
|
|
10989
|
+
})] })] }),
|
|
10990
|
+
problem && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
10991
|
+
message: problem.message,
|
|
10992
|
+
error: problem.error
|
|
10993
|
+
}),
|
|
10994
|
+
/* @__PURE__ */ jsx(PublishFailure, { failure }),
|
|
10995
|
+
publishing ? /* @__PURE__ */ jsx("p", {
|
|
10996
|
+
role: "status",
|
|
10997
|
+
children: t("import.publishing")
|
|
10998
|
+
}) : /* @__PURE__ */ jsxs("p", {
|
|
10999
|
+
className: "gfa-import-actions",
|
|
11000
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
11001
|
+
variant: "primary",
|
|
11002
|
+
onClick: () => void importNow(),
|
|
11003
|
+
disabled: !plan || nothing,
|
|
11004
|
+
children: t("import.start")
|
|
11005
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
11006
|
+
onClick: () => {
|
|
11007
|
+
setLoaded(void 0);
|
|
11008
|
+
setPlan(void 0);
|
|
11009
|
+
},
|
|
11010
|
+
children: t("siteTransfer.import.another")
|
|
11011
|
+
})]
|
|
11012
|
+
})
|
|
11013
|
+
] })
|
|
11014
|
+
]
|
|
11015
|
+
});
|
|
11016
|
+
}
|
|
11017
|
+
//#endregion
|
|
11018
|
+
//#region src/import-screen.tsx
|
|
11019
|
+
/** How many media files are downloaded at once. */
|
|
11020
|
+
const DOWNLOADS = 4;
|
|
11021
|
+
const CONVERTED = {
|
|
11022
|
+
block: "import.note.block",
|
|
11023
|
+
dropped: "import.note.dropped",
|
|
11024
|
+
shortcode: "import.note.shortcode",
|
|
11025
|
+
embed: "import.note.embed",
|
|
11026
|
+
form: "import.note.form",
|
|
11027
|
+
script: "import.note.script",
|
|
11028
|
+
table: "import.note.table",
|
|
11029
|
+
posts: "import.note.posts",
|
|
11030
|
+
video: "import.note.video"
|
|
11031
|
+
};
|
|
11032
|
+
const LEFT_OUT = {
|
|
11033
|
+
drafts: "import.note.leftOut.drafts",
|
|
11034
|
+
private: "import.note.leftOut.private",
|
|
11035
|
+
trash: "import.note.leftOut.trash",
|
|
11036
|
+
comments: "import.note.leftOut.comments"
|
|
11037
|
+
};
|
|
11038
|
+
/** What the import couldn't bring across as it was, in words. */
|
|
11039
|
+
function noteText(t, note) {
|
|
11040
|
+
switch (note.kind) {
|
|
11041
|
+
case "left-out": return t(LEFT_OUT[note.what] ?? "import.note.leftOut.other", {
|
|
11042
|
+
count: String(note.count),
|
|
11043
|
+
name: note.what
|
|
11044
|
+
});
|
|
11045
|
+
case "converted": {
|
|
11046
|
+
const shown = note.items.slice(0, 3).join(", ");
|
|
11047
|
+
const items = note.items.length > 3 ? t("import.note.more", {
|
|
11048
|
+
items: shown,
|
|
11049
|
+
count: String(note.items.length - 3)
|
|
11050
|
+
}) : shown;
|
|
11051
|
+
return t(CONVERTED[note.note] ?? "import.note.block", {
|
|
11052
|
+
name: note.name,
|
|
10248
11053
|
items
|
|
10249
11054
|
});
|
|
10250
11055
|
}
|
|
@@ -10600,7 +11405,10 @@ function ImportScreen() {
|
|
|
10600
11405
|
children: t("import.reading")
|
|
10601
11406
|
}),
|
|
10602
11407
|
/* @__PURE__ */ jsx(MarkdownImportSection, {}),
|
|
10603
|
-
/* @__PURE__ */ jsx(
|
|
11408
|
+
/* @__PURE__ */ jsx(SiteImportSection, {}),
|
|
11409
|
+
/* @__PURE__ */ jsx(ExportSection, {}),
|
|
11410
|
+
/* @__PURE__ */ jsx(MarkdownExportSection, {}),
|
|
11411
|
+
/* @__PURE__ */ jsx(SiteExportSection, {})
|
|
10604
11412
|
] }) : stage.kind === "done" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
10605
11413
|
/* @__PURE__ */ jsx("p", {
|
|
10606
11414
|
role: "status",
|
|
@@ -11817,73 +12625,437 @@ function PluginDetails({ name, installed, record, canInstall, canInstallEarly =
|
|
|
11817
12625
|
]
|
|
11818
12626
|
});
|
|
11819
12627
|
}
|
|
11820
|
-
/** Packages every site has, which reviews leave out: Goodfellow's own, React and Puck. */
|
|
11821
|
-
const KNOWN_PACKAGES = [
|
|
11822
|
-
"react",
|
|
11823
|
-
"react-dom",
|
|
11824
|
-
"@puckeditor/core",
|
|
11825
|
-
"@goodfellow-cms/core",
|
|
11826
|
-
"@goodfellow-cms/react",
|
|
11827
|
-
"@goodfellow-cms/blocks",
|
|
11828
|
-
"@goodfellow-cms/admin"
|
|
11829
|
-
];
|
|
11830
|
-
const NOTE_KINDS = [
|
|
11831
|
-
"network",
|
|
11832
|
-
"programs",
|
|
11833
|
-
"eval",
|
|
11834
|
-
"native",
|
|
11835
|
-
"install-script"
|
|
11836
|
-
];
|
|
11837
|
-
/** One sentence on what the packages read could do, for the confirmation. */
|
|
11838
|
-
function noteSummary(t, reviews) {
|
|
11839
|
-
const kinds = NOTE_KINDS.filter((kind) => reviews.some((review) => (review.counts[kind] ?? 0) > 0));
|
|
11840
|
-
const unread = reviews.filter((review) => review.unread).length;
|
|
11841
|
-
return [kinds.length > 0 ? t("plugins.code.summary", { list: kinds.map((kind) => t(`plugins.note.${kind}`)).join("; ") }) : t("plugins.code.nothing"), ...unread > 0 ? [t("plugins.code.unreadSummary", { count: String(unread) })] : []].join(" ");
|
|
11842
|
-
}
|
|
11843
|
-
/** What each package's code could do, as the review found it. */
|
|
11844
|
-
function CodeReview({ reviews }) {
|
|
12628
|
+
/** Packages every site has, which reviews leave out: Goodfellow's own, React and Puck. */
|
|
12629
|
+
const KNOWN_PACKAGES = [
|
|
12630
|
+
"react",
|
|
12631
|
+
"react-dom",
|
|
12632
|
+
"@puckeditor/core",
|
|
12633
|
+
"@goodfellow-cms/core",
|
|
12634
|
+
"@goodfellow-cms/react",
|
|
12635
|
+
"@goodfellow-cms/blocks",
|
|
12636
|
+
"@goodfellow-cms/admin"
|
|
12637
|
+
];
|
|
12638
|
+
const NOTE_KINDS = [
|
|
12639
|
+
"network",
|
|
12640
|
+
"programs",
|
|
12641
|
+
"eval",
|
|
12642
|
+
"native",
|
|
12643
|
+
"install-script"
|
|
12644
|
+
];
|
|
12645
|
+
/** One sentence on what the packages read could do, for the confirmation. */
|
|
12646
|
+
function noteSummary(t, reviews) {
|
|
12647
|
+
const kinds = NOTE_KINDS.filter((kind) => reviews.some((review) => (review.counts[kind] ?? 0) > 0));
|
|
12648
|
+
const unread = reviews.filter((review) => review.unread).length;
|
|
12649
|
+
return [kinds.length > 0 ? t("plugins.code.summary", { list: kinds.map((kind) => t(`plugins.note.${kind}`)).join("; ") }) : t("plugins.code.nothing"), ...unread > 0 ? [t("plugins.code.unreadSummary", { count: String(unread) })] : []].join(" ");
|
|
12650
|
+
}
|
|
12651
|
+
/** What each package's code could do, as the review found it. */
|
|
12652
|
+
function CodeReview({ reviews }) {
|
|
12653
|
+
const t = useStrings();
|
|
12654
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
12655
|
+
className: "gfa-plugin-review",
|
|
12656
|
+
children: reviews.map((review) => {
|
|
12657
|
+
const kinds = NOTE_KINDS.filter((kind) => (review.counts[kind] ?? 0) > 0);
|
|
12658
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
12659
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
12660
|
+
/* @__PURE__ */ jsxs("code", { children: [
|
|
12661
|
+
review.name,
|
|
12662
|
+
" ",
|
|
12663
|
+
review.version
|
|
12664
|
+
] }),
|
|
12665
|
+
" ",
|
|
12666
|
+
review.via.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
12667
|
+
className: "gfa-hint",
|
|
12668
|
+
children: t("plugins.code.via", { via: review.via.join(" → ") })
|
|
12669
|
+
})
|
|
12670
|
+
] }),
|
|
12671
|
+
kinds.length === 0 && !review.unread && /* @__PURE__ */ jsx("p", {
|
|
12672
|
+
className: "gfa-hint",
|
|
12673
|
+
children: t("plugins.code.none")
|
|
12674
|
+
}),
|
|
12675
|
+
kinds.map((kind) => /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsxs("summary", { children: [
|
|
12676
|
+
t(`plugins.note.${kind}`),
|
|
12677
|
+
" (",
|
|
12678
|
+
review.counts[kind],
|
|
12679
|
+
")"
|
|
12680
|
+
] }), /* @__PURE__ */ jsx("ul", { children: review.notes.filter((note) => note.kind === kind).map((note) => /* @__PURE__ */ jsxs("li", { children: [
|
|
12681
|
+
/* @__PURE__ */ jsxs("span", {
|
|
12682
|
+
className: "gfa-hint",
|
|
12683
|
+
children: [note.file, note.line ? `:${note.line}` : ""]
|
|
12684
|
+
}),
|
|
12685
|
+
" ",
|
|
12686
|
+
/* @__PURE__ */ jsx("code", { children: note.excerpt })
|
|
12687
|
+
] }, `${note.file}:${note.line ?? 0}:${note.excerpt}`)) })] }, kind)),
|
|
12688
|
+
review.unread && /* @__PURE__ */ jsx("p", {
|
|
12689
|
+
className: "gfa-hint",
|
|
12690
|
+
children: t(`plugins.unread.${review.unread}`)
|
|
12691
|
+
})
|
|
12692
|
+
] }, `${review.name}@${review.version}`);
|
|
12693
|
+
})
|
|
12694
|
+
});
|
|
12695
|
+
}
|
|
12696
|
+
const DEPLOY_TO_CLOUDFLARE = `https://deploy.workers.cloudflare.com/?url=https://github.com/goodfellow-cms/goodfellow-sign-in`;
|
|
12697
|
+
const HOST_NAMES = {
|
|
12698
|
+
github: "GitHub",
|
|
12699
|
+
gitlab: "GitLab",
|
|
12700
|
+
bitbucket: "Bitbucket"
|
|
12701
|
+
};
|
|
12702
|
+
/** The permissions a Bitbucket consumer needs, as Bitbucket's form names them. */
|
|
12703
|
+
const BITBUCKET_PERMISSIONS = "Account: Read, Repositories: Write, Pull requests: Write, Pipelines: Write";
|
|
12704
|
+
function SignInSection({ config: given, contentOnly, hostName, save }) {
|
|
12705
|
+
const t = useStrings();
|
|
12706
|
+
const config = given ?? "";
|
|
12707
|
+
const fromAccount = Object.entries(HOST_NAMES).find(([, name]) => name === hostName)?.[0];
|
|
12708
|
+
const host = (given ? configBackendHost(given) : void 0) ?? (contentOnly ? fromAccount : void 0);
|
|
12709
|
+
if (!host) return null;
|
|
12710
|
+
const key = host === "github" ? "signIn" : "clientId";
|
|
12711
|
+
const current = configBackendOption(config, key);
|
|
12712
|
+
const writable = !contentOnly && withBackendOption(config, key, "placeholder-value") !== void 0;
|
|
12713
|
+
const publish = async (value, message) => {
|
|
12714
|
+
const next = withBackendOption(config, key, value);
|
|
12715
|
+
if (next === void 0) throw new Error("goodfellow.config.tsx's backend line can't be changed here.");
|
|
12716
|
+
await save(next, message);
|
|
12717
|
+
};
|
|
12718
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
12719
|
+
className: "gfa-form",
|
|
12720
|
+
"aria-labelledby": "gfa-sign-in",
|
|
12721
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
12722
|
+
className: "gfa-section-title",
|
|
12723
|
+
id: "gfa-sign-in",
|
|
12724
|
+
children: t(host === "github" ? "signInSetup.service.title" : "signInSetup.app.title", { host: HOST_NAMES[host] })
|
|
12725
|
+
}), host === "github" ? /* @__PURE__ */ jsx(ServiceSettings, {
|
|
12726
|
+
current,
|
|
12727
|
+
writable,
|
|
12728
|
+
contentOnly,
|
|
12729
|
+
publish
|
|
12730
|
+
}) : /* @__PURE__ */ jsx(AppSettings, {
|
|
12731
|
+
host,
|
|
12732
|
+
config,
|
|
12733
|
+
current,
|
|
12734
|
+
writable,
|
|
12735
|
+
contentOnly,
|
|
12736
|
+
publish
|
|
12737
|
+
})]
|
|
12738
|
+
});
|
|
12739
|
+
}
|
|
12740
|
+
function Current({ text, remove }) {
|
|
12741
|
+
const t = useStrings();
|
|
12742
|
+
const [busy, setBusy] = useState(false);
|
|
12743
|
+
const [error, setError] = useState();
|
|
12744
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
12745
|
+
className: "gfa-form",
|
|
12746
|
+
children: [
|
|
12747
|
+
/* @__PURE__ */ jsx("p", {
|
|
12748
|
+
className: "gfa-notice gfa-notice-success",
|
|
12749
|
+
role: "status",
|
|
12750
|
+
children: text
|
|
12751
|
+
}),
|
|
12752
|
+
error !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
12753
|
+
message: t("signInSetup.error"),
|
|
12754
|
+
error
|
|
12755
|
+
}),
|
|
12756
|
+
remove && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
12757
|
+
variant: "ghost",
|
|
12758
|
+
disabled: busy,
|
|
12759
|
+
onClick: async () => {
|
|
12760
|
+
setBusy(true);
|
|
12761
|
+
setError(void 0);
|
|
12762
|
+
try {
|
|
12763
|
+
await remove();
|
|
12764
|
+
} catch (caught) {
|
|
12765
|
+
setError(caught);
|
|
12766
|
+
} finally {
|
|
12767
|
+
setBusy(false);
|
|
12768
|
+
}
|
|
12769
|
+
},
|
|
12770
|
+
children: t("signInSetup.remove")
|
|
12771
|
+
}) })
|
|
12772
|
+
]
|
|
12773
|
+
});
|
|
12774
|
+
}
|
|
12775
|
+
/** What a developer runs where the config is in the code's repository. */
|
|
12776
|
+
function Command({ command }) {
|
|
12777
|
+
const t = useStrings();
|
|
12778
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", { children: t("signInSetup.contentOnly") }), /* @__PURE__ */ jsx("pre", { children: command })] });
|
|
12779
|
+
}
|
|
12780
|
+
function ServiceSettings({ current, writable, contentOnly, publish }) {
|
|
12781
|
+
const t = useStrings();
|
|
12782
|
+
const [input, setInput] = useState("");
|
|
12783
|
+
const [problem, setProblem] = useState();
|
|
12784
|
+
const [busy, setBusy] = useState(false);
|
|
12785
|
+
const [error, setError] = useState();
|
|
12786
|
+
if (current) return /* @__PURE__ */ jsx(Current, {
|
|
12787
|
+
text: t("signInSetup.service.using", { address: current }),
|
|
12788
|
+
remove: writable ? () => publish(void 0, t("signInSetup.service.removeMessage", { address: current })) : void 0
|
|
12789
|
+
});
|
|
12790
|
+
const submit = async () => {
|
|
12791
|
+
const url = input.trim().replace(/\/+$/, "");
|
|
12792
|
+
if (signInServiceProblem(url)) {
|
|
12793
|
+
setProblem("invalid");
|
|
12794
|
+
return;
|
|
12795
|
+
}
|
|
12796
|
+
setBusy(true);
|
|
12797
|
+
setError(void 0);
|
|
12798
|
+
try {
|
|
12799
|
+
const origin = new URL(url).origin;
|
|
12800
|
+
const health = await fetch(`${origin}/health`).then((response) => response.ok ? response.json() : void 0, () => void 0);
|
|
12801
|
+
if (!health) {
|
|
12802
|
+
setProblem("unreachable");
|
|
12803
|
+
return;
|
|
12804
|
+
}
|
|
12805
|
+
if (health.ok !== true) {
|
|
12806
|
+
setProblem("unset");
|
|
12807
|
+
return;
|
|
12808
|
+
}
|
|
12809
|
+
await publish(origin, t("signInSetup.service.message", { address: origin }));
|
|
12810
|
+
} catch (caught) {
|
|
12811
|
+
setError(caught);
|
|
12812
|
+
} finally {
|
|
12813
|
+
setBusy(false);
|
|
12814
|
+
}
|
|
12815
|
+
};
|
|
12816
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
12817
|
+
/* @__PURE__ */ jsx("p", { children: t("signInSetup.service.intro") }),
|
|
12818
|
+
/* @__PURE__ */ jsxs("ol", {
|
|
12819
|
+
className: "gfa-steps",
|
|
12820
|
+
children: [
|
|
12821
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
12822
|
+
t("signInSetup.service.step.deploy"),
|
|
12823
|
+
" ",
|
|
12824
|
+
/* @__PURE__ */ jsx("a", {
|
|
12825
|
+
href: DEPLOY_TO_CLOUDFLARE,
|
|
12826
|
+
target: "_blank",
|
|
12827
|
+
rel: "noopener noreferrer",
|
|
12828
|
+
children: t("signInSetup.service.deploy")
|
|
12829
|
+
})
|
|
12830
|
+
] }),
|
|
12831
|
+
/* @__PURE__ */ jsx("li", { children: t("signInSetup.service.step.setup") }),
|
|
12832
|
+
/* @__PURE__ */ jsx("li", { children: t("signInSetup.service.step.use") })
|
|
12833
|
+
]
|
|
12834
|
+
}),
|
|
12835
|
+
/* @__PURE__ */ jsx("p", {
|
|
12836
|
+
className: "gfa-hint",
|
|
12837
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
12838
|
+
href: docsLink("sign-in-service"),
|
|
12839
|
+
target: "_blank",
|
|
12840
|
+
rel: "noopener noreferrer",
|
|
12841
|
+
children: t("signInSetup.service.more")
|
|
12842
|
+
})
|
|
12843
|
+
}),
|
|
12844
|
+
contentOnly ? /* @__PURE__ */ jsx(Command, { command: "npx goodfellow sign-in service https://sign-in.your-name.workers.dev" }) : !writable ? /* @__PURE__ */ jsx("p", { children: t("signInSetup.handWritten", { key: "signIn" }) }) : /* @__PURE__ */ jsxs("form", {
|
|
12845
|
+
className: "gfa-form",
|
|
12846
|
+
onSubmit: (event) => {
|
|
12847
|
+
event.preventDefault();
|
|
12848
|
+
submit();
|
|
12849
|
+
},
|
|
12850
|
+
children: [
|
|
12851
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
12852
|
+
label: t("signInSetup.service.field"),
|
|
12853
|
+
hint: t("signInSetup.service.fieldHint"),
|
|
12854
|
+
placeholder: "https://sign-in.your-name.workers.dev",
|
|
12855
|
+
value: input,
|
|
12856
|
+
error: problem ? t(`signInSetup.service.problem.${problem}`) : void 0,
|
|
12857
|
+
onChange: (value) => {
|
|
12858
|
+
setInput(value);
|
|
12859
|
+
setProblem(void 0);
|
|
12860
|
+
}
|
|
12861
|
+
}),
|
|
12862
|
+
error !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
12863
|
+
message: t("signInSetup.error"),
|
|
12864
|
+
error
|
|
12865
|
+
}),
|
|
12866
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
12867
|
+
variant: "primary",
|
|
12868
|
+
type: "submit",
|
|
12869
|
+
disabled: busy || !input.trim(),
|
|
12870
|
+
children: t("signInSetup.service.use")
|
|
12871
|
+
}) })
|
|
12872
|
+
]
|
|
12873
|
+
})
|
|
12874
|
+
] });
|
|
12875
|
+
}
|
|
12876
|
+
function CopyValue({ label, value }) {
|
|
12877
|
+
const t = useStrings();
|
|
12878
|
+
const [copied, setCopied] = useState(false);
|
|
12879
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
12880
|
+
className: "gfa-copy-value",
|
|
12881
|
+
children: [
|
|
12882
|
+
/* @__PURE__ */ jsx("span", {
|
|
12883
|
+
className: "gfa-copy-label",
|
|
12884
|
+
children: label
|
|
12885
|
+
}),
|
|
12886
|
+
/* @__PURE__ */ jsx("code", { children: value }),
|
|
12887
|
+
/* @__PURE__ */ jsx(Button, {
|
|
12888
|
+
variant: "ghost",
|
|
12889
|
+
onClick: () => {
|
|
12890
|
+
navigator.clipboard?.writeText(value).then(() => setCopied(true), () => {});
|
|
12891
|
+
},
|
|
12892
|
+
children: copied ? t("signInSetup.copied") : t("signInSetup.copy")
|
|
12893
|
+
})
|
|
12894
|
+
]
|
|
12895
|
+
});
|
|
12896
|
+
}
|
|
12897
|
+
function AppSettings({ host, config, current, writable, contentOnly, publish }) {
|
|
11845
12898
|
const t = useStrings();
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
12899
|
+
const { content } = useSiteContent();
|
|
12900
|
+
const [input, setInput] = useState("");
|
|
12901
|
+
const [problem, setProblem] = useState(false);
|
|
12902
|
+
const [busy, setBusy] = useState(false);
|
|
12903
|
+
const [error, setError] = useState();
|
|
12904
|
+
const name = HOST_NAMES[host];
|
|
12905
|
+
const admin = configAdmin(config);
|
|
12906
|
+
const addresses = signInRedirectAddresses(content.settings.url, {
|
|
12907
|
+
admin: admin?.url,
|
|
12908
|
+
broker: configBroker(config)?.url
|
|
12909
|
+
});
|
|
12910
|
+
if (current) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Current, {
|
|
12911
|
+
text: t("signInSetup.app.on", { host: name }),
|
|
12912
|
+
remove: writable ? () => publish(void 0, t("signInSetup.app.removeMessage", { host: name })) : void 0
|
|
12913
|
+
}), addresses.length > 0 && /* @__PURE__ */ jsx("p", {
|
|
12914
|
+
className: "gfa-hint",
|
|
12915
|
+
children: t(host === "gitlab" ? "signInSetup.app.gitlab.keep" : "signInSetup.app.bitbucket.keep", { addresses: addresses.join(", ") })
|
|
12916
|
+
})] });
|
|
12917
|
+
const submit = async () => {
|
|
12918
|
+
const id = input.trim();
|
|
12919
|
+
if (signInClientIdProblem(id)) {
|
|
12920
|
+
setProblem(true);
|
|
12921
|
+
return;
|
|
12922
|
+
}
|
|
12923
|
+
setBusy(true);
|
|
12924
|
+
setError(void 0);
|
|
12925
|
+
try {
|
|
12926
|
+
await publish(id, t("signInSetup.app.message", { host: name }));
|
|
12927
|
+
} catch (caught) {
|
|
12928
|
+
setError(caught);
|
|
12929
|
+
} finally {
|
|
12930
|
+
setBusy(false);
|
|
12931
|
+
}
|
|
12932
|
+
};
|
|
12933
|
+
const title = `Goodfellow ${content.settings.title ?? ""}`.trim().slice(0, 100);
|
|
12934
|
+
let steps;
|
|
12935
|
+
if (host === "gitlab") {
|
|
12936
|
+
const web = (configBackendOption(config, "url") ?? "https://gitlab.com").replace(/\/$/, "");
|
|
12937
|
+
const project = configBackendOption(config, "project") ?? "";
|
|
12938
|
+
const group = project.includes("/") ? project.slice(0, project.lastIndexOf("/")) : void 0;
|
|
12939
|
+
steps = /* @__PURE__ */ jsxs("ol", {
|
|
12940
|
+
className: "gfa-steps",
|
|
12941
|
+
children: [
|
|
12942
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
12943
|
+
t("signInSetup.app.gitlab.open"),
|
|
12944
|
+
" ",
|
|
12945
|
+
group && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
12946
|
+
/* @__PURE__ */ jsx("a", {
|
|
12947
|
+
href: `${web}/groups/${group}/-/settings/applications`,
|
|
12948
|
+
target: "_blank",
|
|
12949
|
+
rel: "noopener noreferrer",
|
|
12950
|
+
children: t("signInSetup.app.gitlab.group", { group })
|
|
12951
|
+
}),
|
|
11854
12952
|
" ",
|
|
11855
|
-
|
|
12953
|
+
t("signInSetup.or"),
|
|
12954
|
+
" "
|
|
11856
12955
|
] }),
|
|
12956
|
+
/* @__PURE__ */ jsx("a", {
|
|
12957
|
+
href: `${web}/-/user_settings/applications`,
|
|
12958
|
+
target: "_blank",
|
|
12959
|
+
rel: "noopener noreferrer",
|
|
12960
|
+
children: t("signInSetup.app.gitlab.own")
|
|
12961
|
+
})
|
|
12962
|
+
] }),
|
|
12963
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
12964
|
+
t("signInSetup.app.gitlab.fill"),
|
|
12965
|
+
/* @__PURE__ */ jsx(CopyValue, {
|
|
12966
|
+
label: t("signInSetup.app.name"),
|
|
12967
|
+
value: title
|
|
12968
|
+
}),
|
|
12969
|
+
/* @__PURE__ */ jsx(CopyValue, {
|
|
12970
|
+
label: t("signInSetup.app.gitlab.redirect"),
|
|
12971
|
+
value: addresses.join("\n")
|
|
12972
|
+
}),
|
|
12973
|
+
/* @__PURE__ */ jsx("p", {
|
|
12974
|
+
className: "gfa-hint",
|
|
12975
|
+
children: t("signInSetup.app.gitlab.settings")
|
|
12976
|
+
})
|
|
12977
|
+
] }),
|
|
12978
|
+
/* @__PURE__ */ jsx("li", { children: t("signInSetup.app.gitlab.copyId") })
|
|
12979
|
+
]
|
|
12980
|
+
});
|
|
12981
|
+
} else {
|
|
12982
|
+
const workspace = (configBackendOption(config, "repo") ?? "").split("/")[0] ?? "";
|
|
12983
|
+
const callback = admin?.checked ? addresses.find((address) => address.startsWith(new URL(admin.url).origin)) : addresses[0];
|
|
12984
|
+
steps = /* @__PURE__ */ jsxs("ol", {
|
|
12985
|
+
className: "gfa-steps",
|
|
12986
|
+
children: [
|
|
12987
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
12988
|
+
t("signInSetup.app.bitbucket.open"),
|
|
11857
12989
|
" ",
|
|
11858
|
-
|
|
12990
|
+
/* @__PURE__ */ jsx("a", {
|
|
12991
|
+
href: `https://bitbucket.org/${workspace}/workspace/settings/api`,
|
|
12992
|
+
target: "_blank",
|
|
12993
|
+
rel: "noopener noreferrer",
|
|
12994
|
+
children: t("signInSetup.app.bitbucket.consumers", { workspace })
|
|
12995
|
+
})
|
|
12996
|
+
] }),
|
|
12997
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
12998
|
+
t("signInSetup.app.bitbucket.fill"),
|
|
12999
|
+
/* @__PURE__ */ jsx(CopyValue, {
|
|
13000
|
+
label: t("signInSetup.app.name"),
|
|
13001
|
+
value: title
|
|
13002
|
+
}),
|
|
13003
|
+
callback && /* @__PURE__ */ jsx(CopyValue, {
|
|
13004
|
+
label: t("signInSetup.app.bitbucket.callback"),
|
|
13005
|
+
value: callback
|
|
13006
|
+
}),
|
|
13007
|
+
/* @__PURE__ */ jsx(CopyValue, {
|
|
13008
|
+
label: t("signInSetup.app.bitbucket.permissions"),
|
|
13009
|
+
value: BITBUCKET_PERMISSIONS
|
|
13010
|
+
}),
|
|
13011
|
+
/* @__PURE__ */ jsx("p", {
|
|
11859
13012
|
className: "gfa-hint",
|
|
11860
|
-
children: t("
|
|
13013
|
+
children: t("signInSetup.app.bitbucket.settings")
|
|
11861
13014
|
})
|
|
11862
13015
|
] }),
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
13016
|
+
/* @__PURE__ */ jsx("li", { children: t("signInSetup.app.bitbucket.copyId") })
|
|
13017
|
+
]
|
|
13018
|
+
});
|
|
13019
|
+
}
|
|
13020
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
13021
|
+
/* @__PURE__ */ jsx("p", { children: t("signInSetup.app.intro", { host: name }) }),
|
|
13022
|
+
addresses.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
13023
|
+
className: "gfa-notice",
|
|
13024
|
+
children: t("signInSetup.app.noAddress")
|
|
13025
|
+
}) : steps,
|
|
13026
|
+
contentOnly ? /* @__PURE__ */ jsx(Command, { command: "npx goodfellow sign-in client-id <the id>" }) : !writable ? /* @__PURE__ */ jsx("p", { children: t("signInSetup.handWritten", { key: "clientId" }) }) : /* @__PURE__ */ jsxs("form", {
|
|
13027
|
+
className: "gfa-form",
|
|
13028
|
+
onSubmit: (event) => {
|
|
13029
|
+
event.preventDefault();
|
|
13030
|
+
submit();
|
|
13031
|
+
},
|
|
13032
|
+
children: [
|
|
13033
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
13034
|
+
label: t(host === "gitlab" ? "signInSetup.app.gitlab.field" : "signInSetup.app.bitbucket.field"),
|
|
13035
|
+
value: input,
|
|
13036
|
+
error: problem ? t("signInSetup.app.problem", { host: name }) : void 0,
|
|
13037
|
+
onChange: (value) => {
|
|
13038
|
+
setInput(value);
|
|
13039
|
+
setProblem(false);
|
|
13040
|
+
}
|
|
11866
13041
|
}),
|
|
11867
|
-
|
|
11868
|
-
t(
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
/* @__PURE__ */ jsx("code", { children: note.excerpt })
|
|
11879
|
-
] }, `${note.file}:${note.line ?? 0}:${note.excerpt}`)) })] }, kind)),
|
|
11880
|
-
review.unread && /* @__PURE__ */ jsx("p", {
|
|
13042
|
+
error !== void 0 && /* @__PURE__ */ jsx(ErrorMessage, {
|
|
13043
|
+
message: t("signInSetup.error"),
|
|
13044
|
+
error
|
|
13045
|
+
}),
|
|
13046
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
|
|
13047
|
+
variant: "primary",
|
|
13048
|
+
type: "submit",
|
|
13049
|
+
disabled: busy || !input.trim(),
|
|
13050
|
+
children: t("signInSetup.app.turnOn", { host: name })
|
|
13051
|
+
}) }),
|
|
13052
|
+
/* @__PURE__ */ jsx("p", {
|
|
11881
13053
|
className: "gfa-hint",
|
|
11882
|
-
children: t(
|
|
13054
|
+
children: t("signInSetup.app.after", { host: name })
|
|
11883
13055
|
})
|
|
11884
|
-
]
|
|
13056
|
+
]
|
|
11885
13057
|
})
|
|
11886
|
-
});
|
|
13058
|
+
] });
|
|
11887
13059
|
}
|
|
11888
13060
|
//#endregion
|
|
11889
13061
|
//#region src/security-screen.tsx
|
|
@@ -11891,7 +13063,8 @@ function CodeReview({ reviews }) {
|
|
|
11891
13063
|
* Site Settings' Security tab: the site's token broker, a sign-in page on an
|
|
11892
13064
|
* address of its own that keeps editors' sign-in and AI keys and shows the
|
|
11893
13065
|
* admin panel in a frame. Owners publish its address in the config's `broker`
|
|
11894
|
-
* line (`withBrokerConfig()`), or remove it.
|
|
13066
|
+
* line (`withBrokerConfig()`), or remove it. Below it, how editors sign in:
|
|
13067
|
+
* the sign-in service or OAuth application (`SignInSection`).
|
|
11895
13068
|
*/
|
|
11896
13069
|
/**
|
|
11897
13070
|
* Whether editors sign in where the site's own pages could reach their
|
|
@@ -11931,6 +13104,14 @@ function SecurityScreen() {
|
|
|
11931
13104
|
if (!result.ok) throw result.error;
|
|
11932
13105
|
setConfig(next);
|
|
11933
13106
|
}, [config, publish]);
|
|
13107
|
+
const saveConfig = useCallback(async (next, message) => {
|
|
13108
|
+
const result = await publish([{
|
|
13109
|
+
path: CONFIG_FILE$1,
|
|
13110
|
+
content: next
|
|
13111
|
+
}], message, { checks: false });
|
|
13112
|
+
if (!result.ok) throw result.error;
|
|
13113
|
+
setConfig(next);
|
|
13114
|
+
}, [publish]);
|
|
11934
13115
|
let body;
|
|
11935
13116
|
if (demo) body = /* @__PURE__ */ jsx("p", { children: t("security.demo") });
|
|
11936
13117
|
else if (!versions) body = /* @__PURE__ */ jsx("p", { children: t("security.local") });
|
|
@@ -11981,27 +13162,36 @@ function SecurityScreen() {
|
|
|
11981
13162
|
}
|
|
11982
13163
|
return /* @__PURE__ */ jsxs("div", {
|
|
11983
13164
|
className: "gfa-screen",
|
|
11984
|
-
children: [
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
13165
|
+
children: [
|
|
13166
|
+
/* @__PURE__ */ jsx("div", {
|
|
13167
|
+
className: "gfa-screen-header",
|
|
13168
|
+
children: /* @__PURE__ */ jsx("h1", { children: t("settings.tab.security") })
|
|
13169
|
+
}),
|
|
13170
|
+
/* @__PURE__ */ jsxs("section", {
|
|
13171
|
+
className: "gfa-form",
|
|
13172
|
+
"aria-labelledby": "gfa-broker",
|
|
13173
|
+
children: [
|
|
13174
|
+
/* @__PURE__ */ jsx("h2", {
|
|
13175
|
+
className: "gfa-section-title",
|
|
13176
|
+
id: "gfa-broker",
|
|
13177
|
+
children: t("security.broker.title")
|
|
13178
|
+
}),
|
|
13179
|
+
/* @__PURE__ */ jsx("p", { children: t("security.broker.intro") }),
|
|
13180
|
+
exposed === "site" && /* @__PURE__ */ jsx("p", {
|
|
13181
|
+
className: "gfa-notice",
|
|
13182
|
+
role: "note",
|
|
13183
|
+
children: t("security.exposed")
|
|
13184
|
+
}),
|
|
13185
|
+
body
|
|
13186
|
+
]
|
|
13187
|
+
}),
|
|
13188
|
+
!demo && versions && config !== void 0 && account?.role === void 0 && /* @__PURE__ */ jsx(SignInSection, {
|
|
13189
|
+
config,
|
|
13190
|
+
contentOnly,
|
|
13191
|
+
hostName: account?.hostName ?? "",
|
|
13192
|
+
save: saveConfig
|
|
13193
|
+
})
|
|
13194
|
+
]
|
|
12005
13195
|
});
|
|
12006
13196
|
}
|
|
12007
13197
|
function BrokerForm({ initial, check, host, listed, save }) {
|
|
@@ -14955,91 +16145,6 @@ function SettingsScreen({ tab, section }) {
|
|
|
14955
16145
|
});
|
|
14956
16146
|
}
|
|
14957
16147
|
//#endregion
|
|
14958
|
-
//#region src/site-download.ts
|
|
14959
|
-
/**
|
|
14960
|
-
* A copy of the site to keep: its content, its media library and the records
|
|
14961
|
-
* of the blocks and plugins it installed, as one zip file, read at the
|
|
14962
|
-
* revision the admin panel loaded. fflate is loaded only when a copy is made.
|
|
14963
|
-
* No React or DOM, so it's tested in Node.
|
|
14964
|
-
*/
|
|
14965
|
-
/** The folders a copy holds whole. */
|
|
14966
|
-
const DOWNLOAD_FOLDERS = [CONTENT_DIR, MEDIA_DIR];
|
|
14967
|
-
/** Single files a copy holds where the site has them: which blocks and plugins it installed, to add them again. */
|
|
14968
|
-
const DOWNLOAD_RECORDS = [INSTALLED_RECORD_FILE, INSTALLED_PLUGINS_FILE];
|
|
14969
|
-
/** How many files are read at once. */
|
|
14970
|
-
const READ_AT_ONCE = 6;
|
|
14971
|
-
/** Files that are already compressed are stored as they are, which is as small and much quicker. */
|
|
14972
|
-
const COMPRESSED = /\.(jpe?g|png|webp|gif|avif|mp4|webm|mov|m4v|mp3|m4a|ogg|oga|wav|pdf|woff2?|zip|gz)$/i;
|
|
14973
|
-
/** Every file a copy holds, sorted, from the folders and records the site has. */
|
|
14974
|
-
async function downloadPaths(list) {
|
|
14975
|
-
const folders = await Promise.all(DOWNLOAD_FOLDERS.map((dir) => list(dir)));
|
|
14976
|
-
const records = await Promise.all(DOWNLOAD_RECORDS.map(async (path) => {
|
|
14977
|
-
return (await list(path.slice(0, path.lastIndexOf("/")))).includes(path) ? [path] : [];
|
|
14978
|
-
}));
|
|
14979
|
-
return [.../* @__PURE__ */ new Set([...folders.flat(), ...records.flat()])].sort();
|
|
14980
|
-
}
|
|
14981
|
-
/**
|
|
14982
|
-
* The copy's file name: the site's title in plain letters, then the day,
|
|
14983
|
-
* `st-anne-parish-2026-11-01.zip`, or `site-…` for a title with none.
|
|
14984
|
-
*/
|
|
14985
|
-
function downloadName(title, date) {
|
|
14986
|
-
return `${(title ?? "").normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/, "") || "site"}-${[
|
|
14987
|
-
date.getFullYear(),
|
|
14988
|
-
date.getMonth() + 1,
|
|
14989
|
-
date.getDate()
|
|
14990
|
-
].map((part) => String(part).padStart(2, "0")).join("-")}.zip`;
|
|
14991
|
-
}
|
|
14992
|
-
/**
|
|
14993
|
-
* Zips the files, read with `read`, in the order given, a few at a time, and
|
|
14994
|
-
* returns the zip in pieces, for a `Blob`. A file that's gone since it was
|
|
14995
|
-
* listed is left out. Throws what reading throws.
|
|
14996
|
-
*/
|
|
14997
|
-
async function siteArchive(paths, read, onProgress) {
|
|
14998
|
-
const { Zip, ZipDeflate, ZipPassThrough } = await import("fflate");
|
|
14999
|
-
const chunks = [];
|
|
15000
|
-
let finish = () => {};
|
|
15001
|
-
let fail = () => {};
|
|
15002
|
-
const finished = new Promise((resolve, reject) => {
|
|
15003
|
-
finish = resolve;
|
|
15004
|
-
fail = reject;
|
|
15005
|
-
});
|
|
15006
|
-
const zip = new Zip((error, chunk, final) => {
|
|
15007
|
-
if (error) return fail(error);
|
|
15008
|
-
chunks.push(chunk);
|
|
15009
|
-
if (final) finish();
|
|
15010
|
-
});
|
|
15011
|
-
let done = 0;
|
|
15012
|
-
onProgress?.({
|
|
15013
|
-
done,
|
|
15014
|
-
total: paths.length
|
|
15015
|
-
});
|
|
15016
|
-
try {
|
|
15017
|
-
for (let start = 0; start < paths.length; start += READ_AT_ONCE) {
|
|
15018
|
-
const batch = paths.slice(start, start + READ_AT_ONCE);
|
|
15019
|
-
const files = await Promise.all(batch.map((path) => read(path)));
|
|
15020
|
-
batch.forEach((path, index) => {
|
|
15021
|
-
const bytes = files[index];
|
|
15022
|
-
if (bytes) {
|
|
15023
|
-
const entry = COMPRESSED.test(path) ? new ZipPassThrough(path) : new ZipDeflate(path, { level: 6 });
|
|
15024
|
-
zip.add(entry);
|
|
15025
|
-
entry.push(bytes, true);
|
|
15026
|
-
}
|
|
15027
|
-
done += 1;
|
|
15028
|
-
});
|
|
15029
|
-
onProgress?.({
|
|
15030
|
-
done,
|
|
15031
|
-
total: paths.length
|
|
15032
|
-
});
|
|
15033
|
-
}
|
|
15034
|
-
} catch (error) {
|
|
15035
|
-
zip.terminate();
|
|
15036
|
-
throw error;
|
|
15037
|
-
}
|
|
15038
|
-
zip.end();
|
|
15039
|
-
await finished;
|
|
15040
|
-
return chunks;
|
|
15041
|
-
}
|
|
15042
|
-
//#endregion
|
|
15043
16148
|
//#region src/site-history-screen.tsx
|
|
15044
16149
|
const PER_PAGE = 20;
|
|
15045
16150
|
/**
|
|
@@ -15084,7 +16189,7 @@ function DownloadCopy() {
|
|
|
15084
16189
|
total: 0
|
|
15085
16190
|
});
|
|
15086
16191
|
try {
|
|
15087
|
-
const chunks = await
|
|
16192
|
+
const chunks = await siteCopyArchive(listFiles, readBytes, content, setProgress);
|
|
15088
16193
|
const url = URL.createObjectURL(new Blob(chunks, { type: "application/zip" }));
|
|
15089
16194
|
const link = document.createElement("a");
|
|
15090
16195
|
link.href = url;
|