@xleddyl/nuxt-cms 0.4.2 → 0.4.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.
Files changed (59) hide show
  1. package/README.md +74 -96
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +84 -16
  4. package/dist/runtime/app/components/cms/BlocksField.vue +30 -5
  5. package/dist/runtime/app/components/cms/DropdownMenu.vue +1 -5
  6. package/dist/runtime/app/components/cms/EntryDrawer.d.vue.ts +3 -0
  7. package/dist/runtime/app/components/cms/EntryDrawer.vue +32 -13
  8. package/dist/runtime/app/components/cms/EntryDrawer.vue.d.ts +3 -0
  9. package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +1 -0
  10. package/dist/runtime/app/components/cms/EntryForm.vue +20 -3
  11. package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +1 -0
  12. package/dist/runtime/app/components/cms/FieldInput.vue +2 -2
  13. package/dist/runtime/app/components/cms/Form.d.vue.ts +8 -6
  14. package/dist/runtime/app/components/cms/Form.vue +26 -5
  15. package/dist/runtime/app/components/cms/Form.vue.d.ts +8 -6
  16. package/dist/runtime/app/components/cms/FormField.vue +6 -1
  17. package/dist/runtime/app/components/cms/ImportModal.d.vue.ts +18 -0
  18. package/dist/runtime/app/components/cms/ImportModal.vue +252 -0
  19. package/dist/runtime/app/components/cms/ImportModal.vue.d.ts +18 -0
  20. package/dist/runtime/app/components/cms/ListFilters.d.vue.ts +18 -0
  21. package/dist/runtime/app/components/cms/ListFilters.vue +151 -0
  22. package/dist/runtime/app/components/cms/ListFilters.vue.d.ts +18 -0
  23. package/dist/runtime/app/components/cms/RelationField.vue +3 -1
  24. package/dist/runtime/app/components/cms/VisualEditor.vue +81 -12
  25. package/dist/runtime/app/pages/admin-collection.vue +158 -7
  26. package/dist/runtime/app/pages/admin-content.vue +18 -7
  27. package/dist/runtime/app/pages/admin-entry.vue +1 -0
  28. package/dist/runtime/app/pages/admin-preview.vue +6 -3
  29. package/dist/runtime/app/preview/PreviewOverlay.vue +4 -0
  30. package/dist/runtime/app/utils/ui.d.ts +2 -1
  31. package/dist/runtime/app/utils/ui.js +1 -0
  32. package/dist/runtime/assets/main.css +1 -1
  33. package/dist/runtime/server/api/collection-export.get.d.ts +2 -0
  34. package/dist/runtime/server/api/collection-export.get.js +67 -0
  35. package/dist/runtime/server/api/collection-import.post.d.ts +2 -0
  36. package/dist/runtime/server/api/collection-import.post.js +85 -0
  37. package/dist/runtime/server/api/collection.get.js +7 -15
  38. package/dist/runtime/server/utils/import.d.ts +31 -0
  39. package/dist/runtime/server/utils/import.js +202 -0
  40. package/dist/runtime/server/utils/list-query.d.ts +15 -0
  41. package/dist/runtime/server/utils/list-query.js +54 -0
  42. package/dist/runtime/server/utils/media-check.d.ts +2 -0
  43. package/dist/runtime/server/utils/media-check.js +1 -1
  44. package/dist/runtime/server/utils/media-references.js +4 -1
  45. package/dist/runtime/server/utils/registry.js +1 -1
  46. package/dist/runtime/server/utils/search.js +23 -4
  47. package/dist/runtime/shared/duplicate.d.ts +10 -0
  48. package/dist/runtime/shared/duplicate.js +54 -0
  49. package/dist/runtime/shared/index.d.ts +49 -2
  50. package/dist/runtime/shared/index.js +33 -0
  51. package/dist/runtime/shared/layout.d.ts +3 -0
  52. package/dist/runtime/shared/layout.js +35 -2
  53. package/dist/runtime/shared/list-filters.d.ts +20 -0
  54. package/dist/runtime/shared/list-filters.js +83 -0
  55. package/dist/runtime/shared/transfer.d.ts +70 -0
  56. package/dist/runtime/shared/transfer.js +256 -0
  57. package/dist/runtime/shared/validation.d.ts +15 -2
  58. package/dist/runtime/shared/validation.js +102 -9
  59. package/package.json +1 -1
@@ -3,6 +3,7 @@ type __VLS_Props = {
3
3
  collection: string;
4
4
  config: CmsEntry;
5
5
  entryId: string | null;
6
+ initial?: Record<string, unknown> | null;
6
7
  };
7
8
  type __VLS_ModelProps = {
8
9
  'open'?: boolean;
@@ -11,10 +12,12 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
11
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
13
  saved: () => any;
13
14
  deleted: () => any;
15
+ duplicate: (values: Record<string, unknown>) => any;
14
16
  "update:open": (value: boolean) => any;
15
17
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
16
18
  onSaved?: (() => any) | undefined;
17
19
  onDeleted?: (() => any) | undefined;
20
+ onDuplicate?: ((values: Record<string, unknown>) => any) | undefined;
18
21
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
19
22
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
23
  declare const _default: typeof __VLS_export;
@@ -9,6 +9,7 @@ type __VLS_Props = {
9
9
  formId?: string;
10
10
  footer?: boolean;
11
11
  focus?: CmsSearchFocus | null;
12
+ path?: string;
12
13
  };
13
14
  type __VLS_ModelProps = {
14
15
  modelValue: Record<string, unknown>;
@@ -4,6 +4,7 @@
4
4
  ref="form"
5
5
  :state="state"
6
6
  :schema="schema"
7
+ :warnings="warnings"
7
8
  @submit="emit('submit')"
8
9
  @error="onError"
9
10
  >
@@ -112,7 +113,8 @@ import {
112
113
  slugSourceValue
113
114
  } from "#nuxt-cms";
114
115
  import { computed, nextTick, onMounted, ref, watch } from "#imports";
115
- import { buildEntrySchema } from "../../../shared/validation";
116
+ import cmsConfig from "#cms-config";
117
+ import { buildEntrySchema, entryValidationWarnings } from "../../../shared/validation";
116
118
  import { useCmsFieldFocus } from "../../composables/cms-field-focus";
117
119
  import { useCmsRuntime } from "../../composables/cms-runtime";
118
120
  import { entryFormLayout, useCmsSettingsState } from "../../composables/cms-settings";
@@ -125,7 +127,8 @@ const props = defineProps({
125
127
  loading: { type: Boolean, required: false },
126
128
  formId: { type: String, required: false },
127
129
  footer: { type: Boolean, required: false, default: true },
128
- focus: { type: [Object, null], required: false }
130
+ focus: { type: [Object, null], required: false },
131
+ path: { type: String, required: false }
129
132
  });
130
133
  const state = defineModel({ type: Object, ...{ required: true } });
131
134
  const emit = defineEmits(["submit", "error"]);
@@ -193,9 +196,23 @@ function localeFor(key) {
193
196
  function setLocale(key, locale) {
194
197
  activeLocale.value = { ...activeLocale.value, [key]: locale };
195
198
  }
199
+ const validate = computed(
200
+ () => props.entryName ? cmsConfig[props.entryName]?.validate : void 0
201
+ );
196
202
  const schema = computed(
197
- () => buildEntrySchema({ fields: props.fields, drafts: props.drafts }, i18n)
203
+ () => buildEntrySchema(
204
+ { fields: props.fields, drafts: props.drafts, validate: validate.value, path: props.path },
205
+ i18n
206
+ )
198
207
  );
208
+ function warnings(values) {
209
+ return entryValidationWarnings(
210
+ { fields: props.fields, validate: validate.value },
211
+ values,
212
+ i18n,
213
+ props.path
214
+ );
215
+ }
199
216
  const published = computed({
200
217
  get: () => state.value.status === "published",
201
218
  set: (value) => {
@@ -9,6 +9,7 @@ type __VLS_Props = {
9
9
  formId?: string;
10
10
  footer?: boolean;
11
11
  focus?: CmsSearchFocus | null;
12
+ path?: string;
12
13
  };
13
14
  type __VLS_ModelProps = {
14
15
  modelValue: Record<string, unknown>;
@@ -64,7 +64,7 @@
64
64
  </template>
65
65
 
66
66
  <script setup>
67
- import { isTranslatableField } from "#nuxt-cms";
67
+ import { isTranslatableField, selectOptions } from "#nuxt-cms";
68
68
  import { computed } from "#imports";
69
69
  const props = defineProps({
70
70
  field: { type: Object, required: true },
@@ -76,7 +76,7 @@ const props = defineProps({
76
76
  const model = defineModel({ type: null, ...{ required: true } });
77
77
  const selectItems = computed(() => [
78
78
  ...props.field.required || props.field.multiple ? [] : [{ label: "\u2014", value: null }],
79
- ...(props.field.options ?? []).map((option) => ({ label: option, value: option }))
79
+ ...selectOptions(props.field).map(({ value, label }) => ({ label, value }))
80
80
  ]);
81
81
  function proxy(fromModel, toModel = (v) => v) {
82
82
  return computed({
@@ -1,10 +1,11 @@
1
+ interface FormIssue {
2
+ path: readonly PropertyKey[];
3
+ message: string;
4
+ }
1
5
  interface ParseResult {
2
6
  success: boolean;
3
7
  error?: {
4
- issues: readonly {
5
- path: readonly PropertyKey[];
6
- message: string;
7
- }[];
8
+ issues: readonly FormIssue[];
8
9
  };
9
10
  }
10
11
  type __VLS_Props = {
@@ -13,10 +14,11 @@ type __VLS_Props = {
13
14
  schema?: {
14
15
  safeParse: (value: unknown) => ParseResult;
15
16
  };
17
+ warnings?: (value: Record<string, unknown>) => readonly FormIssue[];
16
18
  };
17
- declare var __VLS_1: {};
19
+ declare var __VLS_13: {};
18
20
  type __VLS_Slots = {} & {
19
- default?: (props: typeof __VLS_1) => any;
21
+ default?: (props: typeof __VLS_13) => any;
20
22
  };
21
23
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
22
24
  error: () => any;
@@ -1,20 +1,41 @@
1
1
  <template>
2
2
  <form :id="id" class="cms-form" @submit.prevent="onSubmit">
3
+ <CmsAlert v-if="errors[ROOT_KEY]" color="error">{{ errors[ROOT_KEY] }}</CmsAlert>
4
+ <CmsAlert v-for="message in rootWarnings" :key="message" color="warning">
5
+ {{ message }}
6
+ </CmsAlert>
3
7
  <slot />
4
8
  </form>
5
9
  </template>
6
10
 
7
11
  <script setup>
8
- import { provide, reactive, watch } from "#imports";
9
- import { CMS_FORM_ERRORS } from "../../utils/ui";
12
+ import { computed, provide, reactive, watch } from "#imports";
13
+ import { CMS_FORM_ERRORS, CMS_FORM_WARNINGS } from "../../utils/ui";
14
+ const ROOT_KEY = "";
10
15
  const props = defineProps({
11
16
  id: { type: String, required: false },
12
17
  state: { type: Object, required: true },
13
- schema: { type: Object, required: false }
18
+ schema: { type: Object, required: false },
19
+ warnings: { type: Function, required: false }
14
20
  });
15
21
  const emit = defineEmits(["submit", "error"]);
16
22
  const errors = reactive({});
17
23
  provide(CMS_FORM_ERRORS, errors);
24
+ const warningList = computed(() => props.warnings?.(props.state) ?? []);
25
+ const warningsByKey = computed(() => {
26
+ const grouped = {};
27
+ for (const issue of warningList.value) {
28
+ const key = String(issue.path[0] ?? ROOT_KEY);
29
+ if (key !== ROOT_KEY) grouped[key] ??= issue.message;
30
+ }
31
+ return grouped;
32
+ });
33
+ provide(CMS_FORM_WARNINGS, warningsByKey);
34
+ const rootWarnings = computed(() => [
35
+ ...new Set(
36
+ warningList.value.filter((issue) => !issue.path.length).map((issue) => issue.message)
37
+ )
38
+ ]);
18
39
  let submitted = false;
19
40
  function validate() {
20
41
  for (const key of Object.keys(errors)) delete errors[key];
@@ -22,8 +43,8 @@ function validate() {
22
43
  const result = props.schema.safeParse(props.state);
23
44
  if (result.success) return true;
24
45
  for (const issue of result.error?.issues ?? []) {
25
- const key = String(issue.path[0] ?? "");
26
- if (key && !errors[key]) errors[key] = issue.message;
46
+ const key = String(issue.path[0] ?? ROOT_KEY);
47
+ if (!errors[key]) errors[key] = issue.message;
27
48
  }
28
49
  return false;
29
50
  }
@@ -1,10 +1,11 @@
1
+ interface FormIssue {
2
+ path: readonly PropertyKey[];
3
+ message: string;
4
+ }
1
5
  interface ParseResult {
2
6
  success: boolean;
3
7
  error?: {
4
- issues: readonly {
5
- path: readonly PropertyKey[];
6
- message: string;
7
- }[];
8
+ issues: readonly FormIssue[];
8
9
  };
9
10
  }
10
11
  type __VLS_Props = {
@@ -13,10 +14,11 @@ type __VLS_Props = {
13
14
  schema?: {
14
15
  safeParse: (value: unknown) => ParseResult;
15
16
  };
17
+ warnings?: (value: Record<string, unknown>) => readonly FormIssue[];
16
18
  };
17
- declare var __VLS_1: {};
19
+ declare var __VLS_13: {};
18
20
  type __VLS_Slots = {} & {
19
- default?: (props: typeof __VLS_1) => any;
21
+ default?: (props: typeof __VLS_13) => any;
20
22
  };
21
23
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
22
24
  error: () => any;
@@ -12,12 +12,15 @@
12
12
  <p v-if="error" class="cms-form-error">
13
13
  <CmsIcon name="exclamation-circle" class="size-3.5 shrink-0" />{{ error }}
14
14
  </p>
15
+ <p v-else-if="warning" class="cms-form-warning">
16
+ <CmsIcon name="exclamation-triangle" class="size-3.5 shrink-0" />{{ warning }}
17
+ </p>
15
18
  </div>
16
19
  </template>
17
20
 
18
21
  <script setup>
19
22
  import { computed, inject } from "#imports";
20
- import { CMS_FORM_ERRORS } from "../../utils/ui";
23
+ import { CMS_FORM_ERRORS, CMS_FORM_WARNINGS } from "../../utils/ui";
21
24
  const props = defineProps({
22
25
  label: { type: String, required: false },
23
26
  icon: { type: String, required: false },
@@ -26,5 +29,7 @@ const props = defineProps({
26
29
  required: { type: Boolean, required: false }
27
30
  });
28
31
  const errors = inject(CMS_FORM_ERRORS, null);
32
+ const warnings = inject(CMS_FORM_WARNINGS, null);
29
33
  const error = computed(() => props.name && errors ? errors[props.name] : void 0);
34
+ const warning = computed(() => props.name && warnings ? warnings.value[props.name] : void 0);
30
35
  </script>
@@ -0,0 +1,18 @@
1
+ import type { CmsEntry } from '#nuxt-cms';
2
+ type __VLS_Props = {
3
+ collection: string;
4
+ config: CmsEntry;
5
+ };
6
+ type __VLS_ModelProps = {
7
+ 'open'?: boolean;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ imported: () => any;
12
+ "update:open": (value: boolean) => any;
13
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
14
+ onImported?: (() => any) | undefined;
15
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,252 @@
1
+ <template>
2
+ <CmsModal v-model:open="open" :title="`Import ${config.label}`" size="lg">
3
+ <div class="cms-form">
4
+ <p class="cms-form-description">
5
+ {{ csv ? "A JSON or CSV file" : "A JSON file" }} in the format of the export, up to
6
+ {{ maxSize }} and {{ IMPORT_MAX_ROWS }} rows. Media fields take keys of files that are
7
+ already in the media library. Nothing is written until you confirm.
8
+ </p>
9
+
10
+ <div class="cms-import-file">
11
+ <input
12
+ ref="input"
13
+ type="file"
14
+ class="hidden"
15
+ :accept="csv ? '.json,.csv,application/json,text/csv' : '.json,application/json'"
16
+ @change="onFile"
17
+ />
18
+ <CmsButton
19
+ :label="file ? 'Choose another file' : 'Choose file'"
20
+ icon="document-arrow-up"
21
+ variant="soft"
22
+ color="neutral"
23
+ :disabled="busy"
24
+ @click="input?.click()"
25
+ />
26
+ <span v-if="file" class="cms-import-file-name">
27
+ {{ file.name }} · {{ formatFileSize(file.size) }}
28
+ </span>
29
+ </div>
30
+
31
+ <CmsFormField
32
+ label="Mode"
33
+ :description="
34
+ mode === 'create' ? 'Every row becomes a new entry. A row with the id of an existing entry is an error.' : 'A row with the id of an existing entry replaces it. Other rows become new entries.'
35
+ "
36
+ >
37
+ <div class="cms-media-filters">
38
+ <button
39
+ v-for="option in MODE_OPTIONS"
40
+ :key="option.value"
41
+ type="button"
42
+ class="cms-pill"
43
+ :class="{ 'is-active': mode === option.value }"
44
+ :disabled="busy"
45
+ @click="setMode(option.value)"
46
+ >
47
+ {{ option.label }}
48
+ </button>
49
+ </div>
50
+ </CmsFormField>
51
+
52
+ <CmsSpinner v-if="checking" />
53
+
54
+ <CmsAlert v-else-if="fileErrors.length" color="error" title="The file cannot be imported">
55
+ <ul class="cms-import-list">
56
+ <li v-for="message in fileErrors" :key="message">{{ message }}</li>
57
+ </ul>
58
+ </CmsAlert>
59
+
60
+ <template v-else-if="preview">
61
+ <CmsAlert :color="invalidCount ? 'warning' : 'success'" :title="summary" />
62
+ <div class="cms-card cms-table-shell cms-import-preview">
63
+ <div class="cms-table-scroll">
64
+ <table class="cms-table">
65
+ <thead>
66
+ <tr>
67
+ <th>Row</th>
68
+ <th>Entry</th>
69
+ <th>Action</th>
70
+ <th>Problems</th>
71
+ </tr>
72
+ </thead>
73
+ <tbody>
74
+ <tr v-for="row in shownRows" :key="row.index">
75
+ <td>{{ row.index }}</td>
76
+ <td class="cms-import-title">{{ row.title || row.id || "Untitled" }}</td>
77
+ <td>
78
+ <span class="cms-tag">
79
+ {{ row.action === "update" ? "Update" : "New" }}
80
+ </span>
81
+ </td>
82
+ <td>
83
+ <ul
84
+ v-if="row.errors.length"
85
+ class="cms-import-list cms-import-issues"
86
+ >
87
+ <li v-for="(issue, index) in row.errors" :key="index">
88
+ {{ issue.path ? `${issue.path}: ` : "" }}{{ issue.message }}
89
+ </li>
90
+ </ul>
91
+ <span v-else class="cms-import-ok">
92
+ <CmsIcon name="check" class="size-3.5" />Ready
93
+ </span>
94
+ </td>
95
+ </tr>
96
+ </tbody>
97
+ </table>
98
+ </div>
99
+ </div>
100
+ </template>
101
+
102
+ <div class="cms-actions is-end">
103
+ <CmsButton label="Cancel" variant="soft" color="neutral" @click="open = false" />
104
+ <CmsButton
105
+ :label="importLabel"
106
+ icon="arrow-down-tray"
107
+ :disabled="!canImport"
108
+ :loading="importing"
109
+ @click="commit"
110
+ />
111
+ </div>
112
+ </div>
113
+ </CmsModal>
114
+ </template>
115
+
116
+ <script setup>
117
+ import { formatFileSize } from "#nuxt-cms";
118
+ import { computed, ref, watch } from "#imports";
119
+ import { canTransferCsv, IMPORT_MAX_BYTES, IMPORT_MAX_ROWS } from "../../../shared/transfer";
120
+ import { useCmsToast } from "../../composables/cms-toast";
121
+ import { errorMessage } from "../../utils/ui";
122
+ const MODE_OPTIONS = [
123
+ { value: "create", label: "Create only" },
124
+ { value: "upsert", label: "Create or update by id" }
125
+ ];
126
+ const props = defineProps({
127
+ collection: { type: String, required: true },
128
+ config: { type: Object, required: true }
129
+ });
130
+ const open = defineModel("open", { type: Boolean, ...{ default: false } });
131
+ const emit = defineEmits(["imported"]);
132
+ const toast = useCmsToast();
133
+ const csv = canTransferCsv(props.config);
134
+ const maxSize = formatFileSize(IMPORT_MAX_BYTES);
135
+ const input = ref(null);
136
+ const file = ref(null);
137
+ const text = ref("");
138
+ const mode = ref("create");
139
+ const preview = ref(null);
140
+ const fileErrors = ref([]);
141
+ const checking = ref(false);
142
+ const importing = ref(false);
143
+ const busy = computed(() => checking.value || importing.value);
144
+ const invalidCount = computed(() => preview.value ? preview.value.total - preview.value.valid : 0);
145
+ const summary = computed(() => {
146
+ const current = preview.value;
147
+ if (!current) return "";
148
+ if (!current.total) return "The file has no rows";
149
+ const parts = [
150
+ current.create ? `${current.create} new` : "",
151
+ current.update ? `${current.update} to update` : ""
152
+ ].filter(Boolean);
153
+ const ready = parts.length ? parts.join(", ") : "nothing ready";
154
+ return invalidCount.value ? `${invalidCount.value} of ${current.total} rows have problems; fix the file and choose it again` : `${current.total} rows checked: ${ready}`;
155
+ });
156
+ const shownRows = computed(() => {
157
+ const rows = preview.value?.rows ?? [];
158
+ return invalidCount.value ? rows.filter((row) => row.errors.length) : rows;
159
+ });
160
+ const canImport = computed(
161
+ () => !!preview.value && !busy.value && !fileErrors.value.length && preview.value.total > 0 && !invalidCount.value
162
+ );
163
+ const importLabel = computed(() => {
164
+ const total = preview.value?.total ?? 0;
165
+ return total ? `Import ${total} ${total === 1 ? "entry" : "entries"}` : "Import";
166
+ });
167
+ function formatOf(name) {
168
+ return name.toLowerCase().endsWith(".csv") ? "csv" : "json";
169
+ }
170
+ function reset() {
171
+ file.value = null;
172
+ text.value = "";
173
+ preview.value = null;
174
+ fileErrors.value = [];
175
+ mode.value = "create";
176
+ if (input.value) input.value.value = "";
177
+ }
178
+ watch(open, (value) => {
179
+ if (value) reset();
180
+ });
181
+ function send(dryRun) {
182
+ return $fetch(`/api/cms/import/${props.collection}`, {
183
+ method: "POST",
184
+ query: { format: formatOf(file.value.name), mode: mode.value, dryRun: String(dryRun) },
185
+ body: text.value,
186
+ headers: { "content-type": "text/plain" }
187
+ });
188
+ }
189
+ function readPreview(error) {
190
+ const data = error.data?.data;
191
+ return data && typeof data === "object" && Array.isArray(data.rows) ? data : null;
192
+ }
193
+ async function check() {
194
+ if (!file.value) return;
195
+ checking.value = true;
196
+ preview.value = null;
197
+ fileErrors.value = [];
198
+ try {
199
+ const result = await send(true);
200
+ preview.value = result;
201
+ fileErrors.value = result.errors;
202
+ } catch (error) {
203
+ fileErrors.value = [errorMessage(error) ?? "The file could not be checked"];
204
+ } finally {
205
+ checking.value = false;
206
+ }
207
+ }
208
+ async function onFile(event) {
209
+ const picked = event.target.files?.[0];
210
+ if (!picked) return;
211
+ file.value = picked;
212
+ preview.value = null;
213
+ if (picked.size > IMPORT_MAX_BYTES) {
214
+ fileErrors.value = [`The file is larger than ${maxSize}`];
215
+ return;
216
+ }
217
+ if (formatOf(picked.name) === "csv" && !csv) {
218
+ fileErrors.value = ["This entry has blocks or many-to-many fields: use a JSON file"];
219
+ return;
220
+ }
221
+ text.value = await picked.text();
222
+ await check();
223
+ }
224
+ function setMode(value) {
225
+ if (mode.value === value) return;
226
+ mode.value = value;
227
+ if (text.value) void check();
228
+ }
229
+ async function commit() {
230
+ if (!canImport.value) return;
231
+ importing.value = true;
232
+ try {
233
+ const result = await send(false);
234
+ const count = result.create + result.update;
235
+ toast.add({
236
+ title: `Imported ${count} ${count === 1 ? "entry" : "entries"}`,
237
+ color: "success"
238
+ });
239
+ emit("imported");
240
+ open.value = false;
241
+ } catch (error) {
242
+ const returned = readPreview(error);
243
+ if (returned) {
244
+ preview.value = returned;
245
+ fileErrors.value = returned.errors;
246
+ }
247
+ toast.add({ title: "Import failed", description: errorMessage(error), color: "error" });
248
+ } finally {
249
+ importing.value = false;
250
+ }
251
+ }
252
+ </script>
@@ -0,0 +1,18 @@
1
+ import type { CmsEntry } from '#nuxt-cms';
2
+ type __VLS_Props = {
3
+ collection: string;
4
+ config: CmsEntry;
5
+ };
6
+ type __VLS_ModelProps = {
7
+ 'open'?: boolean;
8
+ };
9
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ imported: () => any;
12
+ "update:open": (value: boolean) => any;
13
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
14
+ onImported?: (() => any) | undefined;
15
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import type { CmsEntry } from '#nuxt-cms';
2
+ import type { CmsListFilters } from '../../../shared/list-filters.js';
3
+ type __VLS_Props = {
4
+ config: CmsEntry;
5
+ keys: string[];
6
+ relations?: Record<string, Record<string, unknown>>;
7
+ };
8
+ type __VLS_ModelProps = {
9
+ modelValue: CmsListFilters;
10
+ };
11
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
12
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:modelValue": (value: CmsListFilters) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
15
+ "onUpdate:modelValue"?: ((value: CmsListFilters) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: typeof __VLS_export;
18
+ export default _default;