@xleddyl/nuxt-cms 0.2.1 → 0.2.2
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/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/app/components/cms/BrandMark.vue +2 -3
- package/dist/runtime/app/components/cms/ConfirmModal.vue +14 -1
- package/dist/runtime/app/components/cms/MediaGallery.vue +89 -11
- package/dist/runtime/app/components/cms/Modal.vue +0 -2
- package/dist/runtime/app/composables/cms-confirm.d.ts +16 -7
- package/dist/runtime/app/composables/cms-confirm.js +1 -0
- package/dist/runtime/app/composables/cms-media-library.d.ts +1 -2
- package/dist/runtime/app/composables/cms-media-library.js +12 -19
- package/dist/runtime/app/utils/ui.d.ts +0 -2
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/seed.js +11 -1
- package/dist/runtime/server/api/collection.post.js +20 -14
- package/dist/runtime/server/api/collection.put.js +12 -12
- package/dist/runtime/server/api/item.put.js +14 -13
- package/dist/runtime/server/api/media-folder.patch.js +10 -16
- package/dist/runtime/server/api/media-move.post.js +12 -8
- package/dist/runtime/server/api/media.delete.js +1 -1
- package/dist/runtime/server/api/media.post.js +2 -1
- package/dist/runtime/server/api/media.put.js +2 -1
- package/dist/runtime/server/plugins/migrate-sqlite.js +2 -2
- package/dist/runtime/server/routes/auth/login.post.js +28 -22
- package/dist/runtime/server/utils/db-d1.d.ts +0 -1
- package/dist/runtime/server/utils/db-d1.js +3 -4
- package/dist/runtime/server/utils/db-libsql.d.ts +0 -1
- package/dist/runtime/server/utils/db-libsql.js +3 -4
- package/dist/runtime/server/utils/db-postgres.d.ts +0 -1
- package/dist/runtime/server/utils/db-postgres.js +1 -4
- package/dist/runtime/server/utils/db-sqlite.d.ts +0 -1
- package/dist/runtime/server/utils/db-sqlite.js +4 -10
- package/dist/runtime/server/utils/graphql.js +2 -1
- package/dist/runtime/server/utils/libsql-migrations.d.ts +1 -0
- package/dist/runtime/server/utils/libsql-migrations.js +1 -1
- package/dist/runtime/server/utils/media-folders.js +14 -23
- package/dist/runtime/server/utils/media.js +2 -0
- package/dist/runtime/server/utils/migrate.d.ts +1 -0
- package/dist/runtime/server/utils/migrate.js +32 -0
- package/dist/runtime/server/utils/registry.d.ts +3 -0
- package/dist/runtime/server/utils/registry.js +10 -1
- package/dist/runtime/server/utils/relations.d.ts +1 -1
- package/dist/runtime/server/utils/relations.js +6 -9
- package/dist/runtime/server/utils/timestamps.d.ts +12 -0
- package/dist/runtime/server/utils/timestamps.js +48 -0
- package/dist/runtime/server/utils/users.js +3 -2
- package/dist/runtime/server/utils/write-statements.d.ts +33 -0
- package/dist/runtime/server/utils/write-statements.js +75 -0
- package/dist/runtime/shared/index.d.ts +14 -0
- package/dist/runtime/shared/index.js +13 -0
- package/dist/runtime/shared/timestamps.d.ts +4 -0
- package/dist/runtime/shared/timestamps.js +24 -0
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { eq, getTableColumns, inArray } from "drizzle-orm";
|
|
2
|
+
import { isMediaFolderMarker, mediaFolderDepth, mediaFolderMarkerKey } from "../../shared/index.js";
|
|
3
|
+
import { nowTimestamp } from "../../shared/timestamps.js";
|
|
4
|
+
export const KEY_CHUNK = 90;
|
|
5
|
+
const JOIN_INSERT_CHUNK = 30;
|
|
6
|
+
export function chunks(items, size) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (let index = 0; index < items.length; index += size) {
|
|
9
|
+
out.push(items.slice(index, index + size));
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
export function joinWriteStatements(db, join, sourceId, ids) {
|
|
14
|
+
const statements = [
|
|
15
|
+
db.delete(join.table).where(eq(join.sourceId, sourceId))
|
|
16
|
+
];
|
|
17
|
+
const rows = ids.map((targetId, position) => ({ sourceId, targetId, position }));
|
|
18
|
+
for (const chunk of chunks(rows, JOIN_INSERT_CHUNK)) {
|
|
19
|
+
statements.push(db.insert(join.table).values(chunk));
|
|
20
|
+
}
|
|
21
|
+
return statements;
|
|
22
|
+
}
|
|
23
|
+
export function mediaFolderMarkerValues(folder) {
|
|
24
|
+
return {
|
|
25
|
+
key: mediaFolderMarkerKey(folder),
|
|
26
|
+
mime: "application/x-empty",
|
|
27
|
+
size: 0,
|
|
28
|
+
width: null,
|
|
29
|
+
height: null,
|
|
30
|
+
alt: null,
|
|
31
|
+
folder
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function planMediaFolderRename(rows) {
|
|
35
|
+
const folders = /* @__PURE__ */ new Map();
|
|
36
|
+
const markers = /* @__PURE__ */ new Set();
|
|
37
|
+
for (const row of rows) {
|
|
38
|
+
if (!isMediaFolderMarker(row.key)) {
|
|
39
|
+
if (row.folder) folders.set(row.folder, row.target);
|
|
40
|
+
} else if (row.target) {
|
|
41
|
+
markers.add(row.target);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const markerKeys = new Set([...markers].map(mediaFolderMarkerKey));
|
|
45
|
+
return {
|
|
46
|
+
folders: [...folders].sort(([a], [b]) => mediaFolderDepth(a) - mediaFolderDepth(b)),
|
|
47
|
+
markers: [...markers],
|
|
48
|
+
staleMarkers: rows.filter((row) => isMediaFolderMarker(row.key) && !markerKeys.has(row.key)).map((row) => row.key)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function mediaColumns(table) {
|
|
52
|
+
const columns = getTableColumns(table);
|
|
53
|
+
return { key: columns.key, folder: columns.folder };
|
|
54
|
+
}
|
|
55
|
+
export function mediaFolderRenameStatements(db, table, plan) {
|
|
56
|
+
const columns = mediaColumns(table);
|
|
57
|
+
const statements = [];
|
|
58
|
+
for (const [folder, target] of plan.folders) {
|
|
59
|
+
statements.push(db.update(table).set({ folder: target }).where(eq(columns.folder, folder)));
|
|
60
|
+
}
|
|
61
|
+
for (const folder of plan.markers) {
|
|
62
|
+
const values = mediaFolderMarkerValues(folder);
|
|
63
|
+
statements.push(
|
|
64
|
+
db.insert(table).values({ ...values, createdAt: nowTimestamp() }).onConflictDoUpdate({ target: columns.key, set: values })
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
for (const keys of chunks(plan.staleMarkers, KEY_CHUNK)) {
|
|
68
|
+
statements.push(db.delete(table).where(inArray(columns.key, keys)));
|
|
69
|
+
}
|
|
70
|
+
return statements;
|
|
71
|
+
}
|
|
72
|
+
export function mediaDeleteStatements(db, table, keys) {
|
|
73
|
+
const { key } = mediaColumns(table);
|
|
74
|
+
return chunks(keys, KEY_CHUNK).map((chunk) => db.delete(table).where(inArray(key, chunk)));
|
|
75
|
+
}
|
|
@@ -60,6 +60,20 @@ export interface MediaUsage {
|
|
|
60
60
|
title: string | null;
|
|
61
61
|
field: string;
|
|
62
62
|
}
|
|
63
|
+
export interface MediaUsageEntry {
|
|
64
|
+
collection: string;
|
|
65
|
+
label: string;
|
|
66
|
+
kind: CmsEntryKind;
|
|
67
|
+
id: string | null;
|
|
68
|
+
title: string | null;
|
|
69
|
+
fields: string[];
|
|
70
|
+
}
|
|
71
|
+
export declare function groupMediaUsage(usage: MediaUsage[]): MediaUsageEntry[];
|
|
72
|
+
export interface MediaUsageGroup {
|
|
73
|
+
key: string;
|
|
74
|
+
entries: MediaUsageEntry[];
|
|
75
|
+
}
|
|
76
|
+
export declare function mediaUsageGroups(usage: Record<string, MediaUsage[]>, keys: string[]): MediaUsageGroup[];
|
|
63
77
|
export interface BlockConfig {
|
|
64
78
|
label: string;
|
|
65
79
|
fields: Record<string, FieldConfig>;
|
|
@@ -110,6 +110,19 @@ export function expandMediaFolders(folders) {
|
|
|
110
110
|
}
|
|
111
111
|
return [...expanded].sort();
|
|
112
112
|
}
|
|
113
|
+
export function groupMediaUsage(usage) {
|
|
114
|
+
const entries = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const { field, ...rest } of usage) {
|
|
116
|
+
const id = `${rest.collection}\0${rest.id ?? ""}`;
|
|
117
|
+
const entry = entries.get(id);
|
|
118
|
+
if (!entry) entries.set(id, { ...rest, fields: [field] });
|
|
119
|
+
else if (!entry.fields.includes(field)) entry.fields.push(field);
|
|
120
|
+
}
|
|
121
|
+
return [...entries.values()];
|
|
122
|
+
}
|
|
123
|
+
export function mediaUsageGroups(usage, keys) {
|
|
124
|
+
return [...new Set(keys)].map((key) => ({ key, entries: groupMediaUsage(usage[key] ?? []) })).filter((group) => group.entries.length > 0);
|
|
125
|
+
}
|
|
113
126
|
export function isPrivateField(field) {
|
|
114
127
|
return !!field.private;
|
|
115
128
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const SQLITE_ISO_FORMAT = "%Y-%m-%dT%H:%M:%fZ";
|
|
2
|
+
export declare function nowTimestamp(): string;
|
|
3
|
+
export declare function normalizeTimestamp<T>(value: T): T | string;
|
|
4
|
+
export declare function normalizeTimestampFields<T extends Record<string, unknown>>(row: T, keys?: readonly string[]): T;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const SQLITE_ISO_FORMAT = "%Y-%m-%dT%H:%M:%fZ";
|
|
2
|
+
export function nowTimestamp() {
|
|
3
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
4
|
+
}
|
|
5
|
+
const TIMESTAMP_PATTERN = /^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})(?:\.(\d+))?(Z|[+-]\d{2}(?::?\d{2})?)?$/;
|
|
6
|
+
export function normalizeTimestamp(value) {
|
|
7
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? value : value.toISOString();
|
|
8
|
+
if (typeof value !== "string") return value;
|
|
9
|
+
const match = TIMESTAMP_PATTERN.exec(value.trim());
|
|
10
|
+
if (!match) return value;
|
|
11
|
+
const [, date, time, fraction = "", zone] = match;
|
|
12
|
+
const millis = fraction.padEnd(3, "0").slice(0, 3);
|
|
13
|
+
const utc = `${date}T${time}.${millis}Z`;
|
|
14
|
+
if (!zone || zone === "Z") return utc;
|
|
15
|
+
const offset = zone.length === 3 ? `${zone}:00` : zone.includes(":") ? zone : `${zone.slice(0, 3)}:${zone.slice(3)}`;
|
|
16
|
+
const parsed = /* @__PURE__ */ new Date(`${date}T${time}.${millis}${offset}`);
|
|
17
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toISOString();
|
|
18
|
+
}
|
|
19
|
+
export function normalizeTimestampFields(row, keys = ["createdAt", "updatedAt"]) {
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
if (Object.hasOwn(row, key)) row[key] = normalizeTimestamp(row[key]);
|
|
22
|
+
}
|
|
23
|
+
return row;
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xleddyl/nuxt-cms",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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)",
|