@xleddyl/nuxt-cms 0.2.2 → 0.3.0

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 (92) hide show
  1. package/README.md +4 -2
  2. package/dist/module.d.mts +4 -0
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +329 -32
  5. package/dist/runtime/app/blocks/CmsBlocks.d.vue.ts +45 -0
  6. package/dist/runtime/app/blocks/CmsBlocks.vue +36 -0
  7. package/dist/runtime/app/blocks/CmsBlocks.vue.d.ts +45 -0
  8. package/dist/runtime/app/blocks/resolve.d.ts +21 -0
  9. package/dist/runtime/app/blocks/resolve.js +37 -0
  10. package/dist/runtime/app/components/cms/BlockOutline.d.vue.ts +17 -0
  11. package/dist/runtime/app/components/cms/BlockOutline.vue +99 -0
  12. package/dist/runtime/app/components/cms/BlockOutline.vue.d.ts +17 -0
  13. package/dist/runtime/app/components/cms/BlockPalette.d.vue.ts +12 -0
  14. package/dist/runtime/app/components/cms/BlockPalette.vue +29 -0
  15. package/dist/runtime/app/components/cms/BlockPalette.vue.d.ts +12 -0
  16. package/dist/runtime/app/components/cms/BlocksField.d.vue.ts +2 -0
  17. package/dist/runtime/app/components/cms/BlocksField.vue +83 -3
  18. package/dist/runtime/app/components/cms/BlocksField.vue.d.ts +2 -0
  19. package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +2 -0
  20. package/dist/runtime/app/components/cms/EntryForm.vue +43 -7
  21. package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +2 -0
  22. package/dist/runtime/app/components/cms/FieldInput.d.vue.ts +2 -0
  23. package/dist/runtime/app/components/cms/FieldInput.vue +18 -1
  24. package/dist/runtime/app/components/cms/FieldInput.vue.d.ts +2 -0
  25. package/dist/runtime/app/components/cms/MediaDetail.vue +25 -13
  26. package/dist/runtime/app/components/cms/Search.d.vue.ts +7 -0
  27. package/dist/runtime/app/components/cms/Search.vue +347 -0
  28. package/dist/runtime/app/components/cms/Search.vue.d.ts +7 -0
  29. package/dist/runtime/app/components/cms/SettingsEntry.vue +3 -2
  30. package/dist/runtime/app/components/cms/SettingsModal.vue +2 -1
  31. package/dist/runtime/app/components/cms/StatusBadge.d.vue.ts +2 -0
  32. package/dist/runtime/app/components/cms/StatusBadge.vue +9 -3
  33. package/dist/runtime/app/components/cms/StatusBadge.vue.d.ts +2 -0
  34. package/dist/runtime/app/components/cms/VisualEditor.d.vue.ts +39 -0
  35. package/dist/runtime/app/components/cms/VisualEditor.vue +553 -0
  36. package/dist/runtime/app/components/cms/VisualEditor.vue.d.ts +39 -0
  37. package/dist/runtime/app/composables/cms-entry-disabled.d.ts +7 -1
  38. package/dist/runtime/app/composables/cms-entry-disabled.js +15 -0
  39. package/dist/runtime/app/composables/cms-entry.d.ts +7 -1
  40. package/dist/runtime/app/composables/cms-entry.js +51 -1
  41. package/dist/runtime/app/composables/cms-field-focus.d.ts +4 -0
  42. package/dist/runtime/app/composables/cms-field-focus.js +20 -0
  43. package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
  44. package/dist/runtime/app/composables/cms-settings.js +8 -2
  45. package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
  46. package/dist/runtime/app/layouts/cms-admin.vue +7 -0
  47. package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
  48. package/dist/runtime/app/layouts/cms-editor.d.vue.ts +13 -0
  49. package/dist/runtime/app/layouts/cms-editor.vue +14 -0
  50. package/dist/runtime/app/layouts/cms-editor.vue.d.ts +13 -0
  51. package/dist/runtime/app/pages/admin-collection.vue +36 -4
  52. package/dist/runtime/app/pages/admin-content.d.vue.ts +3 -0
  53. package/dist/runtime/app/pages/admin-content.vue +287 -0
  54. package/dist/runtime/app/pages/admin-content.vue.d.ts +3 -0
  55. package/dist/runtime/app/pages/admin-entry.vue +3 -0
  56. package/dist/runtime/app/pages/admin-preview.d.vue.ts +3 -0
  57. package/dist/runtime/app/pages/admin-preview.vue +132 -0
  58. package/dist/runtime/app/pages/admin-preview.vue.d.ts +3 -0
  59. package/dist/runtime/app/preview/PreviewOverlay.d.vue.ts +15 -0
  60. package/dist/runtime/app/preview/PreviewOverlay.vue +362 -0
  61. package/dist/runtime/app/preview/PreviewOverlay.vue.d.ts +15 -0
  62. package/dist/runtime/app/utils/ui.js +1 -0
  63. package/dist/runtime/assets/main.css +1 -1
  64. package/dist/runtime/server/api/collection.get.js +4 -5
  65. package/dist/runtime/server/api/collection.post.js +4 -1
  66. package/dist/runtime/server/api/item.delete.js +2 -1
  67. package/dist/runtime/server/api/item.put.js +2 -1
  68. package/dist/runtime/server/api/media-download.get.d.ts +2 -0
  69. package/dist/runtime/server/api/media-download.get.js +90 -0
  70. package/dist/runtime/server/api/search.get.d.ts +3 -0
  71. package/dist/runtime/server/api/search.get.js +24 -0
  72. package/dist/runtime/server/utils/graphql.js +23 -8
  73. package/dist/runtime/server/utils/media-download.d.ts +1 -0
  74. package/dist/runtime/server/utils/media-download.js +8 -0
  75. package/dist/runtime/server/utils/media.d.ts +1 -0
  76. package/dist/runtime/server/utils/media.js +18 -3
  77. package/dist/runtime/server/utils/registry.d.ts +1 -0
  78. package/dist/runtime/server/utils/registry.js +6 -0
  79. package/dist/runtime/server/utils/search.d.ts +20 -0
  80. package/dist/runtime/server/utils/search.js +233 -0
  81. package/dist/runtime/server/utils/visibility.d.ts +4 -0
  82. package/dist/runtime/server/utils/visibility.js +12 -0
  83. package/dist/runtime/shared/graphql-sdl.js +4 -1
  84. package/dist/runtime/shared/index.d.ts +59 -5
  85. package/dist/runtime/shared/index.js +133 -0
  86. package/dist/runtime/shared/preview.d.ts +85 -0
  87. package/dist/runtime/shared/preview.js +219 -0
  88. package/dist/runtime/shared/search.d.ts +52 -0
  89. package/dist/runtime/shared/search.js +207 -0
  90. package/dist/runtime/shared/validation.d.ts +2 -0
  91. package/dist/runtime/shared/validation.js +15 -2
  92. package/package.json +2 -2
@@ -3,6 +3,8 @@ type __VLS_Props = {
3
3
  field: FieldConfig;
4
4
  slugSource?: string | null;
5
5
  locale?: string;
6
+ entryName?: string;
7
+ fieldKey?: string;
6
8
  };
7
9
  type __VLS_ModelProps = {
8
10
  modelValue: unknown;
@@ -13,22 +13,31 @@
13
13
  <CmsIcon v-else :name="mediaIconFor(item.type)" class="size-12" />
14
14
  </div>
15
15
 
16
- <div v-if="item.url" class="cms-actions">
17
- <CmsButton
18
- :label="copied ? 'Copied' : 'Copy URL'"
19
- :icon="copied ? 'check' : 'clipboard-document'"
20
- variant="soft"
21
- color="neutral"
22
- size="sm"
23
- @click="copyUrl"
24
- />
16
+ <div class="cms-actions">
17
+ <template v-if="item.url">
18
+ <CmsButton
19
+ :label="copied ? 'Copied' : 'Copy URL'"
20
+ :icon="copied ? 'check' : 'clipboard-document'"
21
+ variant="soft"
22
+ color="neutral"
23
+ size="sm"
24
+ @click="copyUrl"
25
+ />
26
+ <a
27
+ :href="item.url"
28
+ target="_blank"
29
+ rel="noopener"
30
+ class="cms-btn cms-btn-sm cms-btn-neutral cms-btn-ghost"
31
+ >
32
+ <CmsIcon name="arrow-top-right-on-square" class="size-4 shrink-0" />Open
33
+ </a>
34
+ </template>
25
35
  <a
26
- :href="item.url"
27
- target="_blank"
28
- rel="noopener"
36
+ :href="downloadHref"
37
+ download
29
38
  class="cms-btn cms-btn-sm cms-btn-neutral cms-btn-ghost"
30
39
  >
31
- <CmsIcon name="arrow-top-right-on-square" class="size-4 shrink-0" />Open
40
+ <CmsIcon name="arrow-down-tray" class="size-4 shrink-0" />Download
32
41
  </a>
33
42
  </div>
34
43
 
@@ -128,6 +137,9 @@ const dirty = computed(() => {
128
137
  if (!props.item) return false;
129
138
  return (alt.value.trim() || null) !== (props.item.alt ?? null) || !props.readOnly && folder.value !== (props.item.folder ?? null);
130
139
  });
140
+ const downloadHref = computed(
141
+ () => props.item ? `/api/cms/admin/media/download?key=${encodeURIComponent(props.item.key)}` : ""
142
+ );
131
143
  const facts = computed(() => {
132
144
  const item = props.item;
133
145
  if (!item) return [];
@@ -0,0 +1,7 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
2
+ open: () => any;
3
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
4
+ onOpen?: (() => any) | undefined;
5
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,347 @@
1
+ <template>
2
+ <button
3
+ ref="trigger"
4
+ type="button"
5
+ class="cms-search-trigger"
6
+ aria-haspopup="dialog"
7
+ :aria-expanded="open"
8
+ aria-keyshortcuts="Meta+K Control+K"
9
+ @click="show"
10
+ >
11
+ <CmsIcon name="magnifying-glass" class="cms-search-trigger-icon size-4 shrink-0" />
12
+ <span class="cms-search-trigger-text">Search</span>
13
+ <kbd class="cms-kbd">{{ shortcut }}</kbd>
14
+ </button>
15
+
16
+ <Teleport to="body">
17
+ <Transition name="cms-modal">
18
+ <div v-if="open" class="cms-scope cms-overlay cms-search-overlay" @click.self="close">
19
+ <div
20
+ class="cms-modal cms-search"
21
+ role="dialog"
22
+ aria-modal="true"
23
+ aria-labelledby="cms-search-label"
24
+ >
25
+ <div class="cms-search-bar">
26
+ <label id="cms-search-label" for="cms-search-input" class="sr-only">
27
+ Search the CMS
28
+ </label>
29
+ <CmsIcon name="magnifying-glass" class="cms-search-bar-icon size-5 shrink-0" />
30
+ <input
31
+ id="cms-search-input"
32
+ ref="input"
33
+ v-model="query"
34
+ type="text"
35
+ role="combobox"
36
+ class="cms-search-input"
37
+ placeholder="Search content, fields and pages"
38
+ autocomplete="off"
39
+ spellcheck="false"
40
+ aria-autocomplete="list"
41
+ aria-controls="cms-search-results"
42
+ :aria-expanded="hits.length > 0"
43
+ :aria-activedescendant="activeId"
44
+ @keydown="onKeydown"
45
+ />
46
+ <CmsSpinner v-if="loading" />
47
+ <kbd class="cms-kbd">Esc</kbd>
48
+ </div>
49
+
50
+ <div class="cms-search-body">
51
+ <p v-if="hint" class="cms-search-hint">{{ hint }}</p>
52
+ <div
53
+ id="cms-search-results"
54
+ role="listbox"
55
+ aria-labelledby="cms-search-label"
56
+ class="cms-search-results"
57
+ >
58
+ <div
59
+ v-for="(group, groupIndex) in groups"
60
+ :key="group.key"
61
+ role="group"
62
+ :aria-labelledby="`cms-search-group-${groupIndex}`"
63
+ class="cms-search-group"
64
+ >
65
+ <div :id="`cms-search-group-${groupIndex}`" class="cms-search-group-title">
66
+ <span>{{ group.label }}</span>
67
+ <span class="cms-search-group-kind">{{ KIND_LABELS[group.kind] }}</span>
68
+ </div>
69
+ <div
70
+ v-for="item in group.items"
71
+ :id="optionId(item.index)"
72
+ :key="item.index"
73
+ role="option"
74
+ class="cms-search-option"
75
+ :class="{ 'is-active': item.index === active }"
76
+ :aria-selected="item.index === active"
77
+ @mousemove="active = item.index"
78
+ @click="go(item.hit)"
79
+ >
80
+ <CmsIcon
81
+ :name="iconOf(item.hit)"
82
+ class="cms-search-option-icon size-4 shrink-0"
83
+ />
84
+ <span class="cms-search-option-text">
85
+ <template v-if="isValueHit(item.hit)">
86
+ <span class="cms-search-option-head">
87
+ <span class="cms-search-option-title">
88
+ {{ item.hit.title ?? item.hit.entryLabel }}
89
+ </span>
90
+ <span v-if="item.hit.fieldLabel" class="cms-search-option-meta">
91
+ {{ item.hit.fieldLabel }}
92
+ </span>
93
+ <span v-if="item.hit.locale" class="cms-badge is-muted">
94
+ {{ item.hit.locale }}
95
+ </span>
96
+ </span>
97
+ <span class="cms-search-snippet">
98
+ {{ item.hit.snippet.before
99
+ }}<mark class="cms-search-mark">{{
100
+ item.hit.snippet.match
101
+ }}</mark
102
+ >{{ item.hit.snippet.after }}
103
+ </span>
104
+ </template>
105
+ <template v-else>
106
+ <span class="cms-search-option-title">
107
+ {{ item.hit.snippet.before
108
+ }}<mark class="cms-search-mark">{{
109
+ item.hit.snippet.match
110
+ }}</mark
111
+ >{{ item.hit.snippet.after }}
112
+ </span>
113
+ <span class="cms-search-option-meta">{{
114
+ labelMeta(item.hit)
115
+ }}</span>
116
+ </template>
117
+ </span>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ <p v-if="truncated" class="cms-search-hint">
122
+ Showing the first results. Type more to narrow them down.
123
+ </p>
124
+ </div>
125
+
126
+ <div class="cms-search-footer" aria-hidden="true">
127
+ <span><kbd class="cms-kbd">↑</kbd><kbd class="cms-kbd">↓</kbd> to move</span>
128
+ <span><kbd class="cms-kbd">↵</kbd> to open</span>
129
+ <span><kbd class="cms-kbd">Esc</kbd> to close</span>
130
+ </div>
131
+ <p class="sr-only" aria-live="polite">{{ status }}</p>
132
+ </div>
133
+ </div>
134
+ </Transition>
135
+ </Teleport>
136
+ </template>
137
+
138
+ <script setup>
139
+ import { computed, navigateTo, nextTick, onBeforeUnmount, onMounted, ref, watch } from "#imports";
140
+ import { isSearchableQuery, normalizeSearchQuery } from "../../../shared/search";
141
+ import { useCmsOverlay } from "../../composables/cms-overlay";
142
+ import { cmsApi } from "../../utils/api";
143
+ const DEBOUNCE_MS = 180;
144
+ const KIND_LABELS = {
145
+ collection: "Collection",
146
+ single: "Single",
147
+ content: "Content",
148
+ page: "Pages",
149
+ media: "Library"
150
+ };
151
+ const KIND_ICONS = {
152
+ collection: "square-3-stack-3d",
153
+ single: "document-text",
154
+ content: "newspaper",
155
+ page: "window",
156
+ media: "photo"
157
+ };
158
+ const TYPE_LABELS = {
159
+ entry: "Section",
160
+ page: "Page",
161
+ tab: "Tab",
162
+ field: "Field",
163
+ block: "Block",
164
+ value: "Value",
165
+ media: "Media"
166
+ };
167
+ const TYPE_ICONS = {
168
+ page: "window",
169
+ tab: "rectangle-group",
170
+ field: "tag",
171
+ block: "squares-2x2",
172
+ value: "bars-3-bottom-left",
173
+ media: "photo"
174
+ };
175
+ const emit = defineEmits(["open"]);
176
+ const open = ref(false);
177
+ const query = ref("");
178
+ const hits = ref([]);
179
+ const truncated = ref(false);
180
+ const loading = ref(false);
181
+ const failed = ref(false);
182
+ const searched = ref("");
183
+ const active = ref(0);
184
+ const shortcut = ref("Ctrl K");
185
+ const trigger = ref(null);
186
+ const input = ref(null);
187
+ let timer;
188
+ let request = 0;
189
+ let returnFocus = null;
190
+ const groups = computed(() => {
191
+ const map = /* @__PURE__ */ new Map();
192
+ hits.value.forEach((hit, index2) => {
193
+ const key = hit.collection ?? hit.kind;
194
+ let group = map.get(key);
195
+ if (!group) {
196
+ group = { key, label: hit.entryLabel, kind: hit.kind, items: [] };
197
+ map.set(key, group);
198
+ }
199
+ group.items.push({ hit, index: index2 });
200
+ });
201
+ const ordered = [...map.values()];
202
+ let index = 0;
203
+ for (const group of ordered) {
204
+ for (const item of group.items) item.index = index++;
205
+ }
206
+ return ordered;
207
+ });
208
+ const flat = computed(() => groups.value.flatMap((group) => group.items.map((item) => item.hit)));
209
+ const activeId = computed(() => flat.value.length ? optionId(active.value) : void 0);
210
+ const hint = computed(() => {
211
+ if (!isSearchableQuery(query.value)) return "Type at least 2 characters to search.";
212
+ if (failed.value) return "Search failed. Try again.";
213
+ if (!loading.value && searched.value && !flat.value.length) {
214
+ return `No results for "${searched.value}".`;
215
+ }
216
+ return "";
217
+ });
218
+ const status = computed(() => {
219
+ if (hint.value) return hint.value;
220
+ if (loading.value) return "Searching";
221
+ return `${flat.value.length} results`;
222
+ });
223
+ function optionId(index) {
224
+ return `cms-search-option-${index}`;
225
+ }
226
+ function isValueHit(hit) {
227
+ return hit.type === "value" || hit.type === "media";
228
+ }
229
+ function iconOf(hit) {
230
+ return hit.type === "entry" ? KIND_ICONS[hit.kind] : TYPE_ICONS[hit.type] ?? "tag";
231
+ }
232
+ function labelMeta(hit) {
233
+ const parts = [TYPE_LABELS[hit.type]];
234
+ if (hit.title) parts.push(hit.title);
235
+ if (hit.fieldLabel && hit.type !== "tab") parts.push(hit.fieldLabel);
236
+ if (hit.type === "page" && hit.id) parts.push(hit.to);
237
+ return parts.join(" \xB7 ");
238
+ }
239
+ async function run(term) {
240
+ const current = ++request;
241
+ loading.value = true;
242
+ failed.value = false;
243
+ try {
244
+ const response = await cmsApi("/api/cms/search", { query: { q: term } });
245
+ if (current !== request) return;
246
+ hits.value = response.hits;
247
+ truncated.value = response.truncated;
248
+ searched.value = response.query;
249
+ active.value = 0;
250
+ } catch {
251
+ if (current !== request) return;
252
+ hits.value = [];
253
+ truncated.value = false;
254
+ failed.value = true;
255
+ } finally {
256
+ if (current === request) loading.value = false;
257
+ }
258
+ }
259
+ watch(query, (value) => {
260
+ clearTimeout(timer);
261
+ const term = normalizeSearchQuery(value);
262
+ if (!isSearchableQuery(term)) {
263
+ request++;
264
+ hits.value = [];
265
+ truncated.value = false;
266
+ loading.value = false;
267
+ failed.value = false;
268
+ searched.value = "";
269
+ return;
270
+ }
271
+ timer = setTimeout(() => run(term), DEBOUNCE_MS);
272
+ });
273
+ watch(active, (index) => {
274
+ void nextTick(
275
+ () => document.getElementById(optionId(index))?.scrollIntoView({ block: "nearest" })
276
+ );
277
+ });
278
+ function focusInput() {
279
+ void nextTick(() => {
280
+ input.value?.focus();
281
+ input.value?.select();
282
+ });
283
+ }
284
+ function show() {
285
+ if (open.value) {
286
+ focusInput();
287
+ return;
288
+ }
289
+ returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
290
+ emit("open");
291
+ open.value = true;
292
+ }
293
+ function close() {
294
+ open.value = false;
295
+ }
296
+ const overlay = useCmsOverlay(close);
297
+ watch(open, (value) => {
298
+ if (value) {
299
+ overlay.activate();
300
+ focusInput();
301
+ } else {
302
+ overlay.deactivate();
303
+ const target = returnFocus ?? trigger.value;
304
+ returnFocus = null;
305
+ void nextTick(() => target?.focus());
306
+ }
307
+ });
308
+ function go(hit) {
309
+ if (!hit) return;
310
+ returnFocus = null;
311
+ close();
312
+ void navigateTo(hit.to);
313
+ }
314
+ function onKeydown(event) {
315
+ const count = flat.value.length;
316
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
317
+ event.preventDefault();
318
+ if (!count) return;
319
+ const step = event.key === "ArrowDown" ? 1 : -1;
320
+ active.value = (active.value + step + count) % count;
321
+ } else if (event.key === "Home" && count && event.ctrlKey) {
322
+ event.preventDefault();
323
+ active.value = 0;
324
+ } else if (event.key === "End" && count && event.ctrlKey) {
325
+ event.preventDefault();
326
+ active.value = count - 1;
327
+ } else if (event.key === "Enter") {
328
+ event.preventDefault();
329
+ go(flat.value[active.value]);
330
+ }
331
+ }
332
+ function onGlobalKeydown(event) {
333
+ if (event.key.toLowerCase() !== "k" || event.altKey || event.shiftKey) return;
334
+ if (!event.metaKey && !event.ctrlKey) return;
335
+ event.preventDefault();
336
+ show();
337
+ }
338
+ onMounted(() => {
339
+ const platform = navigator.userAgentData?.platform ?? navigator.platform ?? navigator.userAgent;
340
+ if (/mac|iphone|ipad|ipod/i.test(platform)) shortcut.value = "\u2318K";
341
+ document.addEventListener("keydown", onGlobalKeydown);
342
+ });
343
+ onBeforeUnmount(() => {
344
+ clearTimeout(timer);
345
+ document.removeEventListener("keydown", onGlobalKeydown);
346
+ });
347
+ </script>
@@ -0,0 +1,7 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
2
+ open: () => any;
3
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
4
+ onOpen?: (() => any) | undefined;
5
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -69,7 +69,8 @@ import { errorMessage } from "../../utils/ui";
69
69
  const KIND_LABELS = {
70
70
  collection: "Collection",
71
71
  single: "Single",
72
- page: "Pages"
72
+ page: "Pages",
73
+ content: "Content"
73
74
  };
74
75
  const STATUS_LABELS = {
75
76
  idle: "Changes save automatically",
@@ -85,7 +86,7 @@ const toast = useCmsToast();
85
86
  const entry = computed(() => cmsConfig[props.name]);
86
87
  const fields = computed(() => entryFields(props.name));
87
88
  const parts = computed(() => [
88
- ...entry.value.kind === "collection" ? [{ id: "columns", label: "Table columns" }] : [],
89
+ ...entry.value.kind === "collection" || entry.value.kind === "content" ? [{ id: "columns", label: "Table columns" }] : [],
89
90
  { id: "layout", label: "Form layout" }
90
91
  ]);
91
92
  const activePart = ref(parts.value[0].id);
@@ -144,7 +144,8 @@ const emit = defineEmits(["logout"]);
144
144
  const KIND_ICONS = {
145
145
  collection: "square-3-stack-3d",
146
146
  single: "document-text",
147
- page: "window"
147
+ page: "window",
148
+ content: "newspaper"
148
149
  };
149
150
  const ENTRY_PREFIX = "entry:";
150
151
  const entries = Object.entries(cmsConfig).map(([name, entry]) => ({
@@ -1,5 +1,7 @@
1
1
  type __VLS_Props = {
2
2
  published?: boolean;
3
+ scheduled?: boolean;
4
+ at?: string;
3
5
  };
4
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
7
  declare const _default: typeof __VLS_export;
@@ -1,11 +1,17 @@
1
1
  <template>
2
- <span class="cms-badge" :class="published ? 'is-published' : 'is-draft'">
3
- {{ published ? "Published" : "Draft" }}
2
+ <span
3
+ class="cms-badge"
4
+ :class="scheduled ? 'is-scheduled' : published ? 'is-published' : 'is-draft'"
5
+ :title="scheduled && at ? `Goes live ${at}` : void 0"
6
+ >
7
+ {{ scheduled ? "Scheduled" : published ? "Published" : "Draft" }}
4
8
  </span>
5
9
  </template>
6
10
 
7
11
  <script setup>
8
12
  defineProps({
9
- published: { type: Boolean, required: false }
13
+ published: { type: Boolean, required: false },
14
+ scheduled: { type: Boolean, required: false },
15
+ at: { type: String, required: false }
10
16
  });
11
17
  </script>
@@ -1,5 +1,7 @@
1
1
  type __VLS_Props = {
2
2
  published?: boolean;
3
+ scheduled?: boolean;
4
+ at?: string;
3
5
  };
4
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
7
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,39 @@
1
+ import type { FieldConfig } from '#nuxt-cms';
2
+ import type { CmsSearchFocus } from '../../../shared/search.js';
3
+ type __VLS_Props = {
4
+ entryName: string;
5
+ fieldKey: string;
6
+ fields: Record<string, FieldConfig>;
7
+ title: string;
8
+ content?: boolean;
9
+ focus?: CmsSearchFocus | null;
10
+ };
11
+ declare function validate(): boolean;
12
+ type __VLS_ModelProps = {
13
+ 'state': Record<string, unknown>;
14
+ };
15
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
16
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_15: {};
17
+ type __VLS_Slots = {} & {
18
+ start?: (props: typeof __VLS_1) => any;
19
+ } & {
20
+ status?: (props: typeof __VLS_3) => any;
21
+ } & {
22
+ actions?: (props: typeof __VLS_15) => any;
23
+ };
24
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
25
+ validate: typeof validate;
26
+ locale: import("vue").Ref<string, string>;
27
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
28
+ "update:state": (value: Record<string, unknown>) => any;
29
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
30
+ "onUpdate:state"?: ((value: Record<string, unknown>) => any) | undefined;
31
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
32
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
33
+ declare const _default: typeof __VLS_export;
34
+ export default _default;
35
+ type __VLS_WithSlots<T, S> = T & {
36
+ new (): {
37
+ $slots: S;
38
+ };
39
+ };