@acms-subir/core 0.3.3-next.20260929113209 → 0.3.3
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/CHANGELOG.md +69 -0
- package/cli/common.mjs +2 -2
- package/cli/legacy.mjs +1 -1
- package/cli/main.mjs +82 -8
- package/cli/mobile-doctor.mjs +430 -0
- package/cli/mobile.mjs +315 -0
- package/cli/native-sections.mjs +168 -0
- package/cli/sections.mjs +186 -0
- package/cli/themes.mjs +52 -8
- package/docs/mobile.md +527 -0
- package/docs/sections.md +203 -0
- package/docs/theme-authoring.md +1 -1
- package/integration.mjs +55 -1
- package/mobile-template/client/AGENTS.md +97 -0
- package/mobile-template/client/README.md +65 -0
- package/mobile-template/client/app/_layout.tsx +56 -0
- package/mobile-template/client/app/index.tsx +21 -0
- package/mobile-template/client/app.json +21 -0
- package/mobile-template/client/components/LanguageSwitch.tsx +31 -0
- package/mobile-template/client/eas.json +21 -0
- package/mobile-template/client/env.example +3 -0
- package/mobile-template/client/gitignore +42 -0
- package/mobile-template/client/package.json +38 -0
- package/mobile-template/client/sections/index.ts +11 -0
- package/mobile-template/client/tsconfig.json +7 -0
- package/mobile-template/editor/AGENTS.md +66 -0
- package/mobile-template/editor/README.md +73 -0
- package/mobile-template/editor/app/_layout.tsx +76 -0
- package/mobile-template/editor/app/auth.tsx +13 -0
- package/mobile-template/editor/app/index.tsx +55 -0
- package/mobile-template/editor/app.json +21 -0
- package/mobile-template/editor/components/Screen.tsx +51 -0
- package/mobile-template/editor/components/SignIn.tsx +36 -0
- package/mobile-template/editor/components/TextTabs.tsx +27 -0
- package/mobile-template/editor/eas.json +21 -0
- package/mobile-template/editor/env.example +5 -0
- package/mobile-template/editor/gitignore +42 -0
- package/mobile-template/editor/lib/appearance.tsx +12 -0
- package/mobile-template/editor/lib/config.ts +17 -0
- package/mobile-template/editor/lib/format.ts +24 -0
- package/mobile-template/editor/lib/tools.ts +16 -0
- package/mobile-template/editor/package.json +40 -0
- package/mobile-template/editor/tools/README.md +2 -0
- package/mobile-template/editor/tsconfig.json +7 -0
- package/mobile-template/recipes/booking/files/app/book/[resource].tsx +57 -0
- package/mobile-template/recipes/booking/files/app/book/details.tsx +105 -0
- package/mobile-template/recipes/booking/files/app/book/index.tsx +38 -0
- package/mobile-template/recipes/booking/recipe.json +17 -0
- package/mobile-template/recipes/editor-bookings/files/app/bookings/index.tsx +113 -0
- package/mobile-template/recipes/editor-bookings/files/tools/bookings.ts +1 -0
- package/mobile-template/recipes/editor-bookings/recipe.json +8 -0
- package/mobile-template/recipes/editor-content/files/app/content/[kind]/[id].tsx +13 -0
- package/mobile-template/recipes/editor-content/files/app/content/index.tsx +76 -0
- package/mobile-template/recipes/editor-content/files/components/DocumentDetail.tsx +55 -0
- package/mobile-template/recipes/editor-content/files/tools/content.ts +1 -0
- package/mobile-template/recipes/editor-content/recipe.json +8 -0
- package/mobile-template/recipes/editor-media/files/app/media/index.tsx +73 -0
- package/mobile-template/recipes/editor-media/files/tools/media.ts +1 -0
- package/mobile-template/recipes/editor-media/recipe.json +8 -0
- package/mobile-template/recipes/editor-sections/files/app/sections/[pageId]/[sectionId].tsx +12 -0
- package/mobile-template/recipes/editor-sections/files/app/sections/index.tsx +48 -0
- package/mobile-template/recipes/editor-sections/files/components/SectionEditorPane.tsx +77 -0
- package/mobile-template/recipes/editor-sections/files/tools/sections.ts +1 -0
- package/mobile-template/recipes/editor-sections/recipe.json +8 -0
- package/mobile-template/recipes/editor-translations/files/app/translations/index.tsx +63 -0
- package/mobile-template/recipes/editor-translations/files/app/translations/review.tsx +14 -0
- package/mobile-template/recipes/editor-translations/files/components/ReviewPane.tsx +72 -0
- package/mobile-template/recipes/editor-translations/files/tools/translations.ts +1 -0
- package/mobile-template/recipes/editor-translations/recipe.json +8 -0
- package/mobile-template/recipes/editor-writer/files/app/write/[kind]/[id].tsx +132 -0
- package/mobile-template/recipes/editor-writer/files/app/write/new.tsx +61 -0
- package/mobile-template/recipes/editor-writer/files/tools/writer.ts +1 -0
- package/mobile-template/recipes/editor-writer/recipe.json +8 -0
- package/mobile-template/recipes/events/files/app/events/[slug].tsx +41 -0
- package/mobile-template/recipes/events/files/app/events/index.tsx +50 -0
- package/mobile-template/recipes/events/files/lib/addToCalendar.ts +27 -0
- package/mobile-template/recipes/events/recipe.json +15 -0
- package/mobile-template/recipes/news/files/app/news/[slug].tsx +25 -0
- package/mobile-template/recipes/news/files/app/news/index.tsx +50 -0
- package/mobile-template/recipes/news/recipe.json +13 -0
- package/mobile-template/recipes/push/files/app/notifications.tsx +64 -0
- package/mobile-template/recipes/push/files/components/PushSetup.tsx +15 -0
- package/mobile-template/recipes/push/files/lib/push.ts +15 -0
- package/mobile-template/recipes/push/recipe.json +20 -0
- package/package.json +4 -2
- package/section-library/README.md +16 -0
- package/section-library/contact/Contact.astro +49 -0
- package/section-library/contact/section.json +106 -0
- package/section-library/cta-band/CtaBand.astro +28 -0
- package/section-library/cta-band/section.json +51 -0
- package/section-library/feature-grid/FeatureGrid.astro +54 -0
- package/section-library/feature-grid/section.json +107 -0
- package/section-library/gallery/Gallery.astro +29 -0
- package/section-library/gallery/section.json +51 -0
- package/section-library/hero-banner/HeroBanner.astro +42 -0
- package/section-library/hero-banner/section.json +81 -0
- package/section-library/latest-posts/LatestPosts.astro +22 -0
- package/section-library/latest-posts/section.json +37 -0
- package/section-library/pricing/Pricing.astro +40 -0
- package/section-library/pricing/section.json +117 -0
- package/section-library/questions/Questions.astro +38 -0
- package/section-library/questions/section.json +69 -0
- package/section-library/service-times/ServiceTimes.astro +42 -0
- package/section-library/service-times/section.json +127 -0
- package/section-library/team/Team.astro +45 -0
- package/section-library/team/section.json +88 -0
- package/section-library/upcoming-events/UpcomingEvents.astro +54 -0
- package/section-library/upcoming-events/section.json +51 -0
- package/src/actions/index.ts +19 -171
- package/src/components/AiSettings.tsx +45 -5
- package/src/components/DesignManager.tsx +112 -16
- package/src/components/MobileApp.tsx +392 -0
- package/src/components/ThemeSections.astro +40 -21
- package/src/components/ThemeSectionsEditor.tsx +757 -229
- package/src/components/editor/PostEditor.tsx +174 -10
- package/src/components/import/ImportExport.tsx +4 -1
- package/src/components/import/runner.ts +1 -0
- package/src/components/sections/SectionFields.tsx +560 -0
- package/src/components/sections/SectionPicker.tsx +120 -0
- package/src/components/sections/TranslationPanel.tsx +143 -0
- package/src/components/sections/checks.ts +64 -0
- package/src/layouts/Admin.astro +6 -1
- package/src/layouts/Site.astro +107 -19
- package/src/lib/agent-discovery.ts +11 -7
- package/src/lib/agent.ts +35 -2
- package/src/lib/ai-schema.ts +5 -0
- package/src/lib/ai.ts +5 -0
- package/src/lib/api.ts +12 -0
- package/src/lib/content-service.ts +198 -0
- package/src/lib/design-service.ts +153 -5
- package/src/lib/drafts.ts +3 -3
- package/src/lib/editor/auth.ts +264 -0
- package/src/lib/editor/bookings.ts +60 -0
- package/src/lib/editor/config.ts +25 -0
- package/src/lib/editor/documents.ts +227 -0
- package/src/lib/editor/http.ts +48 -0
- package/src/lib/editor/me.ts +30 -0
- package/src/lib/editor/migration.ts +57 -0
- package/src/lib/editor/next.ts +11 -0
- package/src/lib/editor/openapi.ts +124 -0
- package/src/lib/editor/schemas.ts +91 -0
- package/src/lib/editor/sections.ts +96 -0
- package/src/lib/editor/store.ts +184 -0
- package/src/lib/editor/token-route.ts +74 -0
- package/src/lib/editor/translations.ts +163 -0
- package/src/lib/email/accounts.ts +2 -1
- package/src/lib/email/types.ts +2 -1
- package/src/lib/home-source.ts +34 -0
- package/src/lib/i18n.ts +34 -0
- package/src/lib/import/apply.ts +31 -2
- package/src/lib/import/bundle.ts +4 -0
- package/src/lib/import/export.ts +1 -0
- package/src/lib/import/ghost.ts +1 -0
- package/src/lib/media/upload.ts +83 -0
- package/src/lib/mobile/associations.ts +25 -0
- package/src/lib/mobile/calendar.ts +19 -11
- package/src/lib/mobile/config.mjs +59 -0
- package/src/lib/mobile/content.ts +18 -13
- package/src/lib/mobile/embed.ts +83 -0
- package/src/lib/mobile/http.ts +8 -6
- package/src/lib/mobile/locale.ts +18 -0
- package/src/lib/mobile/openapi.ts +111 -0
- package/src/lib/mobile/pages.ts +71 -0
- package/src/lib/mobile/route.ts +3 -3
- package/src/lib/mobile/schemas.ts +60 -0
- package/src/lib/mobile/site.ts +41 -0
- package/src/lib/push/api.ts +158 -0
- package/src/lib/push/config.ts +20 -0
- package/src/lib/push/expo.ts +76 -0
- package/src/lib/push/migration.ts +60 -0
- package/src/lib/push/service.ts +302 -0
- package/src/lib/push/store.ts +144 -0
- package/src/lib/push/types.ts +43 -0
- package/src/lib/repo/migrations.ts +11 -0
- package/src/lib/repo/sql.ts +40 -7
- package/src/lib/repo/types.ts +9 -3
- package/src/lib/roles.ts +5 -0
- package/src/lib/scheduler.ts +3 -0
- package/src/lib/schema.ts +27 -1
- package/src/lib/section-ai.ts +55 -0
- package/src/lib/section-targets.ts +62 -0
- package/src/lib/sections-agent.ts +146 -0
- package/src/lib/sections.ts +217 -0
- package/src/lib/theme.ts +16 -1
- package/src/lib/translate.ts +97 -0
- package/src/lib/translations.ts +39 -0
- package/src/middleware.ts +32 -0
- package/src/pages/.well-known/apple-app-site-association.ts +6 -0
- package/src/pages/.well-known/assetlinks.json.ts +6 -0
- package/src/pages/[...slug].astro +8 -3
- package/src/pages/admin/app-auth.astro +159 -0
- package/src/pages/admin/c/[collection]/[id].astro +9 -0
- package/src/pages/admin/calendar/events/[id].astro +9 -0
- package/src/pages/admin/login.astro +6 -2
- package/src/pages/admin/logout.ts +5 -2
- package/src/pages/admin/mobile.astro +18 -0
- package/src/pages/admin/pages/[id].astro +62 -0
- package/src/pages/admin/section-preview.astro +61 -0
- package/src/pages/admin/users.astro +107 -11
- package/src/pages/api/agent/docs.ts +6 -2
- package/src/pages/api/agent/index.ts +1 -1
- package/src/pages/api/ai/settings.ts +7 -4
- package/src/pages/api/design/ai.ts +15 -0
- package/src/pages/api/design/index.ts +12 -42
- package/src/pages/api/design/translate.ts +15 -0
- package/src/pages/api/live/thumb.ts +1 -1
- package/src/pages/api/mcp.ts +2 -2
- package/src/pages/api/media/upload.ts +10 -78
- package/src/pages/api/push/admin.ts +27 -0
- package/src/pages/api/v1/admin/bookings/[id]/[action].ts +14 -0
- package/src/pages/api/v1/admin/bookings/index.ts +6 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/[action].ts +17 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/index.ts +20 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/translations/[locale]/[action].ts +26 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/translations/[locale]/index.ts +18 -0
- package/src/pages/api/v1/admin/documents/index.ts +13 -0
- package/src/pages/api/v1/admin/me.ts +6 -0
- package/src/pages/api/v1/admin/media/index.ts +32 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/index.ts +14 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/publish.ts +14 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/translations/[locale]/[action].ts +22 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/translations/[locale]/index.ts +14 -0
- package/src/pages/api/v1/admin/sections/index.ts +6 -0
- package/src/pages/api/v1/auth/revoke.ts +5 -0
- package/src/pages/api/v1/auth/token.ts +5 -0
- package/src/pages/api/v1/devices/[token].ts +20 -0
- package/src/pages/api/v1/devices/index.ts +17 -0
- package/src/pages/api/v1/openapi.json.ts +7 -0
- package/src/pages/api/v1/pages/[...slug].ts +7 -0
- package/src/pages/api/v1/pages/home.ts +7 -0
- package/src/pages/api/v1/push/topics.ts +7 -0
- package/src/pages/api/v1/site.ts +7 -0
- package/src/pages/api/v1/theme.ts +7 -0
- package/src/pages/blog/[slug].astro +8 -2
- package/src/pages/blog/index.astro +11 -4
- package/src/pages/embed/section/[...path].astro +42 -0
- package/src/pages/events/[slug].astro +7 -2
- package/src/pages/index.astro +11 -5
- package/src/pages/sitemap.xml.ts +8 -0
- package/src/styles/admin.css +69 -0
- package/src/styles/editor.css +21 -0
- package/src/styles/mobile-app.css +151 -0
- package/src/styles/sections.css +7 -1
- package/src/styles/theme-editor.css +255 -0
- package/src/theme-sdk/contract.d.mts +2193 -89
- package/src/theme-sdk/contract.mjs +77 -20
- package/src/theme-sdk/fields.d.mts +88 -0
- package/src/theme-sdk/fields.mjs +326 -0
- package/src/theme-sdk/index.ts +10 -2
- package/src/theme-sdk/tokens.d.mts +11 -0
- package/src/virtual.d.ts +1 -0
|
@@ -8,10 +8,14 @@ import { expandDynamicBlocks } from '../lib/dynamicBlocks';
|
|
|
8
8
|
import { highlightCodeHtml } from '../lib/highlightCode';
|
|
9
9
|
import { resolveDesign } from '../lib/theme';
|
|
10
10
|
import { effectiveSettings } from '../lib/designPreview';
|
|
11
|
+
import { requestLocale } from '../lib/i18n';
|
|
12
|
+
import { localizeDoc, isExtraLocale } from '../lib/translations';
|
|
11
13
|
|
|
12
14
|
const { slug } = Astro.params;
|
|
13
15
|
const repo = await getRepo(Astro.locals);
|
|
14
|
-
const
|
|
16
|
+
const locale = requestLocale(Astro.url, Astro.locals);
|
|
17
|
+
const rawPage = slug ? await repo.getPageBySlug(slug) : null;
|
|
18
|
+
const page = rawPage ? localizeDoc(rawPage, locale) : null;
|
|
15
19
|
|
|
16
20
|
// Drafts are visible only to a logged-in admin (doubles as draft preview).
|
|
17
21
|
const isAdmin = (await validateSession(Astro.cookies.get(SESSION_COOKIE)?.value, Astro.locals));
|
|
@@ -20,7 +24,8 @@ if (!page || (page.status !== 'published' && !isAdmin)) {
|
|
|
20
24
|
}
|
|
21
25
|
const bodyHtml = page.bodyHtml ? highlightCodeHtml(await expandDynamicBlocks(page.bodyHtml, repo)) : '';
|
|
22
26
|
// ?designPreview=1 (admin-only) renders the staged Design-screen changes
|
|
23
|
-
const
|
|
27
|
+
const baseSettings = await effectiveSettings(await repo.getSettings(), Astro.url, Astro.cookies, Astro.locals);
|
|
28
|
+
const settings = isExtraLocale(locale) ? { ...baseSettings, locale } : baseSettings;
|
|
24
29
|
// Live state must not survive in a cached page after its snapshot expires.
|
|
25
30
|
if (settings.live.mode !== 'off' || page?.blocks.some(block => block.type === 'live') ||
|
|
26
31
|
(page && settings.sectionPages?.[page.id]?.sections.some(section => section.type === 'live'))) {
|
|
@@ -43,7 +48,7 @@ const PageTemplate = (page.template && settings.sectionPages?.[page.id]?.mode !=
|
|
|
43
48
|
</p>
|
|
44
49
|
)}
|
|
45
50
|
{settings.sectionPages?.[page.id]?.mode === 'sections' ? (
|
|
46
|
-
<ThemeSections page={page} settings={settings} />
|
|
51
|
+
<ThemeSections page={page} settings={settings} locale={locale} />
|
|
47
52
|
) : PageTemplate ? (
|
|
48
53
|
<PageTemplate page={page} html={bodyHtml} settings={settings} />
|
|
49
54
|
) : page.bodyHtml ? (
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { roleLabels } from '../../lib/roles';
|
|
3
|
+
// Editor-app sign-in (OAuth 2.0 authorization code + PKCE, RFC 8252). The
|
|
4
|
+
// app opens this page in the system browser; the middleware has already sent
|
|
5
|
+
// signed-out visitors to /admin/login?next=… and back. Approving issues a
|
|
6
|
+
// one-time code (two minutes, single use) bound to this person, the app, its
|
|
7
|
+
// return address and the PKCE challenge, then returns to the app.
|
|
8
|
+
import { getRepo } from '../../lib/repo';
|
|
9
|
+
import { BRAND } from '../../lib/brand';
|
|
10
|
+
import { validateSession, OWNER_ENV, SESSION_COOKIE } from '../../lib/auth';
|
|
11
|
+
import { EDITOR } from '../../lib/editor/config';
|
|
12
|
+
import { checkAuthorize, checkConsentTicket, consentTicket, issueCode, redirectWith } from '../../lib/editor/auth';
|
|
13
|
+
import { bookingsEnabled } from '../../lib/calendar/config';
|
|
14
|
+
import { I18N } from '../../lib/i18n';
|
|
15
|
+
import ThemeScript from '../../components/ThemeScript.astro';
|
|
16
|
+
import '../../styles/admin.css';
|
|
17
|
+
|
|
18
|
+
// never framed (clickjacking the Approve button), never cached; the query
|
|
19
|
+
// (challenge, state) never leaks as a referrer. Not "no-referrer": that
|
|
20
|
+
// makes browsers send Origin: null on the form POST, failing the CSRF check.
|
|
21
|
+
Astro.response.headers.set('X-Frame-Options', 'DENY');
|
|
22
|
+
Astro.response.headers.set('Content-Security-Policy', "frame-ancestors 'none'");
|
|
23
|
+
Astro.response.headers.set('Cache-Control', 'no-store');
|
|
24
|
+
Astro.response.headers.set('Referrer-Policy', 'same-origin');
|
|
25
|
+
if (!EDITOR.enabled) return new Response(null, { status: 404 });
|
|
26
|
+
|
|
27
|
+
const leave = (to: string) => new Response(null, { status: 303, headers: { Location: to, 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' } });
|
|
28
|
+
const repo = await getRepo(Astro.locals);
|
|
29
|
+
const session = Astro.cookies.get(SESSION_COOKIE)?.value ?? '';
|
|
30
|
+
const userId = await validateSession(session, Astro.locals);
|
|
31
|
+
if (!userId) return Astro.redirect(`/admin/login?next=${encodeURIComponent(Astro.url.pathname + Astro.url.search)}`);
|
|
32
|
+
|
|
33
|
+
const check = checkAuthorize(Astro.url.searchParams);
|
|
34
|
+
const iss = Astro.url.origin;
|
|
35
|
+
// untrustworthy return address/app: show the problem here, never redirect
|
|
36
|
+
if (!check.ok && check.redirect) {
|
|
37
|
+
return leave(redirectWith(check.redirectUri, { error: check.error, error_description: check.message, ...(check.state ? { state: check.state } : {}), iss }));
|
|
38
|
+
}
|
|
39
|
+
const params = check.ok ? check.params : null;
|
|
40
|
+
const envOwner = userId === OWNER_ENV;
|
|
41
|
+
const user = envOwner ? null : await repo.getUser(userId);
|
|
42
|
+
const settings = await repo.getSettings();
|
|
43
|
+
const appName = EDITOR.name || params?.clientId || 'An app';
|
|
44
|
+
const switchUrl = `/admin/logout?next=${encodeURIComponent(Astro.url.pathname + Astro.url.search)}`;
|
|
45
|
+
|
|
46
|
+
let error = '';
|
|
47
|
+
if (Astro.request.method === 'POST' && params && user) {
|
|
48
|
+
const form = await Astro.request.formData();
|
|
49
|
+
const origin = Astro.request.headers.get('origin');
|
|
50
|
+
if (origin !== Astro.url.origin || Astro.request.headers.get('sec-fetch-site') === 'cross-site') error = 'This request didn’t come from this page. Try again.';
|
|
51
|
+
else if (!checkConsentTicket(String(form.get('ticket') ?? ''), session, params)) error = 'This sign-in page expired. Start again from the app.';
|
|
52
|
+
else if (form.get('op') === 'cancel') return leave(redirectWith(params.redirectUri, { error: 'access_denied', error_description: 'Sign-in was cancelled.', state: params.state, iss }));
|
|
53
|
+
else if (form.get('op') === 'approve') {
|
|
54
|
+
if (!await repo.email.rateLimit(`editor:approve:${user.id}`, 10, 60_000)) error = 'Too many sign-ins in a minute. Wait a moment and try again.';
|
|
55
|
+
else {
|
|
56
|
+
const version = await repo.email.sessionVersion(user.id);
|
|
57
|
+
if (version === null) error = 'Your account changed. Sign in again.';
|
|
58
|
+
else {
|
|
59
|
+
const code = await issueCode(repo, { id: user.id, sessionVersion: version }, params, String(form.get('device_name') ?? ''));
|
|
60
|
+
return leave(redirectWith(params.redirectUri, { code, state: params.state, iss }));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const ticket = params && user ? consentTicket(session, params) : '';
|
|
66
|
+
const deviceName = params?.deviceName || 'Phone';
|
|
67
|
+
const returnTo = (() => { if (!params) return ''; try { const u = new URL(params.redirectUri); return u.protocol === 'https:' || u.protocol === 'http:' ? u.host : `${u.protocol}//`; } catch { return ''; } })();
|
|
68
|
+
const owner = user?.role === 'owner';
|
|
69
|
+
const can = [
|
|
70
|
+
'Edit, publish and unpublish posts, pages and events',
|
|
71
|
+
owner ? 'Change section content, including locked sections' : 'Change section text and images (not the layout)',
|
|
72
|
+
'Upload images and files',
|
|
73
|
+
...(I18N.enabled ? ['Write and review translations'] : []),
|
|
74
|
+
...(bookingsEnabled() ? ['Confirm, decline and cancel bookings'] : []),
|
|
75
|
+
];
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
<!doctype html>
|
|
79
|
+
<html lang="en">
|
|
80
|
+
<head>
|
|
81
|
+
<meta charset="utf-8" />
|
|
82
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
83
|
+
<ThemeScript />
|
|
84
|
+
<meta name="robots" content="noindex" />
|
|
85
|
+
<title>Sign in to {appName} · {settings.siteTitle}</title>
|
|
86
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
87
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
88
|
+
</head>
|
|
89
|
+
<body>
|
|
90
|
+
<main class="login-wrap consent-wrap">
|
|
91
|
+
<div class="card consent-card">
|
|
92
|
+
<div class="consent-site">
|
|
93
|
+
<svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
|
|
94
|
+
<rect x="1" y="3" width="22" height="4.4" rx="1.9" fill="currentColor" opacity="0.85" />
|
|
95
|
+
<rect x="1" y="10" width="12.5" height="4.4" rx="1.9" fill="var(--accent)" />
|
|
96
|
+
<rect x="1" y="17" width="17.5" height="4.4" rx="1.9" fill="currentColor" opacity="0.35" />
|
|
97
|
+
</svg>
|
|
98
|
+
<span>{settings.siteTitle || BRAND.name}</span>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{!params && (
|
|
102
|
+
<div class="consent-body">
|
|
103
|
+
<h1>This app can’t sign in here</h1>
|
|
104
|
+
<p class="consent-lead">{!check.ok && check.message}</p>
|
|
105
|
+
<p class="hint">Close this page and return to the app. If you run this site, check the editor app settings.</p>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
|
|
109
|
+
{params && envOwner && (
|
|
110
|
+
<div class="consent-body">
|
|
111
|
+
<h1>Use your own account</h1>
|
|
112
|
+
<p class="consent-lead"><b>{appName}</b> signs in as a person, so each phone can be signed out on its own. You’re signed in with the owner password, which has no account to connect.</p>
|
|
113
|
+
<a class="btn primary block" href={switchUrl}>Sign in with an account</a>
|
|
114
|
+
<p class="hint consent-foot">Owners create accounts in Users.</p>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
{params && user && (
|
|
119
|
+
<form method="post" class="consent-body">
|
|
120
|
+
<h1><span class="consent-app">{appName}</span> wants to sign in</h1>
|
|
121
|
+
<p class="consent-lead">It will use <b>{settings.siteTitle || 'this site'}</b> as you, with your role. You can sign this phone out any time.</p>
|
|
122
|
+
|
|
123
|
+
<div class="consent-who">
|
|
124
|
+
<span class="consent-initial" aria-hidden="true">{(user.name || user.email).trim().charAt(0).toUpperCase()}</span>
|
|
125
|
+
<span class="consent-id">
|
|
126
|
+
<b>{user.name}</b>
|
|
127
|
+
<span>{user.email}</span>
|
|
128
|
+
</span>
|
|
129
|
+
<span class="consent-role"><i class={owner ? 'is-owner' : ''} aria-hidden="true"></i>{user ? roleLabels[user.role] : 'Owner'}</span>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<p class="consent-label">The app will be able to</p>
|
|
133
|
+
<ul class="consent-can">
|
|
134
|
+
{can.map((line) => <li>{line}</li>)}
|
|
135
|
+
</ul>
|
|
136
|
+
|
|
137
|
+
<label class="field consent-device">
|
|
138
|
+
<span>Name this device</span>
|
|
139
|
+
<input type="text" name="device_name" value={deviceName} maxlength="60" autocomplete="off" required />
|
|
140
|
+
</label>
|
|
141
|
+
|
|
142
|
+
{error && <div class="flash error" role="alert">{error}</div>}
|
|
143
|
+
<input type="hidden" name="ticket" value={ticket} />
|
|
144
|
+
<div class="consent-actions">
|
|
145
|
+
<button class="btn" type="submit" name="op" value="cancel" formnovalidate>Cancel</button>
|
|
146
|
+
<button class="btn primary" type="submit" name="op" value="approve">Approve</button>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<dl class="consent-meta">
|
|
150
|
+
<div><dt>Returns to</dt><dd><code>{returnTo}</code></dd></div>
|
|
151
|
+
<div><dt>App ID</dt><dd><code>{params.clientId}</code></dd></div>
|
|
152
|
+
</dl>
|
|
153
|
+
<p class="hint consent-foot">Not you? <a href={switchUrl}>Switch account</a></p>
|
|
154
|
+
</form>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</main>
|
|
158
|
+
</body>
|
|
159
|
+
</html>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { I18N, extraLocales, localeName } from '../../../../lib/i18n';
|
|
3
|
+
import { translationEngine } from '../../../../lib/translate';
|
|
2
4
|
import { getRepo } from '../../../../lib/repo';
|
|
3
5
|
import { renderMarkdown } from '../../../../lib/markdown';
|
|
4
6
|
import { createPreviewToken } from '../../../../lib/preview';
|
|
@@ -37,6 +39,10 @@ const sharePath = `${previewPath}?token=${createPreviewToken('post', post.id)}`;
|
|
|
37
39
|
const unsplashEnabled = Boolean(env('UNSPLASH_ACCESS_KEY'));
|
|
38
40
|
const settings = await repo.getSettings();
|
|
39
41
|
const templates = Object.keys(resolveDesign(settings).theme.postTemplates);
|
|
42
|
+
// other languages this document can be written in (acms({ i18n }))
|
|
43
|
+
const trLanguages = extraLocales().map((code) => ({ code, name: localeName(code, true) }));
|
|
44
|
+
const trMain = { code: I18N.defaultLocale, name: localeName(I18N.defaultLocale || 'en', true) };
|
|
45
|
+
const trDrafts = trLanguages.length > 0 && translationEngine((await repo.getSettings()).ai, Astro.locals) !== null;
|
|
40
46
|
---
|
|
41
47
|
|
|
42
48
|
<!doctype html>
|
|
@@ -54,6 +60,9 @@ const templates = Object.keys(resolveDesign(settings).theme.postTemplates);
|
|
|
54
60
|
<PostEditor
|
|
55
61
|
client:only="react"
|
|
56
62
|
post={editable}
|
|
63
|
+
languages={trLanguages}
|
|
64
|
+
mainLanguage={trMain}
|
|
65
|
+
translationDrafts={trDrafts}
|
|
57
66
|
initialHTML={initialHTML}
|
|
58
67
|
unsplashEnabled={unsplashEnabled}
|
|
59
68
|
previewPath={previewPath}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { I18N, extraLocales, localeName } from '../../../../lib/i18n';
|
|
3
|
+
import { translationEngine } from '../../../../lib/translate';
|
|
2
4
|
import { getRepo } from '../../../../lib/repo';
|
|
3
5
|
import { createPreviewToken } from '../../../../lib/preview';
|
|
4
6
|
import { env } from '../../../../lib/env';
|
|
@@ -24,6 +26,10 @@ const editable = mergeEventDraft(event);
|
|
|
24
26
|
const [settings, calendars] = await Promise.all([repo.getSettings(), repo.listCalendars()]);
|
|
25
27
|
const previewPath = `/preview/event/${event.id}`;
|
|
26
28
|
const sharePath = `${previewPath}?token=${createPreviewToken('event', event.id)}`;
|
|
29
|
+
// other languages this document can be written in (acms({ i18n }))
|
|
30
|
+
const trLanguages = extraLocales().map((code) => ({ code, name: localeName(code, true) }));
|
|
31
|
+
const trMain = { code: I18N.defaultLocale, name: localeName(I18N.defaultLocale || 'en', true) };
|
|
32
|
+
const trDrafts = trLanguages.length > 0 && translationEngine((await repo.getSettings()).ai, Astro.locals) !== null;
|
|
27
33
|
---
|
|
28
34
|
|
|
29
35
|
<!doctype html>
|
|
@@ -42,6 +48,9 @@ const sharePath = `${previewPath}?token=${createPreviewToken('event', event.id)}
|
|
|
42
48
|
client:only="react"
|
|
43
49
|
kind="event"
|
|
44
50
|
event={editable}
|
|
51
|
+
languages={trLanguages}
|
|
52
|
+
mainLanguage={trMain}
|
|
53
|
+
translationDrafts={trDrafts}
|
|
45
54
|
initialHTML={editable.bodyHtml}
|
|
46
55
|
unsplashEnabled={Boolean(env('UNSPLASH_ACCESS_KEY'))}
|
|
47
56
|
previewPath={previewPath}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { safeNext } from '../../lib/editor/next';
|
|
2
3
|
import { checkPassword, createSessionToken, verifyPassword, validateSession, SESSION_COOKIE } from '../../lib/auth';
|
|
3
4
|
import { getRepo } from '../../lib/repo';
|
|
4
5
|
import { BRAND } from '../../lib/brand';
|
|
5
6
|
import ThemeScript from '../../components/ThemeScript.astro';
|
|
6
7
|
import '../../styles/admin.css';
|
|
7
8
|
|
|
9
|
+
// ?next= returns to the editor-app sign-in page only (never another site)
|
|
10
|
+
const next = safeNext(Astro.url.searchParams.get('next'), Astro.url);
|
|
8
11
|
if ((await validateSession(Astro.cookies.get(SESSION_COOKIE)?.value, Astro.locals))) {
|
|
9
|
-
return Astro.redirect('/admin');
|
|
12
|
+
return Astro.redirect(next ?? '/admin');
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
let error = '';
|
|
@@ -40,7 +43,7 @@ if (Astro.request.method === 'POST') {
|
|
|
40
43
|
maxAge: 7 * 24 * 60 * 60,
|
|
41
44
|
secure: Astro.url.protocol === 'https:',
|
|
42
45
|
});
|
|
43
|
-
return Astro.redirect('/admin');
|
|
46
|
+
return Astro.redirect(next ?? '/admin');
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
---
|
|
@@ -67,6 +70,7 @@ if (Astro.request.method === 'POST') {
|
|
|
67
70
|
</svg>
|
|
68
71
|
<div class="brand-name">{BRAND.name}</div>
|
|
69
72
|
</div>
|
|
73
|
+
{next && !error && <p class="hint login-for-app">Sign in to connect the app to your account.</p>}
|
|
70
74
|
{error && <div class="flash error">{error}</div>}
|
|
71
75
|
<form method="post">
|
|
72
76
|
<label class="field">
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro';
|
|
2
2
|
import { SESSION_COOKIE } from '../../lib/auth';
|
|
3
|
+
import { safeNext } from '../../lib/editor/next';
|
|
3
4
|
|
|
4
|
-
export const GET: APIRoute = ({ cookies, redirect }) => {
|
|
5
|
+
export const GET: APIRoute = ({ cookies, redirect, url }) => {
|
|
5
6
|
cookies.delete(SESSION_COOKIE, { path: '/' });
|
|
6
|
-
|
|
7
|
+
// "Switch account" on the editor-app sign-in page comes back to it
|
|
8
|
+
const next = safeNext(url.searchParams.get('next'), url);
|
|
9
|
+
return redirect(next ? `/admin/login?next=${encodeURIComponent(next)}` : '/admin/login');
|
|
7
10
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Admin from '../../layouts/Admin.astro';
|
|
3
|
+
import MobileApp from '../../components/MobileApp';
|
|
4
|
+
import { getRepo } from '../../lib/repo';
|
|
5
|
+
import { requireSession } from '../../lib/api';
|
|
6
|
+
const repo = await getRepo(Astro.locals);
|
|
7
|
+
try { await requireSession(Astro.request, Astro.cookies, repo, true); }
|
|
8
|
+
catch { return new Response('Owner access is required.', { status: 403 }); }
|
|
9
|
+
---
|
|
10
|
+
<Admin title="Mobile app" section="mobile">
|
|
11
|
+
<div class="page-head">
|
|
12
|
+
<div>
|
|
13
|
+
<h1>Mobile app</h1>
|
|
14
|
+
<p class="sub">Notifications for people who installed your app.</p>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<MobileApp client:only="react" />
|
|
18
|
+
</Admin>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { I18N, extraLocales, localeName } from '../../../lib/i18n';
|
|
3
|
+
import { translationEngine } from '../../../lib/translate';
|
|
2
4
|
import { getRepo } from '../../../lib/repo';
|
|
3
5
|
import { blocksToHtml } from '../../../lib/blocksToHtml';
|
|
4
6
|
import { createPreviewToken } from '../../../lib/preview';
|
|
@@ -7,6 +9,7 @@ import { env } from '../../../lib/env';
|
|
|
7
9
|
import { BRAND } from '../../../lib/brand';
|
|
8
10
|
import { mergePageDraft } from '../../../lib/drafts';
|
|
9
11
|
import { localeHomeUrl } from '../../../lib/i18n';
|
|
12
|
+
import { homeSource } from '../../../lib/home-source';
|
|
10
13
|
import ThemeScript from '../../../components/ThemeScript.astro';
|
|
11
14
|
import PostEditor from '../../../components/editor/PostEditor';
|
|
12
15
|
import '../../../styles/admin.css';
|
|
@@ -30,6 +33,12 @@ const sharePath = `${previewPath}?token=${createPreviewToken('page', page.id)}`;
|
|
|
30
33
|
const unsplashEnabled = Boolean(env('UNSPLASH_ACCESS_KEY'));
|
|
31
34
|
const settings = await repo.getSettings();
|
|
32
35
|
const templates = Object.keys(resolveDesign(settings).theme.pageTemplates);
|
|
36
|
+
// the homepage may really be a section layout or a coded template: say so
|
|
37
|
+
const home = homeSource(settings, page);
|
|
38
|
+
// other languages this document can be written in (acms({ i18n }))
|
|
39
|
+
const trLanguages = extraLocales().map((code) => ({ code, name: localeName(code, true) }));
|
|
40
|
+
const trMain = { code: I18N.defaultLocale, name: localeName(I18N.defaultLocale || 'en', true) };
|
|
41
|
+
const trDrafts = trLanguages.length > 0 && translationEngine((await repo.getSettings()).ai, Astro.locals) !== null;
|
|
33
42
|
---
|
|
34
43
|
|
|
35
44
|
<!doctype html>
|
|
@@ -48,6 +57,9 @@ const templates = Object.keys(resolveDesign(settings).theme.pageTemplates);
|
|
|
48
57
|
client:only="react"
|
|
49
58
|
kind="page"
|
|
50
59
|
page={editable}
|
|
60
|
+
languages={trLanguages}
|
|
61
|
+
mainLanguage={trMain}
|
|
62
|
+
translationDrafts={trDrafts}
|
|
51
63
|
initialHTML={initialHTML}
|
|
52
64
|
unsplashEnabled={unsplashEnabled}
|
|
53
65
|
previewPath={previewPath}
|
|
@@ -56,5 +68,55 @@ const templates = Object.keys(resolveDesign(settings).theme.pageTemplates);
|
|
|
56
68
|
templates={templates}
|
|
57
69
|
site={{ title: settings.siteTitle, url: settings.siteUrl }}
|
|
58
70
|
/>
|
|
71
|
+
{home.kind !== 'content' && (
|
|
72
|
+
<dialog class="modal home-source" data-home-source={`${page.id}:${home.kind}`} aria-labelledby="home-source-title">
|
|
73
|
+
<div class="modal-head">
|
|
74
|
+
<span id="home-source-title">{home.kind === 'sections' ? 'Your homepage is built from sections' : 'Your homepage is a coded template'}</span>
|
|
75
|
+
<button class="modal-x" type="button" data-close aria-label="Close">✕</button>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="modal-body">
|
|
78
|
+
{home.kind === 'sections' ? (
|
|
79
|
+
<>
|
|
80
|
+
<p>Visitors see the {home.sections} {home.sections === 1 ? 'section' : 'sections'} laid out in <b>Design → Pages</b>. Change times, events, text and images there.</p>
|
|
81
|
+
<p class="home-source-note">{home.showsPageContent
|
|
82
|
+
? 'This page’s text still appears inside the layout, as the “Existing page content” section.'
|
|
83
|
+
: 'This page’s text isn’t shown on the homepage right now.'}</p>
|
|
84
|
+
</>
|
|
85
|
+
) : (
|
|
86
|
+
<>
|
|
87
|
+
<p>The <b>{home.theme}</b> theme draws your homepage from <code>{home.file}</code>. This page’s text appears as its opening section.</p>
|
|
88
|
+
<p class="home-source-note">Changing the layout means editing that file (or asking your AI agent). To use this page as the homepage instead, set <b>Homepage</b> to your content page in Design → Theme.</p>
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
<label class="home-source-check"><input type="checkbox" data-remember /> Don’t show this again for this page</label>
|
|
92
|
+
<div class="modal-foot">
|
|
93
|
+
<button class="btn ghost" type="button" data-close>Edit page text {home.kind === 'sections' ? 'anyway' : ''}</button>
|
|
94
|
+
<a class="btn primary" href={home.editUrl} data-go autofocus>{home.kind === 'sections' ? 'Go to homepage sections' : 'Open Design'}</a>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</dialog>
|
|
98
|
+
<script is:inline>
|
|
99
|
+
(() => {
|
|
100
|
+
const d = document.querySelector('dialog.home-source');
|
|
101
|
+
if (!d) return;
|
|
102
|
+
const key = `acms-home-source:${d.dataset.homeSource}`;
|
|
103
|
+
try { if (localStorage.getItem(key)) return; } catch {}
|
|
104
|
+
const remember = () => { if (d.querySelector('[data-remember]')?.checked) try { localStorage.setItem(key, '1'); } catch {} };
|
|
105
|
+
d.querySelectorAll('[data-close]').forEach((b) => b.addEventListener('click', () => { remember(); d.close(); }));
|
|
106
|
+
d.querySelector('[data-go]')?.addEventListener('click', remember);
|
|
107
|
+
d.addEventListener('click', (e) => { if (e.target === d) { remember(); d.close(); } });
|
|
108
|
+
d.showModal();
|
|
109
|
+
})();
|
|
110
|
+
</script>
|
|
111
|
+
<style>
|
|
112
|
+
.home-source { width: min(480px, calc(100vw - 32px)); }
|
|
113
|
+
.home-source p { margin: 0 0 10px; font-size: 14px; line-height: 1.55; color: var(--text-2); }
|
|
114
|
+
.home-source p b { color: var(--text); }
|
|
115
|
+
.home-source code { font-size: 12.5px; background: var(--panel-2); padding: 1px 5px; border-radius: 5px; overflow-wrap: anywhere; }
|
|
116
|
+
.home-source-note { color: var(--muted) !important; font-size: 13px !important; }
|
|
117
|
+
.home-source-check { display: flex; align-items: center; gap: 8px; margin-top: 14px; font-size: 13px; color: var(--muted); cursor: pointer; }
|
|
118
|
+
@media (max-width: 480px) { .home-source .modal-foot { flex-direction: column-reverse; } .home-source .modal-foot .btn { width: 100%; justify-content: center; min-height: 40px; } }
|
|
119
|
+
</style>
|
|
120
|
+
)}
|
|
59
121
|
</body>
|
|
60
122
|
</html>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Site from '../../layouts/Site.astro';
|
|
3
|
+
import ThemeSections from '../../components/ThemeSections.astro';
|
|
4
|
+
import { getRepo } from '../../lib/repo';
|
|
5
|
+
import { mergeDesignDraft } from '../../lib/drafts';
|
|
6
|
+
import { getTheme } from '../../lib/theme';
|
|
7
|
+
import { SectionLayout } from '../../theme-sdk/contract.mjs';
|
|
8
|
+
import { initialData } from '../../theme-sdk/fields.mjs';
|
|
9
|
+
import { I18N } from '../../lib/i18n';
|
|
10
|
+
|
|
11
|
+
// Admin-only (middleware guards /admin) section renders for the Design screen:
|
|
12
|
+
// POST { pageId, layout, theme? } → the page's sections with an UNSAVED
|
|
13
|
+
// layout, as a minimal document; the preview iframe swaps them in
|
|
14
|
+
// while you type. Nothing is written.
|
|
15
|
+
// GET ?type=&variant=&theme= → one section with its example data, in
|
|
16
|
+
// the full theme frame; the "Add a section" picker shows it scaled.
|
|
17
|
+
const repo = await getRepo(Astro.locals);
|
|
18
|
+
const settings = mergeDesignDraft(await repo.getSettings());
|
|
19
|
+
Astro.response.headers.set('Cache-Control', 'no-store');
|
|
20
|
+
Astro.response.headers.set('X-Robots-Tag', 'noindex');
|
|
21
|
+
|
|
22
|
+
let mode: 'live' | 'thumb' | 'bad' = 'bad';
|
|
23
|
+
let locale = I18N.defaultLocale;
|
|
24
|
+
let page = null as Awaited<ReturnType<typeof repo.getPage>>;
|
|
25
|
+
let view = settings;
|
|
26
|
+
if (Astro.request.method === 'POST') {
|
|
27
|
+
try {
|
|
28
|
+
const body = await Astro.request.json() as { pageId?: string; layout?: unknown; theme?: string; locale?: string };
|
|
29
|
+
if (typeof body.locale === 'string' && I18N.locales.includes(body.locale)) locale = body.locale;
|
|
30
|
+
page = body.pageId ? await repo.getPage(String(body.pageId)) : null;
|
|
31
|
+
const layout = SectionLayout.safeParse(body.layout);
|
|
32
|
+
if (page && layout.success) {
|
|
33
|
+
mode = 'live';
|
|
34
|
+
view = { ...settings, activeTheme: typeof body.theme === 'string' && body.theme ? getTheme(body.theme).slug : settings.activeTheme, sectionPages: { ...settings.sectionPages, [page.id]: { ...layout.data, mode: 'sections' } } };
|
|
35
|
+
}
|
|
36
|
+
} catch { /* bad JSON → 400 below */ }
|
|
37
|
+
} else {
|
|
38
|
+
const q = Astro.url.searchParams;
|
|
39
|
+
const theme = getTheme(q.get('theme') || settings.activeTheme);
|
|
40
|
+
const type = q.get('type') ?? '';
|
|
41
|
+
const def = theme.sections[type];
|
|
42
|
+
page = (await repo.getHomePage()) ?? (await repo.listPages())[0] ?? null;
|
|
43
|
+
if (def && page) {
|
|
44
|
+
mode = 'thumb';
|
|
45
|
+
const variant = def.variants.includes(q.get('variant') ?? '') ? q.get('variant')! : def.variants[0];
|
|
46
|
+
const example = (def as { example?: Record<string, unknown> }).example ?? {};
|
|
47
|
+
view = { ...settings, activeTheme: theme.slug, sectionPages: { ...settings.sectionPages, [page.id]: { mode: 'sections', sections: [{ id: `thumb-${type}`, type, version: def.version, variant, hidden: false, data: { ...initialData(def), ...example } as never }] } } };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (mode === 'bad' || !page) Astro.response.status = 400;
|
|
51
|
+
---
|
|
52
|
+
{mode === 'live' && page ? (
|
|
53
|
+
<html lang="en"><head><meta charset="utf-8" /></head><body><ThemeSections page={page} settings={view} editable locale={locale} /></body></html>
|
|
54
|
+
) : mode === 'thumb' && page ? (
|
|
55
|
+
<Site title="Section preview" bare>
|
|
56
|
+
<ThemeSections page={page} settings={view} editable={false} />
|
|
57
|
+
<style is:global>html, body { overflow: hidden; } main.site-main { padding-block: 8px; } .acms-sections { margin-block: 0; }</style>
|
|
58
|
+
</Site>
|
|
59
|
+
) : (
|
|
60
|
+
<p>Bad section preview request.</p>
|
|
61
|
+
)}
|