@abraca/nuxt 2.11.0 → 2.13.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/dist/module.json +1 -1
- package/dist/module.mjs +7 -0
- package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
- package/dist/runtime/components/ADocPickerModal.vue +191 -0
- package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
- package/dist/runtime/components/ADocumentTree.vue +65 -0
- package/dist/runtime/components/AEditor.d.vue.ts +17 -10
- package/dist/runtime/components/AEditor.vue +232 -164
- package/dist/runtime/components/AEditor.vue.d.ts +17 -10
- package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
- package/dist/runtime/components/ANodePanel.vue +547 -473
- package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
- package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
- package/dist/runtime/components/ATagsEditor.vue +60 -0
- package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
- package/dist/runtime/components/chat/AChatInput.vue +33 -2
- package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
- package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
- package/dist/runtime/components/chat/AChatList.vue +76 -32
- package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
- package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
- package/dist/runtime/components/chat/AChatMessages.vue +57 -4
- package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
- package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
- package/dist/runtime/components/chat/AChatPanel.vue +17 -1
- package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
- package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
- package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
- package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
- package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
- package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
- package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
- package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
- package/dist/runtime/components/settings/sections.d.ts +37 -0
- package/dist/runtime/components/settings/sections.js +45 -0
- package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
- package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
- package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
- package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
- package/dist/runtime/composables/useChat.d.ts +22 -1
- package/dist/runtime/composables/useChat.js +79 -8
- package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
- package/dist/runtime/composables/useNodeContextMenu.js +18 -0
- package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
- package/dist/runtime/locale.d.ts +8 -0
- package/dist/runtime/locale.js +9 -1
- package/dist/runtime/utils/chatContent.d.ts +20 -2
- package/dist/runtime/utils/chatContent.js +20 -1
- package/dist/runtime/utils/docTypes.js +43 -0
- package/dist/runtime/utils/titleSync.d.ts +130 -0
- package/dist/runtime/utils/titleSync.js +53 -0
- package/package.json +11 -1
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, computed, watch } from "vue";
|
|
2
|
+
import { ref, computed, watch, defineAsyncComponent } from "vue";
|
|
3
3
|
import { useAbracadabra } from "../composables/useAbracadabra";
|
|
4
4
|
import { useSyncedMap } from "../composables/useYDoc";
|
|
5
|
-
import { navigateTo } from "#imports";
|
|
5
|
+
import { navigateTo, useRuntimeConfig } from "#imports";
|
|
6
6
|
import { useChildTree } from "../composables/useChildTree";
|
|
7
7
|
import { useAbraLocale } from "../composables/useAbraLocale";
|
|
8
|
+
import { useDocSlugs } from "../composables/useDocSlugs";
|
|
8
9
|
import { useResizableWidth } from "../composables/useResizableWidth";
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import AConnectionBadge from "./AConnectionBadge.vue";
|
|
11
|
+
import AEditorUndoButton from "./editor/AEditorUndoButton.vue";
|
|
12
|
+
import AEditorRedoButton from "./editor/AEditorRedoButton.vue";
|
|
12
13
|
import { useChat } from "../composables/useChat";
|
|
14
|
+
import { META_FIELD_DEFINITIONS } from "../utils/metaFieldDefinitions";
|
|
15
|
+
import { resolveDocType } from "../utils/docTypes";
|
|
13
16
|
const ANodeChatPanel = defineAsyncComponent(() => import("./chat/ANodeChatPanel.vue"));
|
|
14
17
|
const ANodeSettingsPanel = defineAsyncComponent(() => import("./ANodeSettingsPanel.vue"));
|
|
15
18
|
const ADocPanelServerSettings = defineAsyncComponent(() => import("./shell/ADocPanelServerSettings.vue"));
|
|
16
|
-
import { META_FIELD_DEFINITIONS } from "../utils/metaFieldDefinitions";
|
|
17
|
-
import { resolveDocType } from "../utils/docTypes";
|
|
18
19
|
const props = defineProps({
|
|
19
20
|
nodeId: { type: [String, null], required: true },
|
|
20
21
|
nodeLabel: { type: String, required: false },
|
|
@@ -22,7 +23,8 @@ const props = defineProps({
|
|
|
22
23
|
docType: { type: String, required: false },
|
|
23
24
|
labels: { type: Object, required: false },
|
|
24
25
|
initialTab: { type: String, required: false, default: "editor" },
|
|
25
|
-
tabs: { type: Array, required: false }
|
|
26
|
+
tabs: { type: Array, required: false },
|
|
27
|
+
docHref: { type: Function, required: false }
|
|
26
28
|
});
|
|
27
29
|
const emit = defineEmits(["close"]);
|
|
28
30
|
const open = computed({
|
|
@@ -43,7 +45,12 @@ const entry = computed(() => {
|
|
|
43
45
|
return treeMap.data[props.nodeId] ?? null;
|
|
44
46
|
});
|
|
45
47
|
const meta = computed(() => entry.value?.meta ?? {});
|
|
48
|
+
const resolvedLabel = computed(() => entry.value?.label || props.nodeLabel || "");
|
|
46
49
|
const currentType = computed(() => entry.value?.type ?? "doc");
|
|
50
|
+
const { getDocUrl } = useDocSlugs();
|
|
51
|
+
function hrefFor(id) {
|
|
52
|
+
return props.docHref ? props.docHref(id) : getDocUrl(id);
|
|
53
|
+
}
|
|
47
54
|
function patchMeta(patch) {
|
|
48
55
|
if (!props.nodeId) return;
|
|
49
56
|
const e = treeMap.data[props.nodeId];
|
|
@@ -183,56 +190,109 @@ const addFieldMenuItems = computed(
|
|
|
183
190
|
<USlideover
|
|
184
191
|
v-model:open="open"
|
|
185
192
|
side="right"
|
|
186
|
-
:title="
|
|
193
|
+
:title="resolvedLabel || 'Document'"
|
|
194
|
+
:close="false"
|
|
187
195
|
:content="{ style: resize.style.value }"
|
|
188
|
-
:ui="{ body: 'p-0' }"
|
|
189
196
|
@update:open="(v) => !v && emit('close')"
|
|
190
197
|
>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
198
|
+
<!-- We take full control of the panel chrome via #content (rather than
|
|
199
|
+
USlideover's default header/body slots) so the layout matches cou-sh's
|
|
200
|
+
DocSlideover: a tight 40px control bar with icon-only tabs, then a
|
|
201
|
+
flush content area. -->
|
|
202
|
+
<template #content>
|
|
203
|
+
<div class="flex flex-col h-full relative">
|
|
204
|
+
<!-- Resize handle: left edge of slideover, desktop only -->
|
|
205
|
+
<div
|
|
206
|
+
class="absolute left-0 top-0 bottom-0 w-1.5 z-10 cursor-col-resize hidden sm:block"
|
|
207
|
+
:class="resize.isResizing.value ? 'bg-(--ui-primary)/20' : 'hover:bg-(--ui-primary)/10'"
|
|
208
|
+
@mousedown.stop.prevent="resize.onMouseDown"
|
|
209
|
+
@touchstart.stop.prevent="resize.onTouchStart"
|
|
210
|
+
@dblclick="resize.onDoubleClick"
|
|
211
|
+
/>
|
|
212
|
+
|
|
213
|
+
<!-- Header bar — close + expand on the left, icon-only tab controls on
|
|
214
|
+
the right (cou-sh DocSlideover parity). Consumers can replace the
|
|
215
|
+
whole bar via the #header slot. -->
|
|
216
|
+
<slot
|
|
217
|
+
name="header"
|
|
205
218
|
:node-id="nodeId"
|
|
206
|
-
:node-label="
|
|
207
|
-
:
|
|
208
|
-
:
|
|
209
|
-
:
|
|
210
|
-
:
|
|
211
|
-
@expand="navigateTo(`/app/${nodeId}`)"
|
|
219
|
+
:node-label="resolvedLabel"
|
|
220
|
+
:editor="tiptapEditor"
|
|
221
|
+
:meta="meta"
|
|
222
|
+
:active-tab="activeTab"
|
|
223
|
+
:doc-type-def="docTypeDef"
|
|
212
224
|
>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
<div class="flex items-center justify-between gap-2 px-3 h-10 border-b border-(--ui-border) shrink-0 bg-elevated/75 select-none">
|
|
226
|
+
<!-- Left: close · expand · connection badge -->
|
|
227
|
+
<div class="flex items-center gap-0.5">
|
|
228
|
+
<UTooltip
|
|
229
|
+
:text="locale.close"
|
|
230
|
+
:content="{ side: 'bottom' }"
|
|
231
|
+
>
|
|
232
|
+
<UButton
|
|
233
|
+
icon="i-lucide-x"
|
|
234
|
+
size="xs"
|
|
235
|
+
color="neutral"
|
|
236
|
+
variant="ghost"
|
|
237
|
+
@click="emit('close')"
|
|
238
|
+
/>
|
|
239
|
+
</UTooltip>
|
|
240
|
+
<UTooltip
|
|
241
|
+
:text="locale.openAsFullPage"
|
|
242
|
+
:content="{ side: 'bottom' }"
|
|
243
|
+
>
|
|
244
|
+
<UButton
|
|
245
|
+
icon="i-lucide-expand"
|
|
246
|
+
size="xs"
|
|
247
|
+
color="neutral"
|
|
248
|
+
variant="ghost"
|
|
249
|
+
@click="nodeId && navigateTo(hrefFor(nodeId))"
|
|
250
|
+
/>
|
|
251
|
+
</UTooltip>
|
|
252
|
+
<AConnectionBadge
|
|
253
|
+
v-if="childProvider"
|
|
254
|
+
:provider="childProvider"
|
|
255
|
+
class="ml-1"
|
|
226
256
|
/>
|
|
227
|
-
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<!-- Right: undo/redo (editor tab) · icon-only tab buttons -->
|
|
260
|
+
<div class="flex items-center gap-0.5">
|
|
261
|
+
<template v-if="activeTab === 'editor' && !usesPageRenderer && tiptapEditor">
|
|
262
|
+
<AEditorUndoButton :editor="tiptapEditor" />
|
|
263
|
+
<AEditorRedoButton :editor="tiptapEditor" />
|
|
264
|
+
<USeparator
|
|
265
|
+
orientation="vertical"
|
|
266
|
+
class="h-4 mx-0.5"
|
|
267
|
+
/>
|
|
268
|
+
</template>
|
|
269
|
+
|
|
270
|
+
<UTooltip
|
|
271
|
+
v-if="showEditorTab"
|
|
272
|
+
:text="locale.editorTab"
|
|
273
|
+
:content="{ side: 'bottom' }"
|
|
274
|
+
>
|
|
275
|
+
<UButton
|
|
276
|
+
:icon="usesPageRenderer ? docTypeDef.icon : 'i-lucide-file-text'"
|
|
277
|
+
size="xs"
|
|
278
|
+
:color="activeTab === 'editor' ? 'primary' : 'neutral'"
|
|
279
|
+
:variant="activeTab === 'editor' ? 'soft' : 'ghost'"
|
|
280
|
+
@click="activeTab = 'editor'"
|
|
281
|
+
/>
|
|
282
|
+
</UTooltip>
|
|
283
|
+
<UTooltip
|
|
228
284
|
v-if="showPropertiesTab"
|
|
229
|
-
|
|
230
|
-
:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
285
|
+
:text="locale.propertiesTab"
|
|
286
|
+
:content="{ side: 'bottom' }"
|
|
287
|
+
>
|
|
288
|
+
<UButton
|
|
289
|
+
icon="i-lucide-sliders-horizontal"
|
|
290
|
+
size="xs"
|
|
291
|
+
:color="activeTab === 'properties' ? 'primary' : 'neutral'"
|
|
292
|
+
:variant="activeTab === 'properties' ? 'soft' : 'ghost'"
|
|
293
|
+
@click="activeTab = 'properties'"
|
|
294
|
+
/>
|
|
295
|
+
</UTooltip>
|
|
236
296
|
<UChip
|
|
237
297
|
v-if="showChatTab"
|
|
238
298
|
:text="chatUnread > 0 && activeTab !== 'chat' ? String(chatUnread) : void 0"
|
|
@@ -240,501 +300,515 @@ const addFieldMenuItems = computed(
|
|
|
240
300
|
size="lg"
|
|
241
301
|
:show="chatUnread > 0 && activeTab !== 'chat'"
|
|
242
302
|
:inset="true"
|
|
303
|
+
>
|
|
304
|
+
<UTooltip
|
|
305
|
+
:text="locale.chatTab"
|
|
306
|
+
:content="{ side: 'bottom' }"
|
|
307
|
+
>
|
|
308
|
+
<UButton
|
|
309
|
+
icon="i-lucide-message-circle"
|
|
310
|
+
size="xs"
|
|
311
|
+
:color="activeTab === 'chat' ? 'primary' : 'neutral'"
|
|
312
|
+
:variant="activeTab === 'chat' ? 'soft' : 'ghost'"
|
|
313
|
+
@click="activeTab = 'chat'"
|
|
314
|
+
/>
|
|
315
|
+
</UTooltip>
|
|
316
|
+
</UChip>
|
|
317
|
+
<UTooltip
|
|
318
|
+
v-if="showSettingsTab"
|
|
319
|
+
:text="locale.settingsTab"
|
|
320
|
+
:content="{ side: 'bottom' }"
|
|
243
321
|
>
|
|
244
322
|
<UButton
|
|
245
|
-
icon="i-lucide-
|
|
246
|
-
label="Chat"
|
|
323
|
+
icon="i-lucide-settings-2"
|
|
247
324
|
size="xs"
|
|
248
|
-
:color="activeTab === '
|
|
249
|
-
:variant="activeTab === '
|
|
250
|
-
@click="activeTab = '
|
|
325
|
+
:color="activeTab === 'settings' ? 'primary' : 'neutral'"
|
|
326
|
+
:variant="activeTab === 'settings' ? 'soft' : 'ghost'"
|
|
327
|
+
@click="activeTab = 'settings'"
|
|
251
328
|
/>
|
|
252
|
-
</
|
|
253
|
-
<
|
|
254
|
-
v-if="showSettingsTab"
|
|
255
|
-
icon="i-lucide-settings-2"
|
|
256
|
-
label="Settings"
|
|
257
|
-
size="xs"
|
|
258
|
-
:color="activeTab === 'settings' ? 'primary' : 'neutral'"
|
|
259
|
-
:variant="activeTab === 'settings' ? 'soft' : 'ghost'"
|
|
260
|
-
@click="activeTab = 'settings'"
|
|
261
|
-
/>
|
|
262
|
-
<UButton
|
|
329
|
+
</UTooltip>
|
|
330
|
+
<UTooltip
|
|
263
331
|
v-if="showServerSettingsTab"
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
332
|
+
:text="locale.serverTab"
|
|
333
|
+
:content="{ side: 'bottom' }"
|
|
334
|
+
>
|
|
335
|
+
<UButton
|
|
336
|
+
icon="i-lucide-bolt"
|
|
337
|
+
size="xs"
|
|
338
|
+
:color="activeTab === 'serverSettings' ? 'primary' : 'neutral'"
|
|
339
|
+
:variant="activeTab === 'serverSettings' ? 'soft' : 'ghost'"
|
|
340
|
+
@click="activeTab = 'serverSettings'"
|
|
341
|
+
/>
|
|
342
|
+
</UTooltip>
|
|
343
|
+
<UTooltip
|
|
272
344
|
v-for="slot in pluginSlots"
|
|
273
345
|
:key="slot.id"
|
|
274
|
-
:
|
|
275
|
-
:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
346
|
+
:text="slot.label"
|
|
347
|
+
:content="{ side: 'bottom' }"
|
|
348
|
+
>
|
|
349
|
+
<UButton
|
|
350
|
+
:icon="slot.icon"
|
|
351
|
+
size="xs"
|
|
352
|
+
:color="activeTab === slot.id ? 'primary' : 'neutral'"
|
|
353
|
+
:variant="activeTab === slot.id ? 'soft' : 'ghost'"
|
|
354
|
+
@click="activeTab = slot.id"
|
|
355
|
+
/>
|
|
356
|
+
</UTooltip>
|
|
281
357
|
</div>
|
|
282
|
-
</template>
|
|
283
|
-
</ANodePanelHeader>
|
|
284
|
-
</slot>
|
|
285
|
-
</template>
|
|
286
|
-
|
|
287
|
-
<template #body>
|
|
288
|
-
<!-- Resize handle: left edge of slideover, desktop only -->
|
|
289
|
-
<div
|
|
290
|
-
class="absolute left-0 top-0 bottom-0 w-1.5 z-10 cursor-col-resize hidden sm:block group"
|
|
291
|
-
:class="resize.isResizing.value ? 'bg-(--ui-primary)/20' : 'hover:bg-(--ui-primary)/10'"
|
|
292
|
-
@mousedown.stop.prevent="resize.onMouseDown"
|
|
293
|
-
@touchstart.stop.prevent="resize.onTouchStart"
|
|
294
|
-
@dblclick="resize.onDoubleClick"
|
|
295
|
-
/>
|
|
296
|
-
|
|
297
|
-
<!-- Editor tab — swaps to the registered page-type renderer when the
|
|
298
|
-
doc has a non-'doc' type (kanban / table / calendar / etc.). -->
|
|
299
|
-
<div
|
|
300
|
-
v-show="activeTab === 'editor'"
|
|
301
|
-
class="min-h-[60vh]"
|
|
302
|
-
>
|
|
303
|
-
<component
|
|
304
|
-
:is="docTypeDef.component"
|
|
305
|
-
v-if="nodeId && usesPageRenderer"
|
|
306
|
-
:key="`${nodeId}-${currentType}`"
|
|
307
|
-
:doc-id="nodeId"
|
|
308
|
-
:doc-label="nodeLabel || ''"
|
|
309
|
-
:child-provider="childProvider"
|
|
310
|
-
/>
|
|
311
|
-
<AEditor
|
|
312
|
-
v-else-if="nodeId"
|
|
313
|
-
ref="editorRef"
|
|
314
|
-
:doc-id="nodeId"
|
|
315
|
-
:child-provider="childProvider"
|
|
316
|
-
:parent-type="docType"
|
|
317
|
-
:doc-meta="meta"
|
|
318
|
-
class="h-full overflow-auto"
|
|
319
|
-
@update-meta="patchMeta"
|
|
320
|
-
/>
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
<!-- Chat tab -->
|
|
324
|
-
<div
|
|
325
|
-
v-if="showChatTab"
|
|
326
|
-
v-show="activeTab === 'chat'"
|
|
327
|
-
class="min-h-[60vh] -mx-4 -mb-4 sm:-mx-6"
|
|
328
|
-
>
|
|
329
|
-
<ANodeChatPanel
|
|
330
|
-
v-if="nodeId"
|
|
331
|
-
:doc-id="nodeId"
|
|
332
|
-
:label="nodeLabel || 'Chat'"
|
|
333
|
-
/>
|
|
334
|
-
</div>
|
|
335
|
-
|
|
336
|
-
<!-- Settings tab — snapshots wired by default; permissions and
|
|
337
|
-
encryption are app-supplied via slots. -->
|
|
338
|
-
<div
|
|
339
|
-
v-if="showSettingsTab"
|
|
340
|
-
v-show="activeTab === 'settings'"
|
|
341
|
-
class="min-h-[60vh]"
|
|
342
|
-
>
|
|
343
|
-
<ANodeSettingsPanel
|
|
344
|
-
v-if="nodeId"
|
|
345
|
-
:doc-id="nodeId"
|
|
346
|
-
:doc-type-key="currentType"
|
|
347
|
-
:doc-label="nodeLabel"
|
|
348
|
-
:doc-meta="meta"
|
|
349
|
-
@update-meta="patchMeta"
|
|
350
|
-
/>
|
|
351
|
-
</div>
|
|
352
|
-
|
|
353
|
-
<!-- Server-settings tab (opt-in via `tabs`) — hub-doc server status. -->
|
|
354
|
-
<div
|
|
355
|
-
v-if="showServerSettingsTab"
|
|
356
|
-
v-show="activeTab === 'serverSettings'"
|
|
357
|
-
class="min-h-[60vh] -mx-4 -mb-4 sm:-mx-6"
|
|
358
|
-
>
|
|
359
|
-
<ADocPanelServerSettings
|
|
360
|
-
v-if="nodeId"
|
|
361
|
-
:doc-id="nodeId"
|
|
362
|
-
/>
|
|
363
|
-
</div>
|
|
364
|
-
|
|
365
|
-
<!-- Properties tab -->
|
|
366
|
-
<div
|
|
367
|
-
v-show="activeTab === 'properties'"
|
|
368
|
-
class="space-y-4"
|
|
369
|
-
>
|
|
370
|
-
<!-- Document type -->
|
|
371
|
-
<div class="space-y-1">
|
|
372
|
-
<label class="text-xs font-medium text-muted">{{ locale.docType }}</label>
|
|
373
|
-
<ADocTypeSelect
|
|
374
|
-
:model-value="currentType"
|
|
375
|
-
@update:model-value="patchType"
|
|
376
|
-
/>
|
|
377
|
-
</div>
|
|
378
|
-
|
|
379
|
-
<!-- Icon + Color (row) -->
|
|
380
|
-
<div class="grid grid-cols-2 gap-3">
|
|
381
|
-
<div class="space-y-1">
|
|
382
|
-
<label class="text-xs font-medium text-muted">{{ locale.icon }}</label>
|
|
383
|
-
<AIconPicker
|
|
384
|
-
:model-value="meta.icon"
|
|
385
|
-
@update:model-value="patchMeta({ icon: $event })"
|
|
386
|
-
/>
|
|
387
358
|
</div>
|
|
388
|
-
|
|
389
|
-
<label class="text-xs font-medium text-muted">{{ locale.color }}</label>
|
|
390
|
-
<AColorPicker
|
|
391
|
-
:field-key="`node-panel:meta:color:${props.nodeId}`"
|
|
392
|
-
:model-value="meta.color"
|
|
393
|
-
@update:model-value="patchMeta({ color: $event })"
|
|
394
|
-
/>
|
|
395
|
-
</div>
|
|
396
|
-
</div>
|
|
359
|
+
</slot>
|
|
397
360
|
|
|
398
|
-
<!--
|
|
399
|
-
<div class="
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
361
|
+
<!-- Content area -->
|
|
362
|
+
<div class="flex flex-col flex-1 min-h-0 overflow-hidden">
|
|
363
|
+
<!-- Editor tab — swaps to the registered page-type renderer when the
|
|
364
|
+
doc has a non-'doc' type (kanban / table / calendar / etc.). -->
|
|
365
|
+
<div
|
|
366
|
+
v-show="activeTab === 'editor'"
|
|
367
|
+
class="flex flex-col flex-1 min-h-0"
|
|
368
|
+
>
|
|
369
|
+
<component
|
|
370
|
+
:is="docTypeDef.component"
|
|
371
|
+
v-if="nodeId && usesPageRenderer"
|
|
372
|
+
:key="`${nodeId}-${currentType}`"
|
|
373
|
+
:doc-id="nodeId"
|
|
374
|
+
:doc-label="resolvedLabel"
|
|
375
|
+
:child-provider="childProvider"
|
|
376
|
+
class="flex-1 overflow-hidden"
|
|
409
377
|
/>
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
:
|
|
415
|
-
:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
@update
|
|
378
|
+
<AEditor
|
|
379
|
+
v-else-if="nodeId"
|
|
380
|
+
ref="editorRef"
|
|
381
|
+
:doc-id="nodeId"
|
|
382
|
+
:doc-label="resolvedLabel"
|
|
383
|
+
:child-provider="childProvider"
|
|
384
|
+
:parent-type="docType"
|
|
385
|
+
:doc-meta="meta"
|
|
386
|
+
class="flex-1 overflow-y-auto"
|
|
387
|
+
@update-meta="patchMeta"
|
|
420
388
|
/>
|
|
421
389
|
</div>
|
|
422
|
-
</div>
|
|
423
390
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
<UIcon
|
|
436
|
-
name="i-lucide-star"
|
|
437
|
-
class="size-5"
|
|
438
|
-
:class="(meta.rating ?? 0) >= n ? 'text-warning' : 'text-muted'"
|
|
439
|
-
/>
|
|
440
|
-
</button>
|
|
441
|
-
<button
|
|
442
|
-
v-if="meta.rating"
|
|
443
|
-
type="button"
|
|
444
|
-
class="ml-1 text-xs text-muted hover:text-default"
|
|
445
|
-
@click="patchMeta({ rating: void 0 })"
|
|
446
|
-
>
|
|
447
|
-
<UIcon
|
|
448
|
-
name="i-lucide-x"
|
|
449
|
-
class="size-3"
|
|
450
|
-
/>
|
|
451
|
-
</button>
|
|
452
|
-
</div>
|
|
453
|
-
</div>
|
|
454
|
-
|
|
455
|
-
<!-- URL -->
|
|
456
|
-
<div class="space-y-1">
|
|
457
|
-
<label class="text-xs font-medium text-muted">{{ locale.url }}</label>
|
|
458
|
-
<UInput
|
|
459
|
-
type="url"
|
|
460
|
-
:model-value="meta.url ?? ''"
|
|
461
|
-
placeholder="https://…"
|
|
462
|
-
size="sm"
|
|
463
|
-
@update:model-value="patchMeta({ url: $event || void 0 })"
|
|
464
|
-
/>
|
|
465
|
-
</div>
|
|
466
|
-
|
|
467
|
-
<!-- Dates -->
|
|
468
|
-
<div class="space-y-1">
|
|
469
|
-
<label class="text-xs font-medium text-muted">{{ locale.dates }}</label>
|
|
470
|
-
<div class="grid grid-cols-2 gap-2">
|
|
471
|
-
<UInput
|
|
472
|
-
type="date"
|
|
473
|
-
:model-value="meta.dateStart ?? ''"
|
|
474
|
-
size="sm"
|
|
475
|
-
@update:model-value="patchMeta({ dateStart: $event || void 0 })"
|
|
476
|
-
/>
|
|
477
|
-
<UInput
|
|
478
|
-
type="date"
|
|
479
|
-
:model-value="meta.dateEnd ?? ''"
|
|
480
|
-
size="sm"
|
|
481
|
-
@update:model-value="patchMeta({ dateEnd: $event || void 0 })"
|
|
391
|
+
<!-- Chat tab -->
|
|
392
|
+
<div
|
|
393
|
+
v-if="showChatTab"
|
|
394
|
+
v-show="activeTab === 'chat'"
|
|
395
|
+
class="flex flex-col flex-1 min-h-0"
|
|
396
|
+
>
|
|
397
|
+
<ANodeChatPanel
|
|
398
|
+
v-if="nodeId"
|
|
399
|
+
:doc-id="nodeId"
|
|
400
|
+
:label="resolvedLabel || 'Chat'"
|
|
401
|
+
class="flex-1 min-h-0"
|
|
482
402
|
/>
|
|
483
403
|
</div>
|
|
484
|
-
</div>
|
|
485
404
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
class="size-3 ml-1"
|
|
501
|
-
/>
|
|
502
|
-
</UBadge>
|
|
503
|
-
</div>
|
|
504
|
-
<UInput
|
|
505
|
-
v-model="tagInput"
|
|
506
|
-
placeholder="Add tag…"
|
|
507
|
-
size="sm"
|
|
508
|
-
@keydown.enter.prevent="addTag"
|
|
509
|
-
@keydown="($event2) => {
|
|
510
|
-
if ($event2.key === ',') {
|
|
511
|
-
$event2.preventDefault();
|
|
512
|
-
addTag();
|
|
513
|
-
}
|
|
514
|
-
}"
|
|
515
|
-
/>
|
|
516
|
-
</div>
|
|
517
|
-
|
|
518
|
-
<!-- Cover image -->
|
|
519
|
-
<div
|
|
520
|
-
v-if="meta.coverUploadId || meta.coverDocId"
|
|
521
|
-
class="space-y-1"
|
|
522
|
-
>
|
|
523
|
-
<label class="text-xs font-medium text-muted">{{ locale.cover }}</label>
|
|
524
|
-
<div class="flex items-center gap-2 text-xs text-muted">
|
|
525
|
-
<UIcon
|
|
526
|
-
name="i-lucide-image"
|
|
527
|
-
class="size-4 shrink-0"
|
|
528
|
-
/>
|
|
529
|
-
<span class="truncate">{{ meta.coverUploadId ?? meta.coverDocId }}</span>
|
|
530
|
-
<UButton
|
|
531
|
-
icon="i-lucide-x"
|
|
532
|
-
size="xs"
|
|
533
|
-
variant="ghost"
|
|
534
|
-
color="neutral"
|
|
535
|
-
@click="patchMeta({ coverUploadId: void 0, coverDocId: void 0, coverMimeType: void 0 })"
|
|
405
|
+
<!-- Settings tab — snapshots wired by default; permissions and
|
|
406
|
+
encryption are app-supplied via slots. -->
|
|
407
|
+
<div
|
|
408
|
+
v-if="showSettingsTab"
|
|
409
|
+
v-show="activeTab === 'settings'"
|
|
410
|
+
class="flex-1 min-h-0 overflow-y-auto"
|
|
411
|
+
>
|
|
412
|
+
<ANodeSettingsPanel
|
|
413
|
+
v-if="nodeId"
|
|
414
|
+
:doc-id="nodeId"
|
|
415
|
+
:doc-type-key="currentType"
|
|
416
|
+
:doc-label="resolvedLabel"
|
|
417
|
+
:doc-meta="meta"
|
|
418
|
+
@update-meta="patchMeta"
|
|
536
419
|
/>
|
|
537
420
|
</div>
|
|
538
|
-
</div>
|
|
539
421
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
color="neutral"
|
|
551
|
-
/>
|
|
552
|
-
</UDropdownMenu>
|
|
422
|
+
<!-- Server-settings tab (opt-in via `tabs`) — hub-doc server status. -->
|
|
423
|
+
<div
|
|
424
|
+
v-if="showServerSettingsTab"
|
|
425
|
+
v-show="activeTab === 'serverSettings'"
|
|
426
|
+
class="flex-1 min-h-0 overflow-y-auto"
|
|
427
|
+
>
|
|
428
|
+
<ADocPanelServerSettings
|
|
429
|
+
v-if="nodeId"
|
|
430
|
+
:doc-id="nodeId"
|
|
431
|
+
/>
|
|
553
432
|
</div>
|
|
554
433
|
|
|
434
|
+
<!-- Properties tab -->
|
|
555
435
|
<div
|
|
556
|
-
v-
|
|
557
|
-
|
|
558
|
-
class="space-y-1"
|
|
436
|
+
v-show="activeTab === 'properties'"
|
|
437
|
+
class="flex-1 min-h-0 overflow-y-auto p-4 space-y-4"
|
|
559
438
|
>
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
<
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
color="neutral"
|
|
567
|
-
class="opacity-40 hover:opacity-100"
|
|
568
|
-
@click="removeCustomField(field.id)"
|
|
439
|
+
<!-- Document type -->
|
|
440
|
+
<div class="space-y-1">
|
|
441
|
+
<label class="text-xs font-medium text-muted">{{ locale.docType }}</label>
|
|
442
|
+
<ADocTypeSelect
|
|
443
|
+
:model-value="currentType"
|
|
444
|
+
@update:model-value="patchType"
|
|
569
445
|
/>
|
|
570
446
|
</div>
|
|
571
447
|
|
|
572
|
-
<!--
|
|
573
|
-
<
|
|
574
|
-
<
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
448
|
+
<!-- Icon + Color (row) -->
|
|
449
|
+
<div class="grid grid-cols-2 gap-3">
|
|
450
|
+
<div class="space-y-1">
|
|
451
|
+
<label class="text-xs font-medium text-muted">{{ locale.icon }}</label>
|
|
452
|
+
<AIconPicker
|
|
453
|
+
:model-value="meta.icon"
|
|
454
|
+
@update:model-value="patchMeta({ icon: $event })"
|
|
455
|
+
/>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="space-y-1">
|
|
458
|
+
<label class="text-xs font-medium text-muted">{{ locale.color }}</label>
|
|
459
|
+
<AColorPicker
|
|
460
|
+
:field-key="`node-panel:meta:color:${props.nodeId}`"
|
|
461
|
+
:model-value="meta.color"
|
|
462
|
+
@update:model-value="patchMeta({ color: $event })"
|
|
463
|
+
/>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
589
466
|
|
|
590
|
-
<!--
|
|
591
|
-
<
|
|
592
|
-
<div class="
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
:
|
|
596
|
-
:
|
|
467
|
+
<!-- Status + Priority (row) -->
|
|
468
|
+
<div class="grid grid-cols-2 gap-3">
|
|
469
|
+
<div class="space-y-1">
|
|
470
|
+
<label class="text-xs font-medium text-muted">{{ locale.status }}</label>
|
|
471
|
+
<USelect
|
|
472
|
+
:model-value="meta.status || 'none'"
|
|
473
|
+
:items="statusOptions"
|
|
474
|
+
value-key="value"
|
|
475
|
+
label-key="label"
|
|
597
476
|
size="sm"
|
|
598
|
-
|
|
599
|
-
@update:model-value="setCustomFieldValue(field, $event)"
|
|
477
|
+
@update:model-value="patchMeta({ status: $event === 'none' ? void 0 : $event })"
|
|
600
478
|
/>
|
|
601
|
-
<span class="text-xs text-muted w-8 text-right">{{ getCustomFieldValue(field) ?? field.min ?? 0 }}</span>
|
|
602
479
|
</div>
|
|
603
|
-
|
|
480
|
+
<div class="space-y-1">
|
|
481
|
+
<label class="text-xs font-medium text-muted">{{ locale.priority }}</label>
|
|
482
|
+
<USelect
|
|
483
|
+
:model-value="meta.priority ? String(meta.priority) : 'none'"
|
|
484
|
+
:items="priorityOptions"
|
|
485
|
+
value-key="value"
|
|
486
|
+
label-key="label"
|
|
487
|
+
size="sm"
|
|
488
|
+
@update:model-value="patchMeta({ priority: $event === 'none' ? void 0 : Number($event) })"
|
|
489
|
+
/>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
604
492
|
|
|
605
493
|
<!-- Rating -->
|
|
606
|
-
<
|
|
494
|
+
<div class="space-y-1">
|
|
495
|
+
<label class="text-xs font-medium text-muted">{{ locale.rating }}</label>
|
|
607
496
|
<div class="flex items-center gap-1">
|
|
608
497
|
<button
|
|
609
|
-
v-for="n in
|
|
498
|
+
v-for="n in 5"
|
|
610
499
|
:key="n"
|
|
611
500
|
type="button"
|
|
612
|
-
class="focus:outline-none"
|
|
613
|
-
@click="
|
|
501
|
+
class="transition-colors focus:outline-none"
|
|
502
|
+
@click="patchMeta({ rating: meta.rating === n ? void 0 : n })"
|
|
614
503
|
>
|
|
615
504
|
<UIcon
|
|
616
505
|
name="i-lucide-star"
|
|
617
|
-
class="size-
|
|
618
|
-
:class="
|
|
506
|
+
class="size-5"
|
|
507
|
+
:class="(meta.rating ?? 0) >= n ? 'text-warning' : 'text-muted'"
|
|
508
|
+
/>
|
|
509
|
+
</button>
|
|
510
|
+
<button
|
|
511
|
+
v-if="meta.rating"
|
|
512
|
+
type="button"
|
|
513
|
+
class="ml-1 text-xs text-muted hover:text-default"
|
|
514
|
+
@click="patchMeta({ rating: void 0 })"
|
|
515
|
+
>
|
|
516
|
+
<UIcon
|
|
517
|
+
name="i-lucide-x"
|
|
518
|
+
class="size-3"
|
|
619
519
|
/>
|
|
620
520
|
</button>
|
|
621
521
|
</div>
|
|
622
|
-
</
|
|
522
|
+
</div>
|
|
623
523
|
|
|
624
|
-
<!--
|
|
625
|
-
<
|
|
524
|
+
<!-- URL -->
|
|
525
|
+
<div class="space-y-1">
|
|
526
|
+
<label class="text-xs font-medium text-muted">{{ locale.url }}</label>
|
|
626
527
|
<UInput
|
|
627
|
-
|
|
628
|
-
:model-value="
|
|
528
|
+
type="url"
|
|
529
|
+
:model-value="meta.url ?? ''"
|
|
530
|
+
placeholder="https://…"
|
|
629
531
|
size="sm"
|
|
630
|
-
@update:model-value="
|
|
532
|
+
@update:model-value="patchMeta({ url: $event || void 0 })"
|
|
631
533
|
/>
|
|
632
|
-
</
|
|
534
|
+
</div>
|
|
633
535
|
|
|
634
|
-
<!--
|
|
635
|
-
<
|
|
536
|
+
<!-- Dates -->
|
|
537
|
+
<div class="space-y-1">
|
|
538
|
+
<label class="text-xs font-medium text-muted">{{ locale.dates }}</label>
|
|
636
539
|
<div class="grid grid-cols-2 gap-2">
|
|
637
540
|
<UInput
|
|
638
|
-
|
|
639
|
-
:model-value="
|
|
541
|
+
type="date"
|
|
542
|
+
:model-value="meta.dateStart ?? ''"
|
|
640
543
|
size="sm"
|
|
641
|
-
@update:model-value="
|
|
544
|
+
@update:model-value="patchMeta({ dateStart: $event || void 0 })"
|
|
642
545
|
/>
|
|
643
546
|
<UInput
|
|
644
|
-
|
|
645
|
-
:model-value="
|
|
547
|
+
type="date"
|
|
548
|
+
:model-value="meta.dateEnd ?? ''"
|
|
646
549
|
size="sm"
|
|
647
|
-
@update:model-value="
|
|
550
|
+
@update:model-value="patchMeta({ dateEnd: $event || void 0 })"
|
|
648
551
|
/>
|
|
649
552
|
</div>
|
|
650
|
-
</
|
|
651
|
-
|
|
652
|
-
<!-- Textarea -->
|
|
653
|
-
<template v-else-if="field.type === 'textarea'">
|
|
654
|
-
<UTextarea
|
|
655
|
-
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
656
|
-
:rows="3"
|
|
657
|
-
size="sm"
|
|
658
|
-
@update:model-value="setCustomFieldValue(field, $event || void 0)"
|
|
659
|
-
/>
|
|
660
|
-
</template>
|
|
661
|
-
|
|
662
|
-
<!-- URL -->
|
|
663
|
-
<template v-else-if="field.type === 'url'">
|
|
664
|
-
<UInput
|
|
665
|
-
type="url"
|
|
666
|
-
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
667
|
-
placeholder="https://…"
|
|
668
|
-
size="sm"
|
|
669
|
-
@update:model-value="setCustomFieldValue(field, $event || void 0)"
|
|
670
|
-
/>
|
|
671
|
-
</template>
|
|
553
|
+
</div>
|
|
672
554
|
|
|
673
555
|
<!-- Tags -->
|
|
674
|
-
<
|
|
675
|
-
<
|
|
556
|
+
<div class="space-y-1">
|
|
557
|
+
<label class="text-xs font-medium text-muted">Tags</label>
|
|
558
|
+
<div class="flex flex-wrap gap-1 mb-1">
|
|
676
559
|
<UBadge
|
|
677
|
-
v-for="
|
|
678
|
-
:key="
|
|
560
|
+
v-for="tag in tags"
|
|
561
|
+
:key="tag"
|
|
679
562
|
variant="soft"
|
|
680
563
|
class="cursor-pointer"
|
|
681
|
-
@click="
|
|
564
|
+
@click="removeTag(tag)"
|
|
682
565
|
>
|
|
683
|
-
{{
|
|
566
|
+
{{ tag }}
|
|
567
|
+
<UIcon
|
|
684
568
|
name="i-lucide-x"
|
|
685
569
|
class="size-3 ml-1"
|
|
686
570
|
/>
|
|
687
571
|
</UBadge>
|
|
688
572
|
</div>
|
|
689
|
-
</template>
|
|
690
|
-
|
|
691
|
-
<!-- Icon -->
|
|
692
|
-
<template v-else-if="field.type === 'icon'">
|
|
693
|
-
<AIconPicker
|
|
694
|
-
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
695
|
-
@update:model-value="setCustomFieldValue(field, $event)"
|
|
696
|
-
/>
|
|
697
|
-
</template>
|
|
698
|
-
|
|
699
|
-
<!-- Color Preset / Color Picker -->
|
|
700
|
-
<template v-else-if="['colorPreset', 'colorPicker'].includes(field.type)">
|
|
701
|
-
<AColorPicker
|
|
702
|
-
:field-key="`node-panel:custom:${props.nodeId}:${field.key}`"
|
|
703
|
-
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
704
|
-
@update:model-value="setCustomFieldValue(field, $event)"
|
|
705
|
-
/>
|
|
706
|
-
</template>
|
|
707
|
-
|
|
708
|
-
<!-- Fallback: text input -->
|
|
709
|
-
<template v-else>
|
|
710
573
|
<UInput
|
|
711
|
-
|
|
574
|
+
v-model="tagInput"
|
|
575
|
+
placeholder="Add tag…"
|
|
712
576
|
size="sm"
|
|
713
|
-
@
|
|
577
|
+
@keydown.enter.prevent="addTag"
|
|
578
|
+
@keydown="($event2) => {
|
|
579
|
+
if ($event2.key === ',') {
|
|
580
|
+
$event2.preventDefault();
|
|
581
|
+
addTag();
|
|
582
|
+
}
|
|
583
|
+
}"
|
|
714
584
|
/>
|
|
715
|
-
</
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<!-- Cover image -->
|
|
588
|
+
<div
|
|
589
|
+
v-if="meta.coverUploadId || meta.coverDocId"
|
|
590
|
+
class="space-y-1"
|
|
591
|
+
>
|
|
592
|
+
<label class="text-xs font-medium text-muted">{{ locale.cover }}</label>
|
|
593
|
+
<div class="flex items-center gap-2 text-xs text-muted">
|
|
594
|
+
<UIcon
|
|
595
|
+
name="i-lucide-image"
|
|
596
|
+
class="size-4 shrink-0"
|
|
597
|
+
/>
|
|
598
|
+
<span class="truncate">{{ meta.coverUploadId ?? meta.coverDocId }}</span>
|
|
599
|
+
<UButton
|
|
600
|
+
icon="i-lucide-x"
|
|
601
|
+
size="xs"
|
|
602
|
+
variant="ghost"
|
|
603
|
+
color="neutral"
|
|
604
|
+
@click="patchMeta({ coverUploadId: void 0, coverDocId: void 0, coverMimeType: void 0 })"
|
|
605
|
+
/>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<!-- Custom fields -->
|
|
610
|
+
<div class="space-y-2">
|
|
611
|
+
<div class="flex items-center justify-between">
|
|
612
|
+
<label class="text-xs font-medium text-muted">{{ locale.customFields }}</label>
|
|
613
|
+
<UDropdownMenu :items="addFieldMenuItems">
|
|
614
|
+
<UButton
|
|
615
|
+
:label="locale.addField"
|
|
616
|
+
icon="i-lucide-plus"
|
|
617
|
+
size="xs"
|
|
618
|
+
variant="ghost"
|
|
619
|
+
color="neutral"
|
|
620
|
+
/>
|
|
621
|
+
</UDropdownMenu>
|
|
622
|
+
</div>
|
|
623
|
+
|
|
624
|
+
<div
|
|
625
|
+
v-for="field in customFields"
|
|
626
|
+
:key="field.id"
|
|
627
|
+
class="space-y-1"
|
|
628
|
+
>
|
|
629
|
+
<div class="flex items-center justify-between">
|
|
630
|
+
<label class="text-xs font-medium text-muted">{{ field.label || field.type }}</label>
|
|
631
|
+
<UButton
|
|
632
|
+
icon="i-lucide-trash-2"
|
|
633
|
+
size="xs"
|
|
634
|
+
variant="ghost"
|
|
635
|
+
color="neutral"
|
|
636
|
+
class="opacity-40 hover:opacity-100"
|
|
637
|
+
@click="removeCustomField(field.id)"
|
|
638
|
+
/>
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
<!-- Toggle -->
|
|
642
|
+
<template v-if="field.type === 'toggle'">
|
|
643
|
+
<USwitch
|
|
644
|
+
:model-value="!!getCustomFieldValue(field)"
|
|
645
|
+
@update:model-value="setCustomFieldValue(field, $event)"
|
|
646
|
+
/>
|
|
647
|
+
</template>
|
|
648
|
+
|
|
649
|
+
<!-- Number -->
|
|
650
|
+
<template v-else-if="field.type === 'number'">
|
|
651
|
+
<UInput
|
|
652
|
+
type="number"
|
|
653
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
654
|
+
size="sm"
|
|
655
|
+
@update:model-value="setCustomFieldValue(field, $event ? Number($event) : void 0)"
|
|
656
|
+
/>
|
|
657
|
+
</template>
|
|
658
|
+
|
|
659
|
+
<!-- Slider / Progress -->
|
|
660
|
+
<template v-else-if="field.type === 'slider'">
|
|
661
|
+
<div class="flex items-center gap-2">
|
|
662
|
+
<USlider
|
|
663
|
+
:model-value="Number(getCustomFieldValue(field) ?? field.min ?? 0)"
|
|
664
|
+
:min="field.min ?? 0"
|
|
665
|
+
:max="field.max ?? 100"
|
|
666
|
+
size="sm"
|
|
667
|
+
class="flex-1"
|
|
668
|
+
@update:model-value="setCustomFieldValue(field, $event)"
|
|
669
|
+
/>
|
|
670
|
+
<span class="text-xs text-muted w-8 text-right">{{ getCustomFieldValue(field) ?? field.min ?? 0 }}</span>
|
|
671
|
+
</div>
|
|
672
|
+
</template>
|
|
673
|
+
|
|
674
|
+
<!-- Rating -->
|
|
675
|
+
<template v-else-if="field.type === 'rating'">
|
|
676
|
+
<div class="flex items-center gap-1">
|
|
677
|
+
<button
|
|
678
|
+
v-for="n in field.max ?? 5"
|
|
679
|
+
:key="n"
|
|
680
|
+
type="button"
|
|
681
|
+
class="focus:outline-none"
|
|
682
|
+
@click="setCustomFieldValue(field, getCustomFieldValue(field) === n ? void 0 : n)"
|
|
683
|
+
>
|
|
684
|
+
<UIcon
|
|
685
|
+
name="i-lucide-star"
|
|
686
|
+
class="size-4"
|
|
687
|
+
:class="Number(getCustomFieldValue(field) ?? 0) >= n ? 'text-warning' : 'text-muted'"
|
|
688
|
+
/>
|
|
689
|
+
</button>
|
|
690
|
+
</div>
|
|
691
|
+
</template>
|
|
692
|
+
|
|
693
|
+
<!-- Date / Datetime / Time (single) -->
|
|
694
|
+
<template v-else-if="['date', 'datetime', 'time'].includes(field.type)">
|
|
695
|
+
<UInput
|
|
696
|
+
:type="field.type === 'datetime' ? 'datetime-local' : field.type"
|
|
697
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
698
|
+
size="sm"
|
|
699
|
+
@update:model-value="setCustomFieldValue(field, $event || void 0)"
|
|
700
|
+
/>
|
|
701
|
+
</template>
|
|
702
|
+
|
|
703
|
+
<!-- Date range / Time range / Datetime range -->
|
|
704
|
+
<template v-else-if="['daterange', 'timerange', 'datetimerange'].includes(field.type)">
|
|
705
|
+
<div class="grid grid-cols-2 gap-2">
|
|
706
|
+
<UInput
|
|
707
|
+
:type="field.type === 'datetimerange' ? 'datetime-local' : field.type === 'timerange' ? 'time' : 'date'"
|
|
708
|
+
:model-value="field.startKey ? String(meta[field.startKey] ?? '') : ''"
|
|
709
|
+
size="sm"
|
|
710
|
+
@update:model-value="field.startKey && patchMeta({ [field.startKey]: $event || void 0 })"
|
|
711
|
+
/>
|
|
712
|
+
<UInput
|
|
713
|
+
:type="field.type === 'datetimerange' ? 'datetime-local' : field.type === 'timerange' ? 'time' : 'date'"
|
|
714
|
+
:model-value="field.endKey ? String(meta[field.endKey] ?? '') : ''"
|
|
715
|
+
size="sm"
|
|
716
|
+
@update:model-value="field.endKey && patchMeta({ [field.endKey]: $event || void 0 })"
|
|
717
|
+
/>
|
|
718
|
+
</div>
|
|
719
|
+
</template>
|
|
720
|
+
|
|
721
|
+
<!-- Textarea -->
|
|
722
|
+
<template v-else-if="field.type === 'textarea'">
|
|
723
|
+
<UTextarea
|
|
724
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
725
|
+
:rows="3"
|
|
726
|
+
size="sm"
|
|
727
|
+
@update:model-value="setCustomFieldValue(field, $event || void 0)"
|
|
728
|
+
/>
|
|
729
|
+
</template>
|
|
730
|
+
|
|
731
|
+
<!-- URL -->
|
|
732
|
+
<template v-else-if="field.type === 'url'">
|
|
733
|
+
<UInput
|
|
734
|
+
type="url"
|
|
735
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
736
|
+
placeholder="https://…"
|
|
737
|
+
size="sm"
|
|
738
|
+
@update:model-value="setCustomFieldValue(field, $event || void 0)"
|
|
739
|
+
/>
|
|
740
|
+
</template>
|
|
741
|
+
|
|
742
|
+
<!-- Tags -->
|
|
743
|
+
<template v-else-if="field.type === 'tags'">
|
|
744
|
+
<div class="flex flex-wrap gap-1">
|
|
745
|
+
<UBadge
|
|
746
|
+
v-for="t in Array.isArray(getCustomFieldValue(field)) ? getCustomFieldValue(field) : []"
|
|
747
|
+
:key="t"
|
|
748
|
+
variant="soft"
|
|
749
|
+
class="cursor-pointer"
|
|
750
|
+
@click="setCustomFieldValue(field, getCustomFieldValue(field).filter((x) => x !== t))"
|
|
751
|
+
>
|
|
752
|
+
{{ t }}<UIcon
|
|
753
|
+
name="i-lucide-x"
|
|
754
|
+
class="size-3 ml-1"
|
|
755
|
+
/>
|
|
756
|
+
</UBadge>
|
|
757
|
+
</div>
|
|
758
|
+
</template>
|
|
759
|
+
|
|
760
|
+
<!-- Icon -->
|
|
761
|
+
<template v-else-if="field.type === 'icon'">
|
|
762
|
+
<AIconPicker
|
|
763
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
764
|
+
@update:model-value="setCustomFieldValue(field, $event)"
|
|
765
|
+
/>
|
|
766
|
+
</template>
|
|
767
|
+
|
|
768
|
+
<!-- Color Preset / Color Picker -->
|
|
769
|
+
<template v-else-if="['colorPreset', 'colorPicker'].includes(field.type)">
|
|
770
|
+
<AColorPicker
|
|
771
|
+
:field-key="`node-panel:custom:${props.nodeId}:${field.key}`"
|
|
772
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
773
|
+
@update:model-value="setCustomFieldValue(field, $event)"
|
|
774
|
+
/>
|
|
775
|
+
</template>
|
|
776
|
+
|
|
777
|
+
<!-- Fallback: text input -->
|
|
778
|
+
<template v-else>
|
|
779
|
+
<UInput
|
|
780
|
+
:model-value="String(getCustomFieldValue(field) ?? '')"
|
|
781
|
+
size="sm"
|
|
782
|
+
@update:model-value="setCustomFieldValue(field, $event || void 0)"
|
|
783
|
+
/>
|
|
784
|
+
</template>
|
|
785
|
+
</div>
|
|
786
|
+
</div>
|
|
716
787
|
</div>
|
|
717
|
-
</div>
|
|
718
|
-
</div>
|
|
719
788
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
789
|
+
<!-- Plugin slots -->
|
|
790
|
+
<template
|
|
791
|
+
v-for="slot in pluginSlots"
|
|
792
|
+
:key="slot.id"
|
|
793
|
+
>
|
|
794
|
+
<div
|
|
795
|
+
v-show="activeTab === slot.id"
|
|
796
|
+
class="flex-1 min-h-0 overflow-y-auto p-4"
|
|
797
|
+
>
|
|
798
|
+
<component
|
|
799
|
+
:is="slot.component"
|
|
800
|
+
v-if="nodeId"
|
|
801
|
+
:child-id="nodeId"
|
|
802
|
+
:child-doc="childProvider?.document ?? null"
|
|
803
|
+
:parent-doc-id="nodeId"
|
|
804
|
+
:parent-type="docType"
|
|
805
|
+
:meta="meta"
|
|
806
|
+
:tree="tree"
|
|
807
|
+
/>
|
|
808
|
+
</div>
|
|
809
|
+
</template>
|
|
736
810
|
</div>
|
|
737
|
-
</
|
|
811
|
+
</div>
|
|
738
812
|
</template>
|
|
739
813
|
</USlideover>
|
|
740
814
|
</template>
|