@abraca/nuxt 2.11.0 → 2.14.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 +9 -0
- package/dist/runtime/components/ACodeEditor.vue +123 -22
- 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/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 +66 -1
- package/dist/runtime/components/AEditor.d.vue.ts +17 -10
- package/dist/runtime/components/AEditor.vue +403 -167
- 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 +553 -481
- 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/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/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/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/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/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/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/codeHighlightStyle.d.ts +15 -0
- package/dist/runtime/utils/codeHighlightStyle.js +34 -0
- package/dist/runtime/utils/docTypes.js +43 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
- package/dist/runtime/utils/loadCodeMirror.js +6 -3
- package/dist/runtime/utils/titleSync.d.ts +130 -0
- package/dist/runtime/utils/titleSync.js +53 -0
- package/package.json +12 -1
|
@@ -1,12 +1,24 @@
|
|
|
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";
|
|
7
7
|
import { schemaFieldToAttrs, userFieldToAttrs } from "../extensions/meta-field";
|
|
8
|
+
import { decideTitleSync, setHeaderText as applyHeaderText, UNTITLED } from "../utils/titleSync";
|
|
8
9
|
import { useEditor } from "../composables/useEditor";
|
|
9
10
|
import { useDocTree } from "../composables/useDocTree";
|
|
11
|
+
import { useDocBreadcrumb } from "../composables/useDocBreadcrumb";
|
|
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";
|
|
10
22
|
import { useEditorToolbar } from "../composables/useEditorToolbar";
|
|
11
23
|
import { useEditorSuggestions } from "../composables/useEditorSuggestions";
|
|
12
24
|
import { useEditorDragHandle } from "../composables/useEditorDragHandle";
|
|
@@ -14,6 +26,8 @@ import { useEditorEmojis } from "../composables/useEditorEmojis";
|
|
|
14
26
|
import { useEditorMentions } from "../composables/useEditorMentions";
|
|
15
27
|
import ALinkPopover from "./editor/ALinkPopover.vue";
|
|
16
28
|
import ADocLinkPopover from "./editor/ADocLinkPopover.vue";
|
|
29
|
+
import ANodeContextMenu from "./shell/ANodeContextMenu.vue";
|
|
30
|
+
import ADocSuggestMenu from "./editor/ADocSuggestMenu.vue";
|
|
17
31
|
import ACodeEditor from "./ACodeEditor.vue";
|
|
18
32
|
const props = defineProps({
|
|
19
33
|
docId: { type: String, required: true },
|
|
@@ -28,7 +42,8 @@ const props = defineProps({
|
|
|
28
42
|
parentType: { type: String, required: false },
|
|
29
43
|
metaSchema: { type: Array, required: false },
|
|
30
44
|
variant: { type: String, required: false, default: "doc" },
|
|
31
|
-
showSourceToggle: { type: Boolean, required: false, default: false }
|
|
45
|
+
showSourceToggle: { type: Boolean, required: false, default: false },
|
|
46
|
+
showBreadcrumb: { type: Boolean, required: false, default: true }
|
|
32
47
|
});
|
|
33
48
|
const viewMode = ref("rich");
|
|
34
49
|
const emit = defineEmits(["ready", "update", "rename", "updateMeta"]);
|
|
@@ -48,9 +63,26 @@ watch(provider, async (prov) => {
|
|
|
48
63
|
onBeforeUnmount(() => {
|
|
49
64
|
provider.value?.unpinChild?.(props.docId);
|
|
50
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
|
+
});
|
|
51
82
|
const { extensions, connectedUsers, ready } = useEditor({
|
|
52
83
|
childProvider,
|
|
53
|
-
docId: props.docId
|
|
84
|
+
docId: props.docId,
|
|
85
|
+
extraExtensions: docSuggestEnabled ? [docSuggestExt] : []
|
|
54
86
|
});
|
|
55
87
|
watch(ready, (val) => {
|
|
56
88
|
if (val) emit("ready");
|
|
@@ -77,7 +109,7 @@ const resolvedMetaSchema = computed(() => {
|
|
|
77
109
|
});
|
|
78
110
|
const { items: allSuggestionItems, propertiesOnlyItems } = useEditorSuggestions({ docId: props.docId });
|
|
79
111
|
const { items: emojiItems } = useEditorEmojis();
|
|
80
|
-
const { items: mentionItems } = useEditorMentions({ docId: props.docId });
|
|
112
|
+
const { items: mentionItems } = useEditorMentions({ docId: props.docId, includeDocuments: false });
|
|
81
113
|
const isInDocumentMeta = ref(false);
|
|
82
114
|
watchEffect((onCleanup) => {
|
|
83
115
|
const ed = editorRef.value?.editor;
|
|
@@ -167,85 +199,126 @@ function initDocumentMeta(ed) {
|
|
|
167
199
|
metaInitDone = true;
|
|
168
200
|
}
|
|
169
201
|
let syncingFromEditor = false;
|
|
170
|
-
|
|
202
|
+
let lastSyncedLabel = null;
|
|
171
203
|
function getHeaderText(ed) {
|
|
172
204
|
const first = ed.state.doc.firstChild;
|
|
173
205
|
if (!first || first.type.name !== "documentHeader") return "";
|
|
174
206
|
return first.textContent;
|
|
175
207
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
if (!first || first.type.name !== "documentHeader") return;
|
|
179
|
-
if (first.textContent === text) return;
|
|
180
|
-
const { tr, schema } = ed.state;
|
|
181
|
-
const to = 1 + first.content.size;
|
|
182
|
-
if (text) {
|
|
183
|
-
tr.replaceWith(1, to, schema.text(text));
|
|
184
|
-
} else {
|
|
185
|
-
tr.delete(1, to);
|
|
186
|
-
}
|
|
187
|
-
ed.view.dispatch(tr);
|
|
208
|
+
function currentTreeLabel() {
|
|
209
|
+
return _treeMap.data[props.docId]?.label ?? props.docLabel ?? "";
|
|
188
210
|
}
|
|
189
|
-
function
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
211
|
+
function writeTreeLabel(label) {
|
|
212
|
+
const e = _treeMap.data[props.docId];
|
|
213
|
+
const next = label || UNTITLED;
|
|
214
|
+
if (!e || e.label === next) return;
|
|
193
215
|
syncingFromEditor = true;
|
|
194
|
-
|
|
216
|
+
lastSyncedLabel = next;
|
|
217
|
+
_treeMap.set(props.docId, { ...e, label: next, updatedAt: Date.now() });
|
|
218
|
+
emit("rename", next);
|
|
195
219
|
nextTick(() => {
|
|
196
220
|
syncingFromEditor = false;
|
|
197
221
|
});
|
|
198
222
|
}
|
|
223
|
+
function syncHeader(ed, isRemoteUpdate, initialSyncDone) {
|
|
224
|
+
const action = decideTitleSync({
|
|
225
|
+
headerText: getHeaderText(ed),
|
|
226
|
+
treeLabel: currentTreeLabel(),
|
|
227
|
+
isRemoteUpdate,
|
|
228
|
+
initialSyncDone
|
|
229
|
+
});
|
|
230
|
+
if (action.kind === "noop") return;
|
|
231
|
+
if (action.kind === "header-from-tree") {
|
|
232
|
+
applyHeaderText(ed, action.text);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
writeTreeLabel(action.label);
|
|
236
|
+
}
|
|
199
237
|
watchEffect((onCleanup) => {
|
|
200
238
|
const ed = editorRef.value?.editor;
|
|
201
239
|
if (!ed || !ready.value) return;
|
|
202
240
|
let initialSyncDone = false;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
241
|
+
let headerTimer = null;
|
|
242
|
+
const runSync = (isRemote) => {
|
|
243
|
+
const wasInitial = !initialSyncDone;
|
|
244
|
+
if (wasInitial) initialSyncDone = true;
|
|
245
|
+
syncHeader(ed, isRemote, !wasInitial);
|
|
246
|
+
};
|
|
247
|
+
function onUpdate({ transaction }) {
|
|
248
|
+
const isRemote = !!transaction?.getMeta?.("y-sync$")?.isChangeOrigin;
|
|
249
|
+
initDocumentMeta(ed);
|
|
250
|
+
if (isRemote || !initialSyncDone) {
|
|
251
|
+
if (headerTimer) {
|
|
252
|
+
clearTimeout(headerTimer);
|
|
253
|
+
headerTimer = null;
|
|
215
254
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
255
|
+
runSync(isRemote);
|
|
256
|
+
} else {
|
|
257
|
+
if (headerTimer) clearTimeout(headerTimer);
|
|
258
|
+
headerTimer = setTimeout(() => {
|
|
259
|
+
headerTimer = null;
|
|
260
|
+
runSync(false);
|
|
261
|
+
}, 200);
|
|
222
262
|
}
|
|
223
263
|
}
|
|
224
|
-
function onUpdate() {
|
|
225
|
-
syncHeaderToTree(ed);
|
|
226
|
-
initDocumentMeta(ed);
|
|
227
|
-
}
|
|
228
264
|
nextTick(() => {
|
|
229
|
-
|
|
265
|
+
runSync(false);
|
|
230
266
|
initDocumentMeta(ed);
|
|
231
267
|
});
|
|
232
268
|
ed.on("update", onUpdate);
|
|
233
|
-
onCleanup(() =>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (newLabel === _lastEmittedHeader.value) return;
|
|
238
|
-
if (!_treeMap.lastUpdateLocal.value) return;
|
|
239
|
-
const ed = editorRef.value?.editor;
|
|
240
|
-
if (!ed || !ready.value) return;
|
|
241
|
-
setHeaderText(ed, newLabel || "");
|
|
269
|
+
onCleanup(() => {
|
|
270
|
+
if (headerTimer) clearTimeout(headerTimer);
|
|
271
|
+
ed.off("update", onUpdate);
|
|
272
|
+
});
|
|
242
273
|
});
|
|
274
|
+
watch(
|
|
275
|
+
() => _treeMap.data[props.docId]?.label ?? props.docLabel,
|
|
276
|
+
(newLabel) => {
|
|
277
|
+
if (syncingFromEditor) return;
|
|
278
|
+
if (lastSyncedLabel !== null && newLabel === lastSyncedLabel) {
|
|
279
|
+
lastSyncedLabel = null;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
lastSyncedLabel = null;
|
|
283
|
+
if (newLabel === void 0 || newLabel === "" || newLabel === UNTITLED) return;
|
|
284
|
+
const ed = editorRef.value?.editor;
|
|
285
|
+
if (!ed || !ready.value) return;
|
|
286
|
+
applyHeaderText(ed, newLabel);
|
|
287
|
+
}
|
|
288
|
+
);
|
|
243
289
|
function insertNode(editor, type, attrs, content) {
|
|
244
290
|
const node = { type, attrs };
|
|
245
291
|
if (content) node.content = content;
|
|
246
292
|
return editor.chain().focus().insertContent(node);
|
|
247
293
|
}
|
|
294
|
+
const { pickDoc } = useDocLinkPick();
|
|
248
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
|
+
},
|
|
249
322
|
insertMetaField: {
|
|
250
323
|
canExecute: () => true,
|
|
251
324
|
execute: (editor, item) => editor.chain().focus().insertMetaField(item.attrs),
|
|
@@ -357,6 +430,104 @@ const editorHandlers = {
|
|
|
357
430
|
const _mentionItems = computed(
|
|
358
431
|
() => registry.getAllMentionProviders().flatMap((p) => p.label ? [p] : [])
|
|
359
432
|
);
|
|
433
|
+
const { items: _breadcrumbItems } = useDocBreadcrumb(() => props.docId);
|
|
434
|
+
const { getDocUrl } = useDocSlugs();
|
|
435
|
+
const ancestorChain = computed(
|
|
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
|
+
})
|
|
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
|
+
}
|
|
360
531
|
function onPlusClick(e, onClick) {
|
|
361
532
|
e.stopPropagation();
|
|
362
533
|
onClick();
|
|
@@ -387,7 +558,10 @@ function onPlusClick(e, onClick) {
|
|
|
387
558
|
>
|
|
388
559
|
<div class="aeditor-source-toolbar">
|
|
389
560
|
<span class="aeditor-source-toolbar__label">
|
|
390
|
-
<UIcon
|
|
561
|
+
<UIcon
|
|
562
|
+
name="i-lucide-code-xml"
|
|
563
|
+
class="size-3.5"
|
|
564
|
+
/>
|
|
391
565
|
XML source — read-only mirror of <code>getXmlFragment('default')</code>
|
|
392
566
|
</span>
|
|
393
567
|
<UButton
|
|
@@ -408,136 +582,198 @@ function onPlusClick(e, onClick) {
|
|
|
408
582
|
/>
|
|
409
583
|
</div>
|
|
410
584
|
|
|
411
|
-
|
|
585
|
+
<!-- Editor canvas: ancestor breadcrumb (cou-sh parity) + the TipTap editor,
|
|
586
|
+
stacked in one scroll container (the parent's overflow class falls
|
|
587
|
+
through to this wrapper). -->
|
|
588
|
+
<div
|
|
412
589
|
v-else
|
|
413
|
-
|
|
414
|
-
ref="editorRef"
|
|
415
|
-
v-slot="{ editor }"
|
|
416
|
-
v-model="model"
|
|
417
|
-
:content-type="contentType"
|
|
418
|
-
:editable="editable"
|
|
419
|
-
:extensions="extensions"
|
|
420
|
-
:starter-kit="{ undoRedo: false, codeBlock: false, document: false }"
|
|
421
|
-
:handlers="editorHandlers"
|
|
422
|
-
:placeholder="placeholder"
|
|
423
|
-
@update:model-value="emit('update', $event)"
|
|
590
|
+
class="aeditor-canvas"
|
|
424
591
|
>
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
class="aeditor-
|
|
429
|
-
icon="i-lucide-code-xml"
|
|
430
|
-
size="xs"
|
|
431
|
-
variant="ghost"
|
|
432
|
-
color="neutral"
|
|
433
|
-
:title="'View XML source'"
|
|
434
|
-
@click="viewMode = 'source'"
|
|
435
|
-
/>
|
|
436
|
-
<!-- Default slot: app can override entire editor content -->
|
|
437
|
-
<slot
|
|
438
|
-
:editor="editor"
|
|
439
|
-
:connected-users="connectedUsers"
|
|
440
|
-
:ready="ready"
|
|
592
|
+
<nav
|
|
593
|
+
v-if="showBreadcrumb && ancestorChain.length"
|
|
594
|
+
aria-label="Breadcrumb"
|
|
595
|
+
class="aeditor-breadcrumb"
|
|
441
596
|
>
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
layout="bubble"
|
|
451
|
-
:should-show="({ view, state }) => view.hasFocus() && !state.selection.empty"
|
|
452
|
-
>
|
|
453
|
-
<template #link>
|
|
454
|
-
<slot
|
|
455
|
-
name="link"
|
|
456
|
-
:editor="editor"
|
|
457
|
-
>
|
|
458
|
-
<ALinkPopover :editor="editor" />
|
|
459
|
-
</slot>
|
|
460
|
-
</template>
|
|
461
|
-
<template #doc-link>
|
|
462
|
-
<slot
|
|
463
|
-
name="doc-link"
|
|
464
|
-
:editor="editor"
|
|
597
|
+
<ol class="flex items-center gap-0.5 min-w-0">
|
|
598
|
+
<template
|
|
599
|
+
v-for="(ancestor, idx) in ancestorChain"
|
|
600
|
+
:key="ancestor.id"
|
|
601
|
+
>
|
|
602
|
+
<li
|
|
603
|
+
v-if="idx > 0"
|
|
604
|
+
class="flex shrink-0"
|
|
465
605
|
>
|
|
466
|
-
<
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
606
|
+
<UIcon
|
|
607
|
+
name="i-lucide-chevron-right"
|
|
608
|
+
class="size-4 text-(--ui-text-dimmed)"
|
|
609
|
+
/>
|
|
610
|
+
</li>
|
|
611
|
+
<li class="flex min-w-0">
|
|
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>
|
|
635
|
+
</li>
|
|
480
636
|
</template>
|
|
481
|
-
</
|
|
637
|
+
</ol>
|
|
638
|
+
</nav>
|
|
482
639
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
640
|
+
<UEditor
|
|
641
|
+
ref="editorRef"
|
|
642
|
+
v-slot="{ editor }"
|
|
643
|
+
v-model="model"
|
|
644
|
+
:class="{ 'prose-variant': variant === 'prose' }"
|
|
645
|
+
:content-type="contentType"
|
|
646
|
+
:editable="editable"
|
|
647
|
+
:extensions="extensions"
|
|
648
|
+
:starter-kit="{ undoRedo: false, codeBlock: false, document: false }"
|
|
649
|
+
:handlers="editorHandlers"
|
|
650
|
+
:placeholder="placeholder"
|
|
651
|
+
@update:model-value="emit('update', $event)"
|
|
652
|
+
>
|
|
653
|
+
<!-- Floating source-view toggle (opt-in via :show-source-toggle="true") -->
|
|
654
|
+
<UButton
|
|
655
|
+
v-if="showSourceToggle"
|
|
656
|
+
class="aeditor-source-toggle"
|
|
657
|
+
icon="i-lucide-code-xml"
|
|
658
|
+
size="xs"
|
|
659
|
+
variant="ghost"
|
|
660
|
+
color="neutral"
|
|
661
|
+
:title="'View XML source'"
|
|
662
|
+
@click="viewMode = 'source'"
|
|
488
663
|
/>
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
<UEditorEmojiMenu
|
|
664
|
+
<!-- Default slot: app can override entire editor content -->
|
|
665
|
+
<slot
|
|
492
666
|
:editor="editor"
|
|
493
|
-
:
|
|
494
|
-
|
|
667
|
+
:connected-users="connectedUsers"
|
|
668
|
+
:ready="ready"
|
|
669
|
+
>
|
|
670
|
+
<!-- ── Bubble toolbar (appears when text is selected) ─────────────── -->
|
|
671
|
+
<!-- 4 named slots are forwarded to consumers:
|
|
672
|
+
#link / #doc-link — defaulted to ALinkPopover / ADocLinkPopover
|
|
673
|
+
#create-child-doc / #send-to-chat — empty by default; app-defined -->
|
|
674
|
+
<UEditorToolbar
|
|
675
|
+
v-if="showToolbar"
|
|
676
|
+
:editor="editor"
|
|
677
|
+
:items="toolbarItems"
|
|
678
|
+
layout="bubble"
|
|
679
|
+
:should-show="({ view, state }) => view.hasFocus() && !state.selection.empty"
|
|
680
|
+
>
|
|
681
|
+
<template #link>
|
|
682
|
+
<slot
|
|
683
|
+
name="link"
|
|
684
|
+
:editor="editor"
|
|
685
|
+
>
|
|
686
|
+
<ALinkPopover :editor="editor" />
|
|
687
|
+
</slot>
|
|
688
|
+
</template>
|
|
689
|
+
<template #doc-link>
|
|
690
|
+
<slot
|
|
691
|
+
name="doc-link"
|
|
692
|
+
:editor="editor"
|
|
693
|
+
>
|
|
694
|
+
<ADocLinkPopover :editor="editor" />
|
|
695
|
+
</slot>
|
|
696
|
+
</template>
|
|
697
|
+
<template #create-child-doc>
|
|
698
|
+
<slot
|
|
699
|
+
name="create-child-doc"
|
|
700
|
+
:editor="editor"
|
|
701
|
+
/>
|
|
702
|
+
</template>
|
|
703
|
+
<template #send-to-chat>
|
|
704
|
+
<slot
|
|
705
|
+
name="send-to-chat"
|
|
706
|
+
:editor="editor"
|
|
707
|
+
/>
|
|
708
|
+
</template>
|
|
709
|
+
</UEditorToolbar>
|
|
495
710
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
711
|
+
<!-- ── Slash command menu ──────────────────────────────────────────── -->
|
|
712
|
+
<UEditorSuggestionMenu
|
|
713
|
+
v-if="showSuggestionMenu"
|
|
714
|
+
:editor="editor"
|
|
715
|
+
:items="suggestionItems"
|
|
716
|
+
/>
|
|
501
717
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
color="neutral"
|
|
513
|
-
variant="ghost"
|
|
514
|
-
size="sm"
|
|
515
|
-
:class="ui.handle()"
|
|
516
|
-
@click="(e) => onPlusClick(e, onClick)"
|
|
718
|
+
<!-- ── Emoji menu (`:` trigger) ───────────────────────────────────── -->
|
|
719
|
+
<UEditorEmojiMenu
|
|
720
|
+
:editor="editor"
|
|
721
|
+
:items="emojiItems"
|
|
722
|
+
/>
|
|
723
|
+
|
|
724
|
+
<!-- ── Mention menu (`@` trigger) — users + docs + plugin providers ── -->
|
|
725
|
+
<UEditorMentionMenu
|
|
726
|
+
:editor="editor"
|
|
727
|
+
:items="mentionItems"
|
|
517
728
|
/>
|
|
518
729
|
|
|
519
|
-
<!--
|
|
520
|
-
<
|
|
521
|
-
v-
|
|
522
|
-
|
|
523
|
-
:
|
|
524
|
-
|
|
525
|
-
:ui="{ content: 'w-48', label: 'text-xs' }"
|
|
526
|
-
@update:open="(v) => editor.chain().setMeta('lockDragHandle', v).run()"
|
|
730
|
+
<!-- ── Drag handle — plus button + grip dropdown ───────────────────── -->
|
|
731
|
+
<UEditorDragHandle
|
|
732
|
+
v-if="showDragHandle"
|
|
733
|
+
v-slot="{ ui, onClick }"
|
|
734
|
+
:editor="editor"
|
|
735
|
+
@node-change="dragHandle.onNodeChange"
|
|
527
736
|
>
|
|
737
|
+
<!-- Plus: insert block via slash menu -->
|
|
528
738
|
<UButton
|
|
739
|
+
icon="i-lucide-plus"
|
|
529
740
|
color="neutral"
|
|
530
741
|
variant="ghost"
|
|
531
|
-
:active-variant="'soft'"
|
|
532
742
|
size="sm"
|
|
533
|
-
icon="i-lucide-grip-vertical"
|
|
534
|
-
:active="open"
|
|
535
743
|
:class="ui.handle()"
|
|
744
|
+
@click="(e) => onPlusClick(e, onClick)"
|
|
536
745
|
/>
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
746
|
+
|
|
747
|
+
<!-- Grip: block context menu -->
|
|
748
|
+
<UDropdownMenu
|
|
749
|
+
v-slot="{ open }"
|
|
750
|
+
:modal="false"
|
|
751
|
+
:items="dragHandle.getItems(editor)"
|
|
752
|
+
:content="{ side: 'left' }"
|
|
753
|
+
:ui="{ content: 'w-48', label: 'text-xs' }"
|
|
754
|
+
@update:open="(v) => editor.chain().setMeta('lockDragHandle', v).run()"
|
|
755
|
+
>
|
|
756
|
+
<UButton
|
|
757
|
+
color="neutral"
|
|
758
|
+
variant="ghost"
|
|
759
|
+
:active-variant="'soft'"
|
|
760
|
+
size="sm"
|
|
761
|
+
icon="i-lucide-grip-vertical"
|
|
762
|
+
:active="open"
|
|
763
|
+
:class="ui.handle()"
|
|
764
|
+
/>
|
|
765
|
+
</UDropdownMenu>
|
|
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
|
+
/>
|
|
774
|
+
</slot>
|
|
775
|
+
</UEditor>
|
|
776
|
+
</div>
|
|
541
777
|
|
|
542
778
|
<template #fallback>
|
|
543
779
|
<div
|
|
@@ -553,5 +789,5 @@ function onPlusClick(e, onClick) {
|
|
|
553
789
|
</template>
|
|
554
790
|
|
|
555
791
|
<style scoped>
|
|
556
|
-
.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}
|
|
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}
|
|
557
793
|
</style>
|