@acms-subir/core 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/cli/common.mjs +35 -0
- package/cli/legacy.mjs +317 -0
- package/cli/main.mjs +66 -0
- package/cli/palette.mjs +4 -0
- package/cli/preview.mjs +50 -0
- package/cli/remote.mjs +55 -0
- package/cli/themes.mjs +127 -0
- package/cli.mjs +2 -369
- package/deploy/Caddyfile +8 -0
- package/deploy/Dockerfile +34 -0
- package/deploy/compose.https.yml +21 -0
- package/deploy/compose.yml +46 -0
- package/docs/live-video.md +91 -0
- package/docs/theme-authoring.md +103 -0
- package/email-runner.mjs +32 -0
- package/integration.mjs +189 -2
- package/ops-check.mjs +27 -0
- package/package.json +27 -5
- package/src/actions/index.ts +578 -49
- package/src/components/AiCleaner.tsx +291 -0
- package/src/components/AiSettings.tsx +575 -0
- package/src/components/CommandPalette.tsx +200 -0
- package/src/components/ContentList.astro +246 -0
- package/src/components/DesignAccess.tsx +182 -0
- package/src/components/DesignManager.tsx +418 -46
- package/src/components/EmailSettings.tsx +521 -0
- package/src/components/EventPage.astro +178 -0
- package/src/components/LegalPage.astro +27 -0
- package/src/components/LinksManager.tsx +119 -58
- package/src/components/LiveCard.astro +174 -0
- package/src/components/LiveManager.tsx +910 -0
- package/src/components/NavManager.tsx +42 -43
- package/src/components/ThemeScript.astro +50 -0
- package/src/components/ThemeSections.astro +58 -0
- package/src/components/ThemeSectionsEditor.tsx +513 -0
- package/src/components/WidgetRenderer.astro +3 -0
- package/src/components/booking/BookingFlow.astro +150 -0
- package/src/components/booking/HoursEditor.astro +63 -0
- package/src/components/builder/PageBuilder.tsx +10 -2
- package/src/components/editor/EventPanel.tsx +401 -0
- package/src/components/editor/FormatDock.tsx +81 -0
- package/src/components/editor/LivePane.tsx +122 -0
- package/src/components/editor/MobileToolbar.tsx +93 -0
- package/src/components/editor/PostEditor.tsx +605 -223
- package/src/components/editor/PreviewOverlay.tsx +242 -43
- package/src/components/editor/extensions/calendar.tsx +51 -7
- package/src/components/editor/extensions/events.tsx +153 -0
- package/src/components/editor/extensions/slash.tsx +7 -1
- package/src/components/site/Pager.astro +24 -0
- package/src/components/site/PostCard.astro +64 -0
- package/src/components/site/TagLabel.astro +11 -0
- package/src/layouts/Admin.astro +289 -49
- package/src/layouts/Site.astro +352 -59
- package/src/lib/agent-api.ts +13 -0
- package/src/lib/agent-discovery.ts +40 -0
- package/src/lib/agent.ts +114 -0
- package/src/lib/ai-config.ts +58 -0
- package/src/lib/ai-schema.ts +22 -0
- package/src/lib/ai.ts +90 -0
- package/src/lib/api-keys.ts +65 -0
- package/src/lib/api.ts +56 -0
- package/src/lib/auth.ts +25 -5
- package/src/lib/backup/content.ts +36 -0
- package/src/lib/backup/unzip.ts +12 -1
- package/src/lib/backup/zip.ts +1 -1
- package/src/lib/booking/agent.ts +108 -0
- package/src/lib/booking/availability.ts +221 -0
- package/src/lib/booking/emails.ts +115 -0
- package/src/lib/booking/forms.ts +77 -0
- package/src/lib/booking/service.ts +277 -0
- package/src/lib/booking/view.ts +54 -0
- package/src/lib/calendar/agent-schema.ts +48 -0
- package/src/lib/calendar/agent.ts +136 -0
- package/src/lib/calendar/api.ts +81 -0
- package/src/lib/calendar/cards.ts +102 -0
- package/src/lib/calendar/config.ts +52 -0
- package/src/lib/calendar/data.ts +130 -0
- package/src/lib/calendar/events.ts +261 -0
- package/src/lib/calendar/feeds.ts +43 -0
- package/src/lib/calendar/hooks.ts +38 -0
- package/src/lib/calendar/http.ts +30 -0
- package/src/lib/calendar/ics-write.ts +120 -0
- package/src/lib/calendar/ics.ts +15 -5
- package/src/lib/calendar/import.ts +203 -0
- package/src/lib/calendar/render.ts +120 -37
- package/src/lib/calendar/rrule.ts +149 -0
- package/src/lib/calendar/seo.ts +48 -0
- package/src/lib/design-service.ts +58 -0
- package/src/lib/designDiff.ts +145 -0
- package/src/lib/designPreview.ts +110 -12
- package/src/lib/documents/cleanup.ts +31 -0
- package/src/lib/documents/extract.ts +154 -0
- package/src/lib/documents/vendor.d.ts +4 -0
- package/src/lib/drafts.ts +60 -6
- package/src/lib/dynamicBlocks.ts +40 -8
- package/src/lib/email/accounts.ts +55 -0
- package/src/lib/email/config.ts +86 -0
- package/src/lib/email/migration.ts +24 -0
- package/src/lib/email/providers.ts +89 -0
- package/src/lib/email/service.ts +78 -0
- package/src/lib/email/store.ts +167 -0
- package/src/lib/email/templates.ts +18 -0
- package/src/lib/email/types.ts +32 -0
- package/src/lib/email/webhooks.ts +87 -0
- package/src/lib/email.ts +15 -0
- package/src/lib/env.ts +1 -1
- package/src/lib/legal/index.ts +25 -0
- package/src/lib/live/client.ts +88 -0
- package/src/lib/live/crypto.ts +28 -0
- package/src/lib/live/migration.ts +15 -0
- package/src/lib/live/providers.ts +152 -0
- package/src/lib/live/service.ts +170 -0
- package/src/lib/live/signals.ts +89 -0
- package/src/lib/live/store.ts +49 -0
- package/src/lib/live/types.ts +52 -0
- package/src/lib/live/view.ts +78 -0
- package/src/lib/mobile/bookings.ts +73 -0
- package/src/lib/mobile/calendar.ts +85 -0
- package/src/lib/mobile/content.ts +85 -0
- package/src/lib/mobile/http.ts +63 -0
- package/src/lib/mobile/route.ts +20 -0
- package/src/lib/ops.ts +8 -0
- package/src/lib/preview.ts +21 -1
- package/src/lib/repo/migrations.ts +221 -0
- package/src/lib/repo/sql.ts +907 -68
- package/src/lib/repo/types.ts +125 -5
- package/src/lib/scheduler.ts +27 -0
- package/src/lib/schema.ts +316 -1
- package/src/lib/search-i18n.ts +3 -0
- package/src/lib/socialIcons.ts +32 -0
- package/src/lib/tagColor.ts +11 -0
- package/src/lib/theme.ts +59 -40
- package/src/lib/themeTokens.ts +183 -0
- package/src/lib/umami.ts +108 -0
- package/src/lib/webhook-sql.ts +68 -0
- package/src/lib/webhooks.ts +147 -0
- package/src/lib/widgets.ts +1 -0
- package/src/middleware.ts +45 -3
- package/src/pages/404.astro +23 -0
- package/src/pages/[...slug].astro +14 -5
- package/src/pages/account/[action].astro +53 -0
- package/src/pages/admin/ai.astro +12 -0
- package/src/pages/admin/c/[collection]/[id].astro +4 -1
- package/src/pages/admin/c/[collection]/index.astro +127 -92
- package/src/pages/admin/calendar/bookings.astro +400 -0
- package/src/pages/admin/calendar/calendars.astro +265 -0
- package/src/pages/admin/calendar/events/[id].astro +56 -0
- package/src/pages/admin/calendar/events/index.astro +408 -0
- package/src/pages/admin/calendar/index.ts +4 -0
- package/src/pages/admin/calendar/integrations.astro +4 -0
- package/src/pages/admin/calendar/resources/[id].astro +180 -0
- package/src/pages/admin/calendar/resources/index.astro +106 -0
- package/src/pages/admin/calendar/resources/link.astro +196 -0
- package/src/pages/admin/design.astro +10 -3
- package/src/pages/admin/email.astro +13 -0
- package/src/pages/admin/index.astro +232 -85
- package/src/pages/admin/integrations.astro +237 -0
- package/src/pages/admin/links.astro +3 -3
- package/src/pages/admin/live.astro +12 -0
- package/src/pages/admin/login.astro +14 -10
- package/src/pages/admin/navigation.astro +8 -2
- package/src/pages/admin/pages/[id].astro +4 -1
- package/src/pages/admin/pages/index.astro +41 -48
- package/src/pages/admin/settings.astro +418 -367
- package/src/pages/admin/tags.astro +4 -4
- package/src/pages/admin/users.astro +38 -28
- package/src/pages/api/agent/docs.ts +33 -0
- package/src/pages/api/agent/index.ts +21 -0
- package/src/pages/api/ai/clean.ts +13 -0
- package/src/pages/api/ai/draft.ts +14 -0
- package/src/pages/api/ai/settings.ts +53 -0
- package/src/pages/api/ai/test.ts +12 -0
- package/src/pages/api/backup.ts +8 -21
- package/src/pages/api/calendar/availability.ts +35 -0
- package/src/pages/api/calendar/bookings/[id].ts +49 -0
- package/src/pages/api/calendar/bookings/index.ts +73 -0
- package/src/pages/api/calendar/calendars.ts +35 -0
- package/src/pages/api/calendar/docs.ts +66 -0
- package/src/pages/api/calendar/events/[id].ts +63 -0
- package/src/pages/api/calendar/events/index.ts +112 -0
- package/src/pages/api/calendar/resources.ts +34 -0
- package/src/pages/api/design/capabilities.ts +10 -0
- package/src/pages/api/design/index.ts +58 -0
- package/src/pages/api/design/keys.ts +24 -0
- package/src/pages/api/design/media.ts +32 -0
- package/src/pages/api/email/run.ts +11 -0
- package/src/pages/api/email/settings.ts +61 -0
- package/src/pages/api/email/webhooks/[provider].ts +4 -0
- package/src/pages/api/live/oauth/youtube/callback.ts +19 -0
- package/src/pages/api/live/settings.ts +45 -0
- package/src/pages/api/live/status.ts +7 -0
- package/src/pages/api/live/thumb.ts +48 -0
- package/src/pages/api/mcp.ts +59 -0
- package/src/pages/api/media/upload.ts +2 -2
- package/src/pages/api/ops/status.ts +39 -0
- package/src/pages/api/restore.ts +27 -37
- package/src/pages/api/search.ts +18 -4
- package/src/pages/api/v1/availability.ts +7 -0
- package/src/pages/api/v1/bookings.ts +17 -0
- package/src/pages/api/v1/calendars.ts +7 -0
- package/src/pages/api/v1/content/[id].ts +7 -0
- package/src/pages/api/v1/content/index.ts +7 -0
- package/src/pages/api/v1/events.ts +7 -0
- package/src/pages/api/v1/resources.ts +7 -0
- package/src/pages/api/webhooks/docs.ts +61 -0
- package/src/pages/author/[slug].astro +43 -35
- package/src/pages/blog/[slug].astro +137 -66
- package/src/pages/blog/index.astro +95 -30
- package/src/pages/blog/tag/[slug].astro +45 -40
- package/src/pages/book/[resource]/[link].astro +70 -0
- package/src/pages/book/[resource]/index.astro +63 -0
- package/src/pages/book/[resource].ics.ts +38 -0
- package/src/pages/book/index.astro +47 -0
- package/src/pages/book/manage/[token].astro +108 -0
- package/src/pages/calendar/[slug].ics.ts +27 -0
- package/src/pages/calendar/feed.ics.ts +15 -0
- package/src/pages/calendar/index.astro +191 -0
- package/src/pages/embed/book/[resource]/[link].astro +33 -0
- package/src/pages/embed/calendar/[slug].astro +43 -0
- package/src/pages/events/[slug].astro +74 -0
- package/src/pages/events/[slug].ics.ts +25 -0
- package/src/pages/index.astro +12 -3
- package/src/pages/legal/privacy.astro +4 -0
- package/src/pages/legal/terms.astro +4 -0
- package/src/pages/links.astro +29 -3
- package/src/pages/llms.txt.ts +4 -0
- package/src/pages/preview/[type]/[id].astro +27 -6
- package/src/pages/search.astro +40 -20
- package/src/pages/sitemap.xml.ts +16 -1
- package/src/styles/admin.css +924 -105
- package/src/styles/cards.css +92 -74
- package/src/styles/editor.css +492 -114
- package/src/styles/email-settings.css +117 -0
- package/src/styles/live-admin.css +126 -0
- package/src/styles/live.css +199 -0
- package/src/styles/sections.css +12 -0
- package/src/styles/site.css +938 -281
- package/src/styles/theme-editor.css +135 -0
- package/src/theme-sdk/contract.d.mts +619 -0
- package/src/theme-sdk/contract.mjs +81 -0
- package/src/theme-sdk/index.ts +15 -0
- package/src/theme-sdk/tokens.d.mts +148 -0
- package/src/theme-sdk/tokens.mjs +55 -0
- package/src/themes/editorial/templates/home.astro +4 -4
- package/src/themes/editorial/templates/post-minimal.astro +2 -2
- package/src/themes/editorial/theme.css +27 -37
- package/src/themes/editorial/theme.json +25 -3
- package/src/themes/field/theme.json +27 -9
- package/src/themes/ink/theme.json +30 -12
- package/src/themes/night/theme.css +4 -7
- package/src/themes/night/theme.json +28 -10
- package/src/virtual.d.ts +14 -0
- package/src/worker.ts +5 -1
|
@@ -11,8 +11,10 @@ import type { NavItem, NavArea } from '../lib/schema';
|
|
|
11
11
|
// area is empty); footer renders above the footer text; social feeds the
|
|
12
12
|
// footer social row and share UIs.
|
|
13
13
|
//
|
|
14
|
-
// Edits stay local until "Save changes"
|
|
15
|
-
//
|
|
14
|
+
// Edits stay local until "Save changes", which STAGES the whole list in the
|
|
15
|
+
// design draft: menus go live together with the design on "Publish design"
|
|
16
|
+
// (here or on the Design screen), and preview mode shows them first.
|
|
17
|
+
// Discard restores the last saved state.
|
|
16
18
|
|
|
17
19
|
type SaveState = 'saved' | 'saving' | 'error';
|
|
18
20
|
|
|
@@ -22,8 +24,7 @@ const AREAS: { key: NavArea; title: string; hint: string }[] = [
|
|
|
22
24
|
{ key: 'social', title: 'Social', hint: 'Footer social row — also used for share links on posts.' },
|
|
23
25
|
];
|
|
24
26
|
|
|
25
|
-
//
|
|
26
|
-
const isTemp = (id: string) => id.startsWith('tmp-');
|
|
27
|
+
// new items carry a temp id; Publish design gives them real ids
|
|
27
28
|
|
|
28
29
|
function Row({
|
|
29
30
|
item, onEdit, onArea, onDelete,
|
|
@@ -67,8 +68,10 @@ function Row({
|
|
|
67
68
|
);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export default function NavManager({ items: initial }: { items: NavItem[] }) {
|
|
71
|
+
export default function NavManager({ items: initial, staged: stagedInitially }: { items: NavItem[]; staged: boolean }) {
|
|
71
72
|
const [items, setItems] = useState<NavItem[]>(initial);
|
|
73
|
+
// the saved list is staged (differs from the live menus) until Publish
|
|
74
|
+
const [staged, setStaged] = useState(stagedInitially);
|
|
72
75
|
// last state the server has — dirty compares against this
|
|
73
76
|
const [saved, setSaved] = useState<NavItem[]>(initial);
|
|
74
77
|
const [saveState, setSaveState] = useState<SaveState>('saved');
|
|
@@ -122,45 +125,34 @@ export default function NavManager({ items: initial }: { items: NavItem[] }) {
|
|
|
122
125
|
});
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
// one commit for everything:
|
|
126
|
-
// updates, then the full display order
|
|
128
|
+
// one commit for everything: the full ordered list stages in the draft
|
|
127
129
|
async function saveAll() {
|
|
128
130
|
setSaveState('saving');
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (ordered.length) {
|
|
155
|
-
const r = await actions.nav.reorder({ ids: ordered });
|
|
156
|
-
if (r.error) throw r.error;
|
|
157
|
-
}
|
|
158
|
-
setItems(next);
|
|
159
|
-
setSaved(next);
|
|
160
|
-
setSaveState('saved');
|
|
161
|
-
} catch {
|
|
162
|
-
setSaveState('error');
|
|
163
|
-
}
|
|
131
|
+
const ordered = AREAS.flatMap((a) => items.filter((n) => n.area === a.key));
|
|
132
|
+
const r = await actions.nav.saveDraft({
|
|
133
|
+
items: ordered.map(({ id, label, url, area }) => ({ id, label, url, area })),
|
|
134
|
+
});
|
|
135
|
+
if (r.error) { setSaveState('error'); return; }
|
|
136
|
+
setItems(ordered);
|
|
137
|
+
setSaved(ordered);
|
|
138
|
+
setStaged(r.data.changes.some((c) => c.key === 'nav'));
|
|
139
|
+
setSaveState('saved');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function publish() {
|
|
143
|
+
if (!window.confirm('Publish now? Staged menus and any staged design changes go live together. The current design is saved to History.')) return;
|
|
144
|
+
setSaveState('saving');
|
|
145
|
+
const r = await actions.settings.applyDesign({});
|
|
146
|
+
if (r.error) { setSaveState('error'); return; }
|
|
147
|
+
setStaged(false);
|
|
148
|
+
setSaveState('saved');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function unstage() {
|
|
152
|
+
setSaveState('saving');
|
|
153
|
+
const r = await actions.settings.revertDesignChange({ key: 'nav' });
|
|
154
|
+
if (r.error) { setSaveState('error'); return; }
|
|
155
|
+
location.reload(); // back to the live menus
|
|
164
156
|
}
|
|
165
157
|
|
|
166
158
|
function discard() {
|
|
@@ -172,7 +164,7 @@ export default function NavManager({ items: initial }: { items: NavItem[] }) {
|
|
|
172
164
|
<div className="nm">
|
|
173
165
|
<div className="nm-head">
|
|
174
166
|
<span className={`lm-savestate ${saveState === 'saved' && dirty ? 'dirty' : saveState}`}>
|
|
175
|
-
{saveState === 'saving' ? 'Saving…' : saveState === 'error' ? 'Save failed' : dirty ? 'Unsaved changes
|
|
167
|
+
{saveState === 'saving' ? 'Saving…' : saveState === 'error' ? 'Save failed' : dirty ? 'Unsaved changes' : staged ? 'Staged, not live yet' : 'Live'}
|
|
176
168
|
</span>
|
|
177
169
|
{dirty && (
|
|
178
170
|
<>
|
|
@@ -180,6 +172,13 @@ export default function NavManager({ items: initial }: { items: NavItem[] }) {
|
|
|
180
172
|
<button className="btn sm primary" type="button" onClick={() => void saveAll()} disabled={saveState === 'saving'}>Save changes</button>
|
|
181
173
|
</>
|
|
182
174
|
)}
|
|
175
|
+
{!dirty && staged && (
|
|
176
|
+
<>
|
|
177
|
+
<button className="btn sm ghost" type="button" onClick={() => void unstage()} disabled={saveState === 'saving'}>Discard staged menus</button>
|
|
178
|
+
<a className="btn sm" href="/?designPreview=1" target="_blank" rel="noopener">Preview site ↗</a>
|
|
179
|
+
<button className="btn sm primary" type="button" onClick={() => void publish()} disabled={saveState === 'saving'}>Publish…</button>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
183
182
|
</div>
|
|
184
183
|
{AREAS.map((a) => {
|
|
185
184
|
const group = items.filter((n) => n.area === a.key);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Admin theme (Light / Dark / System). Inlined in <head> of every admin
|
|
3
|
+
// document so data-theme is set before first paint — no light flash on a
|
|
4
|
+
// dark-mode machine. The preference lives in localStorage per browser;
|
|
5
|
+
// "system" follows the OS live. Public-site previews (iframes) never read it.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<meta name="color-scheme" content="light dark" />
|
|
9
|
+
<meta name="theme-color" content="#e8e6e1" />
|
|
10
|
+
<script is:inline>
|
|
11
|
+
(() => {
|
|
12
|
+
const KEY = 'acms-theme';
|
|
13
|
+
const COLORS = { light: '#e8e6e1', dark: '#141311' };
|
|
14
|
+
const mq = matchMedia('(prefers-color-scheme: dark)');
|
|
15
|
+
const root = document.documentElement;
|
|
16
|
+
const pref = () => {
|
|
17
|
+
try { return localStorage.getItem(KEY) || 'system'; } catch { return 'system'; }
|
|
18
|
+
};
|
|
19
|
+
const apply = (p = pref()) => {
|
|
20
|
+
const t = p === 'dark' || (p === 'system' && mq.matches) ? 'dark' : 'light';
|
|
21
|
+
root.dataset.theme = t;
|
|
22
|
+
root.dataset.themePref = p;
|
|
23
|
+
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', COLORS[t]);
|
|
24
|
+
document.querySelectorAll('[data-set-theme]').forEach((el) => {
|
|
25
|
+
el.setAttribute('aria-pressed', String(el.getAttribute('data-set-theme') === p));
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const set = (p) => {
|
|
29
|
+
try { localStorage.setItem(KEY, p); } catch {}
|
|
30
|
+
apply(p);
|
|
31
|
+
window.dispatchEvent(new CustomEvent('acms:theme', { detail: root.dataset.theme }));
|
|
32
|
+
};
|
|
33
|
+
window.acmsTheme = { set, pref };
|
|
34
|
+
apply();
|
|
35
|
+
mq.addEventListener('change', () => pref() === 'system' && apply());
|
|
36
|
+
document.addEventListener('DOMContentLoaded', () => apply());
|
|
37
|
+
document.addEventListener('click', (e) => {
|
|
38
|
+
const el = e.target instanceof Element ? e.target.closest('[data-set-theme]') : null;
|
|
39
|
+
if (el) set(el.getAttribute('data-set-theme'));
|
|
40
|
+
});
|
|
41
|
+
// ⌘⇧L / Ctrl+Shift+L cycles light → dark → system
|
|
42
|
+
document.addEventListener('keydown', (e) => {
|
|
43
|
+
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'l') {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
const order = ['light', 'dark', 'system'];
|
|
46
|
+
set(order[(order.indexOf(pref()) + 1) % order.length]);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
})();
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
import LiveCard from './LiveCard.astro';
|
|
3
|
+
import type { Page, Settings } from '../lib/schema';
|
|
4
|
+
import { getTheme } from '../lib/theme';
|
|
5
|
+
import { getRepo } from '../lib/repo';
|
|
6
|
+
import { CONTENT, collectionUrl } from '../lib/content';
|
|
7
|
+
import { sectionIssues } from '../theme-sdk/contract.mjs';
|
|
8
|
+
import WidgetRenderer from './WidgetRenderer.astro';
|
|
9
|
+
import { expandDynamicBlocks } from '../lib/dynamicBlocks';
|
|
10
|
+
import { highlightCodeHtml } from '../lib/highlightCode';
|
|
11
|
+
import PostCard from './site/PostCard.astro';
|
|
12
|
+
import '../styles/sections.css';
|
|
13
|
+
interface Props { page: Page; settings: Settings }
|
|
14
|
+
const { page, settings } = Astro.props;
|
|
15
|
+
const theme = getTheme(settings.activeTheme);
|
|
16
|
+
const layout = settings.sectionPages[page.id];
|
|
17
|
+
const repo = await getRepo(Astro.locals);
|
|
18
|
+
const bodyHtml = page.bodyHtml ? highlightCodeHtml(await expandDynamicBlocks(page.bodyHtml, repo)) : '';
|
|
19
|
+
const safeUrl = (value: unknown) => {
|
|
20
|
+
const s = String(value ?? '').trim();
|
|
21
|
+
return /^(https?:\/\/|mailto:|tel:|\/(?!\/)|#)/i.test(s) ? s : '';
|
|
22
|
+
};
|
|
23
|
+
const sections = await Promise.all(layout.sections.map(async (section) => {
|
|
24
|
+
const definition = theme.sections[section.type];
|
|
25
|
+
const invalid = sectionIssues({ mode: 'sections', sections: [section] }, theme.sections).length > 0;
|
|
26
|
+
const data = Object.fromEntries(Object.entries(definition?.fields ?? {}).map(([key, field]) => [key, section.data[key] ?? (field as {default?: unknown}).default ?? '']));
|
|
27
|
+
const collection = String(data.collection || 'posts');
|
|
28
|
+
const posts = section.type === 'posts' && !section.hidden && !invalid && CONTENT.collections[collection]
|
|
29
|
+
? (await repo.listPublishedPosts(collection)).slice(0, Math.max(1, Math.min(12, Number(data.limit) || 3))) : [];
|
|
30
|
+
return { ...section, data, invalid, posts, Template: theme.sectionTemplates[section.type] as any };
|
|
31
|
+
}));
|
|
32
|
+
---
|
|
33
|
+
<div class="acms-sections">
|
|
34
|
+
{sections.filter(s => !s.hidden && !s.invalid).map(({ id, type, variant, data, Template, posts }) => (
|
|
35
|
+
<section id={`section-${id}`} class:list={['acms-section', `section-${type}`, `variant-${variant}`]}>
|
|
36
|
+
{type === 'live' && !Template ? <LiveCard display={variant} /> : type === 'page-content' ? (page.bodyHtml ? <article class="page-body" set:html={bodyHtml} /> : <WidgetRenderer pageId={page.id} blocks={page.blocks} />) : Template ? <Template data={data} variant={variant} settings={settings} page={page} /> : (
|
|
37
|
+
<>
|
|
38
|
+
<div class="section-copy">
|
|
39
|
+
{data.heading && <h2>{String(data.heading)}</h2>}
|
|
40
|
+
{data.text && <p class="section-text">{String(data.text)}</p>}
|
|
41
|
+
{data.label && safeUrl(data.href) && <a class="w-btn primary" href={safeUrl(data.href)}>{String(data.label)}</a>}
|
|
42
|
+
</div>
|
|
43
|
+
{type === 'hero' && safeUrl(data.image) && <img class="section-image" src={safeUrl(data.image)} alt={String(data.alt ?? '')} loading="eager" />}
|
|
44
|
+
{['features', 'testimonials', 'faq'].includes(type) && (
|
|
45
|
+
<div class="section-items">
|
|
46
|
+
{String(data.items ?? '').split('\n').filter(Boolean).map((row) => {
|
|
47
|
+
const [title, ...body] = row.split('|');
|
|
48
|
+
return type === 'faq' ? <details><summary>{title}</summary><p>{body.join('|')}</p></details>
|
|
49
|
+
: <article><h3>{title}</h3><p>{body.join('|')}</p></article>;
|
|
50
|
+
})}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
{type === 'posts' && <div class="section-items">{posts.map(post => <PostCard variant={variant === 'list' ? 'row' : 'card'} href={collectionUrl(post.collection, post.slug)} title={post.title} excerpt={post.excerpt} cover={post.cover} coverAlt={post.coverAlt} />)}</div>}
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
</section>
|
|
57
|
+
))}
|
|
58
|
+
</div>
|