@goodfellow-cms/admin 0.3.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/dist/index.d.ts CHANGED
@@ -1,17 +1,7 @@
1
- import { ContentStore, GitHost, GoodfellowConfig, SignInError } from "@goodfellow-cms/core";
1
+ import { AdminSite, ContentStore, GitHost, SignInError } from "@goodfellow-cms/core";
2
2
  import "react";
3
+ import "@goodfellow-cms/react";
3
4
  import "@puckeditor/core";
4
- //#region src/admin-context.d.ts
5
- /** How previews load the site's styles. Provided by the dev server or build that serves the admin panel. */
6
- interface PreviewOptions {
7
- /** The site's compiled stylesheets. */
8
- stylesheets: string[];
9
- /** `@goodfellow-cms/react/theme.css`, so Tailwind in the preview knows the theme's classes. */
10
- themeCss: string;
11
- /** URL of `@tailwindcss/browser`, which styles classes typed in the editor before the site is rebuilt. */
12
- tailwindBrowserUrl?: string;
13
- }
14
- //#endregion
15
5
  //#region src/strings.d.ts
16
6
  /**
17
7
  * Every piece of text the admin panel shows, so it can be reworded or
@@ -58,6 +48,13 @@ export declare const defaultStrings: {
58
48
  readonly "signIn.install": "Install Goodfellow on {host}";
59
49
  readonly "signIn.error.expired": "You were signed out because your sign-in expired. Sign in again to keep editing.";
60
50
  readonly "signIn.error.other": "Couldn't reach {host}. Check your connection and try again.";
51
+ readonly "signIn.app.intro": "Sign in with your account on this site to edit it.";
52
+ readonly "signIn.app.button": "Sign in";
53
+ readonly "signIn.app.expired": "You've been signed out. Sign in again to keep editing.";
54
+ readonly "signIn.app.notAllowed": "You're signed in, but your account isn't allowed to edit this site. Ask one of its owners to give you access.";
55
+ readonly "signIn.app.error": "Couldn't reach the site. Check your connection and try again.";
56
+ readonly "owners.only": "Only the site's owners can change this.";
57
+ readonly "owners.onlyHint": "Ask one of them if something here needs changing.";
61
58
  readonly "account.signedInAs": "Signed in as {name}";
62
59
  readonly "account.signOut": "Sign out";
63
60
  readonly "deploy.building": "Updating the live site…";
@@ -80,6 +77,9 @@ export declare const defaultStrings: {
80
77
  readonly "build.unknown": "{host} didn't say why the rebuild failed. Its details may help a developer.";
81
78
  readonly "build.hostDetails": "See the details on {host}";
82
79
  readonly "loadError.title": "The admin panel couldn't open this site";
80
+ readonly "outdatedAdminPage.title": "This site's admin page needs updating";
81
+ readonly "outdatedAdminPage.message": "This site's admin page is from before the editor kept blocks away from your sign-in, so the admin panel doesn't open on it. Goodfellow's next update changes it, unless someone has changed the page. Whoever looks after the site's code can also change it now: the details say how.";
82
+ readonly "outdatedAdminPage.details": "Run npx goodfellow-next update, or replace these files with the Next.js starter's, as they are below. The site's blocks then run in the editor's sandboxed frames, at /admin/editor/.";
83
83
  readonly "loadError.contentProblems": "Some of the site's files have problems, so they can't be edited until they're fixed.";
84
84
  readonly "loadError.other": "Something went wrong while loading the site. Check your connection and try again.";
85
85
  readonly "action.tryAgain": "Try again";
@@ -92,11 +92,83 @@ export declare const defaultStrings: {
92
92
  readonly "action.redoHint": "Redo what you undid (Ctrl+Shift+Z, or ⇧⌘Z on a Mac)";
93
93
  readonly "details.show": "Details";
94
94
  readonly "publish.button": "Publish";
95
+ readonly "editor.leftSidebar": "Show or hide blocks and outline";
96
+ readonly "editor.rightSidebar": "Show or hide settings";
97
+ readonly "editor.viewport": "Preview width";
98
+ readonly "editor.viewport.full": "Full width";
99
+ readonly "editor.viewport.tablet": "Tablet";
100
+ readonly "editor.viewport.phone": "Phone";
101
+ readonly "editor.failed": "The editor stopped working. Reload the page to start it again.";
102
+ readonly "editor.frame": "Editing {title}";
95
103
  readonly "publish.publishing": "Publishing…";
96
104
  readonly "publish.done": "Published.";
97
105
  readonly "publish.conflict": "Someone else changed the site since you opened this page, so your changes weren't published. Reload to see their changes, then make yours again.";
98
106
  readonly "publish.error": "Your changes couldn't be published. Check your connection and try again.";
99
107
  readonly "publish.invalid": "Fix the highlighted problems before publishing.";
108
+ readonly "draft.label": "Draft";
109
+ readonly "draft.add": "Add to draft";
110
+ readonly "draft.addHint": "Keep this change with others to publish together later, instead of publishing it now";
111
+ readonly "draft.adding": "Adding to the draft…";
112
+ readonly "draft.added": "Added to the draft.";
113
+ readonly "draft.banner": "You're working in a draft started {date}. What you change goes into it, and visitors see it when it's published.";
114
+ readonly "draft.bannerKept": "You're working in a draft started {date}, kept in this browser. What you change goes into it, and visitors see it when it's published.";
115
+ readonly "draft.changes": "{count} files changed";
116
+ readonly "draft.oneChange": "1 file changed";
117
+ readonly "draft.publishAll": "Publish all";
118
+ readonly "draft.publishing": "Publishing the draft…";
119
+ readonly "draft.published": "The draft is published.";
120
+ readonly "draft.submit": "Submit for review";
121
+ readonly "draft.submitted": "Submitted for review.";
122
+ readonly "draft.seeReview": "See it";
123
+ readonly "draft.discard": "Throw away";
124
+ readonly "draft.discardTitle": "Throw away this draft?";
125
+ readonly "draft.discardBody": "Everything in it will be lost. The live site stays as it is.";
126
+ readonly "draft.leave": "Back to the live site";
127
+ readonly "draft.waiting": "A draft started {date} is waiting to be published.";
128
+ readonly "draft.waitingMany": "{count} drafts are waiting to be published.";
129
+ readonly "draft.choose": "Draft to open";
130
+ readonly "draft.started": "Started {date}";
131
+ readonly "draft.open": "Open";
132
+ readonly "draft.gone": "That draft has been published or thrown away.";
133
+ readonly "draft.conflict": "Someone else has published changes to some of the same pages or settings since this draft started, so it can't be published as it is. Submit it for review, or throw it away and make the changes again.";
134
+ readonly "draft.conflictKept": "Someone else has published changes to some of the same pages or settings since this draft started, so it can't be published as it is. Throw it away and make the changes again.";
135
+ readonly "draft.error": "The draft couldn't be published. Check your connection and try again.";
136
+ readonly "draft.submitError": "The draft couldn't be submitted for review. Check your connection and try again.";
137
+ readonly "draft.discardError": "The draft couldn't be thrown away. Check your connection and try again.";
138
+ readonly "draft.message": "Publish the draft {name}";
139
+ readonly "draft.reviewTitle": "Draft {name}, from the admin panel";
140
+ readonly "draft.reviewDescription": "Changes made in the admin panel:";
141
+ readonly "checks.blocked.title": "This can't be published yet";
142
+ readonly "checks.blocked.intro": "Publishing this would leave links that go nowhere. Change or remove them first.";
143
+ readonly "checks.warnings.title": "Publish anyway?";
144
+ readonly "checks.warnings.intro": "A few things could be better. You can publish anyway, or go back and fix them first.";
145
+ readonly "checks.alsoWarnings": "These could be better too:";
146
+ readonly "checks.publishAnyway": "Publish anyway";
147
+ readonly "checks.goBack": "Go back";
148
+ readonly "checks.missing-page": "{place} links to {target}, which isn't a page on the site.";
149
+ readonly "checks.missing-media": "{place} uses {target}, which isn't in the media library.";
150
+ readonly "checks.no-title": "{place} has no title, so browser tabs and search results show only the site's name.";
151
+ readonly "checks.missing-translation": "{place} has no translation in {language} yet. Visitors reading in {language} are sent to its home page instead.";
152
+ readonly "checks.outdated-translation": "{place} has changed since its translation in {language} was made. Update the translation, then mark it as up to date.";
153
+ readonly "checks.bad-translation": "{place} says it's a translation of {target}, but only pages and items in the site's main language have translations.";
154
+ readonly "checks.no-description": "{place} has no description for search results and link previews to show.";
155
+ readonly "checks.low-contrast": 'Text in "{text}" is hard to read on "{background}": its contrast is {ratio} to 1, and text needs at least 4.5 to 1.';
156
+ readonly "checks.low-contrast.dark": 'In dark mode, text in "{text}" is hard to read on "{background}": its contrast is {ratio} to 1, and text needs at least 4.5 to 1.';
157
+ readonly "checks.no-alt": "{place} has an image, {image}, with no description for people who can't see it. Describe it in the image's settings, or say it's only decoration.";
158
+ readonly "checks.skipped-heading": "{place} has the heading \"{text}\" at level {level}, after one at level {previous}. Headings shouldn't skip a level, or people who move through a page by its headings think they've missed something.";
159
+ readonly "checks.skipped-heading.first": '{place} starts with the heading "{text}", at level {level}. Start with the page\'s title (level 1) or a section (level 2).';
160
+ readonly "checks.vague-link": '{place} has a link, "{text}", that doesn\'t say where it goes. Say what people will find there, such as "Our Mass times" rather than "Click here".';
161
+ readonly "checks.empty-link": "{place} has a link to {target} with no text, so people who can't see the page don't know where it goes.";
162
+ readonly "checks.accessibility": "{place}: {help}";
163
+ readonly "checks.place.page": 'The page "{title}"';
164
+ readonly "checks.place.entry": '"{title}" in {collection}';
165
+ readonly "checks.place.design": "The design of {collection}";
166
+ readonly "checks.place.header": "The header";
167
+ readonly "checks.place.footer": "The footer";
168
+ readonly "checks.place.menus": "A menu";
169
+ readonly "checks.place.settings": "Site Settings";
170
+ readonly "checks.place.redirects": "A redirect";
171
+ readonly "checks.place.file": "{file}";
100
172
  readonly "unsaved.confirm": "You have changes that aren't published yet. Leave anyway and lose them?";
101
173
  readonly "unsaved.label": "Unpublished changes";
102
174
  readonly "pages.title": "Pages";
@@ -106,25 +178,62 @@ export declare const defaultStrings: {
106
178
  readonly "pages.column.address": "Address";
107
179
  readonly "pages.edit": "Edit";
108
180
  readonly "pages.move": "Change address";
181
+ readonly "pages.duplicate": "Duplicate";
109
182
  readonly "pages.delete": "Delete";
110
183
  readonly "pages.view": "View";
111
184
  readonly "pages.homeCantDelete": "The home page can't be deleted.";
185
+ readonly "pages.schedule": "Schedule";
186
+ readonly "sharePreview.title": "When a link to this page is shared";
187
+ readonly "sharePreview.noImage": "No image";
188
+ readonly "sharePreview.noAddress": "Link previews show the image once the site's address is set in Site Settings, since they need its full address.";
189
+ readonly "sharePreview.hidden": "This page is hidden from search engines, so it isn't in search results or the sitemap.";
190
+ readonly "schedulePage.title": "When “{title}” is on the site";
191
+ readonly "schedulePage.submit": "Publish";
192
+ readonly "schedulePage.message": 'Schedule page "{title}"';
193
+ readonly "schedule.title": "When it's on the site";
194
+ readonly "schedule.showFrom": "Show from";
195
+ readonly "schedule.showFromHint": "Leave empty to show it as soon as it's published.";
196
+ readonly "schedule.hideAfter": "Hide after";
197
+ readonly "schedule.hideAfterHint": "Leave empty to keep it on the site.";
198
+ readonly "schedule.zone": "Times are in the site's time zone, {zone}.";
199
+ readonly "schedule.zoneNew": "Times are in your computer's time zone, {zone}, which becomes the site's time zone.";
200
+ readonly "schedule.zoneNone": "Times are in UTC, since the site has no time zone. Choose one in Site Settings.";
201
+ readonly "schedule.invalid": "Enter a date and a time.";
202
+ readonly "schedule.order": "Choose a time after the one it's shown from.";
203
+ readonly "schedule.timing.exact": "The site is rebuilt at each of these times, so changes go live then.";
204
+ readonly "schedule.timing.hourly": "The site checks each hour for changes that are due, so changes go live within an hour of their time.";
205
+ readonly "schedule.timing.nightly": "The site is rebuilt each night, so changes go live with the first rebuild after their time.";
206
+ readonly "schedule.timing.none": "Changes go live the next time the site is rebuilt after their time, such as when someone publishes.";
207
+ readonly "schedule.timing.notAllowed": "{host} didn't let you have the site rebuilt at these times, so changes go live with the nightly rebuild after their time, or at their time once an owner opens the admin panel.";
208
+ readonly "schedule.public": "Anyone can read what's scheduled before it's shown, since the site's files on {host} are public. Don't schedule anything that must stay secret until then.";
209
+ readonly "schedule.internal": "Anyone signed in to {host} can read what's scheduled before it's shown, since the site's files are visible to all of them. Don't schedule anything that must stay secret until then.";
210
+ readonly "schedule.status.waiting": "Shown from {time}";
211
+ readonly "schedule.status.hides": "Hidden after {time}";
212
+ readonly "schedule.status.hidden": "Hidden since {time}";
213
+ readonly "comingUp.title": "Coming up";
214
+ readonly "comingUp.show": "Shown";
215
+ readonly "comingUp.hide": "Hidden";
216
+ readonly "comingUp.zone": "Times are in the site's time zone, {zone}.";
112
217
  readonly "newPage.title": "New page";
113
218
  readonly "newPage.pageTitle": "Page title";
114
219
  readonly "newPage.address": "Address";
115
220
  readonly "newPage.addressHint": "The part of the web address after the site's name, such as /about or /events/picnic.";
116
221
  readonly "newPage.create": "Create page";
117
222
  readonly "newPage.message": 'Add page "{title}"';
223
+ readonly "duplicatePage.title": 'Duplicate "{title}"';
224
+ readonly "duplicatePage.pageTitle": "{title} (copy)";
225
+ readonly "duplicatePage.create": "Duplicate page";
226
+ readonly "duplicatePage.message": 'Add page "{title}", a copy of "{from}"';
118
227
  readonly "movePage.title": "Change the address of {title}";
119
228
  readonly "movePage.address": "New address";
120
- readonly "movePage.updateLinks": "Update menu links to this page";
229
+ readonly "movePage.links": "Links to it anywhere on this site, menus included, change to the new address.";
121
230
  readonly "movePage.redirect": "Keep {from} working by sending visitors on to the page";
122
231
  readonly "movePage.redirectHint": "Links to the old address elsewhere, such as on other websites, keep working. Site Settings → Redirects lists it.";
123
- readonly "movePage.warning": "Links to the old address elsewhere, such as in page text or on other websites, will stop working.";
232
+ readonly "movePage.warning": "Links to the old address on other websites will stop working.";
124
233
  readonly "movePage.submit": "Change address";
125
234
  readonly "movePage.message": 'Move page "{title}" from {from} to {to}';
126
235
  readonly "deletePage.title": "Delete {title}?";
127
- readonly "deletePage.body": "The page at {path} will be removed from the site. Menu links to it will stop working.";
236
+ readonly "deletePage.body": "The page at {path} will be removed from the site. If anything on the site links to it, those links need changing first.";
128
237
  readonly "deletePage.submit": "Delete page";
129
238
  readonly "deletePage.message": 'Delete page "{title}"';
130
239
  readonly "address.invalid": "Use lowercase letters, numbers and hyphens, with / between parts, such as /about-us.";
@@ -161,6 +270,8 @@ export declare const defaultStrings: {
161
270
  readonly "versions.unknownBlocks": "This version can't be restored, because it uses blocks the site doesn't have any more: {blocks}.";
162
271
  readonly "layout.header": "Header";
163
272
  readonly "layout.footer": "Footer";
273
+ readonly "layout.headerWord": "header";
274
+ readonly "layout.footerWord": "footer";
164
275
  readonly "layout.headerMessage": "Update the header";
165
276
  readonly "layout.footerMessage": "Update the footer";
166
277
  readonly "collections.title": "Collections";
@@ -208,9 +319,10 @@ export declare const defaultStrings: {
208
319
  readonly "entryName.taken": "Something else on the site already uses this address.";
209
320
  readonly "moveEntry.message": 'Move {entry} "{title}" from {from} to {to}';
210
321
  readonly "deleteEntry.body": "{title} will be removed from {name}.";
211
- readonly "deleteEntry.bodyPage": "{title} will be removed from {name}, and its page at {path} from the site. Menu links to it will stop working.";
322
+ readonly "deleteEntry.bodyPage": "{title} will be removed from {name}, and its page at {path} from the site. If anything on the site links to it, those links need changing first.";
212
323
  readonly "deleteEntry.message": 'Delete {entry} "{title}"';
213
324
  readonly "editEntry.message": 'Update {entry} "{title}"';
325
+ readonly "editEntry.invalidSchedule": "Check when it's on the site: each time needs a date and a time, and it can only be hidden after it's shown.";
214
326
  readonly "editEntry.notFound": "This item doesn't exist. It may have been renamed or deleted.";
215
327
  readonly "editEntry.back": "Back to {name}";
216
328
  readonly "editEntry.required": "Fill in {fields} before publishing.";
@@ -264,7 +376,7 @@ export declare const defaultStrings: {
264
376
  readonly "collectionSettings.general": "General";
265
377
  readonly "collectionSettings.path": "Address of each item's page";
266
378
  readonly "collectionSettings.pathHint": "{slug} stands for each item's own address, as in /videos/{slug}.";
267
- readonly "collectionSettings.pathWarning": "Changing this changes the address of every item's page. Links to the old addresses elsewhere will stop working.";
379
+ readonly "collectionSettings.pathWarning": "Changing this changes the address of every item's page, and links to them on this site change with it. Links to the old addresses on other websites will stop working.";
268
380
  readonly "collectionSettings.sortField": "Order items by";
269
381
  readonly "collectionSettings.sortOrder": "Direction";
270
382
  readonly "collectionSettings.ascending": "A to Z, oldest or smallest first";
@@ -305,12 +417,15 @@ export declare const defaultStrings: {
305
417
  readonly "demo.startOver": "Start over";
306
418
  readonly "demo.startOverTitle": "Start over?";
307
419
  readonly "demo.startOverBody": "Everything you've changed in this demo, including uploads, will be undone.";
420
+ readonly "blocks.contentOnly": "This site's blocks are added by its developer.";
421
+ readonly "blocks.contentOnlyHint": "Its code is kept apart from its content, so adding or removing a block is done where the code is, and shows up here once the site has been rebuilt.";
308
422
  readonly "blocks.demo": "In this demo you can see which blocks there are, but not add or remove them: a block is added to a real site by updating its code.";
309
423
  readonly "markdown.formatted": "Formatted";
310
424
  readonly "markdown.source": "Markdown";
311
425
  readonly "markdown.sourceOnly": "This text uses things the formatted view can't show, such as tables or pictures, so it can only be edited as Markdown.";
312
426
  readonly "blocks.title": "Blocks";
313
427
  readonly "blocks.intro": "Add blocks to the editor, or remove ones you don't use. A change shows in the editor once the site has been updated, usually within a few minutes.";
428
+ readonly "blocks.otherRegistries": "Blocks from other registries can be added once the admin panel has an address of its own, where the site's pages can't reach it (Site Settings → Domain).";
314
429
  readonly "blocks.installed": "Added to this site";
315
430
  readonly "blocks.noneInstalled": "No blocks have been added yet. The editor offers the built-in ones.";
316
431
  readonly "blocks.loading": "Loading the blocks you can add…";
@@ -352,6 +467,8 @@ export declare const defaultStrings: {
352
467
  readonly "blocks.error.not-installed": "{name} hasn't been added, so there's nothing to remove.";
353
468
  readonly "blocks.error.in-use": "{name} is still used on the site. Take it out first, then remove it.";
354
469
  readonly "media.title": "Media";
470
+ readonly "media.cloudflareVideo": "This site is published with Cloudflare Pages, whose terms limit serving video and other large files on its free plans without one of its paid services. To be safe, put videos on YouTube or Vimeo and show them with the Video block, which plays them from there.";
471
+ readonly "media.cloudflareVideoTerms": "Cloudflare's terms";
355
472
  readonly "media.intro": "Images and files for the site. Upload them here, or while editing a page. Large photos are made smaller, and details hidden in photos, such as where they were taken, are removed.";
356
473
  readonly "media.upload": "Upload files";
357
474
  readonly "media.uploading": "Uploading…";
@@ -369,7 +486,7 @@ export declare const defaultStrings: {
369
486
  readonly "media.deleteMessage": "Delete {name}";
370
487
  readonly "media.deleteTitle": "Delete {name}?";
371
488
  readonly "media.deleteBody": "{name} will be removed from the site.";
372
- readonly "media.deleteUsed": "It's used in: {places}. Those places will show a missing image or a broken link.";
489
+ readonly "media.deleteUsed": "It's used in: {places}. Choose another file there first, since the site can't be published using a file that's gone.";
373
490
  readonly "media.notUsed": "Nothing on the site uses it.";
374
491
  readonly "media.error.type": "{name} can't be uploaded. The site can use images (JPEG, PNG, GIF, WebP, AVIF and SVG), PDFs, office documents, MP3 audio, MP4 and WebM video, and captions for videos (WebVTT).";
375
492
  readonly "media.error.size": "{name} is too large: files can be up to 25 MB. For long videos, upload them to YouTube or Vimeo and link to them instead.";
@@ -401,6 +518,9 @@ export declare const defaultStrings: {
401
518
  readonly "ai.photos.error.busy": "Openverse has had too many requests from this computer, so some images are left empty. Try again later, or choose them from the media library.";
402
519
  readonly "ai.photos.error.unreachable": "Couldn't reach Openverse, so some images are left empty. Check your connection, or choose them from the media library.";
403
520
  readonly "ai.photos.error.publish": "The photos found couldn't be added to the media library, so their images are left empty.";
521
+ readonly "ai.translate": "Translate into {language}";
522
+ readonly "ai.translate.hint": "This page is in {language}. The AI can translate all of it, blocks and settings, keeping its layout. Anything you write below is passed on too, such as how formal to be.";
523
+ readonly "ai.translate.entryHint": "This item is in {language}. The AI can translate all its fields. Anything you write below is passed on too.";
404
524
  readonly "ai.write": "Write it";
405
525
  readonly "ai.change": "Change it";
406
526
  readonly "ai.fill": "Fill it in";
@@ -415,6 +535,8 @@ export declare const defaultStrings: {
415
535
  readonly "ai.provider.manual": "Copy and paste into a chat app (free)";
416
536
  readonly "ai.provider.custom": "Another service (OpenAI-compatible)";
417
537
  readonly "ai.noKey": "(free, no key needed)";
538
+ readonly "ai.siteKey": "(provided by this site)";
539
+ readonly "ai.siteKeyHint": "This site provides {service}, so you don't need a key of your own.";
418
540
  readonly "ai.baseUrl": "Address of the service's API";
419
541
  readonly "ai.baseUrlHint": "Such as https://example.org/v1.";
420
542
  readonly "ai.key": "API key";
@@ -445,11 +567,14 @@ export declare const defaultStrings: {
445
567
  readonly "appearance.light": "Light";
446
568
  readonly "appearance.dark": "Dark";
447
569
  readonly "settings.tab.general": "General";
570
+ readonly "settings.tab.languages": "Languages";
448
571
  readonly "settings.tab.theme": "Colors & fonts";
449
572
  readonly "settings.tab.menus": "Menus";
450
573
  readonly "settings.tab.css": "Custom CSS";
451
574
  readonly "settings.tab.code": "Code";
452
575
  readonly "settings.tab.redirects": "Redirects";
576
+ readonly "settings.sidebar.collapse": "Hide names";
577
+ readonly "settings.sidebar.expand": "Show names";
453
578
  readonly "settings.tab.domain": "Domain";
454
579
  readonly "settings.message": "Update site settings";
455
580
  readonly "settings.preview": "Preview of the home page";
@@ -459,9 +584,41 @@ export declare const defaultStrings: {
459
584
  readonly "domain.connected": "Connected to {domain}.";
460
585
  readonly "domain.signIn": "Editors sign in by going to {host} and coming back here. Add {address} to the redirect URIs of the application they sign in with on {host}, or signing in will stop working once the site moves.";
461
586
  readonly "domain.local": "Domains are connected in the published site's admin panel, where you sign in with GitHub or GitLab.";
587
+ readonly "domain.admin.title": "Admin address";
588
+ readonly "domain.admin.intro": "An address of its own for the admin panel, such as admin.example.org, keeps the site's pages from reaching what the admin panel keeps, such as editors' sign-in. Editors then sign in there. Blocks from registries other than Goodfellow's can be added only once the admin panel has one.";
589
+ readonly "domain.admin.unsupported": "{host} serves each site at one address, so the admin panel can't have one of its own here.";
590
+ readonly "domain.admin.contentOnly": "The admin panel's address is named in the site's code, which is kept apart from its content, so a developer finishes setting it up there.";
591
+ readonly "domain.admin.code.unknown": "This sign-in can't read the site's code, so this screen can't tell whether a developer has done that yet.";
592
+ readonly "domain.admin.code.add": "A developer names the address in the site's code, then publishes the code:";
593
+ readonly "domain.admin.code.check": "Once the site has been rebuilt and works at {address}, the developer checks the address. From the next rebuild, the admin panel opens only there:";
594
+ readonly "domain.admin.code.remove": "To stop using it, a developer removes it from the site's code:";
595
+ readonly "domain.admin.noConfig": "The admin panel's address goes in the site's {file}, which doesn't have the usual defineConfig({ for it, so a developer sets it there.";
596
+ readonly "domain.admin.noSiteAddress": "Connect the site's own domain first: the admin address goes beside it.";
597
+ readonly "domain.admin.field": "Admin address";
598
+ readonly "domain.admin.fieldHint": "An address of the site's domain that nothing else uses, such as admin.example.org.";
599
+ readonly "domain.admin.connect": "Give the admin panel this address";
600
+ readonly "domain.admin.elsewhere": "Add this address to the site's project on {host} too, under its custom domains, so it serves the site there as well. The site's build writes {host}'s rules for it.";
601
+ readonly "domain.admin.message": "Give the admin panel its own address, {domain}";
602
+ readonly "domain.admin.connected": "The admin panel's address is {domain}";
603
+ readonly "domain.admin.ready": "The site answers at {address}. Open the admin panel there to start using it.";
604
+ readonly "domain.admin.pending": "Once the site has been rebuilt and works at {address}, open the admin panel there: it checks the address, then you can start using it.";
605
+ readonly "domain.admin.open": "Open the admin panel at {address}";
606
+ readonly "domain.admin.signInAgain": "You'll sign in again there: it's a different address, so it keeps its own sign-in.";
607
+ readonly "domain.admin.checking": "Checking {address}…";
608
+ readonly "domain.admin.check.ok": "The site's pages at this address leave it for the site's own address before running anything.";
609
+ readonly "domain.admin.check.no-guard": "The site's pages at this address don't start with the script that sends them away yet. Wait for the site to be rebuilt, then check again.";
610
+ readonly "domain.admin.check.no-sandbox": "{host} doesn't sandbox the site's pages at this address yet. Wait for the site to be rebuilt, then check again.";
611
+ readonly "domain.admin.check.failed": "The admin panel couldn't check this address. Try again in a moment.";
612
+ readonly "domain.admin.use": "Start using {address}";
613
+ readonly "domain.admin.useMessage": "Open the admin panel only at {domain}";
614
+ readonly "domain.admin.using": "The admin panel's address is {address}. Once the site has been rebuilt, the admin panel opens only there.";
615
+ readonly "domain.admin.stop": "Stop using the admin address";
616
+ readonly "domain.admin.stopMessage": "Stop using {domain} for the admin panel";
617
+ readonly "domain.admin.stopBody": "The admin panel goes back to the site's own address once the site has been rebuilt, and blocks from registries other than Goodfellow's can't be added there.";
462
618
  readonly "domain.demo": "Domains can't be connected in a demo.";
463
619
  readonly "domain.loading": "Finding where the site is published…";
464
620
  readonly "domain.elsewhere": "This site isn't published with {host} Pages, so its domain is connected where it is published.";
621
+ readonly "domain.elsewhere.cloudflare": "On Cloudflare Pages: open the site's project, go to Custom domains and set up the domain. For a domain such as www.example.org, Cloudflare shows a CNAME record to add at your registrar. For example.org itself, the domain has to use Cloudflare's nameservers, which Cloudflare walks you through. Once the site works at its new address, put the address in General → Site address and publish.";
465
622
  readonly "domain.elsewhere.vercel": "On Vercel: open the site's project, go to Settings → Domains and add the domain. Vercel shows the records to add at your registrar. Once the site works at its new address, put the address in General → Site address and publish.";
466
623
  readonly "domain.field": "Domain";
467
624
  readonly "domain.fieldHint": "Such as example.org or www.example.org. Buy one first from a registrar, such as Cloudflare, Namecheap or Porkbun. For example.org, www.example.org works too.";
@@ -517,6 +674,75 @@ export declare const defaultStrings: {
517
674
  readonly "owner.token.create": "Create an owner token on {host}";
518
675
  readonly "owner.token.chooseSite": "If {host} asks what the token can access, choose only this site.";
519
676
  readonly "owner.token.paste": "Copy the token and paste it here:";
677
+ readonly "settings.tab.security": "Security";
678
+ readonly "security.demo": "A demo has no sign-in to keep safe.";
679
+ readonly "security.local": "The sign-in page is set in the published site's admin panel, where you sign in with GitHub or GitLab.";
680
+ readonly "security.contentOnly": "The sign-in page is set in the site's code, which is kept apart from its content, so a developer sets it there.";
681
+ readonly "security.broker.title": "Sign-in page";
682
+ readonly "security.broker.intro": "A sign-in page of its own, called a token broker, keeps editors' sign-in and AI keys at an address that holds nothing else, and shows the admin panel from there, so nothing on the site's pages can reach them. It asks the editor before publishing the site's code. It needs an address of its own, such as signin.example.org, that serves nothing else.";
683
+ readonly "security.broker.useTitle": "Sign in at a sign-in page that's online";
684
+ readonly "security.add.title": "Add a token broker";
685
+ readonly "security.add.intro": "Makes a project on {host} for the sign-in page, from Goodfellow's released files and a file naming this site, and puts it online where you choose. It needs a token that can create projects, used only now and kept only until you leave this screen.";
686
+ readonly "security.add.place": "Where to put it online";
687
+ readonly "security.add.place.gitlab-pages": "GitLab Pages, at an address GitLab gives it";
688
+ readonly "security.add.place.gitlab-domain": "GitLab Pages, at a domain of yours";
689
+ readonly "security.add.place.github-domain": "GitHub Pages, at a domain of yours";
690
+ readonly "security.add.place.cloudflare-pages": "Cloudflare Pages";
691
+ readonly "security.add.place.vercel": "Vercel";
692
+ readonly "security.add.placeHint.gitlab-pages": "GitLab gives the project an address of its own, which nothing else shares. You'll find it under Deploy → Pages once it's online. No domain needed.";
693
+ readonly "security.add.placeHint.gitlab-domain": "A domain, or part of one, such as signin.example.org, that you point at GitLab Pages as its settings say.";
694
+ readonly "security.add.placeHint.github-domain": "GitHub Pages' own addresses are shared by every site in the same account, so the sign-in page needs a domain, or part of one, such as signin.example.org, which you point at GitHub Pages as its settings say.";
695
+ readonly "security.add.placeHint.cloudflare-pages": "Free, with an address of its own. Once the project is made, connect it to a Cloudflare Pages project, as its README says.";
696
+ readonly "security.add.placeHint.vercel": "Free for non-commercial sites, with an address of its own. Once the project is made, import it as a Vercel project, as its README says.";
697
+ readonly "security.add.domain": "Its domain";
698
+ readonly "security.add.domainHint": "Such as signin.example.org. It serves only the sign-in page.";
699
+ readonly "security.add.problem.domain": "That doesn't look like a domain, such as signin.example.org.";
700
+ readonly "security.add.problem.shared": "github.io and gitlab.io addresses are shared with other sites, so the sign-in page needs a domain of yours.";
701
+ readonly "security.add.problem.site": "That's the site's own address. The sign-in page needs one of its own.";
702
+ readonly "security.add.name": "Its name on {host}";
703
+ readonly "security.add.nameHint": "Made beside the site's on {host}, public, since it holds only Goodfellow's released files.";
704
+ readonly "security.add.token": "A {host} token that can create projects";
705
+ readonly "security.add.tokenHint": "Used only to make the project, and forgotten when you leave this screen.";
706
+ readonly "security.add.create": "Make the sign-in page";
707
+ readonly "security.add.creating": "Making the sign-in page…";
708
+ readonly "security.add.description": "The sign-in page for {site}";
709
+ readonly "security.add.message": "Add the sign-in page";
710
+ readonly "security.add.error.token": "{host} didn't accept that token. Check it's whole, and that it can create projects.";
711
+ readonly "security.add.error.owner": "That token can't create projects anywhere.";
712
+ readonly "security.add.error.taken": "There's already a project with that name. Choose another.";
713
+ readonly "security.add.error.release": "Goodfellow's sign-in page couldn't be downloaded. Try again in a moment.";
714
+ readonly "security.add.error.name-taken": "There's already a project with that name. Choose another.";
715
+ readonly "security.add.error.name-invalid": "{host} can't use that as a project's name.";
716
+ readonly "security.add.error.not-allowed": "That token can't create projects, or write their build setup.";
717
+ readonly "security.add.error.not-installed": "{host} needs the sign-in service's app installed there first.";
718
+ readonly "security.add.error.other": "The sign-in page couldn't be made.";
719
+ readonly "security.added.project": "The sign-in page's project is ready:";
720
+ readonly "security.added.github-pages": "GitHub Pages is putting it online. Point the domain at GitHub Pages as the project's Settings → Pages says, and wait for its certificate.";
721
+ readonly "security.added.gitlab-pages": "GitLab Pages is putting it online. Its address is under Deploy → Pages in the new project once the first build has finished.";
722
+ readonly "security.added.cloudflare-pages": "Now connect it to Cloudflare Pages: create a Pages project from the project, with no build command and site as the output directory.";
723
+ readonly "security.added.vercel": "Now import it as a Vercel project, with site as its root directory and no build command.";
724
+ readonly "security.added.domainUnavailable": "The domain couldn't be set for it, perhaps because another site has it. Set it in the project's Pages settings.";
725
+ readonly "security.added.pagesUnavailable": "Pages couldn't be turned on for it. Turn them on in the project's settings.";
726
+ readonly "security.added.address": "Once it opens at {address}, choose Sign in there from now on below. Editors sign in there once the site has rebuilt.";
727
+ readonly "security.added.then": "Once it's online, type its address below and choose Sign in there from now on. Editors sign in there once the site has rebuilt.";
728
+ readonly "security.broker.field": "The sign-in page's address";
729
+ readonly "security.broker.fieldHint": "An address that serves only the token broker, such as https://broker.example.org.";
730
+ readonly "security.broker.problem.invalid": "That needs to be an https:// address of its own, with nothing after the domain.";
731
+ readonly "security.broker.problem.shared": "github.io addresses are shared with every site in the same account, so the sign-in page needs a domain of its own.";
732
+ readonly "security.broker.problem.same-as-site": "That's the site's own address. The sign-in page needs one of its own.";
733
+ readonly "security.broker.redirect": "Editors sign in by going to {host} and coming back, so add the sign-in page's address to the redirect URIs of the application they sign in with on {host}.";
734
+ readonly "security.broker.use": "Sign in there from now on";
735
+ readonly "security.broker.message": "Sign in at {address}";
736
+ readonly "security.broker.error": "The sign-in page's address couldn't be published.";
737
+ readonly "security.broker.pending": "Once the site has been rebuilt, editors sign in at {address}, and the admin panel opens only there.";
738
+ readonly "security.broker.using": "Editors sign in at {address}, which keeps their sign-in and shows the admin panel.";
739
+ readonly "security.broker.stop": "Stop using the sign-in page";
740
+ readonly "security.broker.stopMessage": "Stop signing in at {address}";
741
+ readonly "security.broker.stopBody": "Once the site has been rebuilt, editors sign in in the admin panel itself again, which keeps their sign-in where the site's pages could reach it, and blocks from registries other than Goodfellow's can't be added there.";
742
+ readonly "broker.elsewhere": "Editors sign in to this site on its own sign-in page, which keeps your sign-in where the site's pages can't reach it, and shows the admin panel from there.";
743
+ readonly "broker.open": "Go to the sign-in page";
744
+ readonly "broker.invalid": "The site's config names a sign-in page whose address isn't one of its own, so the admin panel can't open. Ask the site's developer to fix the broker address in goodfellow.config.tsx.";
745
+ readonly "owner.token.pasteInBroker": "Copy the token, choose Use this token, and paste it where the sign-in page around the admin panel asks for it. It keeps the token, so the admin panel never sees it.";
520
746
  readonly "owner.token.label": "Owner token";
521
747
  readonly "owner.token.use": "Use this token";
522
748
  readonly "owner.token.checking": "Checking…";
@@ -525,7 +751,116 @@ export declare const defaultStrings: {
525
751
  readonly "owner.token.forget": "Stop using it";
526
752
  readonly "settings.tab.editors": "Editors";
527
753
  readonly "settings.tab.updates": "Updates";
754
+ readonly "settings.tab.history": "Site history";
755
+ readonly "settings.tab.import": "WordPress";
756
+ readonly "siteHistory.intro": "Every publish that changed the site's pages, items, settings or media, newest first. Choose one to see how the site looked after it and what has changed since, then restore the whole site as it was.";
757
+ readonly "siteHistory.local": "Site history is in the published site's admin panel, where you sign in with GitHub or GitLab.";
758
+ readonly "siteHistory.demo": "Site history isn't kept in a demo.";
759
+ readonly "siteHistory.error": "The site's history couldn't be loaded. Check your connection and try again.";
760
+ readonly "siteHistory.current": "The site now";
761
+ readonly "siteHistory.preview": "The site on {date}";
762
+ readonly "siteHistory.loadingVersion": "Loading the site as it was…";
763
+ readonly "siteHistory.readError": "The site as it was then couldn't be loaded. Check your connection and try again.";
764
+ readonly "siteHistory.unfit": "The site as it was then can't be shown or restored, because it doesn't fit the site as it is now.";
765
+ readonly "siteHistory.unknownBlocks": "The site as it was then can't be restored, because it uses blocks the site doesn't have any more: {blocks}.";
766
+ readonly "siteHistory.same": "Nothing has changed since: this is how the site is now.";
767
+ readonly "siteHistory.since": "What has changed since";
768
+ readonly "siteHistory.changed": "Changed since (restoring puts them back as they were):";
769
+ readonly "siteHistory.added": "Added since (restoring removes them):";
770
+ readonly "siteHistory.removed": "Removed since (restoring brings them back):";
771
+ readonly "siteHistory.page": "Page to look at";
772
+ readonly "siteHistory.restore": "Restore the site as it was";
773
+ readonly "siteHistory.restoreTitle": "Restore the site as it was on {date}?";
774
+ readonly "siteHistory.restoreBody": "Everything listed ({count}) goes back to how it was then, in one publish. The site as it is now stays in the history, so you can restore it again later. The site's code and update settings stay as they are.";
775
+ readonly "siteHistory.restoreMessage": "Restore the site as it was on {date}";
776
+ readonly "siteHistory.restored": "Restored the site as it was on {date}.";
777
+ readonly "siteHistory.subject.page": "{title} ({path})";
778
+ readonly "siteHistory.subject.entry": "{title}, in {collection}";
779
+ readonly "siteHistory.subject.collection": "The settings and page design of {name}";
780
+ readonly "siteHistory.subject.settings": "Site settings";
781
+ readonly "siteHistory.subject.menus": "Menus";
782
+ readonly "siteHistory.subject.redirects": "Redirects";
783
+ readonly "siteHistory.subject.credits": "Photo credits";
784
+ readonly "siteHistory.subject.css": "Custom CSS";
785
+ readonly "siteHistory.subject.code": "Code added to every page";
786
+ readonly "import.title": "Import from WordPress";
787
+ readonly "import.intro": "Bring a WordPress site's pages, posts, categories, tags, menus and media into this site. Pages keep their addresses, posts become a collection, and old addresses send visitors on to the new ones. You'll see what comes across before anything changes.";
788
+ readonly "import.file": "WordPress export file";
789
+ readonly "import.fileHint": "In WordPress, go to Tools → Export, choose All content and download the file. It brings the menus too.";
790
+ readonly "import.address": "Or the WordPress site's address";
791
+ readonly "import.addressHint": "Such as example.org. Only what visitors can see is read, and menus can't be.";
792
+ readonly "import.read": "Read the site";
793
+ readonly "import.reading": "Reading the WordPress site…";
794
+ readonly "import.ready": "Ready to import {title}";
795
+ readonly "import.summary.pages": "Pages: {count}";
796
+ readonly "import.summary.posts": "Posts: {count}, at {path}, listed on {list}";
797
+ readonly "import.summary.terms": "Categories: {categories}. Tags: {tags}.";
798
+ readonly "import.summary.menus": "Menus: {count}";
799
+ readonly "import.summary.redirects": "Old addresses sent on to new ones: {count}";
800
+ readonly "import.summary.media": "Media files to copy: {count}";
801
+ readonly "import.replaces": "These pages will be replaced: {pages}. Version history keeps the old ones.";
802
+ readonly "import.check": "Check these";
803
+ readonly "import.start": "Import";
804
+ readonly "import.another": "Choose another site";
805
+ readonly "import.copying": "Copying media: {done} of {total}…";
806
+ readonly "import.publishing": "Publishing…";
807
+ readonly "import.done": "Imported {title}.";
808
+ readonly "import.viewPages": "See the pages";
809
+ readonly "import.message": "Import {title} from WordPress";
810
+ readonly "import.note.leftOut.drafts": "Drafts and pages waiting for review aren't imported: {count}.";
811
+ readonly "import.note.leftOut.private": "Private pages and posts aren't imported: {count}.";
812
+ readonly "import.note.leftOut.trash": "Pages and posts in the trash aren't imported: {count}.";
813
+ readonly "import.note.leftOut.comments": "Comments aren't imported: {count}.";
814
+ readonly "import.note.leftOut.other": "WordPress content of the kind “{name}” isn't imported: {count}.";
815
+ readonly "import.note.more": "{items} and {count} more";
816
+ readonly "import.note.block": "The “{name}” block, from another plugin, was brought across as its text and pictures: {items}.";
817
+ readonly "import.note.dropped": "The “{name}” block shows something only WordPress has, so it was left out: {items}.";
818
+ readonly "import.note.shortcode": "The [{name}] shortcode only works in WordPress, so it was left out: {items}.";
819
+ readonly "import.note.embed": "Something from {name} is now a link to it: {items}.";
820
+ readonly "import.note.form": "A form was left out. Add a Form block in its place: {items}.";
821
+ readonly "import.note.script": "A script was left out: {items}.";
822
+ readonly "import.note.table": "A table became text, since pages have no tables: {items}.";
823
+ readonly "import.note.posts": "A list of the latest posts was left out of a post: {items}.";
824
+ readonly "import.note.video": "A video is now a link to it, since posts show videos as links: {items}.";
825
+ readonly "import.note.oldLinks": "Links that still go to the WordPress site, to pages that weren't imported: {count}, such as {url}.";
826
+ readonly "import.note.notCopied": "Media files that couldn't be copied, so pages show them from the WordPress site until you upload them: {count}, such as {url}. Most WordPress sites don't let other sites download their files; importing from the command line copies them.";
827
+ readonly "import.note.unusedMedia": "Files in the media library that no page or post uses, so they weren't copied: {count}.";
828
+ readonly "import.note.noMenus": "Menus can't be read from a site's address, so the main menu lists the top-level pages. Import an export file to bring the menus across.";
829
+ readonly "import.error.notExportFile": "That file isn't a WordPress export file. In WordPress, go to Tools → Export, choose All content and download the file.";
830
+ readonly "import.error.unreachable": "The WordPress site couldn't be read from {address}. Check the address. Some sites don't let other sites read them; then import an export file instead.";
831
+ readonly "import.error.notWordPress": "{address} doesn't look like a WordPress site, or its API is turned off. Import an export file instead.";
832
+ readonly "import.error.blocks": "Importing needs blocks this site doesn't have: {blocks}. A developer can add Goodfellow's built-in blocks to goodfellow.config.tsx.";
833
+ readonly "import.error.other": "The WordPress site couldn't be imported.";
834
+ readonly "import.names.posts": "Posts";
835
+ readonly "import.names.post": "Post";
836
+ readonly "import.names.title": "Title";
837
+ readonly "import.names.date": "Date";
838
+ readonly "import.names.summary": "Summary";
839
+ readonly "import.names.image": "Image";
840
+ readonly "import.names.categories": "Categories";
841
+ readonly "import.names.tags": "Tags";
842
+ readonly "import.names.text": "Text";
843
+ readonly "import.names.allPosts": "All posts";
844
+ readonly "export.title": "Export to WordPress";
845
+ readonly "export.intro": "Download this site as a WordPress export file, to move it to WordPress or keep a copy. Pages keep their addresses and blocks become WordPress's, items of collections with pages become posts, and menus come too. In WordPress, go to Tools → Import → WordPress, choose the file and tick “Download and import file attachments”, which copies the media from this site.";
846
+ readonly "export.download": "Download the export file";
847
+ readonly "export.done": "Downloaded the export file.";
848
+ readonly "export.error": "The export file couldn't be made.";
849
+ readonly "export.summary.pages": "Pages: {count}";
850
+ readonly "export.summary.posts": "Posts: {count}";
851
+ readonly "export.summary.media": "Media files, which WordPress copies from this site: {count}";
852
+ readonly "export.note.leftOutBlock": "WordPress has nothing like the {name} block, so it was left out: {items}.";
853
+ readonly "export.note.noPages": "Collections without pages of their own aren't in the file, since WordPress's posts each have a page: {collections}.";
854
+ readonly "export.note.hidden": "Hidden by their schedule, so they're drafts in the file: {items}.";
855
+ readonly "export.note.redirects": "Redirects aren't in the file, since WordPress needs a plugin for them: {count}.";
856
+ readonly "export.note.noAddress": "The site has no address yet, so WordPress can't copy its media. Set it on the General tab, then download the file again.";
857
+ readonly "export.note.layout": "The header and footer aren't in the file. WordPress's theme shows its own, with the menus.";
528
858
  readonly "updates.intro": "Goodfellow, the software the site runs on, gets fixes and new releases. Fixes install on their own each night, and only once the site has been checked to still build with them.";
859
+ readonly "updates.contentOnly": "This site's code is kept apart from its content, so Goodfellow's updates are installed where the code is, by the site's own setup or its developer.";
860
+ readonly "updates.code": "This site's code is kept apart from its content, at {place}, where Goodfellow's updates are installed.";
861
+ readonly "updates.code.link": "See it on {host}";
862
+ readonly "updates.noAccess": "Your sign-in can't see the site's code, at {place}, where its updates are installed. Ask the site's developer to give you access to it. On GitHub, an access token needs to reach it too, or with Sign in with GitHub, Goodfellow's app needs to be installed there.";
863
+ readonly "updates.back.skip": "Don't install Goodfellow {version} again";
529
864
  readonly "updates.local": "Updates are shown in the published site's admin panel, where you sign in with GitHub or GitLab.";
530
865
  readonly "updates.demo": "Updates aren't installed in a demo.";
531
866
  readonly "updates.loading": "Checking for updates…";
@@ -557,6 +892,19 @@ export declare const defaultStrings: {
557
892
  readonly "updates.skipped.body": "The site went back from these releases, so updates don't install them.";
558
893
  readonly "updates.skipped.allow": "Allow {version} again";
559
894
  readonly "updates.skipped.allowMessage": "Allow Goodfellow {version} again";
895
+ readonly "updates.nightly.title": "Rebuilding overnight";
896
+ readonly "updates.nightly.body": "Each night, the site can check whether it needs rebuilding: for fixes to install, for something it shows by today's date, such as an event's next date, or for a change that didn't go live.";
897
+ readonly "updates.nightly.changed": "Only when something changed (recommended)";
898
+ readonly "updates.nightly.always": "Every night";
899
+ readonly "updates.nightly.off": "Never";
900
+ readonly "updates.nightly.offWarning": "Without rebuilding overnight, what the site shows by today's date, such as lists of what's coming up, goes out of date, and fixes aren't installed on their own. The site is still rebuilt each time someone publishes.";
901
+ readonly "updates.nightly.unknown": "Sign in as an owner to see or change this.";
902
+ readonly "updates.nightly.onlyOwners": "Only the site's owners can change this.";
903
+ readonly "updates.nightly.old": "This site's build setup rebuilds it every night. A newer setup from Goodfellow's starter rebuilds it only when something changed.";
904
+ readonly "updates.rebuild.title": "Rebuild the site";
905
+ readonly "updates.rebuild.body": "Rebuilds the site now without publishing anything, so the live site catches up, such as with another calendar's events or after a rebuild that didn't work.";
906
+ readonly "updates.rebuild.button": "Rebuild now";
907
+ readonly "updates.rebuild.started": "The site is rebuilding. It takes a few minutes.";
560
908
  readonly "updates.started": "The update has started. It takes a few minutes, and this page shows how it went.";
561
909
  readonly "updates.last.title": "Last update";
562
910
  readonly "updates.last.none": "No update has run yet.";
@@ -624,6 +972,21 @@ export declare const defaultStrings: {
624
972
  readonly "general.externalLinksInNewTabHint": "Applies to every link to another site: menus, buttons and text. Links to this site's own pages open in the same tab.";
625
973
  readonly "general.language": "Language code";
626
974
  readonly "general.languageHint": "Such as en, es or fr.";
975
+ readonly "languages.intro": "A site can be in several languages. Pages in the main language are at the site's address; each other language's pages are under its code, such as /es/about for Spanish. Translate a page from the Pages screen once its language is published.";
976
+ readonly "languages.main": "Main language";
977
+ readonly "languages.mainCodeHintUsed": "It can't change while the site has pages or items in other languages.";
978
+ readonly "languages.name": "Name";
979
+ readonly "languages.nameHint": "How the language switch names it, in that language.";
980
+ readonly "languages.new": "New language";
981
+ readonly "languages.add": "Add a language";
982
+ readonly "languages.remove": "Remove this language";
983
+ readonly "languages.codeHint": "Such as es, fr or pt-BR. Its pages go under its code, so it can't change once published.";
984
+ readonly "languages.codeHintPublished": "Its pages are under {prefix}.";
985
+ readonly "languages.codeInvalid": "Use a language code, such as es, fr or pt-BR.";
986
+ readonly "languages.codeTaken": "The site has this language already.";
987
+ readonly "languages.codeUsed": "The site has pages under {prefix} already. Move them first, or choose another code.";
988
+ readonly "languages.titleHint": "The site's title in this language. Left empty, it's the main language's.";
989
+ readonly "languages.inUse": "Pages and items in this language: {count}. To remove the language, delete them or move them out of it first.";
627
990
  readonly "general.timeZone": "Time zone";
628
991
  readonly "general.timeZoneHint": "The time zone events' times are in. Calendars show them in it, and visitors' calendars move them to their own.";
629
992
  readonly "general.timeZoneNone": "Not set: times are shown as they're written";
@@ -716,6 +1079,31 @@ export declare const defaultStrings: {
716
1079
  readonly "menus.moveUp": "Move up";
717
1080
  readonly "menus.moveDown": "Move down";
718
1081
  readonly "menus.remove": "Remove";
1082
+ readonly "translations.add": "Translate into {language}";
1083
+ readonly "translations.current": "Up to date";
1084
+ readonly "translations.outdated": "Out of date";
1085
+ readonly "translations.optionOutdated": "{language} (out of date)";
1086
+ readonly "translations.optionAdd": "Translate into {language}…";
1087
+ readonly "translations.markCurrent": "Mark as up to date";
1088
+ readonly "translations.markCurrentMessage": "Mark {title} as up to date";
1089
+ readonly "translations.of": "Translation of {title}";
1090
+ readonly "translations.entryOf": "In {language}: translation of {title}";
1091
+ readonly "translations.inLanguage": "In {language}";
1092
+ readonly "translations.addTitle": "Translate {title} into {language}";
1093
+ readonly "translations.addHint": "This makes a copy in {language} for you to translate. The Pages screen says when the original changes, so you know to update the translation.";
1094
+ readonly "translations.addSubmit": "Make the copy";
1095
+ readonly "translations.addMessage": "Translate {title} into {language}";
1096
+ readonly "translations.addressLanguage": "Pages in {language} have addresses starting with {prefix}.";
1097
+ readonly "menus.language": "Language";
1098
+ readonly "layout.languageShared": "Pages in {language} show the main language's {part}, with {language}'s menus and site name. Give {language} a {part} of its own to translate the words typed into it.";
1099
+ readonly "layout.languageCopy": "Make a {part} for {language}";
1100
+ readonly "layout.languageCopyMessage": "Make a {part} for {language}";
1101
+ readonly "layout.languageRemove": "Use the main language's {part}";
1102
+ readonly "layout.languageRemoveMessage": "Use the main language's {part} in {language}";
1103
+ readonly "layout.languageMessage": "Update the {part} in {language}";
1104
+ readonly "newEntry.languageHint": "For an item only in this language. To translate an item, use “Translate into” in the list instead, which keeps the two linked.";
1105
+ readonly "menus.ownMenus": "{language} has menus of its own";
1106
+ readonly "menus.sameAsMain": "Pages in {language} show the main language's menus. Give it menus of its own to translate their links' names and point them at its pages.";
719
1107
  readonly "menus.newLink": "New link";
720
1108
  readonly "redirects.intro": "Send visitors from an old address to a new one, such as when a page has moved or this site replaced an older one. Search engines move the old address's place in their results to the new one.";
721
1109
  readonly "redirects.empty": "There are no redirects yet.";
@@ -750,18 +1138,61 @@ export declare const defaultStrings: {
750
1138
  type StringKey = keyof typeof defaultStrings;
751
1139
  type Strings = Record<StringKey, string>;
752
1140
  //#endregion
1141
+ //#region src/frame/host.d.ts
1142
+ /**
1143
+ * Where the admin panel loads the frames that run the site's code from: a
1144
+ * page of their own, such as `goodfellow build`'s `admin/editor/` or the
1145
+ * Next.js starter's `app/admin/editor/`, whose Content Security Policy
1146
+ * (`editorPolicy()`) runs only the site's scripts. Every frame is sandboxed
1147
+ * with `allow-scripts` alone, development servers' too, so the site's code
1148
+ * never shares the admin panel's origin.
1149
+ */
1150
+ interface FrameSource {
1151
+ url: string;
1152
+ }
1153
+ /**
1154
+ * Loads every frame's page again, as development servers do once they've
1155
+ * rebuilt the frames with the site's new code, leaving the admin panel's own
1156
+ * page, and whatever is open on it, as it is. Frames reloaded this way
1157
+ * don't count towards the times one may start again.
1158
+ */
1159
+ export declare function reloadFrames(): void;
1160
+ //#endregion
1161
+ //#region src/admin-context.d.ts
1162
+ /** How previews load the site's styles. Provided by the dev server or build that serves the admin panel. */
1163
+ interface PreviewOptions {
1164
+ /** The site's compiled stylesheets. */
1165
+ stylesheets: string[];
1166
+ /** `@goodfellow-cms/react/theme.css`, so Tailwind in the preview knows the theme's classes. */
1167
+ themeCss: string;
1168
+ /** URL of `@tailwindcss/browser`, which styles classes typed in the editor before the site is rebuilt. */
1169
+ tailwindBrowserUrl?: string;
1170
+ }
1171
+ //#endregion
753
1172
  //#region src/admin.d.ts
754
1173
  interface AdminProps {
755
- /** The site's `goodfellow.config.tsx`. */
756
- config: GoodfellowConfig;
1174
+ /**
1175
+ * What the admin panel knows about the site, made from its config where the
1176
+ * config is loaded (`adminSite()` in `@goodfellow-cms/react`): the admin
1177
+ * panel's own page never loads the config, whose blocks run in sandboxed frames.
1178
+ */
1179
+ site: AdminSite;
1180
+ /** Where those frames are loaded from. */
1181
+ frames: FrameSource;
757
1182
  /**
758
1183
  * Where the site's files are read from and published to, without signing in.
759
1184
  * Used by `goodfellow dev`. Otherwise the admin panel signs in to `host`. In a
760
1185
  * demo (the config's `demo`), it's only read from, and changes stay in the browser.
761
1186
  */
762
1187
  store?: ContentStore;
763
- /** The git host the site is stored on. Defaults to the config's `backend`. */
1188
+ /** The git host the site is stored on, made from `site.backend`. */
764
1189
  host?: GitHost;
1190
+ /**
1191
+ * The address of a full-stack site's API for the admin panel, such as
1192
+ * `/api/goodfellow`. Editors then sign in with the site's own sign-in, and its
1193
+ * server reads and publishes for them, so they never sign in to the git host.
1194
+ */
1195
+ server?: string;
765
1196
  preview: PreviewOptions;
766
1197
  /** The live site's address, for "View" links. Defaults to `/`. */
767
1198
  siteUrl?: string;
@@ -774,8 +1205,24 @@ interface AdminProps {
774
1205
  registry?: string;
775
1206
  }
776
1207
  /** The admin panel. Render it on its own page, such as `/admin`. */
777
- export declare function Admin({ config, store, host, preview, siteUrl, strings, registry }: AdminProps): import("react").JSX.Element;
1208
+ export declare function Admin({ site, frames, store, host, server, preview, siteUrl, strings, registry }: AdminProps): import("react").JSX.Element;
778
1209
  /** Renders the admin panel into an element. */
779
1210
  export declare function mountAdmin(element: Element, props: AdminProps): void;
780
1211
  //#endregion
781
- export type { AdminProps, PreviewOptions, StringKey, Strings };
1212
+ //#region src/outdated-admin-page.d.ts
1213
+ interface OutdatedAdminPageProps {
1214
+ /** The files to change, as the starter now has them. */
1215
+ files: readonly {
1216
+ path: string;
1217
+ content: string;
1218
+ }[];
1219
+ strings?: Partial<Strings>;
1220
+ }
1221
+ /**
1222
+ * Shown in place of the admin panel by an admin page from before the editor's
1223
+ * frames, which loads the site's config, and every block's code, itself. It
1224
+ * never signs in, so no block's code runs beside a token.
1225
+ */
1226
+ export declare function OutdatedAdminPage({ files, strings }: OutdatedAdminPageProps): import("react").JSX.Element;
1227
+ //#endregion
1228
+ export type { AdminProps, FrameSource, OutdatedAdminPageProps, PreviewOptions, StringKey, Strings };