@ekrist1/vulse 0.1.6-alpha.3 → 0.1.7-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +3 -4
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +11 -10
- package/dist/core/blueprints/compile.d.ts +1 -1
- package/dist/core/blueprints/compile.d.ts.map +1 -1
- package/dist/core/blueprints/compile.js +3 -3
- package/dist/core/blueprints/mutations.js +2 -2
- package/dist/core/forms/rate-limit.d.ts +1 -1
- package/dist/core/forms/rate-limit.d.ts.map +1 -1
- package/dist/core/forms/rate-limit.js +3 -3
- package/dist/core/forms/unique.d.ts +1 -1
- package/dist/core/forms/unique.d.ts.map +1 -1
- package/dist/core/forms/unique.js +4 -4
- package/dist/core/globals/compile.d.ts +1 -1
- package/dist/core/globals/compile.d.ts.map +1 -1
- package/dist/core/globals/compile.js +2 -2
- package/dist/core/globals/definition.d.ts +1 -1
- package/dist/core/globals/definition.d.ts.map +1 -1
- package/dist/core/globals/definition.js +3 -3
- package/dist/core/repos/globals.js +3 -3
- package/dist/core/sha256.d.ts +3 -0
- package/dist/core/sha256.d.ts.map +1 -0
- package/dist/core/sha256.js +9 -0
- package/dist/integration/index.js +1 -1
- package/dist/integration/install-hook.d.ts.map +1 -1
- package/dist/integration/install-hook.js +6 -4
- package/dist/integration/wrangler-config.d.ts +12 -0
- package/dist/integration/wrangler-config.d.ts.map +1 -0
- package/dist/integration/wrangler-config.js +97 -0
- package/dist/integration/wrangler-patch.d.ts +1 -0
- package/dist/integration/wrangler-patch.d.ts.map +1 -1
- package/dist/integration/wrangler-patch.js +2 -1
- package/dist/server/routes/form-submit.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -3
- package/src/admin/assets/logo-mark.svg +5 -0
- package/src/admin/client/active-locale.ts +17 -0
- package/src/admin/client/api.ts +21 -0
- package/src/admin/client/form-from-zod.ts +7 -0
- package/src/admin/client/live-preview-enabled.ts +5 -0
- package/src/admin/components/AdminShell.astro +45 -0
- package/src/admin/components/AuthSettings.vue +60 -0
- package/src/admin/components/BlockEditor.vue +53 -0
- package/src/admin/components/BlueprintEditor.vue +1783 -0
- package/src/admin/components/CollectionKindIcon.vue +26 -0
- package/src/admin/components/CollectionTree.vue +220 -0
- package/src/admin/components/EntryEditorWithPreview.vue +130 -0
- package/src/admin/components/EntryForm.vue +411 -0
- package/src/admin/components/EntryList.vue +121 -0
- package/src/admin/components/EntryStatusBadge.vue +24 -0
- package/src/admin/components/FormEditor.vue +233 -0
- package/src/admin/components/FormList.vue +54 -0
- package/src/admin/components/GlobalSetEditor.vue +272 -0
- package/src/admin/components/GlobalSetList.vue +55 -0
- package/src/admin/components/LivePreviewPanel.vue +171 -0
- package/src/admin/components/LoginForm.vue +53 -0
- package/src/admin/components/MediaLibrary.vue +106 -0
- package/src/admin/components/MediaPicker.vue +49 -0
- package/src/admin/components/RevisionDiff.vue +11 -0
- package/src/admin/components/RevisionList.vue +134 -0
- package/src/admin/components/SeoFields.vue +113 -0
- package/src/admin/components/SetEditor.vue +137 -0
- package/src/admin/components/SetList.vue +32 -0
- package/src/admin/components/SettingsForm.vue +189 -0
- package/src/admin/components/SideNav.vue +152 -0
- package/src/admin/components/SubmissionDetail.vue +45 -0
- package/src/admin/components/SubmissionList.vue +89 -0
- package/src/admin/components/ToastHost.vue +33 -0
- package/src/admin/components/TreeRow.vue +163 -0
- package/src/admin/components/UserEditor.vue +186 -0
- package/src/admin/components/UserList.vue +46 -0
- package/src/admin/components/blocks/BlockItem.vue +32 -0
- package/src/admin/components/blocks/BlockToolbar.vue +12 -0
- package/src/admin/components/blocks/edit/CodeEdit.vue +18 -0
- package/src/admin/components/blocks/edit/EmbedEdit.vue +14 -0
- package/src/admin/components/blocks/edit/HeadingEdit.vue +19 -0
- package/src/admin/components/blocks/edit/ImageEdit.vue +40 -0
- package/src/admin/components/blocks/edit/ListEdit.vue +36 -0
- package/src/admin/components/blocks/edit/ParagraphEdit.vue +14 -0
- package/src/admin/components/blocks/edit/QuoteEdit.vue +18 -0
- package/src/admin/components/fields/BlocksField.vue +123 -0
- package/src/admin/components/fields/BlocksSetsPicker.vue +59 -0
- package/src/admin/components/fields/BoolField.vue +10 -0
- package/src/admin/components/fields/DateField.vue +22 -0
- package/src/admin/components/fields/EntriesField.vue +153 -0
- package/src/admin/components/fields/EntryField.vue +138 -0
- package/src/admin/components/fields/EnumField.vue +81 -0
- package/src/admin/components/fields/FieldRenderer.vue +87 -0
- package/src/admin/components/fields/GridField.vue +173 -0
- package/src/admin/components/fields/LinkField.vue +219 -0
- package/src/admin/components/fields/MediaField.vue +69 -0
- package/src/admin/components/fields/NumberField.vue +12 -0
- package/src/admin/components/fields/ObjectField.vue +18 -0
- package/src/admin/components/fields/RefField.vue +170 -0
- package/src/admin/components/fields/RepeaterField.vue +27 -0
- package/src/admin/components/fields/ReplicatorField.vue +121 -0
- package/src/admin/components/fields/TextField.vue +11 -0
- package/src/admin/components/fields/TextareaField.vue +11 -0
- package/src/admin/components/fields/VulseAccordionGroupNodeView.vue +82 -0
- package/src/admin/components/fields/VulseAccordionNodeView.vue +128 -0
- package/src/admin/components/fields/VulseCalloutNodeView.vue +81 -0
- package/src/admin/components/fields/VulseIframeNodeView.vue +112 -0
- package/src/admin/components/fields/VulseSetNodeView.vue +68 -0
- package/src/admin/components/fields/VulseVideoNodeView.vue +104 -0
- package/src/admin/components/fields/blocks-editor-extensions.ts +26 -0
- package/src/admin/components/fields/emoji-extension.ts +54 -0
- package/src/admin/components/fields/link-extension.ts +48 -0
- package/src/admin/components/fields/set-node-utils.ts +115 -0
- package/src/admin/components/fields/url-utils.ts +85 -0
- package/src/admin/components/fields/vulse-accordion-extension.ts +64 -0
- package/src/admin/components/fields/vulse-accordion-group-extension.ts +49 -0
- package/src/admin/components/fields/vulse-callout-extension.ts +53 -0
- package/src/admin/components/fields/vulse-iframe-extension.ts +96 -0
- package/src/admin/components/fields/vulse-set-extension.ts +66 -0
- package/src/admin/components/fields/vulse-video-extension.ts +65 -0
- package/src/admin/composables/toast.ts +35 -0
- package/src/admin/composables/useEntrySearch.ts +112 -0
- package/src/admin/composables/useSets.ts +31 -0
- package/src/admin/pages/collections/[name]/[id]/revisions.astro +27 -0
- package/src/admin/pages/collections/[name]/[id].astro +90 -0
- package/src/admin/pages/collections/[name]/index.astro +31 -0
- package/src/admin/pages/collections/[name]/new.astro +38 -0
- package/src/admin/pages/forms/[handle]/submissions/[id].astro +9 -0
- package/src/admin/pages/forms/[handle]/submissions/index.astro +9 -0
- package/src/admin/pages/forms/[handle].astro +9 -0
- package/src/admin/pages/forms/index.astro +7 -0
- package/src/admin/pages/forms/new.astro +7 -0
- package/src/admin/pages/index.astro +36 -0
- package/src/admin/pages/login.astro +14 -0
- package/src/admin/pages/media.astro +8 -0
- package/src/admin/pages/schema/[handle].astro +10 -0
- package/src/admin/pages/schema/new.astro +9 -0
- package/src/admin/pages/settings/auth.astro +9 -0
- package/src/admin/pages/settings/globals/[handle].astro +9 -0
- package/src/admin/pages/settings/globals/index.astro +7 -0
- package/src/admin/pages/settings/globals/new.astro +7 -0
- package/src/admin/pages/settings/index.astro +8 -0
- package/src/admin/pages/settings/sets/[handle].astro +9 -0
- package/src/admin/pages/settings/sets/index.astro +7 -0
- package/src/admin/pages/settings/sets/new.astro +7 -0
- package/src/admin/pages/users/[id].astro +10 -0
- package/src/admin/pages/users/index.astro +8 -0
- package/src/admin/styles/admin.css +166 -0
- package/src/core/access.ts +9 -0
- package/src/core/blocks/schema.ts +66 -0
- package/src/core/blueprints/code-to-definition.ts +156 -0
- package/src/core/blueprints/compile.ts +176 -0
- package/src/core/blueprints/define.ts +12 -0
- package/src/core/blueprints/definition.ts +185 -0
- package/src/core/blueprints/load.ts +144 -0
- package/src/core/blueprints/mutations.ts +236 -0
- package/src/core/blueprints/preview-path.ts +33 -0
- package/src/core/blueprints/reflect-fields.ts +305 -0
- package/src/core/blueprints/registry.ts +14 -0
- package/src/core/blueprints/seed.ts +20 -0
- package/src/core/blueprints/select-helpers.ts +30 -0
- package/src/core/blueprints/seo.ts +180 -0
- package/src/core/blueprints/types.ts +59 -0
- package/src/core/blueprints/zod-helpers.ts +86 -0
- package/src/core/db.ts +11 -0
- package/src/core/errors.ts +34 -0
- package/src/core/forms/compile.ts +84 -0
- package/src/core/forms/definition.ts +102 -0
- package/src/core/forms/rate-limit.ts +52 -0
- package/src/core/forms/unique.ts +38 -0
- package/src/core/globals/compile.ts +35 -0
- package/src/core/globals/definition.ts +27 -0
- package/src/core/locales.ts +45 -0
- package/src/core/migrations.ts +48 -0
- package/src/core/parse-content.ts +85 -0
- package/src/core/plugins/definition.ts +150 -0
- package/src/core/preview-content.ts +21 -0
- package/src/core/repos/entries.ts +504 -0
- package/src/core/repos/forms.ts +270 -0
- package/src/core/repos/globals.ts +179 -0
- package/src/core/repos/media.ts +106 -0
- package/src/core/repos/preview-sessions.ts +108 -0
- package/src/core/repos/revisions.ts +60 -0
- package/src/core/repos/settings.ts +23 -0
- package/src/core/schema.ts +244 -0
- package/src/core/sets/compile.ts +12 -0
- package/src/core/sets/definition.ts +10 -0
- package/src/core/sets/service.ts +82 -0
- package/src/core/sets/validate-tree.ts +57 -0
- package/src/core/sha256.ts +10 -0
- package/src/core/slug.ts +30 -0
- package/src/scaffold/collection-write.ts +83 -0
- package/src/scaffold/collection.ts +277 -0
- package/src/server/assets/live-preview-bridge.content.ts +2 -0
- package/src/server/assets/live-preview-bridge.js +535 -0
- package/src/server/better-auth.ts +82 -0
- package/src/server/cf-images.ts +34 -0
- package/src/server/cron.ts +37 -0
- package/src/server/email.ts +17 -0
- package/src/server/endpoints/api-auth.ts +10 -0
- package/src/server/endpoints/api-vulse-blueprints.ts +23 -0
- package/src/server/endpoints/api-vulse-entries-locales.ts +12 -0
- package/src/server/endpoints/api-vulse-entries-move.ts +7 -0
- package/src/server/endpoints/api-vulse-entries-publish.ts +7 -0
- package/src/server/endpoints/api-vulse-entries-tree.ts +7 -0
- package/src/server/endpoints/api-vulse-entries.ts +23 -0
- package/src/server/endpoints/api-vulse-form-handle.ts +30 -0
- package/src/server/endpoints/api-vulse-form-public.ts +7 -0
- package/src/server/endpoints/api-vulse-form-submit.ts +7 -0
- package/src/server/endpoints/api-vulse-form-upload.ts +7 -0
- package/src/server/endpoints/api-vulse-forms.ts +12 -0
- package/src/server/endpoints/api-vulse-globals-handle.ts +20 -0
- package/src/server/endpoints/api-vulse-globals-public-handle.ts +7 -0
- package/src/server/endpoints/api-vulse-globals-public.ts +7 -0
- package/src/server/endpoints/api-vulse-globals-value.ts +8 -0
- package/src/server/endpoints/api-vulse-globals.ts +12 -0
- package/src/server/endpoints/api-vulse-media-file.ts +7 -0
- package/src/server/endpoints/api-vulse-media-id.ts +12 -0
- package/src/server/endpoints/api-vulse-media.ts +12 -0
- package/src/server/endpoints/api-vulse-preview-bridge.ts +11 -0
- package/src/server/endpoints/api-vulse-preview-sessions-id.ts +10 -0
- package/src/server/endpoints/api-vulse-preview-sessions.ts +7 -0
- package/src/server/endpoints/api-vulse-preview-start.ts +7 -0
- package/src/server/endpoints/api-vulse-preview-stop.ts +7 -0
- package/src/server/endpoints/api-vulse-revisions-restore.ts +7 -0
- package/src/server/endpoints/api-vulse-revisions.ts +7 -0
- package/src/server/endpoints/api-vulse-search.ts +7 -0
- package/src/server/endpoints/api-vulse-sets.ts +23 -0
- package/src/server/endpoints/api-vulse-settings.ts +12 -0
- package/src/server/endpoints/api-vulse-users-id.ts +12 -0
- package/src/server/endpoints/api-vulse-users-reset-password.ts +7 -0
- package/src/server/endpoints/api-vulse-users-role.ts +9 -0
- package/src/server/endpoints/api-vulse-users.ts +7 -0
- package/src/server/endpoints/with-runtime.ts +11 -0
- package/src/server/env.ts +23 -0
- package/src/server/envelope.ts +21 -0
- package/src/server/forms/email.ts +11 -0
- package/src/server/forms/process-submission.ts +95 -0
- package/src/server/forms/queue.ts +25 -0
- package/src/server/forms/templates.ts +24 -0
- package/src/server/forms/webhook.ts +19 -0
- package/src/server/handler.ts +66 -0
- package/src/server/image-probe.ts +35 -0
- package/src/server/loader.ts +54 -0
- package/src/server/plugins.ts +214 -0
- package/src/server/preview.ts +25 -0
- package/src/server/r2.ts +13 -0
- package/src/server/routes/blueprints.ts +62 -0
- package/src/server/routes/entries.ts +255 -0
- package/src/server/routes/form-submit.ts +168 -0
- package/src/server/routes/form-upload.ts +100 -0
- package/src/server/routes/forms.ts +88 -0
- package/src/server/routes/globals-public.ts +30 -0
- package/src/server/routes/globals.ts +93 -0
- package/src/server/routes/media.ts +145 -0
- package/src/server/routes/preview-sessions.ts +76 -0
- package/src/server/routes/preview.ts +36 -0
- package/src/server/routes/revisions.ts +29 -0
- package/src/server/routes/search.ts +31 -0
- package/src/server/routes/sets.ts +40 -0
- package/src/server/routes/settings.ts +24 -0
- package/src/server/routes/users.ts +127 -0
- package/src/server/runtime.ts +99 -0
- package/src/server/sdk/collections.ts +98 -0
- package/src/server/sdk/index.ts +25 -0
- package/src/server/sdk/media.ts +11 -0
- package/src/server/sdk/search.ts +90 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from '@tiptap/core';
|
|
2
|
+
import { VueNodeViewRenderer } from '@tiptap/vue-3';
|
|
3
|
+
import VulseSetNodeView from './VulseSetNodeView.vue';
|
|
4
|
+
|
|
5
|
+
declare module '@tiptap/core' {
|
|
6
|
+
interface Commands<ReturnType> {
|
|
7
|
+
vulseSet: {
|
|
8
|
+
insertVulseSet: (setHandle: string) => ReturnType;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const VulseSetExtension = Node.create({
|
|
14
|
+
name: 'vulseSet',
|
|
15
|
+
group: 'block',
|
|
16
|
+
atom: true,
|
|
17
|
+
draggable: true,
|
|
18
|
+
|
|
19
|
+
addAttributes() {
|
|
20
|
+
return {
|
|
21
|
+
set: {
|
|
22
|
+
default: null,
|
|
23
|
+
parseHTML: (el: HTMLElement) => el.getAttribute('data-vulse-set'),
|
|
24
|
+
renderHTML: (attrs: { set: string | null }) => ({ 'data-vulse-set': attrs.set ?? '' }),
|
|
25
|
+
},
|
|
26
|
+
data: {
|
|
27
|
+
default: {} as Record<string, unknown>,
|
|
28
|
+
parseHTML: (el: HTMLElement) => {
|
|
29
|
+
const raw = el.getAttribute('data-vulse-data');
|
|
30
|
+
try {
|
|
31
|
+
return raw ? JSON.parse(raw) : {};
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
renderHTML: (attrs: { data: Record<string, unknown> }) => ({
|
|
37
|
+
'data-vulse-data': JSON.stringify(attrs.data ?? {}),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
parseHTML() {
|
|
44
|
+
return [{ tag: 'div[data-vulse-set]' }];
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
renderHTML({ HTMLAttributes }) {
|
|
48
|
+
return ['div', mergeAttributes(HTMLAttributes, { 'data-vulse-set': '' })];
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
addNodeView() {
|
|
52
|
+
return VueNodeViewRenderer(VulseSetNodeView);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
addCommands() {
|
|
56
|
+
return {
|
|
57
|
+
insertVulseSet:
|
|
58
|
+
(setHandle: string) =>
|
|
59
|
+
({ commands }) =>
|
|
60
|
+
commands.insertContent({
|
|
61
|
+
type: this.name,
|
|
62
|
+
attrs: { set: setHandle, data: {} },
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from '@tiptap/core';
|
|
2
|
+
import { VueNodeViewRenderer } from '@tiptap/vue-3';
|
|
3
|
+
import { sanitizeMediaSrc } from './url-utils.js';
|
|
4
|
+
import VulseVideoNodeView from './VulseVideoNodeView.vue';
|
|
5
|
+
|
|
6
|
+
declare module '@tiptap/core' {
|
|
7
|
+
interface Commands<ReturnType> {
|
|
8
|
+
vulseVideo: {
|
|
9
|
+
insertVulseVideo: (src?: string) => ReturnType;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const VulseVideoExtension = Node.create({
|
|
15
|
+
name: 'vulseVideo',
|
|
16
|
+
group: 'block',
|
|
17
|
+
atom: true,
|
|
18
|
+
selectable: true,
|
|
19
|
+
draggable: true,
|
|
20
|
+
|
|
21
|
+
addAttributes() {
|
|
22
|
+
return {
|
|
23
|
+
src: {
|
|
24
|
+
default: null,
|
|
25
|
+
parseHTML: (element: HTMLElement) =>
|
|
26
|
+
sanitizeMediaSrc(element.getAttribute('src') ?? '') ?? null,
|
|
27
|
+
renderHTML: (attrs: { src?: string | null }) => (attrs.src ? { src: attrs.src } : {}),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
parseHTML() {
|
|
33
|
+
return [{ tag: 'video[data-vulse-embed="video"]' }, { tag: 'video[src]' }];
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
renderHTML({ HTMLAttributes }) {
|
|
37
|
+
return [
|
|
38
|
+
'video',
|
|
39
|
+
mergeAttributes(
|
|
40
|
+
{
|
|
41
|
+
'data-vulse-embed': 'video',
|
|
42
|
+
controls: 'true',
|
|
43
|
+
preload: 'metadata',
|
|
44
|
+
},
|
|
45
|
+
HTMLAttributes,
|
|
46
|
+
),
|
|
47
|
+
];
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
addNodeView() {
|
|
51
|
+
return VueNodeViewRenderer(VulseVideoNodeView);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
addCommands() {
|
|
55
|
+
return {
|
|
56
|
+
insertVulseVideo:
|
|
57
|
+
(src = '') =>
|
|
58
|
+
({ commands }) =>
|
|
59
|
+
commands.insertContent({
|
|
60
|
+
type: this.name,
|
|
61
|
+
attrs: { src: sanitizeMediaSrc(src) },
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type ToastKind = 'success' | 'error'
|
|
4
|
+
|
|
5
|
+
export interface ToastMessage {
|
|
6
|
+
id: number
|
|
7
|
+
message: string
|
|
8
|
+
kind: ToastKind
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const toasts = ref<ToastMessage[]>([])
|
|
12
|
+
let nextId = 1
|
|
13
|
+
|
|
14
|
+
function push(message: string, kind: ToastKind, durationMs: number) {
|
|
15
|
+
const id = nextId++
|
|
16
|
+
toasts.value = [...toasts.value, { id, message, kind }]
|
|
17
|
+
window.setTimeout(() => {
|
|
18
|
+
toasts.value = toasts.value.filter((t) => t.id !== id)
|
|
19
|
+
}, durationMs)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useToast() {
|
|
23
|
+
return {
|
|
24
|
+
toasts,
|
|
25
|
+
success(message: string, durationMs = 2800) {
|
|
26
|
+
push(message, 'success', durationMs)
|
|
27
|
+
},
|
|
28
|
+
error(message: string, durationMs = 4000) {
|
|
29
|
+
push(message, 'error', durationMs)
|
|
30
|
+
},
|
|
31
|
+
dismiss(id: number) {
|
|
32
|
+
toasts.value = toasts.value.filter((t) => t.id !== id)
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ref, watch } from 'vue'
|
|
2
|
+
import { adminApi } from '../client/api.js'
|
|
3
|
+
|
|
4
|
+
export interface EntryOption {
|
|
5
|
+
id: string
|
|
6
|
+
collection: string
|
|
7
|
+
title?: string
|
|
8
|
+
email?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function entryOptionLabel(option: EntryOption): string {
|
|
12
|
+
return option.title ?? option.email ?? option.id
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useEntrySearch(collections: () => string[]) {
|
|
16
|
+
const open = ref(false)
|
|
17
|
+
const query = ref('')
|
|
18
|
+
const options = ref<EntryOption[]>([])
|
|
19
|
+
const loading = ref(false)
|
|
20
|
+
|
|
21
|
+
async function loadOptions(search = '') {
|
|
22
|
+
const cols = collections().filter(Boolean)
|
|
23
|
+
if (cols.length === 0) {
|
|
24
|
+
options.value = []
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
loading.value = true
|
|
29
|
+
try {
|
|
30
|
+
const needle = search.trim().toLowerCase()
|
|
31
|
+
const merged: EntryOption[] = []
|
|
32
|
+
|
|
33
|
+
for (const collection of cols) {
|
|
34
|
+
if (collection === 'user') {
|
|
35
|
+
const users = await adminApi.get<{ id: string; email?: string }[]>(
|
|
36
|
+
`/api/vulse/users?q=${encodeURIComponent(search)}`,
|
|
37
|
+
)
|
|
38
|
+
for (const user of users) {
|
|
39
|
+
merged.push({ id: user.id, collection: 'user', email: user.email })
|
|
40
|
+
}
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rows = await adminApi.get<
|
|
45
|
+
{ id: string; content?: { title?: string }; slug?: string }[]
|
|
46
|
+
>(`/api/vulse/entries/${collection}`)
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
merged.push({
|
|
49
|
+
id: row.id,
|
|
50
|
+
collection,
|
|
51
|
+
title: row.content?.title ?? row.slug ?? row.id,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
options.value = merged.filter((row) => {
|
|
57
|
+
if (!needle) return true
|
|
58
|
+
return entryOptionLabel(row).toLowerCase().includes(needle)
|
|
59
|
+
})
|
|
60
|
+
} finally {
|
|
61
|
+
loading.value = false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function resolveLabel(entryId: string, collection: string): Promise<string> {
|
|
66
|
+
if (!entryId) return ''
|
|
67
|
+
if (collection === 'user') {
|
|
68
|
+
const users = await adminApi.get<{ id: string; email?: string }[]>(
|
|
69
|
+
`/api/vulse/users?q=${encodeURIComponent(entryId)}`,
|
|
70
|
+
)
|
|
71
|
+
const match = users.find((user) => user.id === entryId)
|
|
72
|
+
return match ? entryOptionLabel({ id: match.id, collection: 'user', email: match.email }) : entryId
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const row = await adminApi.get<{ id: string; content?: { title?: string }; slug?: string }>(
|
|
76
|
+
`/api/vulse/entries/${collection}/${entryId}`,
|
|
77
|
+
)
|
|
78
|
+
return row.content?.title ?? row.slug ?? row.id
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function openDropdown() {
|
|
82
|
+
open.value = true
|
|
83
|
+
void loadOptions(query.value)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function closeDropdown() {
|
|
87
|
+
open.value = false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function onBlur(event: FocusEvent) {
|
|
91
|
+
const next = event.relatedTarget as Node | null
|
|
92
|
+
if (next && (event.currentTarget as HTMLElement).contains(next)) return
|
|
93
|
+
closeDropdown()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
watch(query, (value) => {
|
|
97
|
+
if (!open.value) return
|
|
98
|
+
void loadOptions(value)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
open,
|
|
103
|
+
query,
|
|
104
|
+
options,
|
|
105
|
+
loading,
|
|
106
|
+
loadOptions,
|
|
107
|
+
resolveLabel,
|
|
108
|
+
openDropdown,
|
|
109
|
+
closeDropdown,
|
|
110
|
+
onBlur,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { onMounted, ref } from 'vue'
|
|
2
|
+
import { adminApi } from '../client/api.js'
|
|
3
|
+
import type { SetDefinition } from '../../core/sets/definition.js'
|
|
4
|
+
|
|
5
|
+
let cache: Map<string, SetDefinition> | null = null
|
|
6
|
+
let loading: Promise<Map<string, SetDefinition>> | null = null
|
|
7
|
+
|
|
8
|
+
export async function hydrateSets(): Promise<Map<string, SetDefinition>> {
|
|
9
|
+
if (cache) return cache
|
|
10
|
+
if (!loading) {
|
|
11
|
+
loading = adminApi.get<SetDefinition[]>('/api/vulse/sets').then((list) => {
|
|
12
|
+
cache = new Map(list.map((s) => [s.handle, s]))
|
|
13
|
+
return cache
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
return loading
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useSets() {
|
|
20
|
+
const sets = ref<Map<string, SetDefinition>>(cache ?? new Map())
|
|
21
|
+
|
|
22
|
+
onMounted(async () => {
|
|
23
|
+
sets.value = await hydrateSets()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function get(handle: string): SetDefinition | undefined {
|
|
27
|
+
return sets.value.get(handle)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { sets, get, hydrate: hydrateSets }
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../../components/AdminShell.astro'
|
|
3
|
+
import RevisionList from '../../../../components/RevisionList.vue'
|
|
4
|
+
import { createDb } from '../../../../../core/db.js'
|
|
5
|
+
import { getRuntimeEnv } from '../../../../../server/env.js'
|
|
6
|
+
import { readLocalesConfig } from '../../../../../core/locales.js'
|
|
7
|
+
|
|
8
|
+
const name = Astro.params.name!
|
|
9
|
+
const id = Astro.params.id!
|
|
10
|
+
|
|
11
|
+
const env = getRuntimeEnv()
|
|
12
|
+
const db = createDb(env.DB)
|
|
13
|
+
const localesCfg = await readLocalesConfig(db)
|
|
14
|
+
const requested = Astro.url.searchParams.get('locale')
|
|
15
|
+
const activeLocale = requested && localesCfg.locales.includes(requested) ? requested : localesCfg.defaultLocale
|
|
16
|
+
---
|
|
17
|
+
<AdminShell title="Revisions" activePath={`/admin/collections/${name}`}>
|
|
18
|
+
<h1 class="text-2xl font-semibold mb-4">Revisions</h1>
|
|
19
|
+
<RevisionList
|
|
20
|
+
client:load
|
|
21
|
+
collection={name}
|
|
22
|
+
entryId={id}
|
|
23
|
+
entryLocale={activeLocale}
|
|
24
|
+
supportedLocales={localesCfg.locales}
|
|
25
|
+
defaultLocale={localesCfg.defaultLocale}
|
|
26
|
+
/>
|
|
27
|
+
</AdminShell>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../components/AdminShell.astro'
|
|
3
|
+
import EntryEditorWithPreview from '../../../components/EntryEditorWithPreview.vue'
|
|
4
|
+
import { createDb } from '../../../../core/db.js'
|
|
5
|
+
import { registryForRequest } from '../../../../core/blueprints/load.js'
|
|
6
|
+
import { fieldDescriptorsFromBlueprint } from '../../../client/form-from-zod.js'
|
|
7
|
+
import { getRuntime } from '../../../../server/runtime.js'
|
|
8
|
+
import { getRuntimeEnv } from '../../../../server/env.js'
|
|
9
|
+
import { readLocalesConfig } from '../../../../core/locales.js'
|
|
10
|
+
import { resolvePreviewConfig } from '../../../../core/blueprints/preview-path.js'
|
|
11
|
+
import { EntriesRepo } from '../../../../core/repos/entries.js'
|
|
12
|
+
|
|
13
|
+
const env = getRuntimeEnv()
|
|
14
|
+
const db = createDb(env.DB)
|
|
15
|
+
const reg = await registryForRequest(db)
|
|
16
|
+
const bp = reg.get(Astro.params.name!)
|
|
17
|
+
if (!bp) return Astro.redirect('/admin')
|
|
18
|
+
const fields = fieldDescriptorsFromBlueprint(bp)
|
|
19
|
+
|
|
20
|
+
const localesCfg = await readLocalesConfig(db)
|
|
21
|
+
const requested = Astro.url.searchParams.get('locale')
|
|
22
|
+
const activeLocale = requested && localesCfg.locales.includes(requested) ? requested : localesCfg.defaultLocale
|
|
23
|
+
|
|
24
|
+
const entryId = Astro.params.id!
|
|
25
|
+
const entriesRepo = new EntriesRepo(db)
|
|
26
|
+
const existingLocales = (await entriesRepo.listLocales(entryId)).map((l) => l.locale)
|
|
27
|
+
|
|
28
|
+
const rt = await getRuntime(env, reg, new URL(Astro.request.url).origin)
|
|
29
|
+
const findUrl = `${Astro.request.url.split('?')[0]}?locale=${encodeURIComponent(activeLocale)}`
|
|
30
|
+
const res = await rt.routes.entries.findById(new Request(findUrl, { headers: Astro.request.headers }), { collection: bp.name, id: entryId })
|
|
31
|
+
const body = await res.json() as {
|
|
32
|
+
ok: true
|
|
33
|
+
data: {
|
|
34
|
+
content: Record<string, unknown>
|
|
35
|
+
draftContent: Record<string, unknown> | null
|
|
36
|
+
slug: string
|
|
37
|
+
status: 'draft' | 'published'
|
|
38
|
+
hasUnpublishedChanges: boolean
|
|
39
|
+
}
|
|
40
|
+
} | { ok: false }
|
|
41
|
+
|
|
42
|
+
// If the entry has no rows at all (none of its locales exist), 404. Otherwise,
|
|
43
|
+
// the requested locale just doesn't have a translation yet — show an empty form
|
|
44
|
+
// so the editor can author one.
|
|
45
|
+
if (!body.ok && existingLocales.length === 0) return new Response('Not found', { status: 404 })
|
|
46
|
+
|
|
47
|
+
const hasTranslation = body.ok
|
|
48
|
+
const row = hasTranslation ? body.data : null
|
|
49
|
+
const editContent = bp.drafts && row?.draftContent ? row.draftContent : (row?.content ?? {})
|
|
50
|
+
const initial = hasTranslation
|
|
51
|
+
? {
|
|
52
|
+
...(editContent as Record<string, unknown>),
|
|
53
|
+
slug: row!.slug,
|
|
54
|
+
status: row!.status,
|
|
55
|
+
hasUnpublishedChanges: row!.hasUnpublishedChanges,
|
|
56
|
+
}
|
|
57
|
+
: { status: 'draft' }
|
|
58
|
+
|
|
59
|
+
const previewConfig = resolvePreviewConfig(bp)
|
|
60
|
+
const previewTemplate = previewConfig.path
|
|
61
|
+
const livePreviewEnabled = previewConfig.live !== false
|
|
62
|
+
const previewPath = previewTemplate.replace('{slug}', encodeURIComponent(row?.slug ?? ''))
|
|
63
|
+
---
|
|
64
|
+
<AdminShell title={`Edit ${bp.label}`} activePath={`/admin/collections/${bp.name}`}>
|
|
65
|
+
<div class="mb-4 flex items-center gap-4">
|
|
66
|
+
<a href={`/admin/collections/${bp.name}/${entryId}/revisions?locale=${encodeURIComponent(activeLocale)}`} class="text-sm text-zinc-600 underline">View history</a>
|
|
67
|
+
{hasTranslation && (
|
|
68
|
+
<a href={`/api/vulse/preview/start?to=${encodeURIComponent(previewPath)}`} class="rounded border border-zinc-300 px-3 py-1 text-sm hover:bg-zinc-50">Preview</a>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
<EntryEditorWithPreview
|
|
72
|
+
client:load
|
|
73
|
+
collection={bp.name}
|
|
74
|
+
entryId={entryId}
|
|
75
|
+
fields={fields}
|
|
76
|
+
initial={initial}
|
|
77
|
+
draftsEnabled={bp.drafts === true}
|
|
78
|
+
seoEnabled={bp.seo === true}
|
|
79
|
+
seoMapping={bp.admin.seoMapping}
|
|
80
|
+
tree={bp.tree === true}
|
|
81
|
+
hasUnpublishedChanges={row?.hasUnpublishedChanges ?? false}
|
|
82
|
+
titleField={bp.admin.titleField}
|
|
83
|
+
previewPath={previewTemplate}
|
|
84
|
+
livePreviewAllowed={livePreviewEnabled}
|
|
85
|
+
entryLocale={activeLocale}
|
|
86
|
+
defaultLocale={localesCfg.defaultLocale}
|
|
87
|
+
supportedLocales={localesCfg.locales}
|
|
88
|
+
existingLocales={existingLocales}
|
|
89
|
+
/>
|
|
90
|
+
</AdminShell>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../components/AdminShell.astro'
|
|
3
|
+
import EntryList from '../../../components/EntryList.vue'
|
|
4
|
+
import { createDb } from '../../../../core/db.js'
|
|
5
|
+
import { getRuntimeEnv } from '../../../../server/env.js'
|
|
6
|
+
import { registryForRequest } from '../../../../core/blueprints/load.js'
|
|
7
|
+
import { readLocalesConfig } from '../../../../core/locales.js'
|
|
8
|
+
|
|
9
|
+
const env = getRuntimeEnv()
|
|
10
|
+
const db = createDb(env.DB)
|
|
11
|
+
const reg = await registryForRequest(db)
|
|
12
|
+
const bp = reg.get(Astro.params.name!)
|
|
13
|
+
if (!bp) return Astro.redirect('/admin')
|
|
14
|
+
const columns = bp.admin.listColumns ?? [bp.admin.titleField]
|
|
15
|
+
|
|
16
|
+
const localesCfg = await readLocalesConfig(db)
|
|
17
|
+
const requested = Astro.url.searchParams.get('locale')
|
|
18
|
+
const activeLocale = requested && localesCfg.locales.includes(requested) ? requested : localesCfg.defaultLocale
|
|
19
|
+
---
|
|
20
|
+
<AdminShell title={bp.label} activePath={`/admin/collections/${bp.name}`}>
|
|
21
|
+
<EntryList
|
|
22
|
+
client:load
|
|
23
|
+
collection={bp.name}
|
|
24
|
+
label={bp.label}
|
|
25
|
+
columns={columns}
|
|
26
|
+
tree={bp.tree === true}
|
|
27
|
+
entryLocale={activeLocale}
|
|
28
|
+
supportedLocales={localesCfg.locales}
|
|
29
|
+
defaultLocale={localesCfg.defaultLocale}
|
|
30
|
+
/>
|
|
31
|
+
</AdminShell>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../components/AdminShell.astro'
|
|
3
|
+
import EntryForm from '../../../components/EntryForm.vue'
|
|
4
|
+
import { createDb } from '../../../../core/db.js'
|
|
5
|
+
import { getRuntimeEnv } from '../../../../server/env.js'
|
|
6
|
+
import { registryForRequest } from '../../../../core/blueprints/load.js'
|
|
7
|
+
import { fieldDescriptorsFromBlueprint } from '../../../client/form-from-zod.js'
|
|
8
|
+
import { readLocalesConfig } from '../../../../core/locales.js'
|
|
9
|
+
|
|
10
|
+
const env = getRuntimeEnv()
|
|
11
|
+
const db = createDb(env.DB)
|
|
12
|
+
const reg = await registryForRequest(db)
|
|
13
|
+
const bp = reg.get(Astro.params.name!)
|
|
14
|
+
if (!bp) return Astro.redirect('/admin')
|
|
15
|
+
const fields = fieldDescriptorsFromBlueprint(bp)
|
|
16
|
+
const parentId = Astro.url.searchParams.get('parent_id')
|
|
17
|
+
|
|
18
|
+
const localesCfg = await readLocalesConfig(db)
|
|
19
|
+
const requested = Astro.url.searchParams.get('locale')
|
|
20
|
+
const activeLocale = requested && localesCfg.locales.includes(requested) ? requested : localesCfg.defaultLocale
|
|
21
|
+
---
|
|
22
|
+
<AdminShell title={`New ${bp.label}`} activePath={`/admin/collections/${bp.name}`}>
|
|
23
|
+
<EntryForm
|
|
24
|
+
client:load
|
|
25
|
+
collection={bp.name}
|
|
26
|
+
fields={fields}
|
|
27
|
+
initial={{}}
|
|
28
|
+
draftsEnabled={bp.drafts === true}
|
|
29
|
+
seoEnabled={bp.seo === true}
|
|
30
|
+
seoMapping={bp.admin.seoMapping}
|
|
31
|
+
tree={bp.tree === true}
|
|
32
|
+
parentId={parentId}
|
|
33
|
+
titleField={bp.admin.titleField}
|
|
34
|
+
entryLocale={activeLocale}
|
|
35
|
+
defaultLocale={localesCfg.defaultLocale}
|
|
36
|
+
supportedLocales={localesCfg.locales}
|
|
37
|
+
/>
|
|
38
|
+
</AdminShell>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../../components/AdminShell.astro'
|
|
3
|
+
import SubmissionDetail from '../../../../components/SubmissionDetail.vue'
|
|
4
|
+
|
|
5
|
+
const { handle, id } = Astro.params
|
|
6
|
+
---
|
|
7
|
+
<AdminShell title="Submission" activePath="/admin/forms">
|
|
8
|
+
<SubmissionDetail client:load formHandle={handle!} submissionId={id!} />
|
|
9
|
+
</AdminShell>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../../components/AdminShell.astro'
|
|
3
|
+
import SubmissionList from '../../../../components/SubmissionList.vue'
|
|
4
|
+
|
|
5
|
+
const { handle } = Astro.params
|
|
6
|
+
---
|
|
7
|
+
<AdminShell title="Submissions" activePath="/admin/forms">
|
|
8
|
+
<SubmissionList client:load formHandle={handle!} />
|
|
9
|
+
</AdminShell>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../components/AdminShell.astro'
|
|
3
|
+
import FormEditor from '../../components/FormEditor.vue'
|
|
4
|
+
|
|
5
|
+
const { handle } = Astro.params
|
|
6
|
+
---
|
|
7
|
+
<AdminShell title={handle ? `Form: ${handle}` : 'Form'} activePath="/admin/forms">
|
|
8
|
+
<FormEditor client:load handle={handle ?? null} />
|
|
9
|
+
</AdminShell>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../components/AdminShell.astro'
|
|
3
|
+
import { getRuntime } from '../../server/runtime.js'
|
|
4
|
+
import { getRuntimeEnv } from '../../server/env.js'
|
|
5
|
+
import { createDb } from '../../core/db.js'
|
|
6
|
+
import { registryForRequest } from '../../core/blueprints/load.js'
|
|
7
|
+
|
|
8
|
+
const env = getRuntimeEnv()
|
|
9
|
+
const db = createDb(env.DB)
|
|
10
|
+
const rt = await getRuntime(env, await registryForRequest(db), new URL(Astro.request.url).origin)
|
|
11
|
+
const reg = rt.registry
|
|
12
|
+
|
|
13
|
+
const counts: { name: string; label: string; total: number; published: number }[] = []
|
|
14
|
+
for (const bp of reg.list()) {
|
|
15
|
+
const all = await rt.routes.entries.list(new Request(Astro.request.url, { headers: Astro.request.headers }), { collection: bp.name })
|
|
16
|
+
const body = await all.json() as { ok: true; data: { status: string }[] } | { ok: false }
|
|
17
|
+
const list = body.ok ? body.data : []
|
|
18
|
+
counts.push({
|
|
19
|
+
name: bp.name, label: bp.label,
|
|
20
|
+
total: list.length,
|
|
21
|
+
published: list.filter((e) => e.status === 'published').length,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
---
|
|
25
|
+
<AdminShell title="Dashboard" activePath="/admin">
|
|
26
|
+
<h1 class="text-2xl font-semibold mb-6">Dashboard</h1>
|
|
27
|
+
<div class="grid grid-cols-3 gap-4">
|
|
28
|
+
{counts.map((c) => (
|
|
29
|
+
<a href={`/admin/collections/${c.name}`} class="block p-4 rounded-xl border bg-white hover:shadow-sm">
|
|
30
|
+
<div class="text-sm text-zinc-500">{c.label}</div>
|
|
31
|
+
<div class="text-3xl font-semibold mt-1">{c.total}</div>
|
|
32
|
+
<div class="text-xs text-zinc-400 mt-1">{c.published} published</div>
|
|
33
|
+
</a>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
</AdminShell>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../styles/admin.css'
|
|
3
|
+
import LoginForm from '../components/LoginForm.vue'
|
|
4
|
+
const next = new URL(Astro.request.url).searchParams.get('next') ?? '/admin'
|
|
5
|
+
---
|
|
6
|
+
<!doctype html>
|
|
7
|
+
<html lang="en" class="vulse-admin">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="utf-8" /><title>Sign in — Vulse</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body class="min-h-screen grid place-items-center bg-zinc-50 vulse-admin">
|
|
12
|
+
<LoginForm client:load next={next} />
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../components/AdminShell.astro'
|
|
3
|
+
import MediaLibrary from '../components/MediaLibrary.vue'
|
|
4
|
+
---
|
|
5
|
+
<AdminShell title="Media" activePath="/admin/media">
|
|
6
|
+
<h1 class="mb-4 text-2xl font-semibold text-zinc-900">Assets</h1>
|
|
7
|
+
<MediaLibrary client:load />
|
|
8
|
+
</AdminShell>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../components/AdminShell.astro'
|
|
3
|
+
import BlueprintEditor from '../../components/BlueprintEditor.vue'
|
|
4
|
+
|
|
5
|
+
const { handle } = Astro.params
|
|
6
|
+
const isAdmin = (Astro.locals as { vulseUser?: { role?: string } }).vulseUser?.role === 'admin'
|
|
7
|
+
---
|
|
8
|
+
<AdminShell title={handle ? `Schema: ${handle}` : 'New collection'} activePath={handle ? `/admin/schema/${handle}` : '/admin/schema/new'}>
|
|
9
|
+
<BlueprintEditor client:only="vue" handle={handle ?? null} is-admin={isAdmin} />
|
|
10
|
+
</AdminShell>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../components/AdminShell.astro'
|
|
3
|
+
import BlueprintEditor from '../../components/BlueprintEditor.vue'
|
|
4
|
+
|
|
5
|
+
const isAdmin = (Astro.locals as { vulseUser?: { role?: string } }).vulseUser?.role === 'admin'
|
|
6
|
+
---
|
|
7
|
+
<AdminShell title="New collection" activePath="/admin/schema/new">
|
|
8
|
+
<BlueprintEditor client:only="vue" handle={null} is-admin={isAdmin} />
|
|
9
|
+
</AdminShell>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../components/AdminShell.astro'
|
|
3
|
+
import AuthSettings from '../../components/AuthSettings.vue'
|
|
4
|
+
---
|
|
5
|
+
<AdminShell title="Auth settings" activePath="/admin/settings/auth">
|
|
6
|
+
<h1 class="mb-4 text-2xl font-semibold text-zinc-900">Authentication</h1>
|
|
7
|
+
<p class="mb-4 text-sm text-zinc-600">Control public member sign-up on your site. End-user sign-in forms are headless — you style them in your Astro pages.</p>
|
|
8
|
+
<AuthSettings client:load />
|
|
9
|
+
</AdminShell>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AdminShell from '../../../components/AdminShell.astro'
|
|
3
|
+
import GlobalSetEditor from '../../../components/GlobalSetEditor.vue'
|
|
4
|
+
|
|
5
|
+
const { handle } = Astro.params
|
|
6
|
+
---
|
|
7
|
+
<AdminShell title={handle ? `Global: ${handle}` : 'Global set'} activePath="/admin/settings/globals">
|
|
8
|
+
<GlobalSetEditor client:load handle={handle ?? null} />
|
|
9
|
+
</AdminShell>
|