@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.
- package/README.md +4 -2
- package/dist/module.d.mts +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +329 -32
- package/dist/runtime/app/blocks/CmsBlocks.d.vue.ts +45 -0
- package/dist/runtime/app/blocks/CmsBlocks.vue +36 -0
- package/dist/runtime/app/blocks/CmsBlocks.vue.d.ts +45 -0
- package/dist/runtime/app/blocks/resolve.d.ts +21 -0
- package/dist/runtime/app/blocks/resolve.js +37 -0
- package/dist/runtime/app/components/cms/BlockOutline.d.vue.ts +17 -0
- package/dist/runtime/app/components/cms/BlockOutline.vue +99 -0
- package/dist/runtime/app/components/cms/BlockOutline.vue.d.ts +17 -0
- package/dist/runtime/app/components/cms/BlockPalette.d.vue.ts +12 -0
- package/dist/runtime/app/components/cms/BlockPalette.vue +29 -0
- package/dist/runtime/app/components/cms/BlockPalette.vue.d.ts +12 -0
- package/dist/runtime/app/components/cms/BlocksField.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/BlocksField.vue +83 -3
- package/dist/runtime/app/components/cms/BlocksField.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/EntryForm.vue +43 -7
- package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/FieldInput.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/FieldInput.vue +18 -1
- package/dist/runtime/app/components/cms/FieldInput.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/MediaDetail.vue +25 -13
- package/dist/runtime/app/components/cms/Search.d.vue.ts +7 -0
- package/dist/runtime/app/components/cms/Search.vue +347 -0
- package/dist/runtime/app/components/cms/Search.vue.d.ts +7 -0
- package/dist/runtime/app/components/cms/SettingsEntry.vue +3 -2
- package/dist/runtime/app/components/cms/SettingsModal.vue +2 -1
- package/dist/runtime/app/components/cms/StatusBadge.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/StatusBadge.vue +9 -3
- package/dist/runtime/app/components/cms/StatusBadge.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/VisualEditor.d.vue.ts +39 -0
- package/dist/runtime/app/components/cms/VisualEditor.vue +553 -0
- package/dist/runtime/app/components/cms/VisualEditor.vue.d.ts +39 -0
- package/dist/runtime/app/composables/cms-entry-disabled.d.ts +7 -1
- package/dist/runtime/app/composables/cms-entry-disabled.js +15 -0
- package/dist/runtime/app/composables/cms-entry.d.ts +7 -1
- package/dist/runtime/app/composables/cms-entry.js +51 -1
- package/dist/runtime/app/composables/cms-field-focus.d.ts +4 -0
- package/dist/runtime/app/composables/cms-field-focus.js +20 -0
- package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
- package/dist/runtime/app/composables/cms-settings.js +8 -2
- package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
- package/dist/runtime/app/layouts/cms-admin.vue +7 -0
- package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
- package/dist/runtime/app/layouts/cms-editor.d.vue.ts +13 -0
- package/dist/runtime/app/layouts/cms-editor.vue +14 -0
- package/dist/runtime/app/layouts/cms-editor.vue.d.ts +13 -0
- package/dist/runtime/app/pages/admin-collection.vue +36 -4
- package/dist/runtime/app/pages/admin-content.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin-content.vue +287 -0
- package/dist/runtime/app/pages/admin-content.vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin-entry.vue +3 -0
- package/dist/runtime/app/pages/admin-preview.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin-preview.vue +132 -0
- package/dist/runtime/app/pages/admin-preview.vue.d.ts +3 -0
- package/dist/runtime/app/preview/PreviewOverlay.d.vue.ts +15 -0
- package/dist/runtime/app/preview/PreviewOverlay.vue +362 -0
- package/dist/runtime/app/preview/PreviewOverlay.vue.d.ts +15 -0
- package/dist/runtime/app/utils/ui.js +1 -0
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/collection.get.js +4 -5
- package/dist/runtime/server/api/collection.post.js +4 -1
- package/dist/runtime/server/api/item.delete.js +2 -1
- package/dist/runtime/server/api/item.put.js +2 -1
- package/dist/runtime/server/api/media-download.get.d.ts +2 -0
- package/dist/runtime/server/api/media-download.get.js +90 -0
- package/dist/runtime/server/api/search.get.d.ts +3 -0
- package/dist/runtime/server/api/search.get.js +24 -0
- package/dist/runtime/server/utils/graphql.js +23 -8
- package/dist/runtime/server/utils/media-download.d.ts +1 -0
- package/dist/runtime/server/utils/media-download.js +8 -0
- package/dist/runtime/server/utils/media.d.ts +1 -0
- package/dist/runtime/server/utils/media.js +18 -3
- package/dist/runtime/server/utils/registry.d.ts +1 -0
- package/dist/runtime/server/utils/registry.js +6 -0
- package/dist/runtime/server/utils/search.d.ts +20 -0
- package/dist/runtime/server/utils/search.js +233 -0
- package/dist/runtime/server/utils/visibility.d.ts +4 -0
- package/dist/runtime/server/utils/visibility.js +12 -0
- package/dist/runtime/shared/graphql-sdl.js +4 -1
- package/dist/runtime/shared/index.d.ts +59 -5
- package/dist/runtime/shared/index.js +133 -0
- package/dist/runtime/shared/preview.d.ts +85 -0
- package/dist/runtime/shared/preview.js +219 -0
- package/dist/runtime/shared/search.d.ts +52 -0
- package/dist/runtime/shared/search.js +207 -0
- package/dist/runtime/shared/validation.d.ts +2 -0
- package/dist/runtime/shared/validation.js +15 -2
- package/package.json +2 -2
|
@@ -13,22 +13,31 @@
|
|
|
13
13
|
<CmsIcon v-else :name="mediaIconFor(item.type)" class="size-12" />
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
<div
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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="
|
|
27
|
-
|
|
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-
|
|
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
|
|
3
|
-
|
|
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
|
+
};
|