@xleddyl/nuxt-cms 0.2.0 → 0.2.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/dist/module.json +1 -1
- package/dist/module.mjs +16 -5
- package/dist/runtime/app/components/cms/Drawer.vue +5 -9
- package/dist/runtime/app/components/cms/EntryDrawer.vue +11 -3
- package/dist/runtime/app/components/cms/EntryForm.vue +0 -1
- package/dist/runtime/app/components/cms/Input.d.vue.ts +0 -1
- package/dist/runtime/app/components/cms/Input.vue +7 -4
- package/dist/runtime/app/components/cms/Input.vue.d.ts +0 -1
- package/dist/runtime/app/components/cms/LocaleSwitch.vue +1 -1
- package/dist/runtime/app/components/cms/MediaFolderPicker.d.vue.ts +0 -1
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue +1 -3
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue.d.ts +0 -1
- package/dist/runtime/app/components/cms/Modal.vue +5 -9
- package/dist/runtime/app/components/cms/PasswordInput.vue +9 -5
- package/dist/runtime/app/components/cms/RelationField.vue +7 -2
- package/dist/runtime/app/components/cms/SelectMenu.d.vue.ts +0 -1
- package/dist/runtime/app/components/cms/SelectMenu.vue +1 -6
- package/dist/runtime/app/components/cms/SelectMenu.vue.d.ts +0 -1
- package/dist/runtime/app/components/cms/SettingsEntry.vue +1 -8
- package/dist/runtime/app/components/cms/SettingsModal.vue +5 -10
- package/dist/runtime/app/components/cms/SettingsUsers.vue +8 -4
- package/dist/runtime/app/components/cms/Table.d.vue.ts +0 -7
- package/dist/runtime/app/components/cms/Table.vue +7 -49
- package/dist/runtime/app/components/cms/Table.vue.d.ts +0 -7
- package/dist/runtime/app/components/cms/Textarea.d.vue.ts +0 -1
- package/dist/runtime/app/components/cms/Textarea.vue +1 -2
- package/dist/runtime/app/components/cms/Textarea.vue.d.ts +0 -1
- package/dist/runtime/app/composables/cms-entry-disabled.js +1 -1
- package/dist/runtime/app/composables/cms-media-keys.d.ts +1 -0
- package/dist/runtime/app/composables/cms-media-keys.js +5 -0
- package/dist/runtime/app/composables/cms-media-library.js +3 -0
- package/dist/runtime/app/composables/cms-media-uploader.d.ts +0 -1
- package/dist/runtime/app/composables/cms-media-uploader.js +1 -1
- package/dist/runtime/app/composables/cms-overlay.d.ts +4 -0
- package/dist/runtime/app/composables/cms-overlay.js +23 -0
- package/dist/runtime/app/composables/cms-settings.d.ts +0 -1
- package/dist/runtime/app/composables/cms-settings.js +1 -1
- package/dist/runtime/app/pages/admin-entry.vue +1 -0
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/generate.js +1 -1
- package/dist/runtime/server/api/collection.get.js +1 -1
- package/dist/runtime/server/api/media.delete.js +2 -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/api/users.post.js +13 -9
- package/dist/runtime/server/routes/auth/login.post.js +2 -2
- package/dist/runtime/server/utils/graphql-depth.js +12 -5
- package/dist/runtime/server/utils/graphql.js +2 -2
- package/dist/runtime/server/utils/media-folders.js +10 -2
- package/dist/runtime/server/utils/media.d.ts +1 -0
- package/dist/runtime/server/utils/media.js +4 -1
- package/dist/runtime/server/utils/migrate.js +1 -1
- package/dist/runtime/server/utils/require-admin.d.ts +0 -2
- package/dist/runtime/server/utils/require-admin.js +0 -1
- package/dist/runtime/shared/layout.d.ts +0 -1
- package/dist/runtime/shared/layout.js +0 -3
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -380,7 +380,7 @@ function validateConfig(config, i18n) {
|
|
|
380
380
|
errors.push(`${fat}: relation target '${field.to}' must be a collection`);
|
|
381
381
|
if (!entry.table && target.table)
|
|
382
382
|
errors.push(
|
|
383
|
-
`${fat}: relation target '${field.to}' uses a custom table
|
|
383
|
+
`${fat}: relation target '${field.to}' uses a custom table: derived relations can only target derived entries`
|
|
384
384
|
);
|
|
385
385
|
}
|
|
386
386
|
if (field.cardinality && !["many-to-one", "one-to-one", "many-to-many"].includes(field.cardinality)) {
|
|
@@ -835,7 +835,12 @@ ${lines.join(
|
|
|
835
835
|
function pageTypesTs(name, entry) {
|
|
836
836
|
const auto = `${typeName(name)}Auto`;
|
|
837
837
|
const lines = pageRoutes(entry).map((route) => {
|
|
838
|
-
const keys = [
|
|
838
|
+
const keys = [
|
|
839
|
+
"id",
|
|
840
|
+
"path",
|
|
841
|
+
"updatedAt",
|
|
842
|
+
...Object.entries(pageFields(entry, route.path)).filter(([, field]) => !isPrivateField(field)).map(([key]) => key)
|
|
843
|
+
];
|
|
839
844
|
return ` ${JSON.stringify(route.path)}: Pick<${auto}, ${quotedKeys(keys)}>`;
|
|
840
845
|
});
|
|
841
846
|
return [
|
|
@@ -1027,7 +1032,7 @@ async function loadCmsConfig(nuxt, resolver, configPathOption, i18n, logger) {
|
|
|
1027
1032
|
cmsConfig = expandMobileMedia(await jiti.import(configPath, { default: true }));
|
|
1028
1033
|
} else {
|
|
1029
1034
|
logger.warn(
|
|
1030
|
-
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry
|
|
1035
|
+
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry. Create a ${configPathOption}.ts with defineCmsConfig().`
|
|
1031
1036
|
);
|
|
1032
1037
|
nuxt.options.alias["#cms-config"] = resolver.resolve("./runtime/shared/empty-config");
|
|
1033
1038
|
}
|
|
@@ -1288,7 +1293,13 @@ const module$1 = defineNuxtModule({
|
|
|
1288
1293
|
` dialect: '${dialect}',`,
|
|
1289
1294
|
` schema: '${toPosix(schemaTemplate.dst)}',`,
|
|
1290
1295
|
` out: '${toPosix(relativeMigrationsDir)}',`,
|
|
1291
|
-
driver === "postgres" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ??
|
|
1296
|
+
driver === "postgres" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ?? ${JSON.stringify(
|
|
1297
|
+
databaseUrl
|
|
1298
|
+
)} },` : driver === "libsql" ? ` dbCredentials: { url: process.env.NUXT_CMS_DATABASE_URL ?? ${JSON.stringify(
|
|
1299
|
+
databaseUrl || `file:${toPosix(resolvedDbPath)}`
|
|
1300
|
+
)}, authToken: (process.env.NUXT_CMS_DATABASE_AUTH_TOKEN ?? ${JSON.stringify(
|
|
1301
|
+
databaseAuthToken
|
|
1302
|
+
)}) || undefined },` : driver === "d1" ? `` : ` dbCredentials: { url: ${JSON.stringify(toPosix(resolvedDbPath))} },`,
|
|
1292
1303
|
`}`,
|
|
1293
1304
|
``
|
|
1294
1305
|
].join("\n")
|
|
@@ -1305,7 +1316,7 @@ const module$1 = defineNuxtModule({
|
|
|
1305
1316
|
const migrations = await collectMigrations(migrationsDir);
|
|
1306
1317
|
if (!migrations && !nuxt.options.dev && Object.keys(cmsConfig).length) {
|
|
1307
1318
|
logger.warn(
|
|
1308
|
-
`[nuxt-cms] No migrations found at ${migrationsDir}. The CMS tables will not be created
|
|
1319
|
+
`[nuxt-cms] No migrations found at ${migrationsDir}. The CMS tables will not be created. Run the dev server once to generate them and commit ${toPosix(
|
|
1309
1320
|
relativeMigrationsDir
|
|
1310
1321
|
)}.`
|
|
1311
1322
|
);
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
<script setup>
|
|
37
37
|
import { onBeforeUnmount, watch } from "#imports";
|
|
38
|
+
import { useCmsOverlay } from "../../composables/cms-overlay";
|
|
38
39
|
defineProps({
|
|
39
40
|
title: { type: String, required: false }
|
|
40
41
|
});
|
|
@@ -43,23 +44,18 @@ const emit = defineEmits(["close"]);
|
|
|
43
44
|
function close() {
|
|
44
45
|
emit("close");
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
if (event.key === "Escape") close();
|
|
48
|
-
}
|
|
47
|
+
const overlay = useCmsOverlay(close);
|
|
49
48
|
watch(open, (value) => {
|
|
50
49
|
if (import.meta.server) return;
|
|
51
50
|
if (value) {
|
|
52
|
-
|
|
51
|
+
overlay.activate();
|
|
53
52
|
document.body.style.overflow = "hidden";
|
|
54
53
|
} else {
|
|
55
|
-
|
|
54
|
+
overlay.deactivate();
|
|
56
55
|
document.body.style.overflow = "";
|
|
57
56
|
}
|
|
58
57
|
});
|
|
59
58
|
onBeforeUnmount(() => {
|
|
60
|
-
if (import.meta.client)
|
|
61
|
-
document.removeEventListener("keydown", onKeydown);
|
|
62
|
-
document.body.style.overflow = "";
|
|
63
|
-
}
|
|
59
|
+
if (import.meta.client) document.body.style.overflow = "";
|
|
64
60
|
});
|
|
65
61
|
</script>
|
|
@@ -121,16 +121,23 @@ function revertStatus() {
|
|
|
121
121
|
if (!drafts.value) return;
|
|
122
122
|
formState.value.status = JSON.parse(snapshot.value).status ?? null;
|
|
123
123
|
}
|
|
124
|
+
let loadRequest = 0;
|
|
124
125
|
async function load() {
|
|
125
|
-
|
|
126
|
+
const request = ++loadRequest;
|
|
127
|
+
const id = props.entryId;
|
|
128
|
+
if (id === null) {
|
|
129
|
+
loading.value = false;
|
|
126
130
|
setState(emptyState());
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
133
|
+
const isStale = () => request !== loadRequest || props.entryId !== id || !open.value;
|
|
129
134
|
loading.value = true;
|
|
130
135
|
try {
|
|
131
|
-
const entry = await cmsApi(`${endpoint.value}/${
|
|
136
|
+
const entry = await cmsApi(`${endpoint.value}/${id}`);
|
|
137
|
+
if (isStale()) return;
|
|
132
138
|
setState(pickFields(entry));
|
|
133
139
|
} catch (error) {
|
|
140
|
+
if (isStale()) return;
|
|
134
141
|
toast.add({
|
|
135
142
|
title: "Could not load entry",
|
|
136
143
|
description: errorMessage(error),
|
|
@@ -138,7 +145,7 @@ async function load() {
|
|
|
138
145
|
});
|
|
139
146
|
open.value = false;
|
|
140
147
|
} finally {
|
|
141
|
-
loading.value = false;
|
|
148
|
+
if (request === loadRequest) loading.value = false;
|
|
142
149
|
}
|
|
143
150
|
}
|
|
144
151
|
watch(
|
|
@@ -165,6 +172,7 @@ async function save() {
|
|
|
165
172
|
emit("saved");
|
|
166
173
|
open.value = false;
|
|
167
174
|
} catch (error) {
|
|
175
|
+
revertStatus();
|
|
168
176
|
toast.add({
|
|
169
177
|
title: "Save failed",
|
|
170
178
|
description: errorMessage(error),
|
|
@@ -125,7 +125,6 @@ watch(tabList, (tabs) => {
|
|
|
125
125
|
if (!tabs.some((tab) => tab.id === activeTab.value)) activeTab.value = tabs[0]?.id ?? "";
|
|
126
126
|
});
|
|
127
127
|
const settings = useCmsSettingsState();
|
|
128
|
-
const fields = computed(() => props.fields);
|
|
129
128
|
const layout = computed(
|
|
130
129
|
() => props.entryName ? entryFormLayout(props.entryName, settings.value, props.fields) : resolveFormLayout(props.fields, void 0)
|
|
131
130
|
);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="cms-input-wrap"
|
|
4
|
+
:class="[$attrs.class, { 'has-lead': icon }]"
|
|
5
|
+
:style="$attrs.style"
|
|
6
|
+
>
|
|
3
7
|
<span v-if="icon" class="cms-input-lead">
|
|
4
8
|
<CmsIcon :name="icon" class="size-4" />
|
|
5
9
|
</span>
|
|
@@ -7,7 +11,7 @@
|
|
|
7
11
|
v-bind="inputAttrs"
|
|
8
12
|
:type="type ?? 'text'"
|
|
9
13
|
:value="model ?? ''"
|
|
10
|
-
:class="['cms-field', `cms-field-${size ?? 'md'}
|
|
14
|
+
:class="['cms-field', `cms-field-${size ?? 'md'}`]"
|
|
11
15
|
@input="onInput"
|
|
12
16
|
/>
|
|
13
17
|
<span v-if="$slots.trailing" class="cms-input-trail">
|
|
@@ -22,8 +26,7 @@ defineOptions({ inheritAttrs: false });
|
|
|
22
26
|
defineProps({
|
|
23
27
|
type: { type: String, required: false },
|
|
24
28
|
size: { type: String, required: false },
|
|
25
|
-
icon: { type: String, required: false }
|
|
26
|
-
color: { type: String, required: false }
|
|
29
|
+
icon: { type: String, required: false }
|
|
27
30
|
});
|
|
28
31
|
const [model, modifiers] = defineModel({ type: [String, Number, null] });
|
|
29
32
|
const attrs = useAttrs();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
type="button"
|
|
7
7
|
role="tab"
|
|
8
8
|
:aria-selected="model === locale"
|
|
9
|
-
:title="filled(locale) ? locale : `${locale}
|
|
9
|
+
:title="filled(locale) ? locale : `${locale}: empty`"
|
|
10
10
|
class="cms-locale-option"
|
|
11
11
|
:class="{ 'is-active': model === locale, 'is-empty': !filled(locale) }"
|
|
12
12
|
@click="model = locale"
|
|
@@ -50,8 +50,7 @@ import { computed, ref } from "#imports";
|
|
|
50
50
|
import { isWithinMediaFolder, mediaFolderDepth, mediaFolderName } from "#nuxt-cms";
|
|
51
51
|
const props = defineProps({
|
|
52
52
|
folders: { type: Array, required: true },
|
|
53
|
-
exclude: { type: [String, null], required: false }
|
|
54
|
-
disabled: { type: Array, required: false }
|
|
53
|
+
exclude: { type: [String, null], required: false }
|
|
55
54
|
});
|
|
56
55
|
const model = defineModel({ type: [String, null], ...{ default: null } });
|
|
57
56
|
const query = ref("");
|
|
@@ -60,7 +59,6 @@ const visible = computed(() => {
|
|
|
60
59
|
return term ? props.folders.filter((folder) => folder.includes(term)) : props.folders;
|
|
61
60
|
});
|
|
62
61
|
function isDisabled(folder) {
|
|
63
|
-
if (props.disabled?.includes(folder)) return true;
|
|
64
62
|
return !!props.exclude && !!folder && isWithinMediaFolder(folder, props.exclude);
|
|
65
63
|
}
|
|
66
64
|
</script>
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
</template>
|
|
28
28
|
|
|
29
29
|
<script setup>
|
|
30
|
-
import {
|
|
30
|
+
import { watch } from "#imports";
|
|
31
|
+
import { useCmsOverlay } from "../../composables/cms-overlay";
|
|
31
32
|
defineProps({
|
|
32
33
|
title: { type: String, required: false },
|
|
33
34
|
size: { type: String, required: false }
|
|
@@ -37,19 +38,14 @@ const emit = defineEmits(["after:leave"]);
|
|
|
37
38
|
function close() {
|
|
38
39
|
open.value = false;
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
if (event.key === "Escape") close();
|
|
42
|
-
}
|
|
41
|
+
const overlay = useCmsOverlay(close);
|
|
43
42
|
watch(open, (value, previous) => {
|
|
44
43
|
if (import.meta.server) return;
|
|
45
44
|
if (value) {
|
|
46
|
-
|
|
45
|
+
overlay.activate();
|
|
47
46
|
} else {
|
|
48
|
-
|
|
47
|
+
overlay.deactivate();
|
|
49
48
|
if (previous) emit("after:leave");
|
|
50
49
|
}
|
|
51
50
|
});
|
|
52
|
-
onBeforeUnmount(() => {
|
|
53
|
-
if (import.meta.client) document.removeEventListener("keydown", onKeydown);
|
|
54
|
-
});
|
|
55
51
|
</script>
|
|
@@ -67,11 +67,15 @@ function regenerate() {
|
|
|
67
67
|
}
|
|
68
68
|
async function copyPassword() {
|
|
69
69
|
if (!model.value) return;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
try {
|
|
71
|
+
await navigator.clipboard.writeText(model.value);
|
|
72
|
+
copied.value = true;
|
|
73
|
+
clearTimeout(copiedTimer);
|
|
74
|
+
copiedTimer = setTimeout(() => {
|
|
75
|
+
copied.value = false;
|
|
76
|
+
}, 1600);
|
|
77
|
+
} catch {
|
|
74
78
|
copied.value = false;
|
|
75
|
-
}
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
</script>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<script setup>
|
|
24
24
|
import { isTranslatableField } from "#nuxt-cms";
|
|
25
|
-
import { computed, onMounted, ref, watch } from "#imports";
|
|
25
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from "#imports";
|
|
26
26
|
import cmsConfig from "#cms-config";
|
|
27
27
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
28
28
|
import { useCmsToast } from "../../composables/cms-toast";
|
|
@@ -40,22 +40,26 @@ const fetched = ref([]);
|
|
|
40
40
|
const selectedRows = ref(/* @__PURE__ */ new Map());
|
|
41
41
|
const loading = ref(true);
|
|
42
42
|
const searchTerm = ref("");
|
|
43
|
+
let fetchRequest = 0;
|
|
43
44
|
async function fetchOptions() {
|
|
45
|
+
const request = ++fetchRequest;
|
|
44
46
|
loading.value = true;
|
|
45
47
|
try {
|
|
46
48
|
const term = searchTerm.value.trim();
|
|
47
49
|
const page = await $fetch(endpoint, {
|
|
48
50
|
query: { light: "true", limit: 25, ...term ? { search: term } : {} }
|
|
49
51
|
});
|
|
52
|
+
if (request !== fetchRequest) return;
|
|
50
53
|
fetched.value = page.items;
|
|
51
54
|
} catch (err) {
|
|
55
|
+
if (request !== fetchRequest) return;
|
|
52
56
|
toast.add({
|
|
53
57
|
title: "Loading failed",
|
|
54
58
|
description: errorMessage(err),
|
|
55
59
|
color: "error"
|
|
56
60
|
});
|
|
57
61
|
} finally {
|
|
58
|
-
loading.value = false;
|
|
62
|
+
if (request === fetchRequest) loading.value = false;
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
let searchTimer;
|
|
@@ -63,6 +67,7 @@ watch(searchTerm, () => {
|
|
|
63
67
|
clearTimeout(searchTimer);
|
|
64
68
|
searchTimer = setTimeout(() => void fetchOptions(), 300);
|
|
65
69
|
});
|
|
70
|
+
onBeforeUnmount(() => clearTimeout(searchTimer));
|
|
66
71
|
const selectedIds = computed(() => {
|
|
67
72
|
if (props.multiple) return model.value ?? [];
|
|
68
73
|
return model.value ? [model.value] : [];
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="root" class="relative w-full">
|
|
3
|
-
<div
|
|
4
|
-
class="cms-field flex flex-wrap items-center gap-1"
|
|
5
|
-
:class="`cms-field-${size ?? 'md'}`"
|
|
6
|
-
@click="focusInput"
|
|
7
|
-
>
|
|
3
|
+
<div class="cms-field cms-field-md flex flex-wrap items-center gap-1" @click="focusInput">
|
|
8
4
|
<template v-if="multiple">
|
|
9
5
|
<span v-for="value in modelArray" :key="value" class="cms-tag">
|
|
10
6
|
{{ labelFor(value) }}
|
|
@@ -70,7 +66,6 @@ const props = defineProps({
|
|
|
70
66
|
multiple: { type: Boolean, required: false },
|
|
71
67
|
ignoreFilter: { type: Boolean, required: false },
|
|
72
68
|
loading: { type: Boolean, required: false },
|
|
73
|
-
size: { type: String, required: false },
|
|
74
69
|
placeholder: { type: String, required: false }
|
|
75
70
|
});
|
|
76
71
|
const model = defineModel({ type: [String, Array, null] });
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
</template>
|
|
56
56
|
|
|
57
57
|
<script setup>
|
|
58
|
-
import { computed, ref
|
|
58
|
+
import { computed, ref } from "#imports";
|
|
59
59
|
import cmsConfig from "#cms-config";
|
|
60
60
|
import {
|
|
61
61
|
entryFields,
|
|
@@ -89,13 +89,6 @@ const parts = computed(() => [
|
|
|
89
89
|
{ id: "layout", label: "Form layout" }
|
|
90
90
|
]);
|
|
91
91
|
const activePart = ref(parts.value[0].id);
|
|
92
|
-
watch(
|
|
93
|
-
() => props.name,
|
|
94
|
-
() => {
|
|
95
|
-
activePart.value = parts.value[0].id;
|
|
96
|
-
status.value = "idle";
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
92
|
const columns = computed(() => entryListColumns(props.name, settings.value));
|
|
100
93
|
const layout = computed(() => entryFormLayout(props.name, settings.value, fields.value));
|
|
101
94
|
const isCustom = computed(() => {
|
|
@@ -132,10 +132,11 @@
|
|
|
132
132
|
</template>
|
|
133
133
|
|
|
134
134
|
<script setup>
|
|
135
|
-
import { computed,
|
|
135
|
+
import { computed, ref, watch } from "#imports";
|
|
136
136
|
import cmsConfig from "#cms-config";
|
|
137
137
|
import { CMS_THEMES, setCmsTheme } from "../../composables/cms-theme";
|
|
138
138
|
import { useCmsAccount } from "../../composables/cms-account";
|
|
139
|
+
import { useCmsOverlay } from "../../composables/cms-overlay";
|
|
139
140
|
const { isSuperAdmin } = useCmsAccount();
|
|
140
141
|
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
141
142
|
const theme = defineModel("theme", { type: String, ...{ required: true } });
|
|
@@ -165,15 +166,9 @@ function previewHalves(value) {
|
|
|
165
166
|
function close() {
|
|
166
167
|
open.value = false;
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
-
if (event.key === "Escape") close();
|
|
170
|
-
}
|
|
169
|
+
const overlay = useCmsOverlay(close);
|
|
171
170
|
watch(open, (value) => {
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
else document.removeEventListener("keydown", onKeydown);
|
|
175
|
-
});
|
|
176
|
-
onBeforeUnmount(() => {
|
|
177
|
-
if (import.meta.client) document.removeEventListener("keydown", onKeydown);
|
|
171
|
+
if (value) overlay.activate();
|
|
172
|
+
else overlay.deactivate();
|
|
178
173
|
});
|
|
179
174
|
</script>
|
|
@@ -224,11 +224,15 @@ async function create() {
|
|
|
224
224
|
}
|
|
225
225
|
async function copyCredentials() {
|
|
226
226
|
if (!created.value) return;
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
try {
|
|
228
|
+
await navigator.clipboard.writeText(
|
|
229
|
+
`Email: ${created.value.email}
|
|
229
230
|
Password: ${created.value.password}`
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
);
|
|
232
|
+
copied.value = true;
|
|
233
|
+
} catch {
|
|
234
|
+
copied.value = false;
|
|
235
|
+
}
|
|
232
236
|
}
|
|
233
237
|
const resetting = ref(null);
|
|
234
238
|
const reset = ref({ password: "" });
|
|
@@ -4,7 +4,6 @@ interface Column {
|
|
|
4
4
|
accessorKey?: string;
|
|
5
5
|
accessorFn?: (row: Row) => unknown;
|
|
6
6
|
header?: string;
|
|
7
|
-
reorderable?: boolean;
|
|
8
7
|
sortable?: boolean;
|
|
9
8
|
}
|
|
10
9
|
interface TableSort {
|
|
@@ -15,11 +14,9 @@ type __VLS_Props = {
|
|
|
15
14
|
data: Row[];
|
|
16
15
|
columns: Column[];
|
|
17
16
|
selectable?: boolean;
|
|
18
|
-
rowKey?: string;
|
|
19
17
|
};
|
|
20
18
|
type __VLS_ModelProps = {
|
|
21
19
|
'selected'?: string[];
|
|
22
|
-
'columnVisibility'?: Record<string, boolean>;
|
|
23
20
|
'sort'?: TableSort | null;
|
|
24
21
|
};
|
|
25
22
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
@@ -35,17 +32,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
35
32
|
select: (event: Event, row: {
|
|
36
33
|
original: Row;
|
|
37
34
|
}) => any;
|
|
38
|
-
reorder: (from: string, to: string) => any;
|
|
39
35
|
"update:selected": (value: string[]) => any;
|
|
40
|
-
"update:columnVisibility": (value: Record<string, boolean>) => any;
|
|
41
36
|
"update:sort": (value: TableSort | null) => any;
|
|
42
37
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
43
38
|
onSelect?: ((event: Event, row: {
|
|
44
39
|
original: Row;
|
|
45
40
|
}) => any) | undefined;
|
|
46
|
-
onReorder?: ((from: string, to: string) => any) | undefined;
|
|
47
41
|
"onUpdate:selected"?: ((value: string[]) => any) | undefined;
|
|
48
|
-
"onUpdate:columnVisibility"?: ((value: Record<string, boolean>) => any) | undefined;
|
|
49
42
|
"onUpdate:sort"?: ((value: TableSort | null) => any) | undefined;
|
|
50
43
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
51
44
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -13,22 +13,14 @@
|
|
|
13
13
|
/>
|
|
14
14
|
</th>
|
|
15
15
|
<th
|
|
16
|
-
v-for="column in
|
|
16
|
+
v-for="column in columns"
|
|
17
17
|
:key="columnId(column)"
|
|
18
18
|
:class="{
|
|
19
|
-
'is-dragging': draggingId === columnId(column),
|
|
20
|
-
'is-drop-target': dropTargetId === columnId(column),
|
|
21
19
|
'is-sortable': column.sortable,
|
|
22
20
|
'is-sorted': sort?.key === columnId(column)
|
|
23
21
|
}"
|
|
24
|
-
:draggable="column.reorderable || void 0"
|
|
25
22
|
:aria-sort="ariaSort(column)"
|
|
26
23
|
@click="toggleSort(column)"
|
|
27
|
-
@dragstart="onDragStart($event, column)"
|
|
28
|
-
@dragover="onDragOver($event, column)"
|
|
29
|
-
@dragleave="onDragLeave(column)"
|
|
30
|
-
@drop="onDrop($event, column)"
|
|
31
|
-
@dragend="resetDrag"
|
|
32
24
|
>
|
|
33
25
|
<span class="cms-th-label">
|
|
34
26
|
{{ column.header }}
|
|
@@ -44,7 +36,7 @@
|
|
|
44
36
|
<tbody>
|
|
45
37
|
<tr
|
|
46
38
|
v-for="(row, index) in data"
|
|
47
|
-
:key="
|
|
39
|
+
:key="keyOf(row)"
|
|
48
40
|
class="__clickable"
|
|
49
41
|
:class="{ 'is-selected': isSelected(row) }"
|
|
50
42
|
:style="{ '--cms-row-delay': `${Math.min(index, 14) * 24}ms` }"
|
|
@@ -57,7 +49,7 @@
|
|
|
57
49
|
@toggle="toggleRow(row)"
|
|
58
50
|
/>
|
|
59
51
|
</td>
|
|
60
|
-
<td v-for="column in
|
|
52
|
+
<td v-for="column in columns" :key="columnId(column)">
|
|
61
53
|
<slot :name="`${columnId(column)}-cell`" :row="{ original: row }">
|
|
62
54
|
{{ cellValue(column, row) }}
|
|
63
55
|
</slot>
|
|
@@ -70,16 +62,15 @@
|
|
|
70
62
|
</template>
|
|
71
63
|
|
|
72
64
|
<script setup>
|
|
73
|
-
import { computed
|
|
65
|
+
import { computed } from "#imports";
|
|
74
66
|
const props = defineProps({
|
|
75
67
|
data: { type: Array, required: true },
|
|
76
68
|
columns: { type: Array, required: true },
|
|
77
|
-
selectable: { type: Boolean, required: false }
|
|
78
|
-
rowKey: { type: String, required: false }
|
|
69
|
+
selectable: { type: Boolean, required: false }
|
|
79
70
|
});
|
|
80
71
|
const selected = defineModel("selected", { type: Array, ...{ default: () => [] } });
|
|
81
72
|
function keyOf(row) {
|
|
82
|
-
return String(row
|
|
73
|
+
return String(row.id);
|
|
83
74
|
}
|
|
84
75
|
function isSelected(row) {
|
|
85
76
|
return selected.value.includes(keyOf(row));
|
|
@@ -95,9 +86,8 @@ const someSelected = computed(() => !allSelected.value && selected.value.length
|
|
|
95
86
|
function toggleAll() {
|
|
96
87
|
selected.value = allSelected.value ? [] : props.data.map(keyOf);
|
|
97
88
|
}
|
|
98
|
-
const visibility = defineModel("columnVisibility", { type: Object, ...{ default: () => ({}) } });
|
|
99
89
|
const sort = defineModel("sort", { type: [Object, null], ...{ default: null } });
|
|
100
|
-
const emit = defineEmits(["select"
|
|
90
|
+
const emit = defineEmits(["select"]);
|
|
101
91
|
function columnId(column) {
|
|
102
92
|
return column.id ?? column.accessorKey ?? "";
|
|
103
93
|
}
|
|
@@ -117,38 +107,6 @@ function ariaSort(column) {
|
|
|
117
107
|
if (sort.value?.key !== columnId(column)) return "none";
|
|
118
108
|
return sort.value.order === "asc" ? "ascending" : "descending";
|
|
119
109
|
}
|
|
120
|
-
const draggingId = ref(null);
|
|
121
|
-
const dropTargetId = ref(null);
|
|
122
|
-
function resetDrag() {
|
|
123
|
-
draggingId.value = null;
|
|
124
|
-
dropTargetId.value = null;
|
|
125
|
-
}
|
|
126
|
-
function onDragStart(event, column) {
|
|
127
|
-
if (!column.reorderable) return;
|
|
128
|
-
draggingId.value = columnId(column);
|
|
129
|
-
event.dataTransfer?.setData("text/plain", columnId(column));
|
|
130
|
-
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
131
|
-
}
|
|
132
|
-
function onDragOver(event, column) {
|
|
133
|
-
if (!draggingId.value || !column.reorderable) return;
|
|
134
|
-
event.preventDefault();
|
|
135
|
-
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
136
|
-
dropTargetId.value = columnId(column);
|
|
137
|
-
}
|
|
138
|
-
function onDragLeave(column) {
|
|
139
|
-
if (dropTargetId.value === columnId(column)) dropTargetId.value = null;
|
|
140
|
-
}
|
|
141
|
-
function onDrop(event, column) {
|
|
142
|
-
if (!draggingId.value || !column.reorderable) return;
|
|
143
|
-
event.preventDefault();
|
|
144
|
-
const from = draggingId.value;
|
|
145
|
-
const to = columnId(column);
|
|
146
|
-
resetDrag();
|
|
147
|
-
if (from !== to) emit("reorder", from, to);
|
|
148
|
-
}
|
|
149
|
-
const visibleColumns = computed(
|
|
150
|
-
() => props.columns.filter((column) => visibility.value[columnId(column)] !== false)
|
|
151
|
-
);
|
|
152
110
|
function cellValue(column, row) {
|
|
153
111
|
if (column.accessorFn) return column.accessorFn(row);
|
|
154
112
|
if (column.accessorKey) return row[column.accessorKey];
|
|
@@ -4,7 +4,6 @@ interface Column {
|
|
|
4
4
|
accessorKey?: string;
|
|
5
5
|
accessorFn?: (row: Row) => unknown;
|
|
6
6
|
header?: string;
|
|
7
|
-
reorderable?: boolean;
|
|
8
7
|
sortable?: boolean;
|
|
9
8
|
}
|
|
10
9
|
interface TableSort {
|
|
@@ -15,11 +14,9 @@ type __VLS_Props = {
|
|
|
15
14
|
data: Row[];
|
|
16
15
|
columns: Column[];
|
|
17
16
|
selectable?: boolean;
|
|
18
|
-
rowKey?: string;
|
|
19
17
|
};
|
|
20
18
|
type __VLS_ModelProps = {
|
|
21
19
|
'selected'?: string[];
|
|
22
|
-
'columnVisibility'?: Record<string, boolean>;
|
|
23
20
|
'sort'?: TableSort | null;
|
|
24
21
|
};
|
|
25
22
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
@@ -35,17 +32,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
35
32
|
select: (event: Event, row: {
|
|
36
33
|
original: Row;
|
|
37
34
|
}) => any;
|
|
38
|
-
reorder: (from: string, to: string) => any;
|
|
39
35
|
"update:selected": (value: string[]) => any;
|
|
40
|
-
"update:columnVisibility": (value: Record<string, boolean>) => any;
|
|
41
36
|
"update:sort": (value: TableSort | null) => any;
|
|
42
37
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
43
38
|
onSelect?: ((event: Event, row: {
|
|
44
39
|
original: Row;
|
|
45
40
|
}) => any) | undefined;
|
|
46
|
-
onReorder?: ((from: string, to: string) => any) | undefined;
|
|
47
41
|
"onUpdate:selected"?: ((value: string[]) => any) | undefined;
|
|
48
|
-
"onUpdate:columnVisibility"?: ((value: Record<string, boolean>) => any) | undefined;
|
|
49
42
|
"onUpdate:sort"?: ((value: TableSort | null) => any) | undefined;
|
|
50
43
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
51
44
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|