@abraca/nuxt 2.13.0 → 2.15.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.d.mts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -0
- package/dist/runtime/assets/editor.css +3 -1
- package/dist/runtime/components/ACodeEditor.vue +138 -23
- package/dist/runtime/components/ADocViewToggle.d.vue.ts +40 -0
- package/dist/runtime/components/ADocViewToggle.vue +234 -0
- package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
- package/dist/runtime/components/ADocumentTree.vue +1 -1
- package/dist/runtime/components/AEditor.vue +183 -15
- package/dist/runtime/components/ANodePanel.vue +91 -91
- 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/aware/ASlider.d.vue.ts +1 -1
- package/dist/runtime/components/aware/ASlider.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/AColorPalettePopover.vue +97 -5
- package/dist/runtime/components/editor/ADocSuggestMenu.d.vue.ts +7 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
- package/dist/runtime/components/editor/AIconPickerPopover.vue +81 -3
- package/dist/runtime/components/editor/AMetaNumberStepper.d.vue.ts +40 -0
- package/dist/runtime/components/editor/AMetaNumberStepper.vue +214 -0
- package/dist/runtime/components/editor/AMetaNumberStepper.vue.d.ts +40 -0
- package/dist/runtime/components/renderers/ACalendarRenderer.vue +7 -1
- 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/composables/useDocLinkPick.d.ts +9 -8
- package/dist/runtime/composables/useDocLinkPick.js +7 -18
- package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
- package/dist/runtime/composables/useDocSuggest.js +56 -0
- package/dist/runtime/composables/useTouchDrag.d.ts +21 -4
- package/dist/runtime/composables/useTouchDrag.js +30 -0
- package/dist/runtime/extensions/doc-link-drop.js +2 -2
- package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
- package/dist/runtime/extensions/doc-suggest.js +85 -0
- package/dist/runtime/extensions/views/MetaFieldView.vue +17 -28
- package/dist/runtime/utils/codeHighlightStyle.d.ts +15 -0
- package/dist/runtime/utils/codeHighlightStyle.js +34 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
- package/dist/runtime/utils/loadCodeMirror.js +6 -3
- package/package.json +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { shallowRef, ref, watch, watchEffect, computed, nextTick, onBeforeUnmount } from "vue";
|
|
3
|
-
import { useNuxtApp } from "#imports";
|
|
3
|
+
import { useNuxtApp, navigateTo, useRuntimeConfig } from "#imports";
|
|
4
4
|
import { Fragment } from "@tiptap/pm/model";
|
|
5
5
|
import { useSyncedMap } from "../composables/useYDoc";
|
|
6
6
|
import { resolveDocType, GEO_TYPE_META_SCHEMAS } from "../utils/docTypes";
|
|
@@ -10,6 +10,15 @@ import { useEditor } from "../composables/useEditor";
|
|
|
10
10
|
import { useDocTree } from "../composables/useDocTree";
|
|
11
11
|
import { useDocBreadcrumb } from "../composables/useDocBreadcrumb";
|
|
12
12
|
import { useDocSlugs } from "../composables/useDocSlugs";
|
|
13
|
+
import { useChildTree } from "../composables/useChildTree";
|
|
14
|
+
import { useFavorites } from "../composables/useFavorites";
|
|
15
|
+
import { useWindowManager } from "../composables/useWindowManager";
|
|
16
|
+
import { useDocDragBus } from "../composables/useDocDragBus";
|
|
17
|
+
import { useDocLinkPick } from "../composables/useDocLinkPick";
|
|
18
|
+
import { makeDocSearch } from "../composables/useDocSuggest";
|
|
19
|
+
import { DocSuggest } from "../extensions/doc-suggest";
|
|
20
|
+
import { useNodeContextMenu } from "../composables/useNodeContextMenu";
|
|
21
|
+
import { DOC_DRAG_MIME, isDocDrag, parseDocDragPayload, isDescendantInMap } from "../utils/docDragDrop";
|
|
13
22
|
import { useEditorToolbar } from "../composables/useEditorToolbar";
|
|
14
23
|
import { useEditorSuggestions } from "../composables/useEditorSuggestions";
|
|
15
24
|
import { useEditorDragHandle } from "../composables/useEditorDragHandle";
|
|
@@ -17,6 +26,8 @@ import { useEditorEmojis } from "../composables/useEditorEmojis";
|
|
|
17
26
|
import { useEditorMentions } from "../composables/useEditorMentions";
|
|
18
27
|
import ALinkPopover from "./editor/ALinkPopover.vue";
|
|
19
28
|
import ADocLinkPopover from "./editor/ADocLinkPopover.vue";
|
|
29
|
+
import ANodeContextMenu from "./shell/ANodeContextMenu.vue";
|
|
30
|
+
import ADocSuggestMenu from "./editor/ADocSuggestMenu.vue";
|
|
20
31
|
import ACodeEditor from "./ACodeEditor.vue";
|
|
21
32
|
const props = defineProps({
|
|
22
33
|
docId: { type: String, required: true },
|
|
@@ -52,9 +63,26 @@ watch(provider, async (prov) => {
|
|
|
52
63
|
onBeforeUnmount(() => {
|
|
53
64
|
provider.value?.unpinChild?.(props.docId);
|
|
54
65
|
});
|
|
66
|
+
const docSuggestEnabled = (useRuntimeConfig().public?.abracadabra?.docPicker ?? "inline") === "inline";
|
|
67
|
+
const _suggestTree = useChildTree(doc, props.docId);
|
|
68
|
+
const docSuggestExt = DocSuggest.configure({
|
|
69
|
+
search: makeDocSearch(_suggestTree, props.docId),
|
|
70
|
+
onCommit: (item, mode, range, query) => {
|
|
71
|
+
const ed = editorRef.value?.editor;
|
|
72
|
+
if (!ed || !props.editable) return;
|
|
73
|
+
let docId = item.id;
|
|
74
|
+
if (item.isCreate) docId = _suggestTree.createChild(props.docId, query.trim() || "Untitled", "doc");
|
|
75
|
+
if (!docId) return;
|
|
76
|
+
const chain = ed.chain().focus().deleteRange(range);
|
|
77
|
+
if (mode === "embed") chain.insertDocEmbed({ docId });
|
|
78
|
+
else chain.insertDocLink({ docId });
|
|
79
|
+
chain.run();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
55
82
|
const { extensions, connectedUsers, ready } = useEditor({
|
|
56
83
|
childProvider,
|
|
57
|
-
docId: props.docId
|
|
84
|
+
docId: props.docId,
|
|
85
|
+
extraExtensions: docSuggestEnabled ? [docSuggestExt] : []
|
|
58
86
|
});
|
|
59
87
|
watch(ready, (val) => {
|
|
60
88
|
if (val) emit("ready");
|
|
@@ -81,7 +109,7 @@ const resolvedMetaSchema = computed(() => {
|
|
|
81
109
|
});
|
|
82
110
|
const { items: allSuggestionItems, propertiesOnlyItems } = useEditorSuggestions({ docId: props.docId });
|
|
83
111
|
const { items: emojiItems } = useEditorEmojis();
|
|
84
|
-
const { items: mentionItems } = useEditorMentions({ docId: props.docId });
|
|
112
|
+
const { items: mentionItems } = useEditorMentions({ docId: props.docId, includeDocuments: false });
|
|
85
113
|
const isInDocumentMeta = ref(false);
|
|
86
114
|
watchEffect((onCleanup) => {
|
|
87
115
|
const ed = editorRef.value?.editor;
|
|
@@ -263,7 +291,34 @@ function insertNode(editor, type, attrs, content) {
|
|
|
263
291
|
if (content) node.content = content;
|
|
264
292
|
return editor.chain().focus().insertContent(node);
|
|
265
293
|
}
|
|
294
|
+
const { pickDoc } = useDocLinkPick();
|
|
266
295
|
const editorHandlers = {
|
|
296
|
+
// Slash → pick a document → embed it as a block. `execute` returns a chain
|
|
297
|
+
// synchronously (the dispatcher .run()s it); the actual insert happens async
|
|
298
|
+
// once the picker resolves.
|
|
299
|
+
docEmbed: {
|
|
300
|
+
canExecute: () => true,
|
|
301
|
+
execute: (editor) => {
|
|
302
|
+
pickDoc().then((result) => {
|
|
303
|
+
if (!result) return;
|
|
304
|
+
editor.chain().focus().insertDocEmbed({ docId: result.id }).run();
|
|
305
|
+
});
|
|
306
|
+
return editor.chain().focus();
|
|
307
|
+
},
|
|
308
|
+
isActive: (editor) => editor.isActive("docEmbed")
|
|
309
|
+
},
|
|
310
|
+
// Slash/drag-handle → pick a document → insert an inline link to it.
|
|
311
|
+
docLink: {
|
|
312
|
+
canExecute: () => true,
|
|
313
|
+
execute: (editor) => {
|
|
314
|
+
pickDoc().then((result) => {
|
|
315
|
+
if (!result) return;
|
|
316
|
+
editor.chain().focus().insertDocLink({ docId: result.id }).run();
|
|
317
|
+
});
|
|
318
|
+
return editor.chain().focus();
|
|
319
|
+
},
|
|
320
|
+
isActive: (editor) => editor.isActive("docLink")
|
|
321
|
+
},
|
|
267
322
|
insertMetaField: {
|
|
268
323
|
canExecute: () => true,
|
|
269
324
|
execute: (editor, item) => editor.chain().focus().insertMetaField(item.attrs),
|
|
@@ -378,8 +433,101 @@ const _mentionItems = computed(
|
|
|
378
433
|
const { items: _breadcrumbItems } = useDocBreadcrumb(() => props.docId);
|
|
379
434
|
const { getDocUrl } = useDocSlugs();
|
|
380
435
|
const ancestorChain = computed(
|
|
381
|
-
() => _breadcrumbItems.value.slice(0, -1).map((a) =>
|
|
436
|
+
() => _breadcrumbItems.value.slice(0, -1).map((a) => {
|
|
437
|
+
const entry = docTree.getEntry(a.id);
|
|
438
|
+
return { ...a, type: entry?.type, to: getDocUrl(a.id) };
|
|
439
|
+
})
|
|
382
440
|
);
|
|
441
|
+
const bcTree = useChildTree(doc, props.docId);
|
|
442
|
+
const { isFavorite, toggleFavorite } = useFavorites(doc);
|
|
443
|
+
const docDragBus = useDocDragBus();
|
|
444
|
+
const breadcrumbDragId = ref(null);
|
|
445
|
+
const breadcrumbDropTargetId = ref(null);
|
|
446
|
+
function onBcDragStart(e, a) {
|
|
447
|
+
if (!props.editable) return;
|
|
448
|
+
breadcrumbDragId.value = a.id;
|
|
449
|
+
if (e.dataTransfer) {
|
|
450
|
+
e.dataTransfer.effectAllowed = "move";
|
|
451
|
+
e.dataTransfer.setData("text/plain", a.id);
|
|
452
|
+
e.dataTransfer.setData(DOC_DRAG_MIME, JSON.stringify({ id: a.id, label: a.label }));
|
|
453
|
+
}
|
|
454
|
+
docDragBus.startDrag({
|
|
455
|
+
docIds: [a.id],
|
|
456
|
+
labels: [a.label],
|
|
457
|
+
icons: a.icon ? [a.icon] : void 0,
|
|
458
|
+
source: "renderer",
|
|
459
|
+
sourceDocId: props.docId,
|
|
460
|
+
pointer: { x: e.clientX, y: e.clientY }
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function onBcDragEnd() {
|
|
464
|
+
breadcrumbDragId.value = null;
|
|
465
|
+
breadcrumbDropTargetId.value = null;
|
|
466
|
+
docDragBus.endDrag();
|
|
467
|
+
}
|
|
468
|
+
function onBcDragOver(e, ancestorId) {
|
|
469
|
+
if (!isDocDrag(e)) return;
|
|
470
|
+
if (breadcrumbDragId.value === ancestorId) return;
|
|
471
|
+
e.preventDefault();
|
|
472
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
|
473
|
+
breadcrumbDropTargetId.value = ancestorId;
|
|
474
|
+
}
|
|
475
|
+
function onBcDragLeave(e) {
|
|
476
|
+
const related = e.relatedTarget;
|
|
477
|
+
if (related && e.currentTarget.contains(related)) return;
|
|
478
|
+
breadcrumbDropTargetId.value = null;
|
|
479
|
+
}
|
|
480
|
+
function onBcDrop(e, ancestorId) {
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
e.stopPropagation();
|
|
483
|
+
breadcrumbDropTargetId.value = null;
|
|
484
|
+
if (!props.editable) return;
|
|
485
|
+
const payload = parseDocDragPayload(e);
|
|
486
|
+
if (!payload) return;
|
|
487
|
+
if (payload.id === ancestorId) return;
|
|
488
|
+
if (isDescendantInMap(bcTree.treeMap.data, payload.id, ancestorId)) return;
|
|
489
|
+
bcTree.moveEntry(payload.id, ancestorId, Date.now());
|
|
490
|
+
breadcrumbDragId.value = null;
|
|
491
|
+
docDragBus.endDrag();
|
|
492
|
+
}
|
|
493
|
+
async function bcOpenAsWindow(id, label) {
|
|
494
|
+
const wm = useWindowManager();
|
|
495
|
+
if (wm.windows.has(id)) {
|
|
496
|
+
wm.focusWindow(id);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const childProv = await provider.value?.loadChild(id);
|
|
500
|
+
if (!childProv) return;
|
|
501
|
+
if (!childProv.isSynced) {
|
|
502
|
+
await new Promise((resolve) => {
|
|
503
|
+
const done = () => {
|
|
504
|
+
childProv.off("synced", done);
|
|
505
|
+
resolve();
|
|
506
|
+
};
|
|
507
|
+
childProv.on("synced", done);
|
|
508
|
+
setTimeout(resolve, 6e3);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const entry = bcTree.treeMap.get(id);
|
|
512
|
+
wm.openWindow({ id, title: label, docId: id, docType: entry?.type, provider: childProv, initialTab: "editor" });
|
|
513
|
+
}
|
|
514
|
+
function bcMenuItems(a) {
|
|
515
|
+
return useNodeContextMenu({
|
|
516
|
+
nodeId: a.id,
|
|
517
|
+
label: a.label,
|
|
518
|
+
type: a.type,
|
|
519
|
+
tree: bcTree,
|
|
520
|
+
registry,
|
|
521
|
+
favorites: { isFavorite, toggleFavorite },
|
|
522
|
+
onOpen: (id) => navigateTo(getDocUrl(id)),
|
|
523
|
+
onOpenInWindow: () => bcOpenAsWindow(a.id, a.label),
|
|
524
|
+
onCopyLink: (id) => {
|
|
525
|
+
if (!import.meta.client || !navigator?.clipboard) return;
|
|
526
|
+
navigator.clipboard.writeText(new URL(getDocUrl(id), location.origin).href).catch(() => {
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}).items;
|
|
530
|
+
}
|
|
383
531
|
function onPlusClick(e, onClick) {
|
|
384
532
|
e.stopPropagation();
|
|
385
533
|
onClick();
|
|
@@ -461,16 +609,29 @@ function onPlusClick(e, onClick) {
|
|
|
461
609
|
/>
|
|
462
610
|
</li>
|
|
463
611
|
<li class="flex min-w-0">
|
|
464
|
-
<
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
:
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
612
|
+
<ANodeContextMenu :items="bcMenuItems(ancestor)">
|
|
613
|
+
<button
|
|
614
|
+
type="button"
|
|
615
|
+
draggable="true"
|
|
616
|
+
class="flex items-center gap-1.5 px-1.5 py-0.5 rounded-md text-sm font-medium transition-colors min-w-0 select-none cursor-pointer"
|
|
617
|
+
:class="[
|
|
618
|
+
breadcrumbDragId === ancestor.id ? 'opacity-30' : '',
|
|
619
|
+
breadcrumbDropTargetId === ancestor.id ? 'bg-(--ui-primary)/10 ring-2 ring-(--ui-primary)/50 text-(--ui-text)' : 'text-(--ui-text-muted) hover:bg-(--ui-bg-elevated)/60 hover:text-(--ui-text)'
|
|
620
|
+
]"
|
|
621
|
+
@click="navigateTo(ancestor.to)"
|
|
622
|
+
@dragstart="onBcDragStart($event, ancestor)"
|
|
623
|
+
@dragend="onBcDragEnd"
|
|
624
|
+
@dragover="onBcDragOver($event, ancestor.id)"
|
|
625
|
+
@dragleave="onBcDragLeave"
|
|
626
|
+
@drop="onBcDrop($event, ancestor.id)"
|
|
627
|
+
>
|
|
628
|
+
<UIcon
|
|
629
|
+
:name="ancestor.icon"
|
|
630
|
+
class="size-4 shrink-0"
|
|
631
|
+
/>
|
|
632
|
+
<span class="truncate">{{ ancestor.label }}</span>
|
|
633
|
+
</button>
|
|
634
|
+
</ANodeContextMenu>
|
|
474
635
|
</li>
|
|
475
636
|
</template>
|
|
476
637
|
</ol>
|
|
@@ -603,6 +764,13 @@ function onPlusClick(e, onClick) {
|
|
|
603
764
|
/>
|
|
604
765
|
</UDropdownMenu>
|
|
605
766
|
</UEditorDragHandle>
|
|
767
|
+
|
|
768
|
+
<!-- Inline `[[` doc-link / `![[` doc-embed mention-style popup
|
|
769
|
+
(only mounted when docPicker === 'inline'). -->
|
|
770
|
+
<ADocSuggestMenu
|
|
771
|
+
v-if="docSuggestEnabled && editor?.storage?.docSuggest"
|
|
772
|
+
:state="editor.storage.docSuggest.popup"
|
|
773
|
+
/>
|
|
606
774
|
</slot>
|
|
607
775
|
</UEditor>
|
|
608
776
|
</div>
|
|
@@ -621,5 +789,5 @@ function onPlusClick(e, onClick) {
|
|
|
621
789
|
</template>
|
|
622
790
|
|
|
623
791
|
<style scoped>
|
|
624
|
-
.aeditor-canvas{display:flex;flex-direction:column;min-height:0}.aeditor-breadcrumb{flex-shrink:0;padding
|
|
792
|
+
.aeditor-canvas{display:flex;flex-direction:column;min-height:0}.aeditor-breadcrumb{flex-shrink:0;padding:1rem 0 0}@media (min-width:640px){.aeditor-breadcrumb{padding-left:2rem;padding-right:2rem}}.prose-variant :deep(.tiptap){color:var(--ui-text);font-family:ui-serif,Georgia,Cambria,Times New Roman,Times,serif;font-size:1.0625rem;line-height:1.75}.prose-variant :deep(.tiptap p){margin-bottom:1em;margin-top:0}.prose-variant :deep(.tiptap h1),.prose-variant :deep(.tiptap h2),.prose-variant :deep(.tiptap h3),.prose-variant :deep(.tiptap h4){font-family:ui-serif,Georgia,Cambria,Times New Roman,Times,serif;letter-spacing:-.01em;line-height:1.25;margin-bottom:.5em;margin-top:1.75em}.prose-variant :deep(.tiptap h1){font-size:2rem;font-weight:700}.prose-variant :deep(.tiptap h2){font-size:1.5rem;font-weight:700}.prose-variant :deep(.tiptap h3){font-size:1.25rem;font-weight:600}.prose-variant :deep(.tiptap blockquote){border-left:3px solid var(--ui-border);color:var(--ui-text-muted);font-style:italic;margin-left:0;padding-left:1em}.prose-variant :deep(.tiptap ol),.prose-variant :deep(.tiptap ul){margin-bottom:1em;padding-left:1.5em}.prose-variant :deep(.tiptap .document-header){font-family:ui-serif,Georgia,Cambria,Times New Roman,Times,serif;font-size:2.75rem;font-weight:700;letter-spacing:-.02em;line-height:1.15;margin-bottom:1.5rem}.aeditor-source-wrap{display:flex;flex:1 1 0;flex-direction:column;min-height:0}.aeditor-source-toolbar{align-items:center;background:var(--ui-bg);border-bottom:1px solid var(--ui-border);display:flex;flex-shrink:0;justify-content:space-between;padding:.375rem .75rem}.aeditor-source-toolbar__label{align-items:center;color:var(--ui-text-muted);display:inline-flex;font-size:.75rem;gap:.375rem}.aeditor-source-toolbar__label code{background:var(--ui-bg-elevated);border:1px solid var(--ui-border);border-radius:var(--ui-radius);font-family:ui-monospace,SF Mono,Menlo,Monaco,Consolas,monospace;font-size:.7rem;padding:.05rem .3rem}.aeditor-source-toggle{opacity:.6;position:absolute;right:.375rem;top:.375rem;transition:opacity .12s ease;z-index:5}.aeditor-source-toggle:hover{opacity:1}
|
|
625
793
|
</style>
|
|
@@ -68,13 +68,29 @@ function patchType(type) {
|
|
|
68
68
|
if (e) treeMap.set(props.nodeId, { ...e, type, updatedAt: Date.now() });
|
|
69
69
|
}
|
|
70
70
|
const activeTab = ref(props.initialTab);
|
|
71
|
+
const userPickedTab = ref(false);
|
|
71
72
|
watch(() => props.nodeId, (id) => {
|
|
72
|
-
if (id)
|
|
73
|
+
if (id) {
|
|
74
|
+
userPickedTab.value = false;
|
|
75
|
+
activeTab.value = props.initialTab;
|
|
76
|
+
}
|
|
73
77
|
});
|
|
78
|
+
function setTab(tab) {
|
|
79
|
+
userPickedTab.value = true;
|
|
80
|
+
activeTab.value = tab;
|
|
81
|
+
}
|
|
74
82
|
const docTypeDef = computed(() => resolveDocType(currentType.value, registry));
|
|
75
83
|
const usesPageRenderer = computed(
|
|
76
84
|
() => docTypeDef.value.key !== "doc" && !!docTypeDef.value.component
|
|
77
85
|
);
|
|
86
|
+
watch([usesPageRenderer, () => props.nodeId], () => {
|
|
87
|
+
if (userPickedTab.value || props.initialTab !== "editor") return;
|
|
88
|
+
activeTab.value = usesPageRenderer.value ? "pageType" : "editor";
|
|
89
|
+
}, { immediate: true });
|
|
90
|
+
const CANONICAL_TABS = ["pageType", "editor", "chat", "settings"];
|
|
91
|
+
const toggleValue = computed(
|
|
92
|
+
() => CANONICAL_TABS.includes(activeTab.value) ? activeTab.value : null
|
|
93
|
+
);
|
|
78
94
|
const editorRef = ref(null);
|
|
79
95
|
const tiptapEditor = computed(() => editorRef.value?.editor ?? null);
|
|
80
96
|
const resize = useResizableWidth({
|
|
@@ -256,9 +272,9 @@ const addFieldMenuItems = computed(
|
|
|
256
272
|
/>
|
|
257
273
|
</div>
|
|
258
274
|
|
|
259
|
-
<!-- Right: undo/redo (editor tab) ·
|
|
275
|
+
<!-- Right: undo/redo (editor tab) · unified view toggle · extras -->
|
|
260
276
|
<div class="flex items-center gap-0.5">
|
|
261
|
-
<template v-if="activeTab === 'editor' &&
|
|
277
|
+
<template v-if="activeTab === 'editor' && tiptapEditor">
|
|
262
278
|
<AEditorUndoButton :editor="tiptapEditor" />
|
|
263
279
|
<AEditorRedoButton :editor="tiptapEditor" />
|
|
264
280
|
<USeparator
|
|
@@ -267,103 +283,78 @@ const addFieldMenuItems = computed(
|
|
|
267
283
|
/>
|
|
268
284
|
</template>
|
|
269
285
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
:
|
|
273
|
-
:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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'"
|
|
286
|
+
<!-- Canonical four-slot toggle: pageType · editor · chat · settings -->
|
|
287
|
+
<ADocViewToggle
|
|
288
|
+
:model-value="toggleValue"
|
|
289
|
+
:doc-id="nodeId || ''"
|
|
290
|
+
:doc-type="currentType"
|
|
291
|
+
:chat-unread="chatUnread"
|
|
292
|
+
:editor-disabled="!showEditorTab"
|
|
293
|
+
:page-type-disabled="!childProvider"
|
|
294
|
+
:show-chat="showChatTab"
|
|
295
|
+
:show-settings="showSettingsTab"
|
|
296
|
+
@update:model-value="setTab"
|
|
297
|
+
/>
|
|
298
|
+
|
|
299
|
+
<!-- Module extras kept outside the toggle (cou-sh parity: extras
|
|
300
|
+
sit beside the canonical four, never inside them). -->
|
|
301
|
+
<template v-if="showPropertiesTab || showServerSettingsTab || pluginSlots.length">
|
|
302
|
+
<USeparator
|
|
303
|
+
orientation="vertical"
|
|
304
|
+
class="h-4 mx-0.5"
|
|
294
305
|
/>
|
|
295
|
-
</UTooltip>
|
|
296
|
-
<UChip
|
|
297
|
-
v-if="showChatTab"
|
|
298
|
-
:text="chatUnread > 0 && activeTab !== 'chat' ? String(chatUnread) : void 0"
|
|
299
|
-
color="error"
|
|
300
|
-
size="lg"
|
|
301
|
-
:show="chatUnread > 0 && activeTab !== 'chat'"
|
|
302
|
-
:inset="true"
|
|
303
|
-
>
|
|
304
306
|
<UTooltip
|
|
305
|
-
|
|
307
|
+
v-if="showPropertiesTab"
|
|
308
|
+
:text="locale.propertiesTab"
|
|
306
309
|
:content="{ side: 'bottom' }"
|
|
307
310
|
>
|
|
308
311
|
<UButton
|
|
309
|
-
icon="i-lucide-
|
|
312
|
+
icon="i-lucide-sliders-horizontal"
|
|
310
313
|
size="xs"
|
|
311
|
-
:color="activeTab === '
|
|
312
|
-
:variant="activeTab === '
|
|
313
|
-
@click="
|
|
314
|
+
:color="activeTab === 'properties' ? 'primary' : 'neutral'"
|
|
315
|
+
:variant="activeTab === 'properties' ? 'soft' : 'ghost'"
|
|
316
|
+
@click="setTab('properties')"
|
|
314
317
|
/>
|
|
315
318
|
</UTooltip>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
v-for="slot in pluginSlots"
|
|
345
|
-
:key="slot.id"
|
|
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>
|
|
319
|
+
<UTooltip
|
|
320
|
+
v-if="showServerSettingsTab"
|
|
321
|
+
:text="locale.serverTab"
|
|
322
|
+
:content="{ side: 'bottom' }"
|
|
323
|
+
>
|
|
324
|
+
<UButton
|
|
325
|
+
icon="i-lucide-bolt"
|
|
326
|
+
size="xs"
|
|
327
|
+
:color="activeTab === 'serverSettings' ? 'primary' : 'neutral'"
|
|
328
|
+
:variant="activeTab === 'serverSettings' ? 'soft' : 'ghost'"
|
|
329
|
+
@click="setTab('serverSettings')"
|
|
330
|
+
/>
|
|
331
|
+
</UTooltip>
|
|
332
|
+
<UTooltip
|
|
333
|
+
v-for="slot in pluginSlots"
|
|
334
|
+
:key="slot.id"
|
|
335
|
+
:text="slot.label"
|
|
336
|
+
:content="{ side: 'bottom' }"
|
|
337
|
+
>
|
|
338
|
+
<UButton
|
|
339
|
+
:icon="slot.icon"
|
|
340
|
+
size="xs"
|
|
341
|
+
:color="activeTab === slot.id ? 'primary' : 'neutral'"
|
|
342
|
+
:variant="activeTab === slot.id ? 'soft' : 'ghost'"
|
|
343
|
+
@click="setTab(slot.id)"
|
|
344
|
+
/>
|
|
345
|
+
</UTooltip>
|
|
346
|
+
</template>
|
|
357
347
|
</div>
|
|
358
348
|
</div>
|
|
359
349
|
</slot>
|
|
360
350
|
|
|
361
351
|
<!-- Content area -->
|
|
362
352
|
<div class="flex flex-col flex-1 min-h-0 overflow-hidden">
|
|
363
|
-
<!--
|
|
364
|
-
|
|
353
|
+
<!-- Page-type tab — the registered renderer for non-'doc' types
|
|
354
|
+
(kanban / table / calendar / …). Now its own slot, so the editor
|
|
355
|
+
tab below always exposes the underlying prose. -->
|
|
365
356
|
<div
|
|
366
|
-
v-show="activeTab === '
|
|
357
|
+
v-show="activeTab === 'pageType'"
|
|
367
358
|
class="flex flex-col flex-1 min-h-0"
|
|
368
359
|
>
|
|
369
360
|
<component
|
|
@@ -375,8 +366,15 @@ const addFieldMenuItems = computed(
|
|
|
375
366
|
:child-provider="childProvider"
|
|
376
367
|
class="flex-1 overflow-hidden"
|
|
377
368
|
/>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<!-- Editor tab — always the TipTap prose editor for the doc body. -->
|
|
372
|
+
<div
|
|
373
|
+
v-show="activeTab === 'editor'"
|
|
374
|
+
class="flex flex-col flex-1 min-h-0"
|
|
375
|
+
>
|
|
378
376
|
<AEditor
|
|
379
|
-
v-
|
|
377
|
+
v-if="nodeId"
|
|
380
378
|
ref="editorRef"
|
|
381
379
|
:doc-id="nodeId"
|
|
382
380
|
:doc-label="resolvedLabel"
|
|
@@ -648,11 +646,13 @@ const addFieldMenuItems = computed(
|
|
|
648
646
|
|
|
649
647
|
<!-- Number -->
|
|
650
648
|
<template v-else-if="field.type === 'number'">
|
|
651
|
-
<
|
|
652
|
-
|
|
653
|
-
:
|
|
654
|
-
|
|
655
|
-
|
|
649
|
+
<AMetaNumberStepper
|
|
650
|
+
:model-value="getCustomFieldValue(field) != null ? Number(getCustomFieldValue(field)) : void 0"
|
|
651
|
+
:min="field.min"
|
|
652
|
+
:max="field.max"
|
|
653
|
+
:step="field.step ?? 1"
|
|
654
|
+
:unit="field.unit"
|
|
655
|
+
@update:model-value="setCustomFieldValue(field, $event)"
|
|
656
656
|
/>
|
|
657
657
|
</template>
|
|
658
658
|
|
|
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
12
12
|
awareness: boolean;
|
|
13
13
|
tag: "video" | "audio";
|
|
14
14
|
live: boolean;
|
|
15
|
-
controls: boolean;
|
|
16
15
|
total: boolean;
|
|
16
|
+
controls: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
19
19
|
export default _default;
|
|
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
12
12
|
awareness: boolean;
|
|
13
13
|
tag: "video" | "audio";
|
|
14
14
|
live: boolean;
|
|
15
|
-
controls: boolean;
|
|
16
15
|
total: boolean;
|
|
16
|
+
controls: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
19
19
|
export default _default;
|
|
@@ -11,8 +11,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
11
11
|
sync: boolean;
|
|
12
12
|
peers: boolean;
|
|
13
13
|
awareness: boolean;
|
|
14
|
-
max: number;
|
|
15
14
|
min: number;
|
|
15
|
+
max: number;
|
|
16
16
|
total: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
@@ -11,8 +11,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
11
11
|
sync: boolean;
|
|
12
12
|
peers: boolean;
|
|
13
13
|
awareness: boolean;
|
|
14
|
-
max: number;
|
|
15
14
|
min: number;
|
|
15
|
+
max: number;
|
|
16
16
|
total: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
@@ -237,9 +237,9 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<import
|
|
|
237
237
|
}>> & Readonly<{}>, {
|
|
238
238
|
color: any;
|
|
239
239
|
disabled: boolean;
|
|
240
|
+
block: boolean;
|
|
240
241
|
square: boolean;
|
|
241
242
|
viewTransition: boolean;
|
|
242
|
-
block: boolean;
|
|
243
243
|
loading: boolean;
|
|
244
244
|
collapsed: boolean;
|
|
245
245
|
tooltip: boolean | Record<string, any>;
|
|
@@ -237,9 +237,9 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<import
|
|
|
237
237
|
}>> & Readonly<{}>, {
|
|
238
238
|
color: any;
|
|
239
239
|
disabled: boolean;
|
|
240
|
+
block: boolean;
|
|
240
241
|
square: boolean;
|
|
241
242
|
viewTransition: boolean;
|
|
242
|
-
block: boolean;
|
|
243
243
|
loading: boolean;
|
|
244
244
|
collapsed: boolean;
|
|
245
245
|
tooltip: boolean | Record<string, any>;
|