@acms-subir/core 0.3.9 → 0.3.11
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 +51 -0
- package/cli/common.mjs +2 -2
- package/cli/legacy.mjs +2 -1
- package/cli/main.mjs +17 -3
- package/cli/migrate.mjs +102 -0
- package/cli/migrations.generated.mjs +1044 -0
- package/cli/mobile-doctor.mjs +9 -0
- package/cli/monitor.mjs +189 -0
- package/cli/preview.mjs +1 -1
- package/deploy/Dockerfile +5 -1
- package/deploy/compose.yml +21 -0
- package/docs/live-video.md +1 -1
- package/docs/mobile.md +23 -2
- package/docs/theme-authoring.md +6 -0
- package/integration.mjs +74 -4
- package/migrate.mjs +5 -0
- package/package.json +9 -11
- package/src/actions/index.ts +11 -31
- package/src/components/AppAccess.tsx +117 -0
- package/src/components/DesignManager.tsx +29 -10
- package/src/components/EmailSettings.tsx +53 -11
- package/src/components/MonitoringSettings.tsx +112 -0
- package/src/components/ThemeTransfer.tsx +211 -0
- package/src/components/auth/ClerkSession.astro +80 -0
- package/src/components/auth/ReverifyForms.astro +61 -0
- package/src/components/auth/clerk.ts +169 -0
- package/src/components/import/ImportExport.tsx +16 -6
- package/src/components/import/runner.ts +3 -0
- package/src/layouts/Admin.astro +42 -12
- package/src/layouts/Site.astro +6 -1
- package/src/lib/api.ts +8 -4
- package/src/lib/auth/accounts.ts +94 -0
- package/src/lib/auth/config.ts +22 -0
- package/src/lib/auth/login-limit.ts +15 -0
- package/src/lib/auth/request.ts +161 -0
- package/src/lib/auth/store.ts +72 -0
- package/src/lib/auth.ts +3 -0
- package/src/lib/backup/archive.ts +26 -0
- package/src/lib/backup/content.ts +1 -1
- package/src/lib/backup/offsite.ts +173 -0
- package/src/lib/backup/store.ts +28 -0
- package/src/lib/booking/availability.ts +15 -2
- package/src/lib/booking/service.ts +53 -1
- package/src/lib/cache/config.ts +7 -0
- package/src/lib/cache/memory.ts +39 -0
- package/src/lib/cache/policy.ts +89 -0
- package/src/lib/cache/purge.ts +89 -0
- package/src/lib/calendar/admin.ts +41 -0
- package/src/lib/designPreview.ts +2 -2
- package/src/lib/dynamicBlocks.ts +1 -1
- package/src/lib/editor/auth.ts +35 -5
- package/src/lib/editor/http.ts +4 -1
- package/src/lib/editor/me.ts +4 -0
- package/src/lib/editor/openapi.ts +49 -1
- package/src/lib/editor/roles.ts +20 -0
- package/src/lib/editor/schedule.ts +340 -0
- package/src/lib/editor/schemas.ts +36 -1
- package/src/lib/editor/site-data.ts +110 -0
- package/src/lib/editor/store.ts +13 -7
- package/src/lib/editor/token-route.ts +2 -1
- package/src/lib/email/accounts.ts +4 -0
- package/src/lib/email/service.ts +9 -1
- package/src/lib/email/store.ts +10 -0
- package/src/lib/email/templates.ts +8 -1
- package/src/lib/email/types.ts +1 -0
- package/src/lib/forms/store.ts +5 -0
- package/src/lib/mobile/access-admin.ts +34 -0
- package/src/lib/mobile/access.ts +28 -0
- package/src/lib/mobile/calendar.ts +26 -13
- package/src/lib/mobile/content.ts +21 -1
- package/src/lib/mobile/forms.ts +77 -0
- package/src/lib/mobile/guest-store.ts +62 -0
- package/src/lib/mobile/guest.ts +160 -0
- package/src/lib/mobile/http.ts +6 -5
- package/src/lib/mobile/links.ts +69 -0
- package/src/lib/mobile/manage.ts +114 -0
- package/src/lib/mobile/openapi.ts +40 -4
- package/src/lib/mobile/route.ts +63 -4
- package/src/lib/mobile/schemas.ts +52 -1
- package/src/lib/mobile/search.ts +95 -0
- package/src/lib/mobile/site.ts +8 -2
- package/src/lib/monitoring.ts +171 -0
- package/src/lib/net.ts +82 -14
- package/src/lib/repo/migrate-runner.ts +96 -0
- package/src/lib/repo/migrations.ts +104 -0
- package/src/lib/repo/sql.ts +122 -44
- package/src/lib/repo/sqlite.ts +14 -1
- package/src/lib/repo/types.ts +31 -3
- package/src/lib/scheduler.ts +14 -0
- package/src/lib/schema.ts +35 -1
- package/src/lib/theme-transfer.ts +163 -0
- package/src/lib/theme.ts +5 -0
- package/src/lib/webhooks.ts +7 -2
- package/src/middleware.ts +41 -11
- package/src/pages/[...slug].astro +3 -2
- package/src/pages/account/[action].astro +6 -0
- package/src/pages/admin/app-auth.astro +21 -7
- package/src/pages/admin/calendar/resources/link.astro +2 -0
- package/src/pages/admin/design.astro +2 -1
- package/src/pages/admin/import.astro +5 -3
- package/src/pages/admin/index.astro +3 -2
- package/src/pages/admin/integrations.astro +3 -2
- package/src/pages/admin/login.astro +236 -25
- package/src/pages/admin/logout.ts +16 -2
- package/src/pages/admin/mobile.astro +6 -2
- package/src/pages/admin/settings.astro +43 -5
- package/src/pages/admin/users.astro +350 -59
- package/src/pages/api/backup.ts +10 -26
- package/src/pages/api/design/export.ts +72 -0
- package/src/pages/api/design/import.ts +41 -0
- package/src/pages/api/email/settings.ts +9 -5
- package/src/pages/api/health.ts +17 -0
- package/src/pages/api/live/oauth/youtube/callback.ts +5 -4
- package/src/pages/api/live/settings.ts +2 -2
- package/src/pages/api/media/upload.ts +2 -2
- package/src/pages/api/mobile/access.ts +22 -0
- package/src/pages/api/monitoring.ts +22 -0
- package/src/pages/api/ops/status.ts +2 -1
- package/src/pages/api/push/admin.ts +3 -2
- package/src/pages/api/restore.ts +6 -3
- package/src/pages/api/v1/admin/bookings/[id]/[action].ts +7 -1
- package/src/pages/api/v1/admin/bookings/index.ts +10 -2
- package/src/pages/api/v1/admin/calendars/[id].ts +16 -0
- package/src/pages/api/v1/admin/calendars/index.ts +12 -0
- package/src/pages/api/v1/admin/forms/[id]/responses.ts +7 -0
- package/src/pages/api/v1/admin/forms/index.ts +6 -0
- package/src/pages/api/v1/admin/links.ts +13 -0
- package/src/pages/api/v1/admin/resources/[id]/blocks/[blockId].ts +10 -0
- package/src/pages/api/v1/admin/resources/[id]/blocks/index.ts +12 -0
- package/src/pages/api/v1/admin/resources/[id]/index.ts +17 -0
- package/src/pages/api/v1/admin/resources/[id]/links/[linkId].ts +16 -0
- package/src/pages/api/v1/admin/resources/[id]/links/index.ts +12 -0
- package/src/pages/api/v1/admin/resources/index.ts +12 -0
- package/src/pages/api/v1/admin/schedule.ts +7 -0
- package/src/pages/api/v1/bookings/manage/availability.ts +6 -0
- package/src/pages/api/v1/bookings/manage/cancel.ts +6 -0
- package/src/pages/api/v1/bookings/manage/ics.ts +6 -0
- package/src/pages/api/v1/bookings/manage/index.ts +6 -0
- package/src/pages/api/v1/bookings/manage/reschedule.ts +6 -0
- package/src/pages/api/v1/calendars.ts +1 -1
- package/src/pages/api/v1/forms/[id]/index.ts +7 -0
- package/src/pages/api/v1/forms/[id]/responses.ts +6 -0
- package/src/pages/api/v1/guest/bookings.ts +6 -0
- package/src/pages/api/v1/guest/code.ts +6 -0
- package/src/pages/api/v1/guest/revoke.ts +6 -0
- package/src/pages/api/v1/guest/token.ts +6 -0
- package/src/pages/api/v1/links.ts +7 -0
- package/src/pages/api/v1/openapi.json.ts +1 -1
- package/src/pages/api/v1/push/topics.ts +1 -1
- package/src/pages/api/v1/resources.ts +1 -1
- package/src/pages/api/v1/search.ts +7 -0
- package/src/pages/api/v1/theme.ts +1 -1
- package/src/pages/blog/[slug].astro +3 -2
- package/src/pages/book/[resource]/[link].astro +3 -3
- package/src/pages/book/manage/[token].astro +11 -7
- package/src/pages/events/[slug].astro +3 -2
- package/src/pages/preview/[type]/[id].astro +3 -3
- package/src/styles/admin.css +169 -16
- package/src/styles/email-settings.css +6 -0
- package/src/virtual.d.ts +1 -0
- package/src/worker.ts +7 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getForm } from '../../../../../lib/mobile/forms';
|
|
2
|
+
import { readRoute } from '../../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../../lib/mobile/http';
|
|
5
|
+
export const GET = readRoute(getForm, true);
|
|
6
|
+
export const HEAD = GET;
|
|
7
|
+
export const ALL = () => methodNotAllowed();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { submitFormResponse } from '../../../../../lib/mobile/forms';
|
|
2
|
+
import { privateRoute } from '../../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../../lib/mobile/http';
|
|
5
|
+
export const POST = privateRoute(submitFormResponse, { write: true, status: 201 });
|
|
6
|
+
export const ALL = () => methodNotAllowed('POST, OPTIONS');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { myBookings } from '../../../../lib/mobile/guest';
|
|
2
|
+
import { privateRoute } from '../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../lib/mobile/http';
|
|
5
|
+
export const GET = privateRoute(myBookings);
|
|
6
|
+
export const ALL = () => methodNotAllowed('GET, OPTIONS');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { requestCode } from '../../../../lib/mobile/guest';
|
|
2
|
+
import { privateRoute } from '../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../lib/mobile/http';
|
|
5
|
+
export const POST = privateRoute(requestCode, { write: true, status: 202 });
|
|
6
|
+
export const ALL = () => methodNotAllowed('POST, OPTIONS');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { revokeGuest } from '../../../../lib/mobile/guest';
|
|
2
|
+
import { privateRoute } from '../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../lib/mobile/http';
|
|
5
|
+
export const POST = privateRoute(revokeGuest, { write: true });
|
|
6
|
+
export const ALL = () => methodNotAllowed('POST, OPTIONS');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { exchangeCode } from '../../../../lib/mobile/guest';
|
|
2
|
+
import { privateRoute } from '../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../lib/mobile/http';
|
|
5
|
+
export const POST = privateRoute(exchangeCode, { write: true });
|
|
6
|
+
export const ALL = () => methodNotAllowed('POST, OPTIONS');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getLinks } from '../../../lib/mobile/links';
|
|
2
|
+
import { readRoute } from '../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../lib/mobile/http';
|
|
5
|
+
export const GET = readRoute(getLinks, true, false, { timeless: true });
|
|
6
|
+
export const HEAD = GET;
|
|
7
|
+
export const ALL = () => methodNotAllowed();
|
|
@@ -2,6 +2,6 @@ import { getOpenApi } from '../../../lib/mobile/openapi';
|
|
|
2
2
|
import { readRoute } from '../../../lib/mobile/route';
|
|
3
3
|
import { methodNotAllowed } from '../../../lib/mobile/http';
|
|
4
4
|
export { preflight as OPTIONS } from '../../../lib/mobile/http';
|
|
5
|
-
export const GET = readRoute(getOpenApi, true, true);
|
|
5
|
+
export const GET = readRoute(getOpenApi, true, true, { timeless: true });
|
|
6
6
|
export const HEAD = GET;
|
|
7
7
|
export const ALL = () => methodNotAllowed();
|
|
@@ -2,6 +2,6 @@ import { listPushTopics } from '../../../../lib/push/api';
|
|
|
2
2
|
import { readRoute } from '../../../../lib/mobile/route';
|
|
3
3
|
import { methodNotAllowed } from '../../../../lib/mobile/http';
|
|
4
4
|
export { preflight as OPTIONS } from '../../../../lib/mobile/http';
|
|
5
|
-
export const GET = readRoute(listPushTopics, true);
|
|
5
|
+
export const GET = readRoute(listPushTopics, true, false, { timeless: true });
|
|
6
6
|
export const HEAD = GET;
|
|
7
7
|
export const ALL = () => methodNotAllowed();
|
|
@@ -2,6 +2,6 @@ import { listResources } from '../../../lib/mobile/calendar';
|
|
|
2
2
|
import { readRoute } from '../../../lib/mobile/route';
|
|
3
3
|
import { methodNotAllowed } from '../../../lib/mobile/http';
|
|
4
4
|
export { preflight as OPTIONS } from '../../../lib/mobile/http';
|
|
5
|
-
export const GET = readRoute(listResources, true);
|
|
5
|
+
export const GET = readRoute(listResources, true, false, { timeless: true });
|
|
6
6
|
export const HEAD = GET;
|
|
7
7
|
export const ALL = () => methodNotAllowed();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { search } from '../../../lib/mobile/search';
|
|
2
|
+
import { readRoute } from '../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../lib/mobile/http';
|
|
5
|
+
export const GET = readRoute(search, true);
|
|
6
|
+
export const HEAD = GET;
|
|
7
|
+
export const ALL = () => methodNotAllowed();
|
|
@@ -2,6 +2,6 @@ import { getThemeTokens } from '../../../lib/mobile/site';
|
|
|
2
2
|
import { readRoute } from '../../../lib/mobile/route';
|
|
3
3
|
import { methodNotAllowed } from '../../../lib/mobile/http';
|
|
4
4
|
export { preflight as OPTIONS } from '../../../lib/mobile/http';
|
|
5
|
-
export const GET = readRoute(getThemeTokens);
|
|
5
|
+
export const GET = readRoute(getThemeTokens, true, false, { timeless: true });
|
|
6
6
|
export const HEAD = GET;
|
|
7
7
|
export const ALL = () => methodNotAllowed();
|
|
@@ -8,7 +8,7 @@ import { expandDynamicBlocks, extractToc } from '../../lib/dynamicBlocks';
|
|
|
8
8
|
import TagLabel from '../../components/site/TagLabel.astro';
|
|
9
9
|
import PostCard from '../../components/site/PostCard.astro';
|
|
10
10
|
import { highlightCodeHtml } from '../../lib/highlightCode';
|
|
11
|
-
import {
|
|
11
|
+
import { validateRequestSession } from '../../lib/auth/request';
|
|
12
12
|
import { slugify, authorSlug, publicTags } from '../../lib/schema';
|
|
13
13
|
import { resolveDesign } from '../../lib/theme';
|
|
14
14
|
import { effectiveSettings } from '../../lib/designPreview';
|
|
@@ -27,7 +27,7 @@ const locale = requestLocale(Astro.url, Astro.locals);
|
|
|
27
27
|
const rawPost = slug ? await repo.getPostBySlug(slug, col.key) : null;
|
|
28
28
|
const post = rawPost ? localizeDoc(rawPost, locale) : null;
|
|
29
29
|
|
|
30
|
-
const isAdmin = (await
|
|
30
|
+
const isAdmin = (await validateRequestSession(Astro.request, Astro.cookies, Astro.locals));
|
|
31
31
|
if (!post || (post.status !== 'published' && !isAdmin)) {
|
|
32
32
|
return new Response(null, { status: 404, statusText: 'Not found' });
|
|
33
33
|
}
|
|
@@ -111,6 +111,7 @@ const shareLi = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURI
|
|
|
111
111
|
---
|
|
112
112
|
|
|
113
113
|
<Site
|
|
114
|
+
staffPreview={!!isAdmin && post.status !== 'published'}
|
|
114
115
|
title={post.seoTitle || post.title}
|
|
115
116
|
exactTitle={Boolean(post.seoTitle)}
|
|
116
117
|
description={post.seoDesc || post.excerpt || undefined}
|
|
@@ -3,7 +3,7 @@ import Site from '../../../layouts/Site.astro';
|
|
|
3
3
|
import BookingFlow from '../../../components/booking/BookingFlow.astro';
|
|
4
4
|
import { getRepo } from '../../../lib/repo';
|
|
5
5
|
import { bookingsEnabled, manageUrl, resourceUrl } from '../../../lib/calendar/config';
|
|
6
|
-
import { availabilityContext, createResourceBooking,
|
|
6
|
+
import { availabilityContext, createResourceBooking, guestMoveLink, guestReschedule, BookingError } from '../../../lib/booking/service';
|
|
7
7
|
import { siteOrigin } from '../../../lib/seo';
|
|
8
8
|
import { waitUntilFrom } from '../../../lib/webhooks';
|
|
9
9
|
|
|
@@ -36,7 +36,7 @@ if (Astro.request.method === 'POST') {
|
|
|
36
36
|
if (token) {
|
|
37
37
|
const b = await repo.getBookingByToken(token);
|
|
38
38
|
if (!b || b.linkId !== link.id) throw new BookingError(404, 'That booking can’t be found.');
|
|
39
|
-
await
|
|
39
|
+
await guestReschedule(repo, b, start, end, flow);
|
|
40
40
|
return Astro.redirect(`${manageUrl(b.manageToken)}?moved=1`);
|
|
41
41
|
}
|
|
42
42
|
const answers: Record<string, string> = {};
|
|
@@ -59,7 +59,7 @@ if (Astro.request.method === 'POST') {
|
|
|
59
59
|
Astro.response.status = err instanceof BookingError ? err.status : 500;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
const ctx = await availabilityContext(repo, resource, link, settings, Date.now(), reschedule?.id);
|
|
62
|
+
const ctx = await availabilityContext(repo, resource, reschedule ? guestMoveLink(link) : link, settings, Date.now(), reschedule?.id);
|
|
63
63
|
---
|
|
64
64
|
|
|
65
65
|
<Site title={`${link.name} · ${resource.name}`} description={link.description || `Book ${resource.name}.`} noindex={link.visibility === 'unlisted' || resource.visibility === 'unlisted'}>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import Site from '../../../layouts/Site.astro';
|
|
3
3
|
import { getRepo } from '../../../lib/repo';
|
|
4
4
|
import { CALENDAR, bookingLinkUrl, bookingsEnabled, eventUrl, resourceUrl } from '../../../lib/calendar/config';
|
|
5
|
-
import {
|
|
5
|
+
import { guestCancel, guestChanges, BookingError } from '../../../lib/booking/service';
|
|
6
6
|
import { bookingIcs, whenText } from '../../../lib/booking/emails';
|
|
7
7
|
import { resourceZone } from '../../../lib/booking/availability';
|
|
8
8
|
import { eventTimeZone, locationLine } from '../../../lib/calendar/events';
|
|
@@ -38,7 +38,7 @@ if (Astro.request.method === 'POST') {
|
|
|
38
38
|
const form = await Astro.request.formData();
|
|
39
39
|
if (form.get('op') === 'cancel' && (booking.status === 'pending' || booking.status === 'confirmed')) {
|
|
40
40
|
try {
|
|
41
|
-
current = await
|
|
41
|
+
current = await guestCancel(repo, booking, { origin, locals: Astro.locals, waitUntil: waitUntilFrom(Astro.locals) });
|
|
42
42
|
} catch (err) {
|
|
43
43
|
error = err instanceof BookingError ? err.message : 'Couldn’t cancel — please try again.';
|
|
44
44
|
}
|
|
@@ -48,6 +48,7 @@ const tz = event ? eventTimeZone(event, settings) : resource ? resourceZone(reso
|
|
|
48
48
|
const when = whenText(current, tz, settings, link?.mode === 'day' || event?.allDay);
|
|
49
49
|
const active = current.status === 'pending' || current.status === 'confirmed';
|
|
50
50
|
const upcoming = current.end > Date.now();
|
|
51
|
+
const can = guestChanges(current, link);
|
|
51
52
|
const isNew = Astro.url.searchParams.has('new');
|
|
52
53
|
const moved = Astro.url.searchParams.has('moved');
|
|
53
54
|
const heading = current.status === 'cancelled' ? 'Booking cancelled'
|
|
@@ -91,15 +92,18 @@ const googleUrl = `https://calendar.google.com/calendar/render?${new URLSearchPa
|
|
|
91
92
|
</div>
|
|
92
93
|
</details>
|
|
93
94
|
)}
|
|
94
|
-
{resource && link &&
|
|
95
|
+
{resource && link && can.reschedule && (
|
|
95
96
|
<a class="pc-manage-btn" href={`${bookingLinkUrl(resource.slug, link.slug)}?reschedule=${encodeURIComponent(current.manageToken)}`}>Reschedule</a>
|
|
96
97
|
)}
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
{can.cancel && (
|
|
99
|
+
<form method="post" onsubmit="return confirm('Cancel this booking?')">
|
|
100
|
+
<input type="hidden" name="op" value="cancel" />
|
|
101
|
+
<button class="pc-manage-btn is-danger" type="submit">Cancel {current.kind === 'rsvp' ? 'RSVP' : 'booking'}</button>
|
|
102
|
+
</form>
|
|
103
|
+
)}
|
|
101
104
|
</div>
|
|
102
105
|
)}
|
|
106
|
+
{can.closed && <p class="pc-manage-note">Changes close {can.cutoffHours} hours before the start. Please contact us to change or cancel.</p>}
|
|
103
107
|
<p class="pc-bk-foot">
|
|
104
108
|
{event ? <a href={eventUrl(event.slug)}>Back to {event.title}</a> : resource ? <a href={resourceUrl(resource.slug)}>Back to {resource.name}</a> : null}
|
|
105
109
|
</p>
|
|
@@ -12,7 +12,7 @@ import EventPage from '../../components/EventPage.astro';
|
|
|
12
12
|
import { getRepo } from '../../lib/repo';
|
|
13
13
|
import { expandDynamicBlocks } from '../../lib/dynamicBlocks';
|
|
14
14
|
import { highlightCodeHtml } from '../../lib/highlightCode';
|
|
15
|
-
import {
|
|
15
|
+
import { validateRequestSession } from '../../lib/auth/request';
|
|
16
16
|
import { effectiveSettings } from '../../lib/designPreview';
|
|
17
17
|
import { resolveDesign } from '../../lib/theme';
|
|
18
18
|
import { siteOrigin } from '../../lib/seo';
|
|
@@ -34,7 +34,7 @@ const rawEvent = await repo.getEventBySlug(Astro.params.slug ?? '');
|
|
|
34
34
|
const event = rawEvent ? localizeDoc(rawEvent, locale) : null;
|
|
35
35
|
const [rawSettings, allCalendars] = await Promise.all([repo.getSettings(), repo.listCalendars()]);
|
|
36
36
|
const calMap = new Map(allCalendars.map((c) => [c.id, c]));
|
|
37
|
-
const isAdmin = (await
|
|
37
|
+
const isAdmin = (await validateRequestSession(Astro.request, Astro.cookies, Astro.locals));
|
|
38
38
|
if (!event || (!eventIsPublic(event, calMap) && !isAdmin)) return new Response(null, { status: 404, statusText: 'Not found' });
|
|
39
39
|
|
|
40
40
|
const baseSettings = await effectiveSettings(rawSettings, Astro.url, Astro.cookies, Astro.locals);
|
|
@@ -81,6 +81,7 @@ const props = { event, occurrence, settings, calendars, html, upcoming, origin,
|
|
|
81
81
|
---
|
|
82
82
|
|
|
83
83
|
<Site
|
|
84
|
+
staffPreview={!!isAdmin && !eventIsPublic(event, calMap)}
|
|
84
85
|
title={event.seoTitle || event.title}
|
|
85
86
|
exactTitle={Boolean(event.seoTitle)}
|
|
86
87
|
description={event.seoDesc || event.excerpt || undefined}
|
|
@@ -6,7 +6,7 @@ import { renderMarkdown } from '../../../lib/markdown';
|
|
|
6
6
|
import { verifyPreviewToken } from '../../../lib/preview';
|
|
7
7
|
import { expandDynamicBlocks } from '../../../lib/dynamicBlocks';
|
|
8
8
|
import { highlightCodeHtml } from '../../../lib/highlightCode';
|
|
9
|
-
import {
|
|
9
|
+
import { validateRequestSession } from '../../../lib/auth/request';
|
|
10
10
|
import { mergeEventDraft, mergePageDraft, mergePostDraft } from '../../../lib/drafts';
|
|
11
11
|
import EventPage from '../../../components/EventPage.astro';
|
|
12
12
|
import { featuredOccurrence } from '../../../lib/calendar/events';
|
|
@@ -19,7 +19,7 @@ import { siteOrigin } from '../../../lib/seo';
|
|
|
19
19
|
const { type, id } = Astro.params;
|
|
20
20
|
if (type !== 'post' && type !== 'page' && type !== 'event') return new Response(null, { status: 404 });
|
|
21
21
|
|
|
22
|
-
const isAdmin = (await
|
|
22
|
+
const isAdmin = (await validateRequestSession(Astro.request, Astro.cookies, Astro.locals));
|
|
23
23
|
const token = Astro.url.searchParams.get('token') ?? undefined;
|
|
24
24
|
if (!isAdmin && !verifyPreviewToken(type, id!, token)) {
|
|
25
25
|
return new Response(null, { status: 404 });
|
|
@@ -53,7 +53,7 @@ const fmt = (ts: number | null) =>
|
|
|
53
53
|
ts ? new Date(ts).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : '';
|
|
54
54
|
---
|
|
55
55
|
|
|
56
|
-
<Site title={post?.title ?? event?.title ?? page?.seoTitle ?? page?.title}>
|
|
56
|
+
<Site title={post?.title ?? event?.title ?? page?.seoTitle ?? page?.title} staffPreview={!!isAdmin}>
|
|
57
57
|
{event && eventView?.occurrence && (
|
|
58
58
|
<EventPage event={event} occurrence={eventView.occurrence} settings={eventView.settings} calendars={eventView.calendars} html={eventView.html} upcoming={[]} origin={eventView.origin} preview />
|
|
59
59
|
)}
|
package/src/styles/admin.css
CHANGED
|
@@ -757,6 +757,25 @@ dialog.modal[open] { animation: modal-in 0.16s ease-out; }
|
|
|
757
757
|
.btn.block { width: 100%; justify-content: center; min-height: 38px; }
|
|
758
758
|
.login-note { margin-top: 14px; }
|
|
759
759
|
.login-for-app { text-align: center; margin: -8px 0 14px; color: var(--muted); }
|
|
760
|
+
.login-card.is-clerk { max-width: 420px; }
|
|
761
|
+
.login-mount { min-height: 180px; }
|
|
762
|
+
.login-mount[hidden] { display: none; }
|
|
763
|
+
.login-loading { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 180px; margin: 0; color: var(--muted); font-size: 13px; }
|
|
764
|
+
.login-spinner { width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--line-strong); border-top-color: var(--accent); animation: login-spin 0.8s linear infinite; }
|
|
765
|
+
@keyframes login-spin { to { transform: rotate(360deg); } }
|
|
766
|
+
@media (prefers-reduced-motion: reduce) { .login-spinner { animation-duration: 2.4s; } }
|
|
767
|
+
.login-clerk [data-clerk-error] { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
|
|
768
|
+
.login-clerk [data-clerk-error][hidden] { display: none; }
|
|
769
|
+
.login-state { text-align: center; }
|
|
770
|
+
.login-state h1 { font-size: 19px; letter-spacing: -0.02em; margin: 10px 0 6px; }
|
|
771
|
+
.login-state p { color: var(--text-2); margin: 0 0 10px; overflow-wrap: anywhere; }
|
|
772
|
+
.login-state .hint { margin-bottom: 18px; line-height: 1.5; }
|
|
773
|
+
.login-state-icon { display: inline-grid; place-items: center; width: 40px; height: 40px; border-radius: var(--r-md); background: var(--sel); color: var(--text-2); }
|
|
774
|
+
.login-state-icon svg { width: 20px; height: 20px; }
|
|
775
|
+
.login-state-icon.is-warn { background: var(--amber-soft); color: var(--amber); }
|
|
776
|
+
.login-state-icon.is-error { background: var(--red-soft); color: var(--red); }
|
|
777
|
+
.login-actions { display: grid; gap: 8px; }
|
|
778
|
+
[data-clerk-profile][hidden] { display: none; }
|
|
760
779
|
|
|
761
780
|
/* editor-app sign-in (consent) — same paper card as login, a little wider */
|
|
762
781
|
.consent-wrap { align-items: start; padding-top: max(8vh, 20px); }
|
|
@@ -1238,19 +1257,72 @@ input.mono { font-family: ui-monospace, 'SF Mono', monospace; font-size: 12.5px;
|
|
|
1238
1257
|
.grow-text { flex: 1; min-width: 220px; }
|
|
1239
1258
|
.grow-text .hint { margin-left: 6px; }
|
|
1240
1259
|
.hint.tight { margin: 2px 0 0; }
|
|
1241
|
-
.user-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
1242
|
-
.user-form-row { display: flex; gap: 8px; flex-wrap: wrap; width: 100%; align-items: center; }
|
|
1243
|
-
.user-form .w-name { width: 150px; }
|
|
1244
|
-
.user-form .w-role { width: 100px; }
|
|
1245
|
-
.user-form .w-pass { width: 180px; }
|
|
1246
|
-
.user-form .w-slug { width: 140px; }
|
|
1247
|
-
.user-form .w-grow { flex: 1; min-width: 220px; }
|
|
1248
|
-
.user-form .w-full { width: 100%; }
|
|
1249
|
-
.user-email { min-width: 180px; }
|
|
1250
1260
|
.td-actions { text-align: right; width: 80px; }
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1261
|
+
/* Users: sign-in strip, tabs, member table, profile drawer */
|
|
1262
|
+
.auth-strip { display: flex; align-items: center; gap: 12px; padding: 12px 16px; margin-bottom: 18px; }
|
|
1263
|
+
.auth-mark { flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--r-md); background: var(--sel); color: var(--text-2); }
|
|
1264
|
+
.auth-mark svg { width: 17px; height: 17px; }
|
|
1265
|
+
.auth-main { flex: 1; min-width: 0; }
|
|
1266
|
+
.auth-main h2 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13.5px; margin: 0 0 2px; }
|
|
1267
|
+
.auth-main .hint { margin: 0; line-height: 1.5; }
|
|
1268
|
+
.auth-where { flex: none; margin: 0; font-size: 12px; }
|
|
1269
|
+
.users-tabs { margin-bottom: 12px; }
|
|
1270
|
+
.users-tabs .count { font-size: 11px; font-weight: 600; color: var(--muted); background: var(--panel-2); border-radius: var(--r-full); padding: 0 6px; min-width: 18px; text-align: center; }
|
|
1271
|
+
.users-tabs [aria-selected='true'] .count { color: var(--text-2); background: var(--sel); }
|
|
1272
|
+
.users-panel[hidden] { display: none; }
|
|
1273
|
+
.users-panel.devices-card { margin-top: 0; }
|
|
1274
|
+
.users-panel-hint { margin: 0; padding: 14px 18px 4px; }
|
|
1275
|
+
.users-table tr[data-user-row] { cursor: pointer; }
|
|
1276
|
+
.users-table td { padding-top: 10px; padding-bottom: 10px; }
|
|
1277
|
+
.user-cell { display: flex; align-items: center; gap: 11px; min-width: 0; }
|
|
1278
|
+
.avatar.user-av { width: 32px; height: 32px; font-size: 11px; }
|
|
1279
|
+
.user-who { display: flex; flex-direction: column; min-width: 0; line-height: 1.35; }
|
|
1280
|
+
.user-who .t-title, .user-who .t-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1281
|
+
.user-you { margin-left: 7px; font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); }
|
|
1282
|
+
dialog.modal.drawer {
|
|
1283
|
+
margin: 0 0 0 auto; width: min(440px, 100vw); height: 100dvh; max-height: 100dvh; max-width: 100vw;
|
|
1284
|
+
border-radius: var(--r-lg) 0 0 var(--r-lg); border-left: 1px solid var(--line-strong);
|
|
1285
|
+
flex-direction: column; overflow: hidden;
|
|
1286
|
+
}
|
|
1287
|
+
dialog.modal.drawer[open] { display: flex; animation: drawer-in 0.2s ease-out; }
|
|
1288
|
+
@keyframes drawer-in { from { opacity: 0.4; transform: translateX(28px); } }
|
|
1289
|
+
.drawer-head { display: flex; align-items: center; gap: 12px; padding: 18px 20px 16px; border-bottom: 1px solid var(--line); }
|
|
1290
|
+
.avatar.drawer-av { width: 46px; height: 46px; font-size: 15px; }
|
|
1291
|
+
.drawer-title { flex: 1; min-width: 0; }
|
|
1292
|
+
.drawer-title h2 { font-size: 16px; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1293
|
+
.drawer-title a { font-size: 12px; color: var(--muted); text-decoration: none; }
|
|
1294
|
+
.drawer-title a:hover { color: var(--accent-deep); }
|
|
1295
|
+
.drawer-body { flex: 1; overflow-y: auto; padding: 4px 20px 8px; }
|
|
1296
|
+
.drawer-body > .flash { margin: 14px 0 0; }
|
|
1297
|
+
.drawer-sec { padding: 16px 0 6px; border-bottom: 1px solid var(--line); }
|
|
1298
|
+
.drawer-sec:last-child { border-bottom: 0; }
|
|
1299
|
+
.drawer-sec h3 { margin: 0 0 10px; font-size: 11px; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase; color: var(--faint); }
|
|
1300
|
+
.drawer-kv { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; font-size: 13px; }
|
|
1301
|
+
.drawer-kv .k { color: var(--muted); flex: none; }
|
|
1302
|
+
.drawer-kv .v { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
1303
|
+
.drawer-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1304
|
+
.drawer-link { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
1305
|
+
.drawer-link input { flex: 1; min-width: 0; font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12px; }
|
|
1306
|
+
.drawer-link .hint { flex-basis: 100%; margin: 0; }
|
|
1307
|
+
label.field > .slug-wrap { display: flex; margin: 0; font-size: inherit; font-weight: inherit; color: var(--text); }
|
|
1308
|
+
.drawer-sec .slug-wrap input { flex: 1; min-width: 0; }
|
|
1309
|
+
.drawer-foot { display: flex; align-items: center; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--line); background: var(--panel-2); }
|
|
1310
|
+
.drawer-foot .spacer { flex: 1; }
|
|
1311
|
+
input.is-readonly { background: transparent; border-style: dashed; color: var(--text-2); }
|
|
1312
|
+
.callout { margin: 0 0 12px; padding: 10px 12px; border-radius: var(--r-md); background: var(--blue-soft); color: var(--blue); font-size: 12.5px; line-height: 1.5; }
|
|
1313
|
+
.callout.warn { background: var(--amber-soft); color: var(--amber); }
|
|
1314
|
+
@media (max-width: 640px) {
|
|
1315
|
+
dialog.modal.drawer { width: 100vw; border-radius: 0; border-left: 0; }
|
|
1316
|
+
.auth-strip { flex-wrap: wrap; }
|
|
1317
|
+
.auth-where { flex-basis: 100%; margin-left: 44px; }
|
|
1318
|
+
.users-table th:nth-child(3), .users-table td:nth-child(3) { display: none; }
|
|
1319
|
+
}
|
|
1320
|
+
/* Users: flash messages + Clerk re-verification */
|
|
1321
|
+
.users-flash { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
|
|
1322
|
+
.users-flash-actions { display: inline-flex; gap: 6px; flex-wrap: wrap; }
|
|
1323
|
+
.reverify-text { margin: 0 0 8px; font-size: 14px; line-height: 1.5; }
|
|
1324
|
+
.reverify-dialog .hint { margin: 0; }
|
|
1325
|
+
[data-acms-reverify][hidden] { display: none; }
|
|
1254
1326
|
.sheet:focus, .sheet:focus-visible { outline: none; }
|
|
1255
1327
|
|
|
1256
1328
|
/* ---- calendar module (events list, calendars) --------------------------- */
|
|
@@ -1511,10 +1583,6 @@ a.bk-row:hover, .bk-row.is-sel { background: var(--hover); }
|
|
|
1511
1583
|
.dm-status-actions { margin-left: 0; width: 100%; }
|
|
1512
1584
|
}
|
|
1513
1585
|
|
|
1514
|
-
/* role: marker + text (no badges) */
|
|
1515
|
-
.role-mark { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--text-2); }
|
|
1516
|
-
.role-mark i { width: 7px; height: 7px; border-radius: 2px; background: var(--blue); }
|
|
1517
|
-
.role-mark i.is-owner { background: var(--accent); }
|
|
1518
1586
|
|
|
1519
1587
|
/* Users → Signed-in apps */
|
|
1520
1588
|
.devices-card { margin-top: 20px; }
|
|
@@ -1590,3 +1658,88 @@ a.bk-row:hover, .bk-row.is-sel { background: var(--hover); }
|
|
|
1590
1658
|
/* Bookings → Day view: a room held by an event (tinted, dashed; links to the event) */
|
|
1591
1659
|
.bk-blk.is-hold { background: color-mix(in srgb, var(--accent) 10%, var(--panel)); border: 1px dashed var(--accent-ring); z-index: 0; }
|
|
1592
1660
|
.bk-blk:not(.is-hold) { z-index: 1; }
|
|
1661
|
+
/* Admin → Mobile app → App access: plain rows, square switches (no pills) */
|
|
1662
|
+
.acc { display: grid; gap: 16px; margin-bottom: 24px; }
|
|
1663
|
+
.acc .card { margin: 0; }
|
|
1664
|
+
.acc-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 0; border-top: 1px solid var(--line); }
|
|
1665
|
+
.acc .card-head + .acc-row { border-top: 0; }
|
|
1666
|
+
.acc-row > div:first-child { min-width: 0; }
|
|
1667
|
+
.acc-row b { display: block; font-size: 14px; }
|
|
1668
|
+
.acc-row small { display: block; color: var(--muted); font-size: 12.5px; margin-top: 2px; }
|
|
1669
|
+
.acc-row small.acc-warn { color: var(--amber); }
|
|
1670
|
+
.acc-actions { display: flex; align-items: center; gap: 10px; flex: none; }
|
|
1671
|
+
.acc-switch { width: 38px; height: 22px; border-radius: 7px; border: 0; padding: 0; background: var(--line-strong); position: relative; flex: none; cursor: pointer; }
|
|
1672
|
+
.acc-switch i { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 5px; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / 25%); transition: left 0.15s; }
|
|
1673
|
+
.acc-switch.on { background: var(--accent); }
|
|
1674
|
+
.acc-switch.on i { left: 19px; }
|
|
1675
|
+
.acc-switch:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
|
|
1676
|
+
.acc-switch:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1677
|
+
.acc-row input.acc-num { width: 96px; flex: none; text-align: right; }
|
|
1678
|
+
@media (max-width: 640px) {
|
|
1679
|
+
/* switches and numbers stay beside their label; the role picker takes its own line */
|
|
1680
|
+
.acc-row { flex-wrap: wrap; }
|
|
1681
|
+
.acc-row > div:first-child { flex: 1 1 180px; }
|
|
1682
|
+
.acc-row .seg { width: 100%; }
|
|
1683
|
+
.acc-row .seg > * { flex: 1; }
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/* Settings → Monitoring (Uptime Kuma) */
|
|
1687
|
+
.mon { display: grid; gap: 16px; }
|
|
1688
|
+
.mon .flash { margin: 0; }
|
|
1689
|
+
.mon-dot { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 8px; vertical-align: 1px; background: var(--faint); }
|
|
1690
|
+
.mon-dot.up { background: var(--green); }
|
|
1691
|
+
.mon-dot.down { background: var(--red); }
|
|
1692
|
+
.mon-dot.pending, .mon-dot.maintenance { background: var(--amber); }
|
|
1693
|
+
.mon-self { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; font-size: 13px; color: var(--text-2); padding: 12px 14px; background: var(--panel-2); border-radius: var(--r-md); }
|
|
1694
|
+
.mon-self b { color: var(--text); }
|
|
1695
|
+
.mon-self a { margin-left: auto; color: var(--muted); font-size: 12.5px; }
|
|
1696
|
+
.mon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
|
|
1697
|
+
.mon-tile { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px 14px; min-width: 0; }
|
|
1698
|
+
.mon-tile.is-down { background: var(--red-soft); border-color: rgb(192 59 59 / 25%); }
|
|
1699
|
+
.mon-name { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1700
|
+
.mon-sub { color: var(--muted); font-size: 12.5px; margin-top: 2px; }
|
|
1701
|
+
.mon-sub .nw { white-space: nowrap; }
|
|
1702
|
+
.mon-msg { color: var(--red); font-size: 12.5px; margin-top: 4px; overflow-wrap: anywhere; }
|
|
1703
|
+
.mon-beats { display: flex; gap: 2px; margin-top: 10px; height: 14px; }
|
|
1704
|
+
.mon-beats i { flex: 1; max-width: 8px; border-radius: 2px; background: var(--green); opacity: 0.75; }
|
|
1705
|
+
.mon-beats i.down { background: var(--red); opacity: 1; }
|
|
1706
|
+
.mon-beats i.pending, .mon-beats i.maintenance { background: var(--amber); }
|
|
1707
|
+
.mon-empty { border: 1px dashed var(--line-strong); border-radius: var(--r-md); padding: 14px 16px; }
|
|
1708
|
+
.mon-empty p { margin: 4px 0 0; color: var(--text-2); font-size: 13px; }
|
|
1709
|
+
.mon-cmd { display: block; margin: 8px 0; padding: 8px 10px; overflow-x: auto; white-space: nowrap; font-size: 12.5px; }
|
|
1710
|
+
.mon-fields { display: grid; gap: 14px; }
|
|
1711
|
+
.mon-field { display: grid; gap: 6px; font-size: 13px; }
|
|
1712
|
+
.mon-field > span { font-weight: 600; color: var(--text); }
|
|
1713
|
+
.mon-field > span small { font-weight: 400; color: var(--muted); margin-left: 6px; }
|
|
1714
|
+
.mon-field > small { color: var(--muted); }
|
|
1715
|
+
.mon-field input { width: 100%; min-width: 0; }
|
|
1716
|
+
.mon-inline { display: flex; gap: 8px; }
|
|
1717
|
+
.mon-inline input { flex: 1; }
|
|
1718
|
+
.mon-inline .btn { flex: none; }
|
|
1719
|
+
.mon-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
1720
|
+
@media (max-width: 640px) {
|
|
1721
|
+
.mon-self a { margin-left: 0; }
|
|
1722
|
+
.mon-field > span small { display: block; margin: 2px 0 0; }
|
|
1723
|
+
.mon-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
1724
|
+
.mon-tile { padding: 10px 12px; }
|
|
1725
|
+
.mon-sub { font-size: 12px; }
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
/* Design → Export / Import theme (ThemeTransfer) */
|
|
1729
|
+
.tt-muted { color: var(--muted); }
|
|
1730
|
+
.tt-small { font-size: 12px; margin: 10px 0 0; }
|
|
1731
|
+
.check-row .tt-muted { font-size: 12px; margin-left: 2px; }
|
|
1732
|
+
.tt-note { font-size: 12.5px; color: var(--text-2); background: var(--panel-2); border-radius: var(--r-md); padding: 9px 11px; margin: 10px 0 0; }
|
|
1733
|
+
.tt-note.warn { background: var(--amber-soft); color: var(--amber); }
|
|
1734
|
+
.tt-drop { display: flex; align-items: center; justify-content: center; border: 1.5px dashed var(--line-strong); border-radius: var(--r-md); padding: 26px 12px; cursor: pointer; color: var(--text-2); }
|
|
1735
|
+
.tt-drop:hover { border-color: var(--accent); color: var(--text); }
|
|
1736
|
+
.tt-drop input { position: absolute; width: 1px; height: 1px; opacity: 0; }
|
|
1737
|
+
.tt-drop:focus-within { box-shadow: 0 0 0 3px var(--accent-ring); }
|
|
1738
|
+
.tt-pkg { border: 1px solid var(--line); border-radius: var(--r-md); background: var(--panel); padding: 10px 12px; margin-bottom: 12px; }
|
|
1739
|
+
.tt-pkg small { display: block; color: var(--muted); font-size: 12px; }
|
|
1740
|
+
.tt-list { list-style: none; margin: 0; padding: 0; }
|
|
1741
|
+
.tt-list li { padding: 9px 0; border-top: 1px solid var(--line); font-size: 13px; }
|
|
1742
|
+
.tt-list li:first-child { border-top: 0; }
|
|
1743
|
+
.tt-list small { display: block; color: var(--muted); font-size: 12px; }
|
|
1744
|
+
.tt-list .check-row { margin: 0; }
|
|
1745
|
+
.tt-list .imp-mark { font-size: 11px; font-weight: 600; color: var(--green); background: var(--green-soft); border-radius: 999px; padding: 1px 7px; margin-left: 4px; }
|
|
@@ -63,6 +63,12 @@
|
|
|
63
63
|
.ems-note { background: var(--amber-soft); color: var(--text-2); border-radius: var(--r-md); padding: 9px 13px; margin: 0 0 12px; font-size: 13px; }
|
|
64
64
|
.ems-pw { max-width: 340px; margin-top: 4px; }
|
|
65
65
|
.ems-pw .hint { display: block; margin-top: 4px; }
|
|
66
|
+
.ems-verify { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 4px; padding: 10px 12px; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-md); }
|
|
67
|
+
.ems-verify p { flex: 1; min-width: 200px; margin: 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
|
|
68
|
+
.ems-verify p b { color: var(--text-2); font-weight: 600; }
|
|
69
|
+
.ems-verify-mark { flex: none; display: grid; place-items: center; width: 28px; height: 28px; border-radius: var(--r-sm); background: var(--sel); color: var(--text-2); }
|
|
70
|
+
.ems-verify-mark svg { width: 16px; height: 16px; }
|
|
71
|
+
.ems-verify-actions { display: inline-flex; gap: 6px; }
|
|
66
72
|
.ems-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
67
73
|
.ems-row { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 12px; }
|
|
68
74
|
.ems-row .field { margin-bottom: 0; }
|
package/src/virtual.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
declare module 'virtual:acms-config' {
|
|
4
4
|
export const userConfig: {
|
|
5
5
|
coreVersion: string;
|
|
6
|
+
auth?: { provider: 'builtin' | 'clerk' };
|
|
6
7
|
brand: { name?: string; version?: string; stack?: string };
|
|
7
8
|
seed: { siteTitle?: string; tagline?: string; heroHeading?: string; heroSub?: string };
|
|
8
9
|
// resolved (defaults applied + validated) by integration.mjs; `posts`
|
package/src/worker.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { App } from 'astro/app';
|
|
|
3
3
|
import { handle } from '@astrojs/cloudflare/handler';
|
|
4
4
|
import { D1Repository, type D1Database } from './lib/repo/d1';
|
|
5
5
|
import { runScheduledTasks } from './lib/scheduler';
|
|
6
|
+
import { EXECUTION_CONTEXT } from './lib/cache/purge';
|
|
6
7
|
|
|
7
8
|
// Custom Workers entrypoint (wired via the adapter's workerEntryPoint
|
|
8
9
|
// option): identical fetch behavior to the stock entry, plus a cron-driven
|
|
@@ -19,11 +20,16 @@ export function createExports(manifest: SSRManifest) {
|
|
|
19
20
|
return {
|
|
20
21
|
default: {
|
|
21
22
|
async fetch(request: HandleRequest, env: HandleEnv, context: HandleContext) {
|
|
23
|
+
// the adapter's locals.runtime.ctx has no `cache`; the middleware
|
|
24
|
+
// reads Workers Cache from here (lib/cache/purge.ts)
|
|
25
|
+
Reflect.set(request, EXECUTION_CONTEXT, context);
|
|
22
26
|
return handle(manifest, app, request, env, context);
|
|
23
27
|
},
|
|
24
28
|
async scheduled(_controller: unknown, env: HandleEnv, context: HandleContext) {
|
|
25
29
|
const repo = new D1Repository(env.DB as D1Database);
|
|
26
|
-
|
|
30
|
+
// ctx carries Workers Cache (ctx.cache) for the purge job; `scheduled`
|
|
31
|
+
// tells it a missing ctx.cache here doesn't mean caching is off
|
|
32
|
+
context.waitUntil(runScheduledTasks(repo, { runtime: { env, ctx: context, scheduled: true } }).then(report => {
|
|
27
33
|
if (!report.ok) throw new Error('Scheduled maintenance failed; see authenticated operations status.');
|
|
28
34
|
}));
|
|
29
35
|
},
|