@goodfellow-cms/next 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/goodfellow-next.js +208 -15
- package/dist/admin.d.ts +19 -9
- package/dist/admin.js +88 -18
- package/dist/components.d.ts +21 -0
- package/dist/components.js +27 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +80 -23
- package/dist/dev-editor-DiPKrIAS.js +124 -0
- package/dist/editor.d.ts +14 -0
- package/dist/editor.js +32 -0
- package/dist/export.d.ts +90 -3
- package/dist/export.js +195 -12
- package/dist/index.d.ts +12 -4
- package/dist/index.js +86 -41
- package/dist/site-files-BeLnLxCl.js +50 -0
- package/package.json +18 -12
package/bin/goodfellow-next.js
CHANGED
|
@@ -1,43 +1,122 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// After `next build`: `goodfellow-next finish out` fixes the export's prefetch files on Windows,
|
|
3
|
-
// writes the calendar files of collections of events
|
|
3
|
+
// writes the calendar files of collections of events, collections' feeds, the pages at redirects' old addresses and Cloudflare Pages' _redirects, and the search index for sites with a search block,
|
|
4
|
+
// copies the site's Google Fonts into the export, starts every page with the admin address's guard for a site with one, and reports what automated checks find on the exported pages. `index` only writes the index.
|
|
4
5
|
// `goodfellow-next update` updates Goodfellow, as `goodfellow update` does for other sites.
|
|
6
|
+
// `goodfellow-next due [--since <time>]` says whether a scheduled change has come due, as `goodfellow due` does.
|
|
7
|
+
// `goodfellow-next nightly [--mode <mode>]` says whether tonight's build is needed, as `goodfellow nightly` does.
|
|
5
8
|
// `goodfellow-next content link|copy [folder]` puts a content repository's files in place, for a site whose
|
|
6
9
|
// content is kept in a repository of its own.
|
|
10
|
+
// `goodfellow-next import wordpress <file or address> [--dry-run]` imports a WordPress site, as `goodfellow import` does.
|
|
11
|
+
// `goodfellow-next export wordpress [file]` writes the site as a WordPress export file, as `goodfellow export` does.
|
|
12
|
+
// `goodfellow-next admin add <address>|check|remove` sets the admin address, and `goodfellow-next broker use <address>|remove`
|
|
13
|
+
// the token broker, in goodfellow.config.tsx, as `goodfellow admin` and `goodfellow broker` do.
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
15
|
import { rm } from "node:fs/promises";
|
|
8
16
|
import { relative } from "node:path";
|
|
9
17
|
import { parseArgs } from "node:util";
|
|
10
|
-
import { formatUpdateResult } from "@goodfellow-cms/core";
|
|
11
|
-
import {
|
|
18
|
+
import { buildNotes, CLOUDFLARE_REDIRECTS_LIMIT, configAdmin, formatUpdateResult } from "@goodfellow-cms/core";
|
|
19
|
+
import { reportSiteProblems } from "@goodfellow-cms/core/accessibility";
|
|
20
|
+
import {
|
|
21
|
+
AdminSetupError,
|
|
22
|
+
BrokerSetupError,
|
|
23
|
+
ContentRepositoryError,
|
|
24
|
+
checkSiteAdminAddress,
|
|
25
|
+
contentRepositoryProblem,
|
|
26
|
+
exportWordPress,
|
|
27
|
+
formatAdminResult,
|
|
28
|
+
formatWordPressExport,
|
|
29
|
+
formatWordPressImport,
|
|
30
|
+
importWordPress,
|
|
31
|
+
nightlyCheck,
|
|
32
|
+
reportDue,
|
|
33
|
+
reportNightly,
|
|
34
|
+
scheduleDue,
|
|
35
|
+
setAdminAddress,
|
|
36
|
+
setBrokerAddress,
|
|
37
|
+
setUpContentRepository,
|
|
38
|
+
update,
|
|
39
|
+
} from "@goodfellow-cms/core/node";
|
|
12
40
|
import { writePagesSearchIndex, writeSearchIndex } from "@goodfellow-cms/core/search-index";
|
|
13
|
-
import {
|
|
41
|
+
import { WordPressReadError } from "@goodfellow-cms/core/wordpress";
|
|
42
|
+
import {
|
|
43
|
+
buildFileFolders,
|
|
44
|
+
builtPages,
|
|
45
|
+
checkPages,
|
|
46
|
+
exportedPages,
|
|
47
|
+
fixSegmentFiles,
|
|
48
|
+
guardExport,
|
|
49
|
+
NEXT_SITE_FILES,
|
|
50
|
+
writeBuildRecordFile,
|
|
51
|
+
writeCalendarFiles,
|
|
52
|
+
writeEditorPolicy,
|
|
53
|
+
writeFeedFiles,
|
|
54
|
+
writeFontFiles,
|
|
55
|
+
writePageLanguages,
|
|
56
|
+
writeRedirectFiles,
|
|
57
|
+
} from "@goodfellow-cms/next/export";
|
|
14
58
|
|
|
15
59
|
const [command, dir = "out"] = process.argv.slice(2);
|
|
60
|
+
const CONFIG_FILES = ["goodfellow.config.tsx", "goodfellow.config.ts", "goodfellow.config.jsx", "goodfellow.config.js"];
|
|
16
61
|
// A full-stack site (withGoodfellow(config, { server: true })): Next.js serves its pages and sends redirects
|
|
17
|
-
// itself, so the calendar files and the search index are written into public/, which `next start` and hosts
|
|
62
|
+
// itself, so the calendar files, feeds and the search index are written into public/, which `next start` and hosts
|
|
18
63
|
// serve as static files, as they are for an export.
|
|
19
64
|
if (command === "finish" && dir === "--server") {
|
|
20
|
-
|
|
65
|
+
// Next.js renders a full-stack site's pages as it serves them, so nothing can put a guard first in each.
|
|
66
|
+
const config = CONFIG_FILES.find((file) => existsSync(file));
|
|
67
|
+
if (config && configAdmin(readFileSync(config, "utf8"))) {
|
|
68
|
+
console.error(
|
|
69
|
+
"A full-stack site can't have an admin address: its admin panel signs in with the site's own sign-in.",
|
|
70
|
+
);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
for (const folder of await buildFileFolders()) await rm(`public/${folder}`, { recursive: true, force: true });
|
|
74
|
+
// The editor's frames' page, which next start serves as it was rendered.
|
|
75
|
+
await writeEditorPolicy(".next/server/app");
|
|
21
76
|
const calendars = await writeCalendarFiles("public");
|
|
22
77
|
if (calendars > 0) console.log(`Wrote ${calendars} calendar files, in public/calendars`);
|
|
23
|
-
const
|
|
78
|
+
const feeds = await writeFeedFiles("public");
|
|
79
|
+
if (feeds > 0) console.log(`Wrote ${feeds} feeds, in public/feeds`);
|
|
80
|
+
// Before the pages are read, so the search index and the checks see each page's language.
|
|
81
|
+
const languages = await writePageLanguages(undefined);
|
|
82
|
+
if (languages > 0) console.log(`Marked ${languages} pages with their language`);
|
|
83
|
+
const pages = await builtPages();
|
|
84
|
+
const indexed = await writePagesSearchIndex(pages, "public/pagefind");
|
|
24
85
|
console.log(
|
|
25
86
|
indexed === undefined
|
|
26
87
|
? "No page has a search block, so there's nothing to index"
|
|
27
88
|
: `Indexed ${indexed} pages for search, in public/pagefind`,
|
|
28
89
|
);
|
|
90
|
+
for (const line of reportSiteProblems(await checkPages(pages))) console.log(line);
|
|
29
91
|
process.exit(0);
|
|
30
92
|
}
|
|
31
93
|
if (command === "update") {
|
|
32
94
|
const { values } = parseArgs({
|
|
33
95
|
args: process.argv.slice(3),
|
|
34
|
-
options: { to: { type: "string" }, publish: { type: "boolean" } },
|
|
96
|
+
options: { to: { type: "string" }, publish: { type: "boolean" }, commit: { type: "boolean" } },
|
|
35
97
|
});
|
|
36
|
-
|
|
98
|
+
// The starter's admin pages, kept up to date where nobody has changed them, on sites with an admin panel.
|
|
99
|
+
const siteFiles = existsSync("app/admin/page.tsx") ? NEXT_SITE_FILES : [];
|
|
100
|
+
const result = await update({ to: values.to, publish: values.publish, commit: values.commit, siteFiles });
|
|
37
101
|
// For the admin panel, which reads it in the host's log.
|
|
38
102
|
console.log(formatUpdateResult(result));
|
|
39
103
|
process.exit(result.state === "failed" ? 1 : 0);
|
|
40
104
|
}
|
|
105
|
+
if (command === "due") {
|
|
106
|
+
const { values } = parseArgs({ args: process.argv.slice(3), options: { since: { type: "string" } } });
|
|
107
|
+
for (const line of await reportDue(await scheduleDue({ since: values.since }))) console.log(line);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
if (command === "nightly") {
|
|
111
|
+
const { values } = parseArgs({
|
|
112
|
+
args: process.argv.slice(3),
|
|
113
|
+
options: { mode: { type: "string" }, output: { type: "string" } },
|
|
114
|
+
});
|
|
115
|
+
const siteFiles = existsSync("app/admin/page.tsx") ? NEXT_SITE_FILES : [];
|
|
116
|
+
const result = await nightlyCheck({ siteFiles, ...(values.mode && { mode: values.mode }) });
|
|
117
|
+
for (const line of await reportNightly(result, values.output ?? process.env.GITHUB_OUTPUT)) console.log(line);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
41
120
|
if (command === "content") {
|
|
42
121
|
const [action, from] = process.argv.slice(3);
|
|
43
122
|
if (action === "link" || action === "copy") {
|
|
@@ -57,37 +136,146 @@ if (command === "content") {
|
|
|
57
136
|
}
|
|
58
137
|
}
|
|
59
138
|
}
|
|
139
|
+
if (command === "admin" || command === "broker") {
|
|
140
|
+
const [action, argument] = process.argv.slice(3);
|
|
141
|
+
try {
|
|
142
|
+
if (command === "admin" && ((action === "add" && argument) || action === "check" || action === "remove")) {
|
|
143
|
+
const result =
|
|
144
|
+
action === "check"
|
|
145
|
+
? await checkSiteAdminAddress(".")
|
|
146
|
+
: await setAdminAddress(".", action === "add" ? argument : undefined);
|
|
147
|
+
const { lines, ok } = formatAdminResult(action, result, "goodfellow-next");
|
|
148
|
+
for (const line of lines) (ok ? console.log : console.error)(line);
|
|
149
|
+
process.exit(ok ? 0 : 1);
|
|
150
|
+
}
|
|
151
|
+
if (command === "broker" && ((action === "use" && argument) || action === "remove")) {
|
|
152
|
+
const address = await setBrokerAddress(".", action === "use" ? argument : undefined);
|
|
153
|
+
console.log(
|
|
154
|
+
address
|
|
155
|
+
? `Editors sign in at ${address} once the site has rebuilt with goodfellow.config.tsx.`
|
|
156
|
+
: "Editors sign in in the admin panel itself once the site has rebuilt with goodfellow.config.tsx.",
|
|
157
|
+
);
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (!(error instanceof AdminSetupError || error instanceof BrokerSetupError)) throw error;
|
|
162
|
+
console.error(error.message);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (command === "import") {
|
|
167
|
+
const { positionals, values } = parseArgs({
|
|
168
|
+
args: process.argv.slice(3),
|
|
169
|
+
allowPositionals: true,
|
|
170
|
+
options: { "dry-run": { type: "boolean" } },
|
|
171
|
+
});
|
|
172
|
+
const [kind, from] = positionals;
|
|
173
|
+
const problem = contentRepositoryProblem(".");
|
|
174
|
+
if (kind === "wordpress" && from && !problem) {
|
|
175
|
+
try {
|
|
176
|
+
const result = await importWordPress(".", from, { dryRun: values["dry-run"], log: (line) => console.log(line) });
|
|
177
|
+
console.log(formatWordPressImport(result));
|
|
178
|
+
process.exit(0);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (!(error instanceof WordPressReadError)) throw error;
|
|
181
|
+
console.error(error.message);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (problem) {
|
|
186
|
+
console.error(problem);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (command === "export") {
|
|
191
|
+
const [kind, file] = process.argv.slice(3);
|
|
192
|
+
const problem = contentRepositoryProblem(".");
|
|
193
|
+
if (problem) {
|
|
194
|
+
console.error(problem);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
if (kind === "wordpress") {
|
|
198
|
+
console.log(formatWordPressExport(await exportWordPress(".", file)));
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
60
202
|
if (command !== "finish" && command !== "index") {
|
|
61
203
|
console.error(
|
|
62
204
|
[
|
|
63
205
|
"Usage: goodfellow-next finish [folder]",
|
|
64
206
|
" goodfellow-next finish --server",
|
|
65
207
|
" goodfellow-next index [folder]",
|
|
66
|
-
" goodfellow-next update [--to fixes|automatic|latest|<release>] [--publish]",
|
|
208
|
+
" goodfellow-next update [--to fixes|automatic|latest|<release>] [--publish | --commit]",
|
|
209
|
+
" goodfellow-next due [--since <time>]",
|
|
210
|
+
" goodfellow-next nightly [--mode changed|always|off] [--output <file>]",
|
|
67
211
|
" goodfellow-next content link|copy [folder]",
|
|
212
|
+
" goodfellow-next import wordpress <file or address> [--dry-run]",
|
|
213
|
+
" goodfellow-next export wordpress [file]",
|
|
214
|
+
" goodfellow-next admin add <address> | check | remove",
|
|
215
|
+
" goodfellow-next broker use <address> | remove",
|
|
68
216
|
"",
|
|
69
217
|
"finish Gets the exported site in folder (default: out) ready to publish: puts the files",
|
|
70
218
|
" Next.js uses to load pages ahead where browsers look for them, writes calendar files",
|
|
71
|
-
" for collections of events and pages that send visitors on from
|
|
72
|
-
" indexes it for search
|
|
73
|
-
"
|
|
74
|
-
" public/
|
|
219
|
+
" for collections of events, collections' feeds and pages that send visitors on from",
|
|
220
|
+
" old addresses, indexes it for search, and lists what automated accessibility checks",
|
|
221
|
+
" find on its pages. With --server, for a full-stack site, writes the calendar files",
|
|
222
|
+
" and feeds into public/calendars and public/feeds and indexes and checks the pages",
|
|
223
|
+
" next build rendered, with the index in public/pagefind, instead.",
|
|
75
224
|
"index Only indexes the exported site for its search block.",
|
|
76
225
|
"update Updates Goodfellow, and blocks from its registry, if the site still builds.",
|
|
226
|
+
" --publish pushes it to the main branch; --commit only commits it, for a job",
|
|
227
|
+
" that runs none of the site's code to publish.",
|
|
228
|
+
"due Says whether a scheduled change has come due since --since (default: the last",
|
|
229
|
+
" hour), so the site needs building again, and sets the step's due output on",
|
|
230
|
+
" GitHub Actions.",
|
|
231
|
+
"nightly Says whether tonight's build is needed: when the site has changed since the live",
|
|
232
|
+
" site was built, what it shows by today's date has, or an update is available",
|
|
233
|
+
" (--mode, or GOODFELLOW_NIGHTLY: changed, the default, always or off), and sets the",
|
|
234
|
+
" step's build and update outputs on GitHub Actions, or adds them to --output.",
|
|
77
235
|
"content For a site whose content is kept in a repository of its own: links content/ and",
|
|
78
236
|
" public/media/ to the content repository in folder (default: content-repository),",
|
|
79
237
|
" or copies them from it, as builds do.",
|
|
238
|
+
"import Brings a WordPress site's pages, posts, categories, tags, menus and media into",
|
|
239
|
+
" this site, from its export file (Tools → Export) or its address. With --dry-run,",
|
|
240
|
+
" says what it would bring across and changes nothing.",
|
|
241
|
+
"export Writes this site as a WordPress export file (default: named after the site),",
|
|
242
|
+
" which WordPress imports with Tools → Import → WordPress.",
|
|
243
|
+
"admin The admin panel's address of its own, such as admin.example.org, in",
|
|
244
|
+
" goodfellow.config.tsx: add names it; check checks it once the site has rebuilt with",
|
|
245
|
+
" it, then starts the admin panel only there; remove stops using it.",
|
|
246
|
+
"broker The site's token broker in goodfellow.config.tsx: use signs editors in there once",
|
|
247
|
+
" it's online; remove stops using it.",
|
|
80
248
|
].join("\n"),
|
|
81
249
|
);
|
|
82
250
|
process.exit(1);
|
|
83
251
|
}
|
|
252
|
+
/** What checking the exported pages found, reported once the export is ready. */
|
|
253
|
+
let problems = [];
|
|
84
254
|
if (command === "finish") {
|
|
85
255
|
const moved = await fixSegmentFiles(dir);
|
|
256
|
+
await writeEditorPolicy(dir);
|
|
86
257
|
if (moved > 0) console.log(`Moved ${moved} of Next.js's prefetch files to where browsers look for them`);
|
|
87
258
|
const calendars = await writeCalendarFiles(dir);
|
|
88
259
|
if (calendars > 0) console.log(`Wrote ${calendars} calendar files`);
|
|
260
|
+
const feeds = await writeFeedFiles(dir);
|
|
261
|
+
if (feeds > 0) console.log(`Wrote ${feeds} feeds`);
|
|
262
|
+
const languages = await writePageLanguages(dir);
|
|
263
|
+
if (languages > 0) console.log(`Marked ${languages} pages with their language`);
|
|
264
|
+
// Before the redirects' pages, which aren't the site's.
|
|
265
|
+
problems = await checkPages(await exportedPages(dir));
|
|
89
266
|
const redirects = await writeRedirectFiles(dir);
|
|
90
|
-
if (redirects > 0) console.log(`Wrote ${redirects} redirects`);
|
|
267
|
+
if (redirects.pages > 0) console.log(`Wrote ${redirects.pages} redirects`);
|
|
268
|
+
if (redirects.cloudflareLeft > 0) {
|
|
269
|
+
console.log(
|
|
270
|
+
`Cloudflare Pages reads only the first ${CLOUDFLARE_REDIRECTS_LIMIT.toLocaleString("en")} lines of _redirects, so ${redirects.cloudflareLeft} old address${redirects.cloudflareLeft === 1 ? " is" : "es are"} left to the pages that send visitors on.`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
for (const line of buildNotes(await writeFontFiles(dir))) console.log(line);
|
|
274
|
+
// What the site was built from, and when it stops being current, for the nightly check.
|
|
275
|
+
const currentUntil = await writeBuildRecordFile(dir);
|
|
276
|
+
if (currentUntil) {
|
|
277
|
+
console.log(`What the site shows by today's date changes at ${currentUntil}, when it needs building again`);
|
|
278
|
+
}
|
|
91
279
|
}
|
|
92
280
|
const indexed = await writeSearchIndex(dir);
|
|
93
281
|
console.log(
|
|
@@ -95,3 +283,8 @@ console.log(
|
|
|
95
283
|
? `No page in ${dir} has a search block, so there's nothing to index`
|
|
96
284
|
: `Indexed ${indexed} pages for search`,
|
|
97
285
|
);
|
|
286
|
+
if (command === "finish") {
|
|
287
|
+
const guarded = await guardExport(dir);
|
|
288
|
+
if (guarded !== undefined) console.log(`Started ${guarded} pages with the admin address's guard`);
|
|
289
|
+
}
|
|
290
|
+
for (const line of reportSiteProblems(problems)) console.log(line);
|
package/dist/admin.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import { GoodfellowConfig } from "@goodfellow-cms/core";
|
|
1
|
+
import { AdminSite, GoodfellowConfig } from "@goodfellow-cms/core";
|
|
2
2
|
import { Strings } from "@goodfellow-cms/admin";
|
|
3
|
-
import "@puckeditor/core/puck.css";
|
|
4
3
|
import "@goodfellow-cms/admin/styles.css";
|
|
5
4
|
//#region src/admin.d.ts
|
|
6
5
|
export interface GoodfellowAdminProps {
|
|
7
|
-
/**
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* What the admin panel knows about the site, from `site.admin()` in the
|
|
8
|
+
* server component `app/admin/page.tsx`. The admin panel's page then never
|
|
9
|
+
* loads the site's config: its blocks run in the editor's frames, at `app/admin/editor/`.
|
|
10
|
+
*/
|
|
11
|
+
site?: AdminSite;
|
|
12
|
+
/**
|
|
13
|
+
* The site's `goodfellow.config.tsx`, which an `app/admin/page.tsx` from
|
|
14
|
+
* before the editor's frames passes. That page loads every block's code
|
|
15
|
+
* itself, so the admin panel only says how to update it, and never signs in
|
|
16
|
+
* there: `goodfellow-next update` replaces it where it's still the starter's.
|
|
17
|
+
*/
|
|
18
|
+
config?: GoodfellowConfig;
|
|
9
19
|
/** The live site's address, for "View" links. Defaults to the site's base path, such as `/` or `/my-site/`. */
|
|
10
20
|
siteUrl?: string;
|
|
11
21
|
/** Replaces any of the admin panel's text, for rewording or translation. */
|
|
@@ -19,10 +29,10 @@ export interface GoodfellowAdminProps {
|
|
|
19
29
|
server?: string;
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
22
|
-
* The admin panel, for a Next.js site's `app/admin/page.tsx`,
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
32
|
+
* The admin panel, for a Next.js site's `app/admin/page.tsx`, given
|
|
33
|
+
* `site.admin()` there. In `next dev` it saves to the files on disk; on the
|
|
34
|
+
* live site, editors sign in to the config's git backend, or on a full-stack
|
|
35
|
+
* site (`server`), with the site's own sign-in.
|
|
26
36
|
*/
|
|
27
|
-
export declare function GoodfellowAdmin({
|
|
37
|
+
export declare function GoodfellowAdmin({ site, config, strings, ...props }: GoodfellowAdminProps): import("react").JSX.Element;
|
|
28
38
|
//#endregion
|
package/dist/admin.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { n as EDITOR_PAGE, t as ADMIN_PAGE } from "./site-files-BeLnLxCl.js";
|
|
3
|
+
import { ADMIN_POLICY, DEV_EDITOR_PREFIX } from "@goodfellow-cms/core";
|
|
1
4
|
import { useEffect, useState } from "react";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { Admin } from "@goodfellow-cms/admin";
|
|
4
|
-
import "@puckeditor/core/puck.css";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Admin, OutdatedAdminPage, reloadFrames } from "@goodfellow-cms/admin";
|
|
5
7
|
import "@goodfellow-cms/admin/styles.css";
|
|
6
8
|
//#region src/generated/theme-css.ts
|
|
7
9
|
var theme_css_default = "/*\n * Maps the site theme (CSS variables written from content/site.json) onto\n * Tailwind, so classes like bg-primary, text-muted-foreground, font-heading and\n * rounded-lg follow the theme. Import after \"tailwindcss\".\n */\n/*\n * Blocks' dark: classes apply only where the site has dark mode\n * (Site Settings), which gives its pages <meta name=\"color-scheme\"\n * content=\"light dark\">: when the visitor's computer is set to dark, or when\n * the admin panel's preview shows the site dark. Light-only sites never\n * use them, even on a computer set to dark.\n */\n@custom-variant dark {\n @media (prefers-color-scheme: dark) {\n &:where(\n :root:has(meta[name=\"color-scheme\"][content~=\"dark\"]):not([data-gf-color-scheme=\"light\"]),\n :root:has(meta[name=\"color-scheme\"][content~=\"dark\"]):not([data-gf-color-scheme=\"light\"]) *\n ) {\n @slot;\n }\n }\n &:where([data-gf-color-scheme=\"dark\"], [data-gf-color-scheme=\"dark\"] *) {\n @slot;\n }\n}\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-border: var(--border);\n /* The rest of shadcn/ui's colors, for blocks built with it, follow the ones above unless custom CSS sets them. */\n --color-card: var(--card, var(--background));\n --color-card-foreground: var(--card-foreground, var(--foreground));\n --color-popover: var(--popover, var(--background));\n --color-popover-foreground: var(--popover-foreground, var(--foreground));\n --color-destructive: var(--destructive, #dc2626);\n --color-input: var(--input, var(--border));\n --color-ring: var(--ring, var(--primary));\n --font-sans: var(--gf-font-body);\n --font-heading: var(--gf-font-heading);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n@layer base {\n *,\n ::before,\n ::after {\n border-color: var(--color-border);\n }\n\n body {\n background-color: var(--color-background);\n color: var(--color-foreground);\n /* Keeps the footer at the bottom of short pages. */\n display: flex;\n flex-direction: column;\n min-height: 100dvh;\n }\n\n .gf-main {\n flex: 1;\n }\n\n /* The credits of the photos on a page, which their licenses ask for. */\n .gf-credits {\n max-width: 72rem;\n margin: 0 auto;\n padding: 1.5rem 1rem;\n font-size: 0.75rem;\n line-height: 1.5;\n color: var(--color-muted-foreground);\n }\n\n .gf-credits a {\n text-decoration: underline;\n }\n\n h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n font-family: var(--font-heading);\n }\n}\n";
|
|
@@ -24,30 +26,92 @@ async function siteStylesheets(siteUrl) {
|
|
|
24
26
|
}
|
|
25
27
|
/** Set by `withGoodfellow()` from Next.js's `basePath`, such as `/my-site`, or empty. */
|
|
26
28
|
const BASE_PATH = process.env.GOODFELLOW_BASE_PATH ?? "";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Where the editor's frames are: `app/admin/editor/`, or in `next dev` the
|
|
31
|
+
* bundle the local backend serves, since Next.js refuses its development
|
|
32
|
+
* files to sandboxed frames.
|
|
33
|
+
*/
|
|
34
|
+
const EDITOR_URL = process.env.NODE_ENV === "development" ? `${BASE_PATH}${DEV_EDITOR_PREFIX}` : `${BASE_PATH}/admin/editor/`;
|
|
35
|
+
/**
|
|
36
|
+
* In `next dev`, reloads the editor's frames once they're rebuilt, so they
|
|
37
|
+
* show the site's new code, leaving whatever is open on the admin panel's page as it is.
|
|
38
|
+
*/
|
|
39
|
+
function useEditorRebuilds() {
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (process.env.NODE_ENV !== "development") return;
|
|
42
|
+
let shown;
|
|
43
|
+
const check = async () => {
|
|
44
|
+
try {
|
|
45
|
+
const { version } = await (await fetch(`${EDITOR_URL}version.json`, { cache: "no-store" })).json();
|
|
46
|
+
if (shown !== void 0 && version !== shown) reloadFrames();
|
|
47
|
+
shown = version;
|
|
48
|
+
} catch {}
|
|
49
|
+
};
|
|
50
|
+
check();
|
|
51
|
+
const timer = setInterval(check, 1500);
|
|
52
|
+
return () => clearInterval(timer);
|
|
53
|
+
}, []);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Goodfellow's git hosts, which a site's config names as `site.backend`. A
|
|
57
|
+
* bundler needs every package it may load named here, as the starter's
|
|
58
|
+
* dependencies are.
|
|
59
|
+
*/
|
|
60
|
+
const HOSTS = {
|
|
61
|
+
"@goodfellow-cms/github": () => import("@goodfellow-cms/github"),
|
|
62
|
+
"@goodfellow-cms/gitlab": () => import("@goodfellow-cms/gitlab")
|
|
63
|
+
};
|
|
64
|
+
async function hostFrom(source) {
|
|
65
|
+
const load = Object.hasOwn(HOSTS, source.package) ? HOSTS[source.package] : void 0;
|
|
66
|
+
const make = load && (await load())[source.export];
|
|
67
|
+
if (typeof make !== "function") throw new Error(`The admin panel can't sign in to ${source.package} yet.`);
|
|
68
|
+
return make(source.options);
|
|
69
|
+
}
|
|
70
|
+
async function setUp(siteUrl, backend) {
|
|
71
|
+
const [stylesheets, tailwindBrowser, store, host] = await Promise.all([
|
|
29
72
|
siteStylesheets(siteUrl),
|
|
30
73
|
import("./tailwind-browser-Ds-ZH7sO.js").then((module) => module.default),
|
|
31
|
-
process.env.NODE_ENV === "development" ? import("@goodfellow-cms/admin/dev").then((module) => module.localStore(`${BASE_PATH}/__goodfellow/api`)) : void 0
|
|
74
|
+
process.env.NODE_ENV === "development" ? import("@goodfellow-cms/admin/dev").then((module) => module.localStore(`${BASE_PATH}/__goodfellow/api`)) : void 0,
|
|
75
|
+
backend ? hostFrom(backend) : void 0
|
|
32
76
|
]);
|
|
33
77
|
return {
|
|
34
78
|
stylesheets,
|
|
35
79
|
tailwindBrowserUrl: URL.createObjectURL(new Blob([tailwindBrowser], { type: "text/javascript" })),
|
|
36
|
-
...store && { store }
|
|
80
|
+
...store && { store },
|
|
81
|
+
...host && { host }
|
|
37
82
|
};
|
|
38
83
|
}
|
|
39
84
|
/**
|
|
40
|
-
* The admin panel, for a Next.js site's `app/admin/page.tsx`,
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
85
|
+
* The admin panel, for a Next.js site's `app/admin/page.tsx`, given
|
|
86
|
+
* `site.admin()` there. In `next dev` it saves to the files on disk; on the
|
|
87
|
+
* live site, editors sign in to the config's git backend, or on a full-stack
|
|
88
|
+
* site (`server`), with the site's own sign-in.
|
|
44
89
|
*/
|
|
45
|
-
function GoodfellowAdmin({ config,
|
|
90
|
+
function GoodfellowAdmin({ site, config, strings, ...props }) {
|
|
91
|
+
if (site) return /* @__PURE__ */ jsx(SiteAdmin, {
|
|
92
|
+
site,
|
|
93
|
+
...strings && { strings },
|
|
94
|
+
...props
|
|
95
|
+
});
|
|
96
|
+
if (config) return /* @__PURE__ */ jsx(OutdatedAdminPage, {
|
|
97
|
+
files: [{
|
|
98
|
+
path: "app/admin/page.tsx",
|
|
99
|
+
content: ADMIN_PAGE
|
|
100
|
+
}, {
|
|
101
|
+
path: "app/admin/editor/page.tsx",
|
|
102
|
+
content: EDITOR_PAGE
|
|
103
|
+
}],
|
|
104
|
+
...strings && { strings }
|
|
105
|
+
});
|
|
106
|
+
throw new Error("GoodfellowAdmin needs `site`, from `site.admin()` in app/admin/page.tsx.");
|
|
107
|
+
}
|
|
108
|
+
function SiteAdmin({ site, siteUrl = `${BASE_PATH}/`, strings, server }) {
|
|
46
109
|
const [setup, setSetup] = useState(null);
|
|
110
|
+
useEditorRebuilds();
|
|
47
111
|
useEffect(() => {
|
|
48
112
|
let current = true;
|
|
49
113
|
let url;
|
|
50
|
-
setUp(siteUrl).then((result) => {
|
|
114
|
+
setUp(siteUrl, site.backend).then((result) => {
|
|
51
115
|
url = result.tailwindBrowserUrl;
|
|
52
116
|
if (current) setSetup(result);
|
|
53
117
|
else URL.revokeObjectURL(url);
|
|
@@ -56,10 +120,16 @@ function GoodfellowAdmin({ config, siteUrl = `${BASE_PATH}/`, strings, server })
|
|
|
56
120
|
current = false;
|
|
57
121
|
if (url) URL.revokeObjectURL(url);
|
|
58
122
|
};
|
|
59
|
-
}, [siteUrl]);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
123
|
+
}, [siteUrl, site]);
|
|
124
|
+
const policy = /* @__PURE__ */ jsx("meta", {
|
|
125
|
+
httpEquiv: "Content-Security-Policy",
|
|
126
|
+
content: ADMIN_POLICY
|
|
127
|
+
});
|
|
128
|
+
if (!setup) return policy;
|
|
129
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [policy, /* @__PURE__ */ jsx(Admin, {
|
|
130
|
+
site,
|
|
131
|
+
frames: { url: EDITOR_URL },
|
|
132
|
+
...setup.host && { host: setup.host },
|
|
63
133
|
...setup.store && {
|
|
64
134
|
store: setup.store,
|
|
65
135
|
registry: `${BASE_PATH}/__goodfellow/api/registry/{name}.json`
|
|
@@ -72,7 +142,7 @@ function GoodfellowAdmin({ config, siteUrl = `${BASE_PATH}/`, strings, server })
|
|
|
72
142
|
},
|
|
73
143
|
siteUrl,
|
|
74
144
|
...strings && { strings }
|
|
75
|
-
});
|
|
145
|
+
})] });
|
|
76
146
|
}
|
|
77
147
|
//#endregion
|
|
78
148
|
export { GoodfellowAdmin };
|
package/dist/components.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ImageSize } from "@goodfellow-cms/core";
|
|
2
2
|
import { SiteImageProps, SiteLinkProps } from "@goodfellow-cms/react";
|
|
3
|
+
import { ReactNode } from "react";
|
|
3
4
|
//#region src/components.d.ts
|
|
4
5
|
/** A link to one of the site's pages, with Next.js's client-side navigation. Next.js adds the base path. */
|
|
5
6
|
export declare function NextLink(props: SiteLinkProps): import("react").JSX.Element;
|
|
@@ -14,4 +15,24 @@ export declare function NextImage({ src, alt, width, height, loading, ...props }
|
|
|
14
15
|
export declare function CurrentMenuLinks({ basePath }: {
|
|
15
16
|
basePath?: string;
|
|
16
17
|
}): null;
|
|
18
|
+
/**
|
|
19
|
+
* On a site in several languages, says the page's language on `<html>`, which
|
|
20
|
+
* the root layout shared by every page can't. `goodfellow-next finish` writes
|
|
21
|
+
* it into exported pages; this keeps it right as visitors move between pages.
|
|
22
|
+
*/
|
|
23
|
+
export declare function PageLanguage({ language }: {
|
|
24
|
+
language: string;
|
|
25
|
+
}): null;
|
|
26
|
+
/**
|
|
27
|
+
* On a site in several languages, shows the layout's header or footer in the
|
|
28
|
+
* language of the page at the current address: the layout has one of each,
|
|
29
|
+
* already rendered, for every language. Blocks inside it learn the address
|
|
30
|
+
* from `useCurrentPath()`, such as the Language switch, whose links follow it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ByLanguage({ variants }: {
|
|
33
|
+
variants: Array<{
|
|
34
|
+
prefix: string;
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
}>;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
17
38
|
//#endregion
|
package/dist/components.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { CurrentPathProvider } from "@goodfellow-cms/react";
|
|
2
3
|
import { usePathname } from "next/navigation";
|
|
3
4
|
import { useEffect } from "react";
|
|
4
5
|
import Image from "next/image";
|
|
@@ -47,5 +48,30 @@ function CurrentMenuLinks({ basePath = "" }) {
|
|
|
47
48
|
}, [pathname, basePath]);
|
|
48
49
|
return null;
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* On a site in several languages, says the page's language on `<html>`, which
|
|
53
|
+
* the root layout shared by every page can't. `goodfellow-next finish` writes
|
|
54
|
+
* it into exported pages; this keeps it right as visitors move between pages.
|
|
55
|
+
*/
|
|
56
|
+
function PageLanguage({ language }) {
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
document.documentElement.lang = language;
|
|
59
|
+
}, [language]);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* On a site in several languages, shows the layout's header or footer in the
|
|
64
|
+
* language of the page at the current address: the layout has one of each,
|
|
65
|
+
* already rendered, for every language. Blocks inside it learn the address
|
|
66
|
+
* from `useCurrentPath()`, such as the Language switch, whose links follow it.
|
|
67
|
+
*/
|
|
68
|
+
function ByLanguage({ variants }) {
|
|
69
|
+
const pathname = usePathname();
|
|
70
|
+
const shown = variants.find((variant) => variant.prefix && (pathname === variant.prefix || pathname.startsWith(`${variant.prefix}/`))) ?? variants.find((variant) => !variant.prefix);
|
|
71
|
+
return /* @__PURE__ */ jsx(CurrentPathProvider, {
|
|
72
|
+
path: pathname,
|
|
73
|
+
children: shown?.children
|
|
74
|
+
});
|
|
75
|
+
}
|
|
50
76
|
//#endregion
|
|
51
|
-
export { CurrentMenuLinks, NextImage, NextLink };
|
|
77
|
+
export { ByLanguage, CurrentMenuLinks, NextImage, NextLink, PageLanguage };
|
package/dist/config.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface GoodfellowNextOptions {
|
|
|
23
23
|
* - Serves images as they are unless the site sets an image loader, since static files can't be optimized on request.
|
|
24
24
|
* - Adds the site's redirects (Site Settings → Redirects) ahead of the site's own, for hosts that
|
|
25
25
|
* send them, such as Vercel. Others get the pages `goodfellow-next finish` writes at old addresses.
|
|
26
|
+
* - For a site with an admin address, adds header rules that sandbox every page there but the
|
|
27
|
+
* admin panel's own, for hosts that send headers, such as Vercel.
|
|
26
28
|
* - With `{ server: true }`, builds a full-stack site for a server instead of as static
|
|
27
29
|
* files: see `goodfellowApi()` in `@goodfellow-cms/next/server`.
|
|
28
30
|
* - In `next dev`, runs the admin panel's local backend, so publishing saves to
|