@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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Admin from '../../../../layouts/Admin.astro';
|
|
3
3
|
import Modal from '../../../../components/Modal.astro';
|
|
4
|
+
import ContentList, { type ContentItem } from '../../../../components/ContentList.astro';
|
|
4
5
|
import { getRepo } from '../../../../lib/repo';
|
|
5
6
|
import { slugify, type Status } from '../../../../lib/schema';
|
|
6
|
-
import { CONTENT } from '../../../../lib/content';
|
|
7
|
+
import { CONTENT, collectionUrl } from '../../../../lib/content';
|
|
7
8
|
|
|
8
9
|
// One list page for every collection; [collection] is the config key from
|
|
9
10
|
// acms({ content.collections }). Unknown keys 404 — content in removed
|
|
@@ -14,8 +15,6 @@ if (!col) return new Response(null, { status: 404, statusText: 'Not found' });
|
|
|
14
15
|
|
|
15
16
|
const { singular, plural, base } = col;
|
|
16
17
|
const lower = singular.toLowerCase();
|
|
17
|
-
// lower goes inside an inline confirm('…') — a quote in the name must not break it
|
|
18
|
-
const lowerJs = lower.replace(/['\\]/g, '\\$&');
|
|
19
18
|
const repo = await getRepo(Astro.locals);
|
|
20
19
|
|
|
21
20
|
if (Astro.request.method === 'POST') {
|
|
@@ -29,43 +28,88 @@ if (Astro.request.method === 'POST') {
|
|
|
29
28
|
const post = await repo.createPost({ title, slug, collection: key });
|
|
30
29
|
return Astro.redirect(`/admin/c/${key}/${post.id}`);
|
|
31
30
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// row menu, swipe actions and the bulk bar all post here
|
|
32
|
+
if (op === 'bulk') {
|
|
33
|
+
const to = String(form.get('to'));
|
|
34
|
+
const ids = form.getAll('ids').map(String).filter(Boolean);
|
|
35
|
+
for (const id of ids) {
|
|
36
|
+
const p = await repo.getPost(id);
|
|
37
|
+
if (!p || p.collection !== key) continue;
|
|
38
|
+
if (to === 'delete') await repo.deletePost(id);
|
|
39
|
+
else if (to === 'published' || to === 'draft' || to === 'archived') {
|
|
40
|
+
await repo.updatePost(id, { status: to as Status, ...(to === 'published' ? { scheduledAt: null } : {}) });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Astro.redirect(`/admin/c/${key}${Astro.url.search}`);
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
const all = await repo.listPosts(key);
|
|
47
|
+
const [all, users, settings] = await Promise.all([repo.listPosts(key), repo.listUsers(), repo.getSettings()]);
|
|
48
|
+
const userById = new Map(users.map((u) => [u.id, u]));
|
|
39
49
|
const q = (Astro.url.searchParams.get('q') ?? '').toLowerCase();
|
|
40
50
|
const statusFilter = Astro.url.searchParams.get('status') ?? 'all';
|
|
41
51
|
const tagFilter = Astro.url.searchParams.get('tag') ?? '';
|
|
52
|
+
const authorFilter = Astro.url.searchParams.get('author') ?? '';
|
|
53
|
+
const sort = Astro.url.searchParams.get('sort') ?? 'updated';
|
|
42
54
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
const authorsOf = (p: (typeof all)[number]) => (p.authorIds.length ? p.authorIds : p.authorId ? [p.authorId] : []);
|
|
56
|
+
const posts = all
|
|
57
|
+
.filter((p) => {
|
|
58
|
+
if (statusFilter !== 'all' && p.status !== statusFilter) return false;
|
|
59
|
+
if (tagFilter && !p.tags.includes(tagFilter)) return false;
|
|
60
|
+
if (authorFilter && !authorsOf(p).includes(authorFilter)) return false;
|
|
61
|
+
if (q && !p.title.toLowerCase().includes(q) && !p.slug.toLowerCase().includes(q)) return false;
|
|
62
|
+
return true;
|
|
63
|
+
})
|
|
64
|
+
.sort((a, b) => (sort === 'oldest' ? a.updatedAt - b.updatedAt : sort === 'title' ? a.title.localeCompare(b.title) : b.updatedAt - a.updatedAt));
|
|
49
65
|
|
|
50
66
|
const allTags = [...new Set(all.flatMap((p) => p.tags))].sort();
|
|
67
|
+
const authorIds = [...new Set(all.flatMap(authorsOf))].filter((id) => userById.has(id));
|
|
51
68
|
const statusCounts = { all: all.length } as Record<string, number>;
|
|
52
69
|
for (const p of all) statusCounts[p.status] = (statusCounts[p.status] ?? 0) + 1;
|
|
53
70
|
const STATUSES: ('all' | Status)[] = ['all', 'draft', 'scheduled', 'published', 'archived'];
|
|
71
|
+
const statusLabel = (s: string) => (s === 'all' ? `All ${plural.toLowerCase()}` : s[0].toUpperCase() + s.slice(1));
|
|
72
|
+
const SORTS = [['updated', 'Recently updated'], ['oldest', 'Oldest first'], ['title', 'Title A–Z']];
|
|
73
|
+
|
|
74
|
+
const items: ContentItem[] = posts.map((p) => {
|
|
75
|
+
const names = authorsOf(p).map((id) => userById.get(id)?.name).filter(Boolean);
|
|
76
|
+
const meta = [names.length ? `By ${names.join(', ')}` : '', p.tags[0] ? `in ${p.tags[0]}` : ''].filter(Boolean).join(' ');
|
|
77
|
+
return {
|
|
78
|
+
id: p.id,
|
|
79
|
+
href: `/admin/c/${key}/${p.id}`,
|
|
80
|
+
title: p.title,
|
|
81
|
+
sub: collectionUrl(key, p.slug),
|
|
82
|
+
meta,
|
|
83
|
+
at: p.updatedAt,
|
|
84
|
+
status: p.status,
|
|
85
|
+
edited: Boolean(p.draft),
|
|
86
|
+
scheduledAt: p.scheduledAt,
|
|
87
|
+
image: p.cover,
|
|
88
|
+
viewHref: p.status === 'published' ? collectionUrl(key, p.slug) : undefined,
|
|
89
|
+
featured: p.featured,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
54
92
|
|
|
55
93
|
const showNew = Astro.url.searchParams.has('new');
|
|
56
|
-
const fmt = (ts: number) => new Date(ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
57
94
|
const filterHref = (patch: Record<string, string>) => {
|
|
58
95
|
const p = new URLSearchParams(Astro.url.search);
|
|
59
96
|
for (const [k, v] of Object.entries(patch)) v ? p.set(k, v) : p.delete(k);
|
|
60
97
|
const s = p.toString();
|
|
61
98
|
return `/admin/c/${key}${s ? `?${s}` : ''}`;
|
|
62
99
|
};
|
|
100
|
+
const chev = 'M6 9l6 6 6-6';
|
|
63
101
|
---
|
|
64
102
|
|
|
65
103
|
<Admin title={plural} section={`c:${key}`}>
|
|
66
104
|
<div class="page-head">
|
|
67
|
-
<
|
|
68
|
-
|
|
105
|
+
<div>
|
|
106
|
+
<h1>{plural}</h1>
|
|
107
|
+
<p class="sub">{all.length} {all.length === 1 ? lower : plural.toLowerCase()} · published under {base}</p>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="page-actions">
|
|
110
|
+
<a class="btn" href={`/admin/ai?kind=posts&collection=${encodeURIComponent(key)}`}>Create from document</a>
|
|
111
|
+
<button class="btn primary" type="button" data-modal="new-post">New {lower}</button>
|
|
112
|
+
</div>
|
|
69
113
|
</div>
|
|
70
114
|
|
|
71
115
|
<Modal id="new-post" title={`New ${lower}`} open={showNew}>
|
|
@@ -79,86 +123,77 @@ const filterHref = (patch: Record<string, string>) => {
|
|
|
79
123
|
<button class="btn ghost" type="button" data-close>Cancel</button>
|
|
80
124
|
<button class="btn primary" type="submit">Create {lower}</button>
|
|
81
125
|
</div>
|
|
82
|
-
<p class="hint
|
|
126
|
+
<p class="hint modal-note">Creates a draft at {base}/<slug-from-title> and opens the editor — the slug is editable there.</p>
|
|
83
127
|
</form>
|
|
84
128
|
</Modal>
|
|
85
129
|
|
|
86
|
-
|
|
87
|
-
<div class="
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<span class="filter-count">{statusCounts[s] ?? 0}</span>
|
|
92
|
-
</a>
|
|
93
|
-
))}
|
|
130
|
+
{all.length === 0 ? (
|
|
131
|
+
<div class="empty-state">
|
|
132
|
+
<b>No {plural.toLowerCase()} yet</b>
|
|
133
|
+
<p>Write your first {lower} — it stays a draft until you publish it.</p>
|
|
134
|
+
<button class="btn primary" type="button" data-modal="new-post">New {lower}</button>
|
|
94
135
|
</div>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)}
|
|
103
|
-
<input type="search" name="q" placeholder={`Search ${plural.toLowerCase()}…`} value={q} style="min-width:180px" />
|
|
104
|
-
</form>
|
|
105
|
-
</div>
|
|
106
|
-
|
|
107
|
-
<div class="card">
|
|
108
|
-
<table class="list">
|
|
109
|
-
<thead><tr><th>Title</th><th>Status</th><th>Tags</th><th>Updated</th><th></th></tr></thead>
|
|
110
|
-
<tbody>
|
|
111
|
-
{posts.map((p) => (
|
|
112
|
-
<tr>
|
|
113
|
-
<td>
|
|
114
|
-
<a class="t-title" href={`/admin/c/${key}/${p.id}`}>{p.featured && <span title="Featured" style="color:#e8a13a">★ </span>}{p.title}</a>
|
|
115
|
-
<div class="t-sub">{base}/{p.slug}{p.excerpt ? ` — ${p.excerpt.slice(0, 60)}${p.excerpt.length > 60 ? '…' : ''}` : ''}</div>
|
|
116
|
-
</td>
|
|
117
|
-
<td>
|
|
118
|
-
<span class={`pill ${p.status}`}>{p.status}</span>
|
|
119
|
-
{p.draft && <span class="pill edited" title="Has pending edits — press Update in the editor to push them live">edited</span>}
|
|
120
|
-
{p.status === 'scheduled' && p.scheduledAt && (
|
|
121
|
-
<div class="t-sub" style="margin-top:2px">
|
|
122
|
-
{new Date(p.scheduledAt).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}
|
|
123
|
-
</div>
|
|
124
|
-
)}
|
|
125
|
-
</td>
|
|
126
|
-
<td class="t-sub">{p.tags.join(', ')}</td>
|
|
127
|
-
<td class="t-sub">{fmt(p.updatedAt)}</td>
|
|
128
|
-
<td style="text-align:right">
|
|
129
|
-
<form class="inline-form" method="post" onsubmit={`return confirm('Delete this ${lowerJs} permanently? Consider archiving instead (from the editor).')`}>
|
|
130
|
-
<input type="hidden" name="op" value="delete" />
|
|
131
|
-
<input type="hidden" name="id" value={p.id} />
|
|
132
|
-
<button class="btn sm ghost danger" type="submit">Delete</button>
|
|
133
|
-
</form>
|
|
134
|
-
</td>
|
|
135
|
-
</tr>
|
|
136
|
+
) : (
|
|
137
|
+
<>
|
|
138
|
+
<nav class="chips" aria-label="Status">
|
|
139
|
+
{STATUSES.map((s) => (
|
|
140
|
+
<a class={statusFilter === s ? 'on' : ''} href={filterHref({ status: s === 'all' ? '' : s })}>
|
|
141
|
+
{s === 'all' ? 'All' : s[0].toUpperCase() + s.slice(1)}<small>{statusCounts[s] ?? 0}</small>
|
|
142
|
+
</a>
|
|
136
143
|
))}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
</nav>
|
|
145
|
+
<div class="list-tools">
|
|
146
|
+
<form method="get" class="list-search" role="search">
|
|
147
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-4-4" /></svg>
|
|
148
|
+
{statusFilter !== 'all' && <input type="hidden" name="status" value={statusFilter} />}
|
|
149
|
+
<input type="search" name="q" value={q} placeholder={`Filter ${plural.toLowerCase()}…`} data-live-filter aria-label={`Filter ${plural.toLowerCase()}`} />
|
|
150
|
+
</form>
|
|
151
|
+
<div class="fmenus">
|
|
152
|
+
<details class={`fmenu ${statusFilter !== 'all' ? 'is-set' : ''}`} data-kind="status">
|
|
153
|
+
<summary>{statusLabel(statusFilter)} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d={chev} /></svg></summary>
|
|
154
|
+
<div class="fmenu-pop">
|
|
155
|
+
{STATUSES.map((s) => (
|
|
156
|
+
<a class={statusFilter === s ? 'on' : ''} href={filterHref({ status: s === 'all' ? '' : s })}>{statusLabel(s)}<small>{statusCounts[s] ?? 0}</small></a>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
</details>
|
|
160
|
+
{authorIds.length > 1 && (
|
|
161
|
+
<details class={`fmenu ${authorFilter ? 'is-set' : ''}`}>
|
|
162
|
+
<summary>{authorFilter ? userById.get(authorFilter)?.name ?? 'Author' : 'All authors'} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d={chev} /></svg></summary>
|
|
163
|
+
<div class="fmenu-pop">
|
|
164
|
+
<a class={!authorFilter ? 'on' : ''} href={filterHref({ author: '' })}>All authors</a>
|
|
165
|
+
{authorIds.map((id) => <a class={authorFilter === id ? 'on' : ''} href={filterHref({ author: id })}>{userById.get(id)?.name}</a>)}
|
|
166
|
+
</div>
|
|
167
|
+
</details>
|
|
168
|
+
)}
|
|
169
|
+
{allTags.length > 0 && (
|
|
170
|
+
<details class={`fmenu ${tagFilter ? 'is-set' : ''}`}>
|
|
171
|
+
<summary>{tagFilter ? `#${tagFilter}` : 'All tags'} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d={chev} /></svg></summary>
|
|
172
|
+
<div class="fmenu-pop">
|
|
173
|
+
<a class={!tagFilter ? 'on' : ''} href={filterHref({ tag: '' })}>All tags</a>
|
|
174
|
+
{allTags.map((t) => <a class={tagFilter === t ? 'on' : ''} href={filterHref({ tag: t })}>#{t}</a>)}
|
|
175
|
+
</div>
|
|
176
|
+
</details>
|
|
177
|
+
)}
|
|
178
|
+
<details class="fmenu">
|
|
179
|
+
<summary>{SORTS.find(([k]) => k === sort)?.[1] ?? 'Recently updated'} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d={chev} /></svg></summary>
|
|
180
|
+
<div class="fmenu-pop">
|
|
181
|
+
{SORTS.map(([k, l]) => <a class={sort === k ? 'on' : ''} href={filterHref({ sort: k === 'updated' ? '' : k })}>{l}</a>)}
|
|
182
|
+
</div>
|
|
183
|
+
</details>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<ContentList items={items} noun={lower} timeZone={settings.timezone || undefined} groupByMonth={sort !== 'title'} empty={`No ${plural.toLowerCase()} match the current filters.`} />
|
|
187
|
+
{items.length === 0 && <p class="hint" style="padding:24px 8px">No {plural.toLowerCase()} match the current filters. <a href={`/admin/c/${key}`}>Clear filters</a></p>}
|
|
188
|
+
</>
|
|
189
|
+
)}
|
|
155
190
|
</Admin>
|
|
156
191
|
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</
|
|
192
|
+
<script>
|
|
193
|
+
// one filter menu open at a time; outside click closes
|
|
194
|
+
document.addEventListener('click', (e) => {
|
|
195
|
+
document.querySelectorAll<HTMLDetailsElement>('details.fmenu[open]').forEach((d) => {
|
|
196
|
+
if (!(e.target instanceof Node && d.contains(e.target))) d.open = false;
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
</script>
|