@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.
Files changed (93) hide show
  1. package/dist/module.d.mts +15 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +9 -0
  4. package/dist/runtime/components/ACodeEditor.vue +123 -22
  5. package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
  6. package/dist/runtime/components/ADocPickerModal.vue +191 -0
  7. package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
  8. package/dist/runtime/components/ADocViewToggle.d.vue.ts +40 -0
  9. package/dist/runtime/components/ADocViewToggle.vue +234 -0
  10. package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
  11. package/dist/runtime/components/ADocumentTree.vue +66 -1
  12. package/dist/runtime/components/AEditor.d.vue.ts +17 -10
  13. package/dist/runtime/components/AEditor.vue +403 -167
  14. package/dist/runtime/components/AEditor.vue.d.ts +17 -10
  15. package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
  16. package/dist/runtime/components/ANodePanel.vue +553 -481
  17. package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
  18. package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
  19. package/dist/runtime/components/ATagsEditor.vue +60 -0
  20. package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
  21. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  22. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  23. package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
  24. package/dist/runtime/components/chat/AChatInput.vue +33 -2
  25. package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
  26. package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
  27. package/dist/runtime/components/chat/AChatList.vue +76 -32
  28. package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
  29. package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
  30. package/dist/runtime/components/chat/AChatMessages.vue +57 -4
  31. package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
  32. package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
  33. package/dist/runtime/components/chat/AChatPanel.vue +17 -1
  34. package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
  35. package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
  36. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
  37. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
  38. package/dist/runtime/components/editor/ADocSuggestMenu.d.vue.ts +7 -0
  39. package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
  40. package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
  41. package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
  42. package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
  43. package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
  44. package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
  45. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
  46. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
  47. package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
  48. package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
  49. package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
  50. package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
  51. package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
  52. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
  53. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
  54. package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
  55. package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
  56. package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
  57. package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
  58. package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
  59. package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
  60. package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
  61. package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
  62. package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
  63. package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
  64. package/dist/runtime/components/settings/sections.d.ts +37 -0
  65. package/dist/runtime/components/settings/sections.js +45 -0
  66. package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
  67. package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
  68. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  69. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  70. package/dist/runtime/composables/useChat.d.ts +22 -1
  71. package/dist/runtime/composables/useChat.js +79 -8
  72. package/dist/runtime/composables/useDocLinkPick.d.ts +9 -8
  73. package/dist/runtime/composables/useDocLinkPick.js +7 -18
  74. package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
  75. package/dist/runtime/composables/useDocSuggest.js +56 -0
  76. package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
  77. package/dist/runtime/composables/useNodeContextMenu.js +18 -0
  78. package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
  79. package/dist/runtime/extensions/doc-link-drop.js +2 -2
  80. package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
  81. package/dist/runtime/extensions/doc-suggest.js +85 -0
  82. package/dist/runtime/locale.d.ts +8 -0
  83. package/dist/runtime/locale.js +9 -1
  84. package/dist/runtime/utils/chatContent.d.ts +20 -2
  85. package/dist/runtime/utils/chatContent.js +20 -1
  86. package/dist/runtime/utils/codeHighlightStyle.d.ts +15 -0
  87. package/dist/runtime/utils/codeHighlightStyle.js +34 -0
  88. package/dist/runtime/utils/docTypes.js +43 -0
  89. package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
  90. package/dist/runtime/utils/loadCodeMirror.js +6 -3
  91. package/dist/runtime/utils/titleSync.d.ts +130 -0
  92. package/dist/runtime/utils/titleSync.js +53 -0
  93. package/package.json +12 -1
package/dist/module.d.mts CHANGED
@@ -59,6 +59,7 @@ declare module '@nuxt/schema' {
59
59
  };
60
60
  debug: boolean;
61
61
  docBasePath: string;
62
+ docPicker: 'inline' | 'command-palette';
62
63
  guestName: {
63
64
  adjectives: string[];
64
65
  nouns: string[];
@@ -258,6 +259,20 @@ interface ModuleOptions {
258
259
  * docBasePath: '' → /{docId}
259
260
  */
260
261
  docBasePath?: string;
262
+ /**
263
+ * How documents are picked for cross-document references (doc links/embeds).
264
+ *
265
+ * - `'inline'` (default) — typing `[[` opens an inline mention-style doc-link
266
+ * popup, and `![[` an inline doc-embed popup (flat search + ancestor-path
267
+ * prefix). The slash commands + doc-link toolbar button still open the
268
+ * command palette.
269
+ * - `'command-palette'` — disables the inline `[[` / `![[` triggers; the only
270
+ * doc-reference entry points are the slash commands + toolbar button, which
271
+ * open a `UCommandPalette` modal.
272
+ *
273
+ * Default: 'inline'.
274
+ */
275
+ docPicker?: 'inline' | 'command-palette';
261
276
  /**
262
277
  * Mapbox GL access token for the Map renderer.
263
278
  * Can be overridden at runtime via NUXT_PUBLIC_ABRACADABRA_MAPBOX_TOKEN.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "2.11.0",
7
+ "version": "2.14.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -50,6 +50,7 @@ const module$1 = defineNuxtModule({
50
50
  },
51
51
  debug: false,
52
52
  docBasePath: "/doc",
53
+ docPicker: "inline",
53
54
  guestName: {},
54
55
  webrtc: {
55
56
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
@@ -96,6 +97,7 @@ const module$1 = defineNuxtModule({
96
97
  server: options.server ?? {},
97
98
  debug: options.debug ?? false,
98
99
  docBasePath: options.docBasePath ?? "/doc",
100
+ docPicker: options.docPicker ?? "inline",
99
101
  mapboxToken: options.mapboxToken ?? "",
100
102
  guestName: {
101
103
  adjectives: options.guestName?.adjectives ?? [],
@@ -292,6 +294,13 @@ const module$1 = defineNuxtModule({
292
294
  prebundleDeps.push("mapbox-gl");
293
295
  } catch {
294
296
  }
297
+ for (const pkg of ["@unovis/vue", "@unovis/ts"]) {
298
+ try {
299
+ createRequire(`${nuxt.options.rootDir}/`).resolve(pkg);
300
+ prebundleDeps.push(pkg);
301
+ } catch {
302
+ }
303
+ }
295
304
  if (options.features?.code !== false) {
296
305
  const cmPeers = [
297
306
  "@codemirror/view",
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { ref, shallowRef, watch, computed, onMounted, onBeforeUnmount } from "vue";
3
3
  import { loadCodeMirror } from "../utils/loadCodeMirror";
4
+ import { buildAtomOneNuxtHighlight } from "../utils/codeHighlightStyle";
4
5
  import { useNuxtApp } from "#imports";
5
6
  const props = defineProps({
6
7
  provider: { type: null, required: true },
@@ -47,60 +48,134 @@ function buildTheme(bundle) {
47
48
  backgroundColor: "var(--ui-bg)",
48
49
  color: "var(--ui-text-highlighted)",
49
50
  height: "100%",
50
- fontSize: "13px",
51
- fontFamily: 'ui-monospace, "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, Monaco, Consolas, monospace'
51
+ fontSize: "13px"
52
52
  },
53
+ // Honour a host-defined code font (`--font-code`) when present; falls back
54
+ // to the system monospace stack. `!important` + the `.cm-content`/
55
+ // `.cm-gutters` specificity guards against an aggressive host body-font
56
+ // rule (e.g. `html[data-font] *`) leaking the UI sans-serif into the editor.
53
57
  ".cm-content": {
54
- fontFamily: "inherit",
58
+ fontFamily: 'var(--font-code, ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace) !important',
55
59
  caretColor: "var(--ui-text-highlighted)",
56
60
  padding: "8px 0"
57
61
  },
58
- ".cm-cursor": {
59
- borderLeftColor: "var(--ui-text-highlighted)"
62
+ // Local caret — `drawSelection()` hides the native caret and draws this
63
+ // bar, so style it to read like the native caret used elsewhere in the
64
+ // app: a crisp 2px stroke in the body text colour.
65
+ ".cm-cursor, .cm-dropCursor": {
66
+ borderLeftColor: "var(--ui-text-highlighted)",
67
+ borderLeftWidth: "2px"
60
68
  },
61
69
  ".cm-activeLine": {
62
70
  backgroundColor: "color-mix(in srgb, var(--ui-bg-elevated) 50%, transparent)"
63
71
  },
64
72
  ".cm-activeLineGutter": {
65
- backgroundColor: "color-mix(in srgb, var(--ui-bg-elevated) 50%, transparent)"
73
+ backgroundColor: "color-mix(in srgb, var(--ui-bg-elevated) 50%, transparent)",
74
+ color: "var(--ui-text)"
66
75
  },
67
76
  ".cm-gutters": {
68
- fontFamily: "inherit",
77
+ fontFamily: 'var(--font-code, ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace) !important',
69
78
  backgroundColor: "var(--ui-bg)",
70
79
  color: "var(--ui-text-dimmed)",
71
80
  border: "none",
72
81
  borderRight: "1px solid var(--ui-border)"
73
82
  },
83
+ ".cm-foldPlaceholder": {
84
+ backgroundColor: "var(--ui-bg-elevated)",
85
+ color: "var(--ui-text-muted)",
86
+ border: "1px solid var(--ui-border)"
87
+ },
88
+ // Local selection — translucent primary tint, brighter while focused.
74
89
  ".cm-selectionBackground": {
75
90
  backgroundColor: "color-mix(in srgb, var(--ui-primary) 25%, transparent) !important"
76
91
  },
77
92
  "&.cm-focused .cm-selectionBackground": {
78
- backgroundColor: "color-mix(in srgb, var(--ui-primary) 30%, transparent) !important"
93
+ backgroundColor: "color-mix(in srgb, var(--ui-primary) 40%, transparent) !important"
79
94
  },
80
95
  ".cm-matchingBracket": {
81
96
  backgroundColor: "color-mix(in srgb, var(--ui-primary) 20%, transparent)",
82
97
  outline: "1px solid color-mix(in srgb, var(--ui-primary) 40%, transparent)"
83
98
  },
99
+ // Occurrences of the current selection (highlightSelectionMatches).
100
+ ".cm-selectionMatch": {
101
+ backgroundColor: "color-mix(in srgb, var(--ui-primary) 15%, transparent)"
102
+ },
103
+ // Find/replace panel + search hits — themed to the UI tokens.
104
+ ".cm-panels": {
105
+ backgroundColor: "var(--ui-bg-elevated)",
106
+ color: "var(--ui-text)"
107
+ },
108
+ ".cm-panels.cm-panels-top": { borderBottom: "1px solid var(--ui-border)" },
109
+ ".cm-panels.cm-panels-bottom": { borderTop: "1px solid var(--ui-border)" },
110
+ ".cm-searchMatch": {
111
+ backgroundColor: "color-mix(in srgb, var(--ui-primary) 25%, transparent)",
112
+ outline: "1px solid color-mix(in srgb, var(--ui-primary) 45%, transparent)",
113
+ borderRadius: "2px"
114
+ },
115
+ ".cm-searchMatch-selected": {
116
+ backgroundColor: "color-mix(in srgb, var(--ui-primary) 45%, transparent) !important"
117
+ },
118
+ ".cm-textfield": {
119
+ backgroundColor: "var(--ui-bg)",
120
+ color: "var(--ui-text)",
121
+ border: "1px solid var(--ui-border)",
122
+ borderRadius: "4px"
123
+ },
124
+ ".cm-button": {
125
+ backgroundColor: "var(--ui-bg)",
126
+ backgroundImage: "none",
127
+ color: "var(--ui-text)",
128
+ border: "1px solid var(--ui-border)",
129
+ borderRadius: "4px"
130
+ },
131
+ // Autocomplete tooltip.
132
+ ".cm-tooltip": {
133
+ backgroundColor: "var(--ui-bg-elevated)",
134
+ color: "var(--ui-text)",
135
+ border: "1px solid var(--ui-border)",
136
+ borderRadius: "6px"
137
+ },
138
+ ".cm-tooltip-autocomplete > ul > li[aria-selected]": {
139
+ backgroundColor: "var(--ui-primary)",
140
+ color: "var(--ui-bg)"
141
+ },
142
+ // ── Remote presence (y-codemirror.next) — matches the TipTap editor's
143
+ // `.collaboration-carets__*` look so multiplayer feels identical across
144
+ // both editors. Per-user colour stays inline (set from awareness
145
+ // `user.color`); we restyle shape only. The caret already matches TipTap
146
+ // (1px border bars, -1px margins); we only fix the label badge + hide the
147
+ // caret dot.
148
+ ".cm-ySelection": { borderRadius: "1px" },
149
+ ".cm-ySelectionCaretDot": { display: "none" },
84
150
  ".cm-ySelectionInfo": {
85
- fontSize: "10px",
86
- fontFamily: "system-ui, sans-serif",
87
- padding: "1px 4px",
88
- borderRadius: "3px",
89
- opacity: "0.9",
90
- position: "absolute",
91
- top: "-1.3em",
151
+ opacity: "1",
152
+ top: "-1.4em",
92
153
  left: "-1px",
154
+ padding: "0.1rem 0.3rem",
155
+ borderRadius: "3px 3px 3px 0",
156
+ fontFamily: "system-ui, sans-serif",
157
+ fontSize: "12px",
158
+ fontStyle: "normal",
159
+ fontWeight: "600",
160
+ lineHeight: "normal",
161
+ color: "#0d0d0d",
93
162
  whiteSpace: "nowrap",
94
- color: "#fff"
95
- }
163
+ transitionDelay: "0s"
164
+ },
165
+ ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": { opacity: "1" }
96
166
  }, { dark: true });
97
167
  }
98
168
  let xmlObserverCleanup = null;
169
+ let syncedOff = null;
99
170
  function destroyEditor() {
100
171
  if (xmlObserverCleanup) {
101
172
  xmlObserverCleanup();
102
173
  xmlObserverCleanup = null;
103
174
  }
175
+ if (syncedOff) {
176
+ syncedOff();
177
+ syncedOff = null;
178
+ }
104
179
  if (editorView.value) {
105
180
  editorView.value.destroy();
106
181
  editorView.value = null;
@@ -110,7 +185,7 @@ function createEditor(bundle, container) {
110
185
  const { EditorView, lineNumbers, highlightActiveLine, highlightActiveLineGutter, drawSelection, rectangularSelection, crosshairCursor, highlightSpecialChars, dropCursor, keymap } = bundle.view;
111
186
  const { EditorState } = bundle.state;
112
187
  const { defaultKeymap, history, historyKeymap, indentWithTab } = bundle.commands;
113
- const { syntaxHighlighting, defaultHighlightStyle, indentOnInput, bracketMatching, foldGutter, foldKeymap } = bundle.language;
188
+ const { syntaxHighlighting, HighlightStyle, indentOnInput, bracketMatching, foldGutter, foldKeymap } = bundle.language;
114
189
  const { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap } = bundle.autocomplete;
115
190
  const { searchKeymap, highlightSelectionMatches } = bundle.search;
116
191
  const baseExtensions = [
@@ -122,7 +197,7 @@ function createEditor(bundle, container) {
122
197
  dropCursor(),
123
198
  EditorState.allowMultipleSelections.of(true),
124
199
  indentOnInput(),
125
- syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
200
+ syntaxHighlighting(buildAtomOneNuxtHighlight(HighlightStyle, bundle.lezerHighlight.tags), { fallback: true }),
126
201
  bracketMatching(),
127
202
  closeBrackets(),
128
203
  autocompletion(),
@@ -168,9 +243,14 @@ function createEditor(bundle, container) {
168
243
  const ytext = ydoc.getText(props.fieldName);
169
244
  const awareness = prov.awareness;
170
245
  if (awareness && userName?.value && publicKeyB64?.value) {
246
+ const color = userColor?.value ?? "#888";
171
247
  awareness.setLocalStateField("user", {
172
248
  name: userName.value,
173
- color: userColor?.value ?? "#888",
249
+ color,
250
+ // Translucent fill for remote selection blocks (y-codemirror.next).
251
+ // Its default `color + '33'` is invalid for hsl()/named/var() colours,
252
+ // leaving the selection invisible — supply a format-agnostic value.
253
+ colorLight: `color-mix(in srgb, ${color} 30%, transparent)`,
174
254
  publicKey: publicKeyB64.value
175
255
  });
176
256
  }
@@ -180,10 +260,25 @@ function createEditor(bundle, container) {
180
260
  } else {
181
261
  extensions.push(history());
182
262
  }
183
- return new EditorView({
263
+ const view = new EditorView({
184
264
  state: EditorState.create({ doc: ytext.toString(), extensions }),
185
265
  parent: container
186
266
  });
267
+ let rebuiltOnSync = false;
268
+ const onSynced = () => {
269
+ if (rebuiltOnSync) return;
270
+ const v = editorView.value;
271
+ const liveYtext = prov.document?.getText?.(props.fieldName);
272
+ if (!v || !liveYtext) return;
273
+ if (liveYtext.toString() !== v.state.doc.toString()) {
274
+ rebuiltOnSync = true;
275
+ void mount();
276
+ }
277
+ };
278
+ prov.on?.("synced", onSynced);
279
+ syncedOff = () => prov.off?.("synced", onSynced);
280
+ if (prov.isSynced) Promise.resolve().then(onSynced);
281
+ return view;
187
282
  }
188
283
  return new EditorView({
189
284
  state: EditorState.create({ doc: "", extensions: [...baseExtensions, history()] }),
@@ -224,7 +319,13 @@ watch(
224
319
  ([n, c, k]) => {
225
320
  const awareness = props.provider?.awareness;
226
321
  if (!awareness || !n || !k) return;
227
- awareness.setLocalStateField("user", { name: n, color: c ?? "#888", publicKey: k });
322
+ const color = c ?? "#888";
323
+ awareness.setLocalStateField("user", {
324
+ name: n,
325
+ color,
326
+ colorLight: `color-mix(in srgb, ${color} 30%, transparent)`,
327
+ publicKey: k
328
+ });
228
329
  }
229
330
  );
230
331
  onBeforeUnmount(destroyEditor);
@@ -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;
@@ -0,0 +1,40 @@
1
+ export type DocViewTab = 'pageType' | 'editor' | 'chat' | 'settings';
2
+ type __VLS_Props = {
3
+ /** Active tab. `null` (or absent) = nothing highlighted (e.g. another tab is active). */
4
+ modelValue?: DocViewTab | null;
5
+ docId: string;
6
+ docType?: string;
7
+ chatUnread?: number;
8
+ /** Disable the editor segment (e.g. main view is already the editor). */
9
+ editorDisabled?: boolean;
10
+ /** Disable the page-type segment (e.g. provider not ready). */
11
+ pageTypeDisabled?: boolean;
12
+ /** Whether the user may assign/change the page type (gates the + dropdown). */
13
+ canChangeType?: boolean;
14
+ /** Render the chat slot. Off → omitted (e.g. features.chat disabled). */
15
+ showChat?: boolean;
16
+ /** Render the settings slot. Off → omitted. */
17
+ showSettings?: boolean;
18
+ /** Suppress tooltips (touch devices / during open animations). */
19
+ disableTooltips?: boolean;
20
+ size?: 'sm' | 'md';
21
+ };
22
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ select: (tab: DocViewTab) => any;
24
+ "update:modelValue": (tab: DocViewTab) => any;
25
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
26
+ onSelect?: ((tab: DocViewTab) => any) | undefined;
27
+ "onUpdate:modelValue"?: ((tab: DocViewTab) => any) | undefined;
28
+ }>, {
29
+ size: "sm" | "md";
30
+ modelValue: DocViewTab | null;
31
+ chatUnread: number;
32
+ editorDisabled: boolean;
33
+ pageTypeDisabled: boolean;
34
+ canChangeType: boolean;
35
+ showChat: boolean;
36
+ showSettings: boolean;
37
+ disableTooltips: boolean;
38
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
+ declare const _default: typeof __VLS_export;
40
+ export default _default;