@xleddyl/nuxt-cms 0.1.22 → 0.1.24
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/README.md +2 -2
- package/dist/module.d.mts +2 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -3
- package/dist/runtime/app/components/cms/BlocksField.vue +2 -9
- package/dist/runtime/app/components/cms/Button.vue +4 -2
- package/dist/runtime/app/components/cms/ConfirmModal.vue +5 -14
- package/dist/runtime/app/components/cms/Drawer.d.vue.ts +30 -0
- package/dist/runtime/app/components/cms/Drawer.vue +65 -0
- package/dist/runtime/app/components/cms/Drawer.vue.d.ts +30 -0
- package/dist/runtime/app/components/cms/DropdownMenu.d.vue.ts +0 -3
- package/dist/runtime/app/components/cms/DropdownMenu.vue +2 -7
- package/dist/runtime/app/components/cms/DropdownMenu.vue.d.ts +0 -3
- package/dist/runtime/app/components/cms/EmptyState.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/EmptyState.vue +6 -7
- package/dist/runtime/app/components/cms/EmptyState.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/EntryDrawer.d.vue.ts +21 -0
- package/dist/runtime/app/components/cms/EntryDrawer.vue +193 -0
- package/dist/runtime/app/components/cms/EntryDrawer.vue.d.ts +21 -0
- package/dist/runtime/app/components/cms/EntryForm.vue +19 -7
- package/dist/runtime/app/components/cms/FieldInput.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/FieldInput.vue +13 -27
- package/dist/runtime/app/components/cms/FieldInput.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/Form.vue +1 -1
- package/dist/runtime/app/components/cms/FormField.d.vue.ts +4 -5
- package/dist/runtime/app/components/cms/FormField.vue +9 -7
- package/dist/runtime/app/components/cms/FormField.vue.d.ts +4 -5
- package/dist/runtime/app/components/cms/JsonField.vue +3 -4
- package/dist/runtime/app/components/cms/LocaleSwitch.d.vue.ts +14 -0
- package/dist/runtime/app/components/cms/LocaleSwitch.vue +29 -0
- package/dist/runtime/app/components/cms/LocaleSwitch.vue.d.ts +14 -0
- package/dist/runtime/app/components/cms/MediaField.vue +7 -17
- package/dist/runtime/app/components/cms/MediaGallery.vue +57 -80
- package/dist/runtime/app/components/cms/MediaThumb.d.vue.ts +6 -0
- package/dist/runtime/app/components/cms/MediaThumb.vue +40 -0
- package/dist/runtime/app/components/cms/MediaThumb.vue.d.ts +6 -0
- package/dist/runtime/app/components/cms/Modal.d.vue.ts +1 -4
- package/dist/runtime/app/components/cms/Modal.vue +8 -7
- package/dist/runtime/app/components/cms/Modal.vue.d.ts +1 -4
- package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/PageHeader.vue +6 -6
- package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +1 -1
- package/dist/runtime/app/components/cms/RelationField.vue +0 -4
- package/dist/runtime/app/components/cms/SelectMenu.vue +23 -3
- package/dist/runtime/app/components/cms/SlugField.vue +1 -2
- package/dist/runtime/app/components/cms/Spinner.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/Spinner.vue +5 -0
- package/dist/runtime/app/components/cms/Spinner.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/StatusBadge.d.vue.ts +6 -0
- package/dist/runtime/app/components/cms/StatusBadge.vue +11 -0
- package/dist/runtime/app/components/cms/StatusBadge.vue.d.ts +6 -0
- package/dist/runtime/app/components/cms/Table.d.vue.ts +3 -6
- package/dist/runtime/app/components/cms/Table.vue +72 -27
- package/dist/runtime/app/components/cms/Table.vue.d.ts +3 -6
- package/dist/runtime/app/components/cms/Toaster.vue +4 -4
- package/dist/runtime/app/components/cms/TranslatableField.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/TranslatableField.vue +7 -44
- package/dist/runtime/app/components/cms/TranslatableField.vue.d.ts +1 -0
- package/dist/runtime/app/composables/cms-runtime.d.ts +2 -1
- package/dist/runtime/app/layouts/cms-admin.vue +10 -20
- package/dist/runtime/app/pages/admin-collection.vue +130 -103
- package/dist/runtime/app/pages/admin-entry.vue +6 -24
- package/dist/runtime/app/pages/admin-login.vue +12 -31
- package/dist/runtime/app/pages/admin-media.vue +8 -2
- package/dist/runtime/app/utils/ui.d.ts +0 -7
- package/dist/runtime/app/utils/ui.js +0 -5
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/collection.get.js +8 -3
- package/dist/runtime/server/api/media.post.js +8 -2
- package/dist/runtime/server/api/media.put.js +3 -2
- package/dist/runtime/server/utils/media.d.ts +4 -1
- package/dist/runtime/server/utils/media.js +16 -3
- package/dist/runtime/server/utils/relations.d.ts +1 -0
- package/dist/runtime/server/utils/relations.js +25 -0
- package/dist/runtime/shared/index.d.ts +11 -3
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { createError, defineEventHandler, readValidatedBody } from "h3";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { useDb } from "#cms-db";
|
|
5
5
|
import { cms_media } from "#cms-tables";
|
|
6
|
-
import { toMediaItem, useMediaConfig } from "../utils/media.js";
|
|
6
|
+
import { assertMediaWritable, toMediaItem, useMediaConfig } from "../utils/media.js";
|
|
7
7
|
import { requireAdmin } from "../utils/require-admin.js";
|
|
8
8
|
const bodySchema = z.object({
|
|
9
9
|
id: z.number().int().positive(),
|
|
@@ -12,7 +12,8 @@ const bodySchema = z.object({
|
|
|
12
12
|
});
|
|
13
13
|
export default defineEventHandler(async (event) => {
|
|
14
14
|
await requireAdmin(event);
|
|
15
|
-
const { publicUrl } = useMediaConfig(event);
|
|
15
|
+
const { media, publicUrl } = useMediaConfig(event);
|
|
16
|
+
assertMediaWritable(media);
|
|
16
17
|
const body = await readValidatedBody(event, bodySchema.parse);
|
|
17
18
|
const updates = {};
|
|
18
19
|
if (body.alt !== void 0) updates.alt = body.alt;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import { AwsClient } from 'aws4fetch';
|
|
3
|
-
import type { MediaItem } from '../../shared/index.js';
|
|
3
|
+
import type { MediaItem, MediaStorageMode } from '../../shared/index.js';
|
|
4
4
|
interface MediaConfig {
|
|
5
|
+
storage: MediaStorageMode;
|
|
5
6
|
endpoint: string;
|
|
6
7
|
region: string;
|
|
7
8
|
bucket: string;
|
|
@@ -11,6 +12,8 @@ interface MediaConfig {
|
|
|
11
12
|
}
|
|
12
13
|
export declare function assertUploadContentType(contentType: string): void;
|
|
13
14
|
export declare function encodeKey(key: string): string;
|
|
15
|
+
export declare function assertMediaConfigured(media: MediaConfig): void;
|
|
16
|
+
export declare function assertMediaWritable(media: MediaConfig): void;
|
|
14
17
|
export declare function useMediaConfig(event: H3Event): {
|
|
15
18
|
media: MediaConfig;
|
|
16
19
|
publicUrl: (key: string) => string | null;
|
|
@@ -32,21 +32,34 @@ export function assertUploadContentType(contentType) {
|
|
|
32
32
|
export function encodeKey(key) {
|
|
33
33
|
return key.split("/").map(encodeURIComponent).join("/");
|
|
34
34
|
}
|
|
35
|
-
export function
|
|
36
|
-
|
|
37
|
-
const media = config.cms.media;
|
|
35
|
+
export function assertMediaConfigured(media) {
|
|
36
|
+
if (media.storage !== "s3") return;
|
|
38
37
|
if (!media.endpoint || !media.bucket || !media.accessKeyId || !media.secretAccessKey) {
|
|
39
38
|
throw createError({
|
|
40
39
|
statusCode: 501,
|
|
41
40
|
statusMessage: "Media storage is not configured (cms.media in nuxt.config)"
|
|
42
41
|
});
|
|
43
42
|
}
|
|
43
|
+
}
|
|
44
|
+
export function assertMediaWritable(media) {
|
|
45
|
+
if (media.storage === "local") {
|
|
46
|
+
throw createError({
|
|
47
|
+
statusCode: 501,
|
|
48
|
+
statusMessage: "Media storage is local; the media library is read-only"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function useMediaConfig(event) {
|
|
53
|
+
const config = useRuntimeConfig(event);
|
|
54
|
+
const media = config.cms.media;
|
|
55
|
+
assertMediaConfigured(media);
|
|
44
56
|
const { mediaBaseUrl } = config.public.cms;
|
|
45
57
|
const publicUrl = (key) => mediaPublicUrl(mediaBaseUrl, key);
|
|
46
58
|
return { media, publicUrl };
|
|
47
59
|
}
|
|
48
60
|
export function useMediaStorage(event) {
|
|
49
61
|
const { media, publicUrl } = useMediaConfig(event);
|
|
62
|
+
assertMediaWritable(media);
|
|
50
63
|
const client = new AwsClient({
|
|
51
64
|
accessKeyId: media.accessKeyId,
|
|
52
65
|
secretAccessKey: media.secretAccessKey,
|
|
@@ -9,5 +9,6 @@ export declare function splitRelationValues(entry: CmsEntry, body: Row): {
|
|
|
9
9
|
};
|
|
10
10
|
export declare function assertRelationTargets(entry: CmsEntry, lists: Record<string, string[]>): Promise<void>;
|
|
11
11
|
export declare function saveManyToMany(db: CmsDb, name: string, sourceId: string, lists: Record<string, string[]>): Promise<void>;
|
|
12
|
+
export declare function relationTitles(db: CmsDb, entry: CmsEntry, rows: Row[]): Promise<Record<string, Record<string, unknown>>>;
|
|
12
13
|
export declare function attachManyToMany<T extends Row>(db: CmsDb, name: string, entry: CmsEntry, rows: T[]): Promise<T[]>;
|
|
13
14
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { asc, eq, inArray } from "drizzle-orm";
|
|
2
2
|
import { createError } from "h3";
|
|
3
|
+
import cmsConfig from "#cms-config";
|
|
3
4
|
import { useDb } from "#cms-db";
|
|
4
5
|
import * as cmsTables from "#cms-tables";
|
|
5
6
|
import { resolveTable, tableColumns } from "./registry.js";
|
|
@@ -57,6 +58,30 @@ export async function saveManyToMany(db, name, sourceId, lists) {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
}
|
|
61
|
+
export async function relationTitles(db, entry, rows) {
|
|
62
|
+
const config = cmsConfig;
|
|
63
|
+
const titles = {};
|
|
64
|
+
for (const [key, field] of Object.entries(entry.fields)) {
|
|
65
|
+
if (field.type !== "relation" || !field.to) continue;
|
|
66
|
+
const targetEntry = config[field.to];
|
|
67
|
+
const target = resolveTable(field.to);
|
|
68
|
+
if (!targetEntry?.titleField || !target) continue;
|
|
69
|
+
const ids = /* @__PURE__ */ new Set();
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
const value = row[key];
|
|
72
|
+
for (const id of Array.isArray(value) ? value : [value]) {
|
|
73
|
+
if (id != null) ids.add(String(id));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!ids.size) continue;
|
|
77
|
+
const columns = tableColumns(target);
|
|
78
|
+
const titleColumn = columns[targetEntry.titleField];
|
|
79
|
+
if (!titleColumn) continue;
|
|
80
|
+
const found = await db.select({ id: columns.id, title: titleColumn }).from(target).where(inArray(columns.id, [...ids]));
|
|
81
|
+
titles[key] = Object.fromEntries(found.map((row) => [row.id, row.title]));
|
|
82
|
+
}
|
|
83
|
+
return titles;
|
|
84
|
+
}
|
|
60
85
|
export async function attachManyToMany(db, name, entry, rows) {
|
|
61
86
|
const keys = manyToManyKeys(entry);
|
|
62
87
|
const ids = rows.map((row) => row.id).filter((id) => id != null);
|
|
@@ -6,6 +6,7 @@ export interface CmsI18n {
|
|
|
6
6
|
locales: string[];
|
|
7
7
|
defaultLocale: string;
|
|
8
8
|
}
|
|
9
|
+
export type MediaStorageMode = 's3' | 'local';
|
|
9
10
|
export declare const MEDIA_TYPES: readonly ["image", "video", "file"];
|
|
10
11
|
export type MediaType = (typeof MEDIA_TYPES)[number];
|
|
11
12
|
export declare function mediaTypeForKey(key: string): MediaType;
|
|
@@ -120,14 +121,21 @@ export interface BlocksFieldInput extends FieldInputBase {
|
|
|
120
121
|
blocks: Record<string, BlockInput>;
|
|
121
122
|
}
|
|
122
123
|
export type CmsFieldInput = TextFieldInput | RichtextFieldInput | NumberFieldInput | BooleanFieldInput | DateFieldInput | EmailFieldInput | SlugFieldInput | SelectFieldInput | JsonFieldInput | MediaFieldInput | RelationFieldInput | BlocksFieldInput;
|
|
123
|
-
|
|
124
|
+
interface CmsEntryInputBase {
|
|
124
125
|
id: string;
|
|
125
126
|
label: string;
|
|
126
|
-
kind: 'collection' | 'single';
|
|
127
|
-
titleField?: string;
|
|
128
127
|
drafts?: boolean;
|
|
129
128
|
fields: Record<string, CmsFieldInput>;
|
|
130
129
|
}
|
|
130
|
+
export interface CmsCollectionInput extends CmsEntryInputBase {
|
|
131
|
+
kind: 'collection';
|
|
132
|
+
titleField: string;
|
|
133
|
+
}
|
|
134
|
+
export interface CmsSingleInput extends CmsEntryInputBase {
|
|
135
|
+
kind: 'single';
|
|
136
|
+
titleField?: never;
|
|
137
|
+
}
|
|
138
|
+
export type CmsEntryInput = CmsCollectionInput | CmsSingleInput;
|
|
131
139
|
export type CmsConfigInput = Record<string, CmsEntryInput>;
|
|
132
140
|
export declare function defineCmsConfig<T extends CmsConfigInput>(config: T): T;
|
|
133
141
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xleddyl/nuxt-cms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Edoardo Alberti (https://github.com/xleddyl)",
|