@abraca/nuxt 2.10.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.
Files changed (127) hide show
  1. package/dist/module.d.mts +14 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +9 -0
  4. package/dist/runtime/assets/editor.css +1 -1
  5. package/dist/runtime/components/AConnectionBadge.d.vue.ts +29 -0
  6. package/dist/runtime/components/AConnectionBadge.vue +79 -0
  7. package/dist/runtime/components/AConnectionBadge.vue.d.ts +29 -0
  8. package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
  9. package/dist/runtime/components/ADocPickerModal.vue +191 -0
  10. package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
  11. package/dist/runtime/components/ADocumentTree.vue +65 -0
  12. package/dist/runtime/components/AEditor.d.vue.ts +19 -12
  13. package/dist/runtime/components/AEditor.vue +243 -165
  14. package/dist/runtime/components/AEditor.vue.d.ts +19 -12
  15. package/dist/runtime/components/AEncryptionModePicker.d.vue.ts +33 -0
  16. package/dist/runtime/components/AEncryptionModePicker.vue +211 -0
  17. package/dist/runtime/components/AEncryptionModePicker.vue.d.ts +33 -0
  18. package/dist/runtime/components/AModalShell.d.vue.ts +48 -0
  19. package/dist/runtime/components/AModalShell.vue +105 -0
  20. package/dist/runtime/components/AModalShell.vue.d.ts +48 -0
  21. package/dist/runtime/components/ANodePanel.d.vue.ts +17 -7
  22. package/dist/runtime/components/ANodePanel.vue +550 -451
  23. package/dist/runtime/components/ANodePanel.vue.d.ts +17 -7
  24. package/dist/runtime/components/ANodePanelHeader.d.vue.ts +20 -10
  25. package/dist/runtime/components/ANodePanelHeader.vue +17 -3
  26. package/dist/runtime/components/ANodePanelHeader.vue.d.ts +20 -10
  27. package/dist/runtime/components/ANodeSettingsPanel.d.vue.ts +2 -0
  28. package/dist/runtime/components/ANodeSettingsPanel.vue +21 -1
  29. package/dist/runtime/components/ANodeSettingsPanel.vue.d.ts +2 -0
  30. package/dist/runtime/components/ASnapshotPreviewModal.d.vue.ts +33 -0
  31. package/dist/runtime/components/ASnapshotPreviewModal.vue +430 -0
  32. package/dist/runtime/components/ASnapshotPreviewModal.vue.d.ts +33 -0
  33. package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
  34. package/dist/runtime/components/ATagsEditor.vue +60 -0
  35. package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
  36. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  37. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  38. package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
  39. package/dist/runtime/components/chat/AChatInput.vue +33 -2
  40. package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
  41. package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
  42. package/dist/runtime/components/chat/AChatList.vue +76 -32
  43. package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
  44. package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
  45. package/dist/runtime/components/chat/AChatMessages.vue +57 -4
  46. package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
  47. package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
  48. package/dist/runtime/components/chat/AChatPanel.vue +17 -1
  49. package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
  50. package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
  51. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
  52. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
  53. package/dist/runtime/components/editor/ALocationPickerPopover.vue +28 -7
  54. package/dist/runtime/components/registry/APluginDetail.d.vue.ts +2 -2
  55. package/dist/runtime/components/registry/APluginDetail.vue.d.ts +2 -2
  56. package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
  57. package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
  58. package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
  59. package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
  60. package/dist/runtime/components/renderers/AProseRenderer.d.vue.ts +2 -2
  61. package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +2 -2
  62. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
  63. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
  64. package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
  65. package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
  66. package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
  67. package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
  68. package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
  69. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
  70. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
  71. package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
  72. package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
  73. package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
  74. package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
  75. package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
  76. package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
  77. package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
  78. package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
  79. package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
  80. package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
  81. package/dist/runtime/components/settings/sections.d.ts +37 -0
  82. package/dist/runtime/components/settings/sections.js +45 -0
  83. package/dist/runtime/components/shell/ABreadcrumbForDoc.d.vue.ts +6 -0
  84. package/dist/runtime/components/shell/ABreadcrumbForDoc.vue +75 -3
  85. package/dist/runtime/components/shell/ABreadcrumbForDoc.vue.d.ts +6 -0
  86. package/dist/runtime/components/shell/ADocPanelServerSettings.d.vue.ts +17 -0
  87. package/dist/runtime/components/shell/ADocPanelServerSettings.vue +253 -0
  88. package/dist/runtime/components/shell/ADocPanelServerSettings.vue.d.ts +17 -0
  89. package/dist/runtime/components/shell/ADocPanelSettings.d.vue.ts +2 -0
  90. package/dist/runtime/components/shell/ADocPanelSettings.vue +15 -4
  91. package/dist/runtime/components/shell/ADocPanelSettings.vue.d.ts +2 -0
  92. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  93. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  94. package/dist/runtime/composables/useChat.d.ts +22 -1
  95. package/dist/runtime/composables/useChat.js +79 -8
  96. package/dist/runtime/composables/useDocBreadcrumb.d.ts +17 -2
  97. package/dist/runtime/composables/useDocBreadcrumb.js +17 -3
  98. package/dist/runtime/composables/useDocSnapshots.d.ts +2 -1
  99. package/dist/runtime/composables/useDocSnapshots.js +5 -0
  100. package/dist/runtime/composables/useEditor.d.ts +1 -1
  101. package/dist/runtime/composables/useEditor.js +120 -0
  102. package/dist/runtime/composables/useEditorToolbar.d.ts +12 -4
  103. package/dist/runtime/composables/useEditorToolbar.js +78 -56
  104. package/dist/runtime/composables/useNodeContextMenu.d.ts +14 -0
  105. package/dist/runtime/composables/useNodeContextMenu.js +59 -1
  106. package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
  107. package/dist/runtime/composables/useSwipeGesture.d.ts +48 -0
  108. package/dist/runtime/composables/useSwipeGesture.js +140 -0
  109. package/dist/runtime/extensions/document-header.js +16 -6
  110. package/dist/runtime/extensions/document-meta.js +344 -19
  111. package/dist/runtime/extensions/meta-field.js +42 -0
  112. package/dist/runtime/extensions/views/DocumentMetaView.vue +33 -7
  113. package/dist/runtime/extensions/views/FieldView.vue +51 -19
  114. package/dist/runtime/extensions/views/MetaFieldView.vue +30 -4
  115. package/dist/runtime/locale.d.ts +8 -0
  116. package/dist/runtime/locale.js +9 -1
  117. package/dist/runtime/middleware/abracadabra-auth.d.ts +1 -1
  118. package/dist/runtime/plugin-abracadabra.client.d.ts +1 -1
  119. package/dist/runtime/plugin-abracadabra.client.js +12 -2
  120. package/dist/runtime/plugin-abracadabra.server.d.ts +1 -1
  121. package/dist/runtime/plugin-shared-globals.client.d.ts +1 -1
  122. package/dist/runtime/utils/chatContent.d.ts +20 -2
  123. package/dist/runtime/utils/chatContent.js +20 -1
  124. package/dist/runtime/utils/docTypes.js +43 -0
  125. package/dist/runtime/utils/titleSync.d.ts +130 -0
  126. package/dist/runtime/utils/titleSync.js +53 -0
  127. package/package.json +11 -4
package/dist/module.d.mts CHANGED
@@ -30,6 +30,7 @@ declare module '@nuxt/schema' {
30
30
  abracadabra: {
31
31
  url: string;
32
32
  entryDocId: string;
33
+ pinServer: boolean;
33
34
  persistAuth: boolean;
34
35
  authStorageKey: string;
35
36
  disabledBuiltins: string[];
@@ -115,6 +116,19 @@ interface ModuleOptions {
115
116
  * entryDocId > saved server entryDocId > first Space (kind="space" top-level doc)
116
117
  */
117
118
  entryDocId?: string;
119
+ /**
120
+ * Pin the app to the configured `url` and ignore any persisted active
121
+ * server (`abracadabra_current_server`) or `?server=` deep-link override.
122
+ * Default: false — the switcher's last-used / deep-linked server wins, which
123
+ * is what multi-server apps (arcana, aperio) want.
124
+ *
125
+ * Set `true` for single-server consumers (marketing, landing) so a visitor
126
+ * who once connected the origin to another server can't get silently pinned
127
+ * to it on later loads while `entryDocId` still points at the configured
128
+ * server's doc — the failure mode is a phantom/empty entry doc with no sync
129
+ * or awareness. When `true`, boot always uses `url`.
130
+ */
131
+ pinServer?: boolean;
118
132
  /**
119
133
  * Whether to persist the JWT token across page reloads via localStorage.
120
134
  * Default: true.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "2.10.0",
7
+ "version": "2.13.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -19,6 +19,7 @@ const module$1 = defineNuxtModule({
19
19
  },
20
20
  defaults: {
21
21
  url: process.env.ABRACADABRA_URL ?? "https://abra.cou.sh",
22
+ pinServer: false,
22
23
  persistAuth: true,
23
24
  authStorageKey: "abracadabra:auth",
24
25
  disabledBuiltins: [],
@@ -85,6 +86,7 @@ const module$1 = defineNuxtModule({
85
86
  },
86
87
  url: options.url,
87
88
  entryDocId: options.entryDocId ?? "",
89
+ pinServer: options.pinServer ?? false,
88
90
  persistAuth: options.persistAuth,
89
91
  authStorageKey: options.authStorageKey,
90
92
  disabledBuiltins: options.disabledBuiltins,
@@ -290,6 +292,13 @@ const module$1 = defineNuxtModule({
290
292
  prebundleDeps.push("mapbox-gl");
291
293
  } catch {
292
294
  }
295
+ for (const pkg of ["@unovis/vue", "@unovis/ts"]) {
296
+ try {
297
+ createRequire(`${nuxt.options.rootDir}/`).resolve(pkg);
298
+ prebundleDeps.push(pkg);
299
+ } catch {
300
+ }
301
+ }
293
302
  if (options.features?.code !== false) {
294
303
  const cmPeers = [
295
304
  "@codemirror/view",
@@ -1 +1 @@
1
- html.dark .tiptap .shiki,html.dark .tiptap .shiki span{background-color:var(--ui-bg-muted)!important;color:var(--shiki-dark)!important}.collaboration-carets__caret{border-left:1px solid #0d0d0d;border-right:1px solid #0d0d0d;margin-left:-1px;margin-right:-1px;opacity:0;pointer-events:none;position:relative;transition:opacity .3s ease;word-break:normal}.collaboration-carets__caret.is-hidden{display:none}.collaboration-carets__caret.is-active{opacity:1}.collaboration-carets__label{border-radius:3px 3px 3px 0;color:#0d0d0d;font-size:12px;font-style:normal;font-weight:600;left:-1px;line-height:normal;padding:.1rem .3rem;position:absolute;top:-1.4em;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.ProseMirror-yjs-selection,.collaboration-carets__selection{background-color:var(--collaboration-selection-color)!important;pointer-events:none}.search-highlight{background-color:color-mix(in srgb,var(--color-primary-400) 35%,transparent);border-radius:2px;padding:0 1px}.doc-passage-highlight{background-color:color-mix(in srgb,var(--color-success-400) 35%,transparent);border-radius:2px;padding:0 1px}
1
+ html.dark .tiptap .shiki,html.dark .tiptap .shiki span{background-color:var(--ui-bg-muted)!important;color:var(--shiki-dark)!important}.collaboration-carets__caret{border-left:1px solid #0d0d0d;border-right:1px solid #0d0d0d;margin-left:-1px;margin-right:-1px;opacity:0;pointer-events:none;position:relative;transition:opacity .3s ease;word-break:normal}.collaboration-carets__caret.is-hidden{display:none}.collaboration-carets__caret.is-active{opacity:1}.collaboration-carets__label{border-radius:3px 3px 3px 0;color:#0d0d0d;font-size:12px;font-style:normal;font-weight:600;left:-1px;line-height:normal;padding:.1rem .3rem;position:absolute;top:-1.4em;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.ProseMirror-yjs-selection,.collaboration-carets__selection{background-color:var(--collaboration-selection-color)!important;pointer-events:none}.search-highlight{background-color:color-mix(in srgb,var(--color-primary-400) 35%,transparent);border-radius:2px;padding:0 1px}.doc-passage-highlight{background-color:color-mix(in srgb,var(--color-success-400) 35%,transparent);border-radius:2px;padding:0 1px}.tiptap{min-height:100%;padding-bottom:8rem}.tiptap.file-drop-active{border-radius:4px;outline:2px dashed var(--ui-primary);outline-offset:4px}.tiptap .document-header{font-size:2.5rem;font-weight:800;letter-spacing:-.025em;line-height:1.2;margin-bottom:1rem}.tiptap [data-type=document-meta]{align-items:center;display:flex;flex-wrap:wrap;font-size:.875rem;gap:.375rem;line-height:1.75rem;margin-bottom:1.5rem;min-height:1.75rem}.tiptap [data-type=document-meta][data-cursor-in-meta=true][data-empty=true]:after{color:var(--ui-text-dimmed);content:"Type '/' to add a property…";pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.prose-variant .tiptap [data-type=document-meta]{display:none}
@@ -0,0 +1,29 @@
1
+ type __VLS_Props = {
2
+ /** The document's child provider (AbracadabraProvider or a local wrapper). */
3
+ provider?: any;
4
+ /**
5
+ * Override the four status strings. `label` is the short status word
6
+ * (also the aria-label); `tooltip` is the longer hover explanation.
7
+ */
8
+ labels?: Partial<{
9
+ authFailedCached: {
10
+ label: string;
11
+ tooltip: string;
12
+ };
13
+ authFailedNoCache: {
14
+ label: string;
15
+ tooltip: string;
16
+ };
17
+ offlineCached: {
18
+ label: string;
19
+ tooltip: string;
20
+ };
21
+ offlineNoCache: {
22
+ label: string;
23
+ tooltip: string;
24
+ };
25
+ }>;
26
+ };
27
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
+ declare const _default: typeof __VLS_export;
29
+ export default _default;
@@ -0,0 +1,79 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from "vue";
3
+ const props = defineProps({
4
+ provider: { type: null, required: false },
5
+ labels: { type: Object, required: false }
6
+ });
7
+ const DEFAULTS = {
8
+ authFailedCached: {
9
+ label: "Cached copy",
10
+ tooltip: "Server rejected the connection \u2014 showing the last cached copy."
11
+ },
12
+ authFailedNoCache: {
13
+ label: "No access",
14
+ tooltip: "Server rejected the connection and no cached copy is available."
15
+ },
16
+ offlineCached: {
17
+ label: "Offline",
18
+ tooltip: "Server is unreachable \u2014 showing the last cached copy. Edits will sync when you reconnect."
19
+ },
20
+ offlineNoCache: {
21
+ label: "Not yet synced",
22
+ tooltip: "Server is unreachable and this document hasn't been synced to this device yet. Reconnect to load it."
23
+ }
24
+ };
25
+ const isRemoteProvider = computed(
26
+ () => !!props.provider && typeof props.provider.connectionStatus === "string"
27
+ );
28
+ const connStatus = ref("connecting");
29
+ const authFailed = ref(false);
30
+ watch(
31
+ () => props.provider,
32
+ (p, _old, onCleanup) => {
33
+ if (!p || typeof p.connectionStatus !== "string") return;
34
+ connStatus.value = p.connectionStatus ?? "disconnected";
35
+ authFailed.value = false;
36
+ const onStatus = (e) => {
37
+ connStatus.value = e?.status ?? "disconnected";
38
+ if (e?.status === "connected") authFailed.value = false;
39
+ };
40
+ const onAuthFail = () => {
41
+ authFailed.value = true;
42
+ };
43
+ p.on?.("status", onStatus);
44
+ p.on?.("authenticationFailed", onAuthFail);
45
+ onCleanup(() => {
46
+ p.off?.("status", onStatus);
47
+ p.off?.("authenticationFailed", onAuthFail);
48
+ });
49
+ },
50
+ { immediate: true }
51
+ );
52
+ const isOffline = computed(
53
+ () => isRemoteProvider.value && (authFailed.value || connStatus.value !== "connected")
54
+ );
55
+ const badge = computed(() => {
56
+ if (!isRemoteProvider.value || !isOffline.value) return null;
57
+ const hasCache = !!props.provider?.hasCachedContent;
58
+ const strings = { ...DEFAULTS, ...props.labels };
59
+ if (authFailed.value) {
60
+ return hasCache ? strings.authFailedCached : strings.authFailedNoCache;
61
+ }
62
+ return hasCache ? strings.offlineCached : strings.offlineNoCache;
63
+ });
64
+ </script>
65
+
66
+ <template>
67
+ <UTooltip
68
+ v-if="badge"
69
+ :text="badge.tooltip"
70
+ :content="{ side: 'bottom' }"
71
+ >
72
+ <span
73
+ class="size-2 rounded-full shrink-0 ring-1 ring-inset ring-black/10"
74
+ :class="authFailed ? 'bg-(--ui-color-error-500)' : 'bg-(--ui-color-warning-500)'"
75
+ role="status"
76
+ :aria-label="badge.label"
77
+ />
78
+ </UTooltip>
79
+ </template>
@@ -0,0 +1,29 @@
1
+ type __VLS_Props = {
2
+ /** The document's child provider (AbracadabraProvider or a local wrapper). */
3
+ provider?: any;
4
+ /**
5
+ * Override the four status strings. `label` is the short status word
6
+ * (also the aria-label); `tooltip` is the longer hover explanation.
7
+ */
8
+ labels?: Partial<{
9
+ authFailedCached: {
10
+ label: string;
11
+ tooltip: string;
12
+ };
13
+ authFailedNoCache: {
14
+ label: string;
15
+ tooltip: string;
16
+ };
17
+ offlineCached: {
18
+ label: string;
19
+ tooltip: string;
20
+ };
21
+ offlineNoCache: {
22
+ label: string;
23
+ tooltip: string;
24
+ };
25
+ }>;
26
+ };
27
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
+ declare const _default: typeof __VLS_export;
29
+ export default _default;
@@ -0,0 +1,31 @@
1
+ type __VLS_Props = {
2
+ open: boolean;
3
+ /** Modal heading. */
4
+ title?: string;
5
+ /** Confirm button label. */
6
+ confirmLabel?: string;
7
+ /** Doc to exclude as a target along with its whole subtree (e.g. the doc being moved). */
8
+ excludeId?: string | null;
9
+ /** Extra ids to exclude as targets. */
10
+ excludeIds?: string[];
11
+ /** Offer a "Top level" (root) option. */
12
+ allowRoot?: boolean;
13
+ /** Label for the root option. */
14
+ rootLabel?: string;
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
+ select: (targetId: string | null) => any;
18
+ "update:open": (v: boolean) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ onSelect?: ((targetId: string | null) => any) | undefined;
21
+ "onUpdate:open"?: ((v: boolean) => any) | undefined;
22
+ }>, {
23
+ title: string;
24
+ confirmLabel: string;
25
+ excludeId: string | null;
26
+ excludeIds: string[];
27
+ allowRoot: boolean;
28
+ rootLabel: string;
29
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
@@ -0,0 +1,191 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from "vue";
3
+ import { useDocTree } from "../composables/useDocTree";
4
+ import { resolveDocType } from "../utils/docTypes";
5
+ import AModalShell from "./AModalShell.vue";
6
+ const props = defineProps({
7
+ open: { type: Boolean, required: true },
8
+ title: { type: String, required: false, default: "Move to\u2026" },
9
+ confirmLabel: { type: String, required: false, default: "Move here" },
10
+ excludeId: { type: [String, null], required: false, default: null },
11
+ excludeIds: { type: Array, required: false, default: () => [] },
12
+ allowRoot: { type: Boolean, required: false, default: true },
13
+ rootLabel: { type: String, required: false, default: "Top level" }
14
+ });
15
+ const emit = defineEmits(["update:open", "select"]);
16
+ const { entries } = useDocTree();
17
+ const open = computed({
18
+ get: () => props.open,
19
+ set: (v) => emit("update:open", v)
20
+ });
21
+ const idSet = computed(() => new Set(entries.value.map((e) => e.id)));
22
+ const childrenOf = computed(() => {
23
+ const map = /* @__PURE__ */ new Map();
24
+ for (const e of entries.value) {
25
+ if (e.trashed) continue;
26
+ const pid = e.parentId && idSet.value.has(e.parentId) ? e.parentId : null;
27
+ const arr = map.get(pid) ?? [];
28
+ arr.push(e);
29
+ map.set(pid, arr);
30
+ }
31
+ for (const arr of map.values()) arr.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
32
+ return map;
33
+ });
34
+ const excluded = computed(() => {
35
+ const set = new Set(props.excludeIds);
36
+ if (props.excludeId) {
37
+ const queue = [props.excludeId];
38
+ while (queue.length) {
39
+ const id = queue.shift();
40
+ set.add(id);
41
+ for (const child of childrenOf.value.get(id) ?? []) queue.push(child.id);
42
+ }
43
+ }
44
+ return set;
45
+ });
46
+ const expanded = ref(/* @__PURE__ */ new Set());
47
+ const selectedId = ref(null);
48
+ const search = ref("");
49
+ watch(() => props.open, (isOpen) => {
50
+ if (isOpen) {
51
+ selectedId.value = null;
52
+ search.value = "";
53
+ expanded.value = /* @__PURE__ */ new Set();
54
+ }
55
+ });
56
+ function iconFor(entry) {
57
+ return entry?.meta?.icon ? entry.meta.icon.startsWith("i-") ? entry.meta.icon : `i-lucide-${entry.meta.icon}` : resolveDocType(entry?.type ?? "doc").icon;
58
+ }
59
+ const rows = computed(() => {
60
+ const q = search.value.trim().toLowerCase();
61
+ if (q) {
62
+ return entries.value.filter((e) => !e.trashed && !excluded.value.has(e.id) && (e.label || "").toLowerCase().includes(q)).sort((a, b) => (a.label || "").localeCompare(b.label || "")).slice(0, 100).map((e) => ({ id: e.id, label: e.label || "Untitled", icon: iconFor(e), depth: 0, hasChildren: false, expanded: false }));
63
+ }
64
+ const out = [];
65
+ const walk = (pid, depth) => {
66
+ for (const e of childrenOf.value.get(pid) ?? []) {
67
+ if (excluded.value.has(e.id)) continue;
68
+ const kids = (childrenOf.value.get(e.id) ?? []).filter((c) => !excluded.value.has(c.id));
69
+ const isExp = expanded.value.has(e.id);
70
+ out.push({ id: e.id, label: e.label || "Untitled", icon: iconFor(e), depth, hasChildren: kids.length > 0, expanded: isExp });
71
+ if (isExp) walk(e.id, depth + 1);
72
+ }
73
+ };
74
+ walk(null, 0);
75
+ return out;
76
+ });
77
+ function toggle(id) {
78
+ const next = new Set(expanded.value);
79
+ if (next.has(id)) next.delete(id);
80
+ else next.add(id);
81
+ expanded.value = next;
82
+ }
83
+ function confirm() {
84
+ emit("select", selectedId.value);
85
+ emit("update:open", false);
86
+ }
87
+ </script>
88
+
89
+ <template>
90
+ <AModalShell
91
+ :open="open"
92
+ :title="title"
93
+ max-width="sm:max-w-lg"
94
+ @update:open="open = $event"
95
+ >
96
+ <div class="flex flex-col gap-3">
97
+ <UInput
98
+ v-model="search"
99
+ icon="i-lucide-search"
100
+ placeholder="Search documents…"
101
+ size="sm"
102
+ class="w-full"
103
+ autofocus
104
+ />
105
+
106
+ <div class="max-h-[50vh] overflow-y-auto rounded-md border border-(--ui-border) divide-y divide-(--ui-border)/60">
107
+ <!-- Root / top-level option -->
108
+ <button
109
+ v-if="allowRoot && !search"
110
+ type="button"
111
+ class="w-full flex items-center gap-2 px-3 py-2 text-left text-sm hover:bg-(--ui-bg-elevated) transition-colors"
112
+ :class="selectedId === null ? 'bg-(--ui-primary)/10 text-(--ui-primary)' : ''"
113
+ @click="selectedId = null"
114
+ >
115
+ <UIcon
116
+ name="i-lucide-home"
117
+ class="size-4 shrink-0 text-(--ui-text-muted)"
118
+ />
119
+ <span class="flex-1 truncate">{{ rootLabel }}</span>
120
+ <UIcon
121
+ v-if="selectedId === null"
122
+ name="i-lucide-check"
123
+ class="size-4 shrink-0"
124
+ />
125
+ </button>
126
+
127
+ <div
128
+ v-for="row in rows"
129
+ :key="row.id"
130
+ class="flex items-center hover:bg-(--ui-bg-elevated) transition-colors"
131
+ :class="selectedId === row.id ? 'bg-(--ui-primary)/10' : ''"
132
+ :style="{ paddingLeft: `${row.depth * 16}px` }"
133
+ >
134
+ <button
135
+ type="button"
136
+ class="size-6 shrink-0 flex items-center justify-center text-(--ui-text-dimmed) hover:text-(--ui-text)"
137
+ :class="row.hasChildren ? '' : 'invisible'"
138
+ @click="toggle(row.id)"
139
+ >
140
+ <UIcon
141
+ :name="row.expanded ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
142
+ class="size-4"
143
+ />
144
+ </button>
145
+ <button
146
+ type="button"
147
+ class="flex-1 min-w-0 flex items-center gap-2 px-1 py-2 text-left text-sm"
148
+ :class="selectedId === row.id ? 'text-(--ui-primary)' : ''"
149
+ @click="selectedId = row.id"
150
+ >
151
+ <UIcon
152
+ :name="row.icon"
153
+ class="size-4 shrink-0 text-(--ui-text-muted)"
154
+ />
155
+ <span class="flex-1 truncate">{{ row.label }}</span>
156
+ <UIcon
157
+ v-if="selectedId === row.id"
158
+ name="i-lucide-check"
159
+ class="size-4 shrink-0 mr-2"
160
+ />
161
+ </button>
162
+ </div>
163
+
164
+ <p
165
+ v-if="rows.length === 0"
166
+ class="px-3 py-6 text-center text-sm text-(--ui-text-dimmed)"
167
+ >
168
+ {{ search ? "No matching documents." : "No documents." }}
169
+ </p>
170
+ </div>
171
+ </div>
172
+
173
+ <template #footer>
174
+ <div class="flex items-center justify-end gap-2 w-full">
175
+ <UButton
176
+ label="Cancel"
177
+ color="neutral"
178
+ variant="ghost"
179
+ @click="emit('update:open', false)"
180
+ />
181
+ <UButton
182
+ :label="confirmLabel"
183
+ color="primary"
184
+ icon="i-lucide-corner-down-right"
185
+ :disabled="selectedId === null && !allowRoot"
186
+ @click="confirm"
187
+ />
188
+ </div>
189
+ </template>
190
+ </AModalShell>
191
+ </template>
@@ -0,0 +1,31 @@
1
+ type __VLS_Props = {
2
+ open: boolean;
3
+ /** Modal heading. */
4
+ title?: string;
5
+ /** Confirm button label. */
6
+ confirmLabel?: string;
7
+ /** Doc to exclude as a target along with its whole subtree (e.g. the doc being moved). */
8
+ excludeId?: string | null;
9
+ /** Extra ids to exclude as targets. */
10
+ excludeIds?: string[];
11
+ /** Offer a "Top level" (root) option. */
12
+ allowRoot?: boolean;
13
+ /** Label for the root option. */
14
+ rootLabel?: string;
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
+ select: (targetId: string | null) => any;
18
+ "update:open": (v: boolean) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ onSelect?: ((targetId: string | null) => any) | undefined;
21
+ "onUpdate:open"?: ((v: boolean) => any) | undefined;
22
+ }>, {
23
+ title: string;
24
+ confirmLabel: string;
25
+ excludeId: string | null;
26
+ excludeIds: string[];
27
+ allowRoot: boolean;
28
+ rootLabel: string;
29
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
@@ -298,6 +298,45 @@ function deleteDoc(docId) {
298
298
  if (!docId || !isReady.value) return;
299
299
  moveToTrash(docId, userName.value);
300
300
  }
301
+ const moveTargetId = ref(null);
302
+ const movePickerOpen = ref(false);
303
+ function openMovePicker(docId) {
304
+ moveTargetId.value = docId;
305
+ movePickerOpen.value = true;
306
+ }
307
+ function performMove(newParentId) {
308
+ const id = moveTargetId.value;
309
+ if (!id || !isReady.value) return;
310
+ const entry = treeMap.data[id];
311
+ if (!entry) return;
312
+ if ((entry.parentId ?? null) === newParentId) return;
313
+ treeMap.set(id, { ...entry, parentId: newParentId, order: Date.now() });
314
+ if (newParentId) {
315
+ client.value?.createChild(newParentId, { child_id: id, kind: "page" }).catch(() => {
316
+ });
317
+ expandedIds.value.add(newParentId);
318
+ expandedIds.value = new Set(expandedIds.value);
319
+ }
320
+ emit("navigate", id);
321
+ }
322
+ const tagsTargetId = ref(null);
323
+ const tagsEditorOpen = ref(false);
324
+ function openTagsEditor(docId) {
325
+ tagsTargetId.value = docId;
326
+ tagsEditorOpen.value = true;
327
+ }
328
+ const tagsTargetTags = computed(() => {
329
+ const id = tagsTargetId.value;
330
+ if (!id) return [];
331
+ return treeMap.data[id]?.meta?.tags ?? [];
332
+ });
333
+ function saveTags(tags) {
334
+ const id = tagsTargetId.value;
335
+ if (!id || !isReady.value) return;
336
+ const entry = treeMap.data[id];
337
+ if (!entry) return;
338
+ treeMap.set(id, { ...entry, meta: { ...entry.meta ?? {}, tags } });
339
+ }
301
340
  const renameId = ref(null);
302
341
  const renameValue = ref("");
303
342
  const renameInputRef = ref(null);
@@ -770,6 +809,16 @@ function treeNodeMenuItems(item) {
770
809
  label: "Add child page",
771
810
  icon: "i-lucide-file-plus",
772
811
  onSelect: () => createDirectly(item.id)
812
+ },
813
+ {
814
+ label: "Move to\u2026",
815
+ icon: "i-lucide-corner-down-right",
816
+ onSelect: () => openMovePicker(item.id)
817
+ },
818
+ {
819
+ label: "Edit tags\u2026",
820
+ icon: "i-lucide-tags",
821
+ onSelect: () => openTagsEditor(item.id)
773
822
  }
774
823
  ],
775
824
  [
@@ -1517,6 +1566,22 @@ defineExpose({
1517
1566
  @close="closeOverlay"
1518
1567
  />
1519
1568
  </div>
1569
+
1570
+ <!-- Move-to reparent picker -->
1571
+ <ADocPickerModal
1572
+ v-model:open="movePickerOpen"
1573
+ :exclude-id="moveTargetId"
1574
+ title="Move to…"
1575
+ confirm-label="Move here"
1576
+ @select="performMove"
1577
+ />
1578
+
1579
+ <!-- Edit-tags editor -->
1580
+ <ATagsEditor
1581
+ v-model:open="tagsEditorOpen"
1582
+ :tags="tagsTargetTags"
1583
+ @save="saveTags"
1584
+ />
1520
1585
  </template>
1521
1586
 
1522
1587
  <style scoped>
@@ -35,12 +35,18 @@ type __VLS_Props = {
35
35
  * Useful for inspecting CRDT state during development. Default: false.
36
36
  */
37
37
  showSourceToggle?: boolean;
38
+ /**
39
+ * Show the in-canvas ancestor breadcrumb above the document (matching
40
+ * cou-sh's DocRenderer). Only renders when the doc actually has ancestors,
41
+ * so root/space docs show nothing. Set false for embeds/previews.
42
+ */
43
+ showBreadcrumb?: boolean;
38
44
  };
39
45
  type __VLS_ModelProps = {
40
46
  modelValue?: any;
41
47
  };
42
48
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
43
- declare var __VLS_42: {
49
+ declare var __VLS_58: {
44
50
  editor: any;
45
51
  connectedUsers: {
46
52
  name: string;
@@ -49,40 +55,40 @@ declare var __VLS_42: {
49
55
  docId?: string | undefined;
50
56
  }[];
51
57
  ready: boolean;
52
- }, __VLS_51: {
58
+ }, __VLS_67: {
53
59
  editor: any;
54
- }, __VLS_59: {
60
+ }, __VLS_75: {
55
61
  editor: any;
56
- }, __VLS_67: {
62
+ }, __VLS_83: {
57
63
  editor: any;
58
- }, __VLS_70: {
64
+ }, __VLS_86: {
59
65
  editor: any;
60
66
  };
61
67
  type __VLS_Slots = {} & {
62
- default?: (props: typeof __VLS_42) => any;
68
+ default?: (props: typeof __VLS_58) => any;
63
69
  } & {
64
- link?: (props: typeof __VLS_51) => any;
70
+ link?: (props: typeof __VLS_67) => any;
65
71
  } & {
66
- 'doc-link'?: (props: typeof __VLS_59) => any;
72
+ 'doc-link'?: (props: typeof __VLS_75) => any;
67
73
  } & {
68
- 'create-child-doc'?: (props: typeof __VLS_67) => any;
74
+ 'create-child-doc'?: (props: typeof __VLS_83) => any;
69
75
  } & {
70
- 'send-to-chat'?: (props: typeof __VLS_70) => any;
76
+ 'send-to-chat'?: (props: typeof __VLS_86) => any;
71
77
  };
72
78
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
73
79
  editor: import("vue").ComputedRef<any>;
74
80
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
75
81
  rename: (label: string) => any;
82
+ ready: () => any;
76
83
  update: (content: any) => any;
77
84
  "update:modelValue": (value: any) => any;
78
85
  updateMeta: (patch: Partial<DocPageMeta>) => any;
79
- ready: () => any;
80
86
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
81
87
  onRename?: ((label: string) => any) | undefined;
88
+ onReady?: (() => any) | undefined;
82
89
  onUpdate?: ((content: any) => any) | undefined;
83
90
  "onUpdate:modelValue"?: ((value: any) => any) | undefined;
84
91
  onUpdateMeta?: ((patch: Partial<DocPageMeta>) => any) | undefined;
85
- onReady?: (() => any) | undefined;
86
92
  }>, {
87
93
  contentType: "json" | "html" | "markdown";
88
94
  editable: boolean;
@@ -91,6 +97,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
91
97
  showDragHandle: boolean;
92
98
  variant: "doc" | "prose";
93
99
  showSourceToggle: boolean;
100
+ showBreadcrumb: boolean;
94
101
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
95
102
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
96
103
  declare const _default: typeof __VLS_export;