@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
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from "vue";
3
3
  import { useRuntimeConfig } from "#imports";
4
- import { useRafFn, useEventListener } from "@vueuse/core";
4
+ import { useRafFn, useEventListener, useLocalStorage } from "@vueuse/core";
5
5
  import { getIcon, loadIcon } from "@iconify/vue";
6
6
  import { useRendererBase } from "../../composables/useRendererBase";
7
7
  import { useNodePanel } from "../../composables/useNodePanel";
@@ -161,20 +161,39 @@ function svgCoord(clientX, clientY) {
161
161
  }
162
162
  function onWheel(e) {
163
163
  e.preventDefault();
164
- const sensitivity = e.ctrlKey ? 8e-3 : 25e-4;
165
- const delta = Math.sign(e.deltaY) * Math.min(Math.abs(e.deltaY), 100);
166
- const scale = 1 + delta * sensitivity;
167
- const rect = svgRef.value.getBoundingClientRect();
168
- const tv = targetVb.value;
169
- const px = (e.clientX - rect.left) / rect.width * tv.w + tv.x;
170
- const py = (e.clientY - rect.top) / rect.height * tv.h + tv.y;
171
- targetVb.value = {
172
- x: px - (px - tv.x) * scale,
173
- y: py - (py - tv.y) * scale,
174
- w: Math.max(100, Math.min(8e3, tv.w * scale)),
175
- h: Math.max(100, Math.min(8e3, tv.h * scale))
176
- };
177
- if (!zoomRafId) zoomRafId = requestAnimationFrame(animateZoom);
164
+ const wantsZoom = e.ctrlKey ? scrollBehavior.value === "pan" : scrollBehavior.value === "zoom";
165
+ if (wantsZoom) {
166
+ const sensitivity = e.ctrlKey ? 8e-3 : 25e-4;
167
+ const delta = Math.sign(e.deltaY) * Math.min(Math.abs(e.deltaY), 100);
168
+ const scale = 1 + delta * sensitivity;
169
+ const rect = svgRef.value.getBoundingClientRect();
170
+ const tv = targetVb.value;
171
+ const px = (e.clientX - rect.left) / rect.width * tv.w + tv.x;
172
+ const py = (e.clientY - rect.top) / rect.height * tv.h + tv.y;
173
+ targetVb.value = {
174
+ x: px - (px - tv.x) * scale,
175
+ y: py - (py - tv.y) * scale,
176
+ w: Math.max(100, Math.min(8e3, tv.w * scale)),
177
+ h: Math.max(100, Math.min(8e3, tv.h * scale))
178
+ };
179
+ if (!zoomRafId) zoomRafId = requestAnimationFrame(animateZoom);
180
+ } else {
181
+ if (zoomRafId) {
182
+ cancelAnimationFrame(zoomRafId);
183
+ zoomRafId = null;
184
+ }
185
+ vb.value = { ...targetVb.value };
186
+ const rect = svgRef.value.getBoundingClientRect();
187
+ const sx = vb.value.w / rect.width;
188
+ const sy = vb.value.h / rect.height;
189
+ vb.value = {
190
+ x: vb.value.x + e.deltaX * sx,
191
+ y: vb.value.y + e.deltaY * sy,
192
+ w: vb.value.w,
193
+ h: vb.value.h
194
+ };
195
+ targetVb.value = { ...vb.value };
196
+ }
178
197
  }
179
198
  const activePointers = /* @__PURE__ */ new Map();
180
199
  let isPinching = false;
@@ -371,6 +390,7 @@ watch(() => savedDocMeta.value?.graphEdgeThickness, (v) => {
371
390
  if (v && v !== edgeThickness.value) edgeThickness.value = v;
372
391
  });
373
392
  const edgeScale = computed(() => EDGE_THICKNESS_SCALE[edgeThickness.value]);
393
+ const scrollBehavior = useLocalStorage("abracadabra_graph_scroll_behavior", "pan");
374
394
  const spacingIcon = computed(() => {
375
395
  const icons = {
376
396
  compact: "i-lucide-dot",
@@ -1179,6 +1199,35 @@ defineExpose({ connectedUsers });
1179
1199
  </UButton>
1180
1200
  </div>
1181
1201
  </div>
1202
+
1203
+ <!-- Personal (device-local) — not shared via the doc -->
1204
+ <div class="h-px bg-(--ui-border)" />
1205
+ <div class="flex items-center justify-between gap-2">
1206
+ <div class="flex flex-col min-w-0">
1207
+ <span class="text-xs text-(--ui-text-muted)">{{ locale.scrollBehavior }}</span>
1208
+ <span class="text-[10px] text-(--ui-text-dimmed)">{{ locale.personalSetting }}</span>
1209
+ </div>
1210
+ <div class="flex gap-0.5 shrink-0">
1211
+ <UButton
1212
+ size="xs"
1213
+ :variant="scrollBehavior === 'pan' ? 'soft' : 'ghost'"
1214
+ :color="scrollBehavior === 'pan' ? 'primary' : 'neutral'"
1215
+ class="text-[10px] px-1.5"
1216
+ @click="scrollBehavior = 'pan'"
1217
+ >
1218
+ {{ locale.scrollPan }}
1219
+ </UButton>
1220
+ <UButton
1221
+ size="xs"
1222
+ :variant="scrollBehavior === 'zoom' ? 'soft' : 'ghost'"
1223
+ :color="scrollBehavior === 'zoom' ? 'primary' : 'neutral'"
1224
+ class="text-[10px] px-1.5"
1225
+ @click="scrollBehavior = 'zoom'"
1226
+ >
1227
+ {{ locale.scrollZoom }}
1228
+ </UButton>
1229
+ </div>
1230
+ </div>
1182
1231
  </div>
1183
1232
  </template>
1184
1233
  </UPopover>
@@ -20,16 +20,16 @@ type __VLS_Props = {
20
20
  };
21
21
  };
22
22
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ next: () => any;
23
24
  "update:viewMode": (mode: CalendarViewMode) => any;
24
25
  prev: () => any;
25
- next: () => any;
26
26
  today: () => any;
27
27
  "add-event": () => any;
28
28
  "navigate-to-month": (year: number, month: number) => any;
29
29
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
30
+ onNext?: (() => any) | undefined;
30
31
  "onUpdate:viewMode"?: ((mode: CalendarViewMode) => any) | undefined;
31
32
  onPrev?: (() => any) | undefined;
32
- onNext?: (() => any) | undefined;
33
33
  onToday?: (() => any) | undefined;
34
34
  "onAdd-event"?: (() => any) | undefined;
35
35
  "onNavigate-to-month"?: ((year: number, month: number) => any) | undefined;
@@ -20,16 +20,16 @@ type __VLS_Props = {
20
20
  };
21
21
  };
22
22
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ next: () => any;
23
24
  "update:viewMode": (mode: CalendarViewMode) => any;
24
25
  prev: () => any;
25
- next: () => any;
26
26
  today: () => any;
27
27
  "add-event": () => any;
28
28
  "navigate-to-month": (year: number, month: number) => any;
29
29
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
30
+ onNext?: (() => any) | undefined;
30
31
  "onUpdate:viewMode"?: ((mode: CalendarViewMode) => any) | undefined;
31
32
  onPrev?: (() => any) | undefined;
32
- onNext?: (() => any) | undefined;
33
33
  onToday?: (() => any) | undefined;
34
34
  "onAdd-event"?: (() => any) | undefined;
35
35
  "onNavigate-to-month"?: ((year: number, month: number) => any) | undefined;
@@ -8,13 +8,13 @@ type __VLS_Props = {
8
8
  isLoading: boolean;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
- prev: () => any;
12
11
  next: () => any;
12
+ prev: () => any;
13
13
  togglePlay: () => any;
14
14
  seek: (position: number) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- onPrev?: (() => any) | undefined;
17
16
  onNext?: (() => any) | undefined;
17
+ onPrev?: (() => any) | undefined;
18
18
  onTogglePlay?: (() => any) | undefined;
19
19
  onSeek?: ((position: number) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -8,13 +8,13 @@ type __VLS_Props = {
8
8
  isLoading: boolean;
9
9
  };
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
- prev: () => any;
12
11
  next: () => any;
12
+ prev: () => any;
13
13
  togglePlay: () => any;
14
14
  seek: (position: number) => any;
15
15
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- onPrev?: (() => any) | undefined;
17
16
  onNext?: (() => any) | undefined;
17
+ onPrev?: (() => any) | undefined;
18
18
  onTogglePlay?: (() => any) | undefined;
19
19
  onSeek?: ((position: number) => any) | undefined;
20
20
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -60,10 +60,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
60
60
  keydown: (e: KeyboardEvent) => any;
61
61
  paste: (e: ClipboardEvent) => any;
62
62
  renameColumn: (colId: string, label: string) => any;
63
- commitEdit: () => any;
64
63
  "update:editValue": (value: string) => any;
65
64
  selectCell: (colIdx: number, rowIdx: number, shiftKey: boolean) => any;
66
65
  activateCell: (colIdx: number, rowIdx: number) => any;
66
+ commitEdit: () => any;
67
67
  cancelEdit: () => any;
68
68
  resizeColumn: (colId: string, width: number) => any;
69
69
  selectRow: (rowIdx: number) => any;
@@ -86,10 +86,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
86
86
  onKeydown?: ((e: KeyboardEvent) => any) | undefined;
87
87
  onPaste?: ((e: ClipboardEvent) => any) | undefined;
88
88
  onRenameColumn?: ((colId: string, label: string) => any) | undefined;
89
- onCommitEdit?: (() => any) | undefined;
90
89
  "onUpdate:editValue"?: ((value: string) => any) | undefined;
91
90
  onSelectCell?: ((colIdx: number, rowIdx: number, shiftKey: boolean) => any) | undefined;
92
91
  onActivateCell?: ((colIdx: number, rowIdx: number) => any) | undefined;
92
+ onCommitEdit?: (() => any) | undefined;
93
93
  onCancelEdit?: (() => any) | undefined;
94
94
  onResizeColumn?: ((colId: string, width: number) => any) | undefined;
95
95
  onSelectRow?: ((rowIdx: number) => any) | undefined;
@@ -60,10 +60,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
60
60
  keydown: (e: KeyboardEvent) => any;
61
61
  paste: (e: ClipboardEvent) => any;
62
62
  renameColumn: (colId: string, label: string) => any;
63
- commitEdit: () => any;
64
63
  "update:editValue": (value: string) => any;
65
64
  selectCell: (colIdx: number, rowIdx: number, shiftKey: boolean) => any;
66
65
  activateCell: (colIdx: number, rowIdx: number) => any;
66
+ commitEdit: () => any;
67
67
  cancelEdit: () => any;
68
68
  resizeColumn: (colId: string, width: number) => any;
69
69
  selectRow: (rowIdx: number) => any;
@@ -86,10 +86,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
86
86
  onKeydown?: ((e: KeyboardEvent) => any) | undefined;
87
87
  onPaste?: ((e: ClipboardEvent) => any) | undefined;
88
88
  onRenameColumn?: ((colId: string, label: string) => any) | undefined;
89
- onCommitEdit?: (() => any) | undefined;
90
89
  "onUpdate:editValue"?: ((value: string) => any) | undefined;
91
90
  onSelectCell?: ((colIdx: number, rowIdx: number, shiftKey: boolean) => any) | undefined;
92
91
  onActivateCell?: ((colIdx: number, rowIdx: number) => any) | undefined;
92
+ onCommitEdit?: (() => any) | undefined;
93
93
  onCancelEdit?: (() => any) | undefined;
94
94
  onResizeColumn?: ((colId: string, width: number) => any) | undefined;
95
95
  onSelectRow?: ((rowIdx: number) => any) | undefined;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref } from "vue";
3
+ import { useNuxtApp } from "#imports";
4
+ const colorMode = useNuxtApp().$colorMode;
5
+ const THEME_OPTIONS = [
6
+ { value: "system", label: "System", icon: "i-lucide-monitor" },
7
+ { value: "light", label: "Light", icon: "i-lucide-sun" },
8
+ { value: "dark", label: "Dark", icon: "i-lucide-moon" }
9
+ ];
10
+ const theme = computed({
11
+ get: () => colorMode?.preference || "system",
12
+ set: (v) => {
13
+ if (colorMode) colorMode.preference = v;
14
+ }
15
+ });
16
+ const REDUCE_MOTION_KEY = "abracadabra_reduce_motion";
17
+ const reduceMotion = ref(false);
18
+ function applyReduceMotion(on) {
19
+ if (typeof document === "undefined") return;
20
+ if (on) document.documentElement.setAttribute("data-reduce-motion", "");
21
+ else document.documentElement.removeAttribute("data-reduce-motion");
22
+ }
23
+ onMounted(() => {
24
+ reduceMotion.value = localStorage.getItem(REDUCE_MOTION_KEY) === "1";
25
+ applyReduceMotion(reduceMotion.value);
26
+ });
27
+ function setReduceMotion(on) {
28
+ reduceMotion.value = on;
29
+ if (typeof localStorage !== "undefined") localStorage.setItem(REDUCE_MOTION_KEY, on ? "1" : "0");
30
+ applyReduceMotion(on);
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <ASettingsGroup
36
+ title="Appearance"
37
+ description="Theme and motion preferences for this device."
38
+ >
39
+ <ASettingsRow
40
+ label="Theme"
41
+ description="Match your system, or always use light or dark."
42
+ >
43
+ <div class="flex items-center gap-1 rounded-(--ui-radius) bg-(--ui-bg-elevated) p-0.5">
44
+ <UButton
45
+ v-for="opt in THEME_OPTIONS"
46
+ :key="opt.value"
47
+ :icon="opt.icon"
48
+ :label="opt.label"
49
+ size="xs"
50
+ :color="theme === opt.value ? 'primary' : 'neutral'"
51
+ :variant="theme === opt.value ? 'soft' : 'ghost'"
52
+ @click="theme = opt.value"
53
+ />
54
+ </div>
55
+ </ASettingsRow>
56
+
57
+ <ASettingsRow
58
+ label="Reduced motion"
59
+ description="Minimise animations and transitions across the app."
60
+ >
61
+ <USwitch
62
+ :model-value="reduceMotion"
63
+ @update:model-value="setReduceMotion($event)"
64
+ />
65
+ </ASettingsRow>
66
+ </ASettingsGroup>
67
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * <ASettingsGroup>
3
+ *
4
+ * A titled group of settings rows: optional title + description header above a
5
+ * column of <ASettingsRow>s. Shared building block — ported from cou-sh
6
+ * SettingsV2/Group.vue.
7
+ */
8
+ type __VLS_Props = {
9
+ title?: string;
10
+ description?: string;
11
+ };
12
+ declare var __VLS_1: {};
13
+ type __VLS_Slots = {} & {
14
+ default?: (props: typeof __VLS_1) => any;
15
+ };
16
+ declare const __VLS_base: 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>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,31 @@
1
+ <script setup>
2
+ defineProps({
3
+ title: { type: String, required: false },
4
+ description: { type: String, required: false }
5
+ });
6
+ </script>
7
+
8
+ <template>
9
+ <section class="flex flex-col gap-3">
10
+ <header
11
+ v-if="title || description"
12
+ class="flex flex-col gap-1"
13
+ >
14
+ <h3
15
+ v-if="title"
16
+ class="text-base font-semibold text-(--ui-text-highlighted)"
17
+ >
18
+ {{ title }}
19
+ </h3>
20
+ <p
21
+ v-if="description"
22
+ class="text-sm text-(--ui-text-muted)"
23
+ >
24
+ {{ description }}
25
+ </p>
26
+ </header>
27
+ <div class="flex flex-col">
28
+ <slot />
29
+ </div>
30
+ </section>
31
+ </template>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * <ASettingsGroup>
3
+ *
4
+ * A titled group of settings rows: optional title + description header above a
5
+ * column of <ASettingsRow>s. Shared building block — ported from cou-sh
6
+ * SettingsV2/Group.vue.
7
+ */
8
+ type __VLS_Props = {
9
+ title?: string;
10
+ description?: string;
11
+ };
12
+ declare var __VLS_1: {};
13
+ type __VLS_Slots = {} & {
14
+ default?: (props: typeof __VLS_1) => any;
15
+ };
16
+ declare const __VLS_base: 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>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -2,6 +2,13 @@
2
2
  import { ref, computed, watch } from "vue";
3
3
  import { useSettingsModal } from "../../composables/useSettingsModal";
4
4
  import { useAbracadabra } from "../../composables/useAbracadabra";
5
+ import {
6
+ BUILTIN_SETTINGS_SECTIONS,
7
+ GROUP_LABELS,
8
+ GROUP_MODE,
9
+ searchSections,
10
+ visibleSections
11
+ } from "./sections";
5
12
  const { isOpen, activeTab } = useSettingsModal();
6
13
  const abra = useAbracadabra();
7
14
  const effectiveRole = computed(() => abra.provider.value?.effectiveRole ?? null);
@@ -9,53 +16,56 @@ const isAdmin = computed(() => {
9
16
  const role = effectiveRole.value;
10
17
  return role === "admin" || role === "service";
11
18
  });
12
- const userLinks = [
13
- { key: "profile", label: "Profile", icon: "i-lucide-user" },
14
- { key: "security", label: "Security", icon: "i-lucide-shield" },
15
- { key: "offline", label: "Offline", icon: "i-lucide-database" }
16
- ];
17
- const adminLinks = computed(() => [
18
- { key: "spaces", label: "Spaces", icon: "i-lucide-layers" },
19
- { key: "members", label: "Members", icon: "i-lucide-users" },
20
- { key: "connection", label: "Connection", icon: "i-lucide-wifi" },
21
- { key: "trash", label: "Trash", icon: "i-lucide-trash-2" },
22
- { key: "plugins", label: "Plugins", icon: "i-lucide-plug" },
23
- ...isAdmin.value ? [
24
- { key: "invites", label: "Invites", icon: "i-lucide-ticket" },
25
- { key: "admin", label: "Admin", icon: "i-lucide-shield-alert" }
26
- ] : []
27
- ]);
19
+ const search = ref("");
28
20
  const settingsMode = ref("user");
29
21
  const headerTabs = [
30
22
  { value: "user", label: "User Settings", icon: "i-lucide-user" },
31
23
  { value: "admin", label: "Administration", icon: "i-lucide-shield" }
32
24
  ];
33
- const currentLinks = computed(
34
- () => settingsMode.value === "admin" ? adminLinks.value : userLinks
25
+ const modeSections = computed(
26
+ () => visibleSections(BUILTIN_SETTINGS_SECTIONS, isAdmin.value).filter((s) => GROUP_MODE[s.group] === settingsMode.value)
35
27
  );
28
+ const filteredSections = computed(() => searchSections(search.value, modeSections.value));
29
+ const groupedSections = computed(() => {
30
+ const groups = [];
31
+ for (const section of filteredSections.value) {
32
+ let g = groups.find((x) => x.group === section.group);
33
+ if (!g) {
34
+ g = { group: section.group, label: GROUP_LABELS[section.group], items: [] };
35
+ groups.push(g);
36
+ }
37
+ g.items.push(section);
38
+ }
39
+ return groups;
40
+ });
36
41
  const currentDescription = computed(
37
42
  () => settingsMode.value === "admin" ? "Manage spaces, members, and server settings." : "Personalize your profile, security, and preferences."
38
43
  );
39
- const selectedItem = computed(
40
- () => currentLinks.value.find((l) => l.key === activeTab.value) ?? currentLinks.value[0]
44
+ const selectedSection = computed(
45
+ () => modeSections.value.find((s) => s.key === activeTab.value) ?? modeSections.value[0]
41
46
  );
42
- function selectSection(item) {
43
- activeTab.value = item.key;
47
+ const activeComponent = computed(() => selectedSection.value?.component ?? null);
48
+ function selectSection(section) {
49
+ activeTab.value = section.key;
44
50
  }
45
51
  const showMobileNav = ref(true);
46
- function selectMobile(item) {
47
- selectSection(item);
52
+ function selectMobile(section) {
53
+ selectSection(section);
48
54
  showMobileNav.value = false;
49
55
  }
50
56
  watch(isOpen, (val) => {
51
57
  if (val) {
52
58
  showMobileNav.value = true;
59
+ search.value = "";
60
+ const target = BUILTIN_SETTINGS_SECTIONS.find((s) => s.key === activeTab.value);
61
+ if (target) settingsMode.value = GROUP_MODE[target.group];
53
62
  }
54
63
  });
55
64
  watch(settingsMode, () => {
56
65
  showMobileNav.value = true;
57
- const first = settingsMode.value === "admin" ? "spaces" : "profile";
58
- activeTab.value = first;
66
+ search.value = "";
67
+ const first = modeSections.value[0];
68
+ if (first) activeTab.value = first.key;
59
69
  });
60
70
  </script>
61
71
 
@@ -112,28 +122,54 @@ watch(settingsMode, () => {
112
122
  showMobileNav ? 'w-full sm:w-52' : 'hidden sm:block sm:w-52'
113
123
  ]"
114
124
  >
115
- <div class="p-4 pb-2">
116
- <p class="text-xs text-(--ui-text-muted)">
125
+ <div class="p-3 pb-2 space-y-2">
126
+ <p class="text-xs text-(--ui-text-muted) px-1">
117
127
  {{ currentDescription }}
118
128
  </p>
129
+ <UInput
130
+ v-model="search"
131
+ icon="i-lucide-search"
132
+ placeholder="Search settings…"
133
+ size="xs"
134
+ class="w-full"
135
+ :ui="{ root: 'w-full' }"
136
+ />
119
137
  </div>
120
138
 
121
- <nav class="flex flex-col gap-0.5 p-2">
122
- <button
123
- v-for="item in currentLinks"
124
- :key="item.key"
125
- class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors text-left w-full"
126
- :class="[
127
- selectedItem?.key === item.key ? 'bg-(--ui-bg-elevated) text-(--ui-text-highlighted) font-medium' : 'text-(--ui-text-muted) hover:text-(--ui-text) hover:bg-(--ui-bg-elevated)/50'
139
+ <nav class="flex flex-col gap-2 p-2">
140
+ <div
141
+ v-for="grp in groupedSections"
142
+ :key="grp.group"
143
+ class="flex flex-col gap-0.5"
144
+ >
145
+ <p
146
+ v-if="groupedSections.length > 1"
147
+ class="px-3 pt-1 pb-0.5 text-[10px] font-semibold uppercase tracking-wider text-(--ui-text-dimmed)"
148
+ >
149
+ {{ grp.label }}
150
+ </p>
151
+ <button
152
+ v-for="item in grp.items"
153
+ :key="item.key"
154
+ class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors text-left w-full"
155
+ :class="[
156
+ selectedSection?.key === item.key ? 'bg-(--ui-bg-elevated) text-(--ui-text-highlighted) font-medium' : 'text-(--ui-text-muted) hover:text-(--ui-text) hover:bg-(--ui-bg-elevated)/50'
128
157
  ]"
129
- @click="selectMobile(item)"
158
+ @click="selectMobile(item)"
159
+ >
160
+ <UIcon
161
+ :name="item.icon"
162
+ class="size-4 shrink-0"
163
+ />
164
+ <span class="truncate">{{ item.label }}</span>
165
+ </button>
166
+ </div>
167
+ <p
168
+ v-if="filteredSections.length === 0"
169
+ class="px-3 py-4 text-center text-xs text-(--ui-text-dimmed)"
130
170
  >
131
- <UIcon
132
- :name="item.icon"
133
- class="size-4 shrink-0"
134
- />
135
- <span class="truncate">{{ item.label }}</span>
136
- </button>
171
+ No matching settings.
172
+ </p>
137
173
  </nav>
138
174
  </div>
139
175
 
@@ -151,21 +187,16 @@ watch(settingsMode, () => {
151
187
  size="xs"
152
188
  @click="showMobileNav = true"
153
189
  />
154
- <span class="text-sm font-medium truncate">{{ selectedItem?.label }}</span>
190
+ <span class="text-sm font-medium truncate">{{ selectedSection?.label }}</span>
155
191
  </div>
156
192
 
157
193
  <div class="p-4 sm:p-6 lg:p-8">
158
194
  <div class="flex flex-col gap-6 max-w-2xl mx-auto">
159
- <ASettingsProfilePanel v-if="activeTab === 'profile'" />
160
- <ASettingsConnectionPanel v-else-if="activeTab === 'connection'" />
161
- <ASettingsSpacesPanel v-else-if="activeTab === 'spaces'" />
162
- <ASettingsSecurityPanel v-else-if="activeTab === 'security'" />
163
- <ASettingsInvitesPanel v-else-if="activeTab === 'invites'" />
164
- <ASettingsMembersPanel v-else-if="activeTab === 'members'" />
165
- <ASettingsTrashPanel v-else-if="activeTab === 'trash'" />
166
- <ASettingsOfflinePanel v-else-if="activeTab === 'offline'" />
167
- <ASettingsPluginsPanel v-else-if="activeTab === 'plugins'" />
168
- <ASettingsAdminPanel v-else-if="activeTab === 'admin'" />
195
+ <component
196
+ :is="activeComponent"
197
+ v-if="activeComponent"
198
+ :key="selectedSection?.key"
199
+ />
169
200
  </div>
170
201
  </div>
171
202
  </div>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * <ASettingsPlaceholder>
3
+ *
4
+ * Centered empty-state card for a not-yet-implemented (or empty) settings
5
+ * section: icon bubble + title + description, with an optional badge.
6
+ * Ported from cou-sh SettingsV2/Placeholder.vue — the badge is configurable
7
+ * here (cou-sh hardcoded "Coming next"); omit `badge` to hide it.
8
+ */
9
+ type __VLS_Props = {
10
+ icon: string;
11
+ title: string;
12
+ description: string;
13
+ /** Optional badge label (e.g. "Coming next"). Omit to hide the badge. */
14
+ badge?: string;
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
+ badge: string;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,32 @@
1
+ <script setup>
2
+ defineProps({
3
+ icon: { type: String, required: true },
4
+ title: { type: String, required: true },
5
+ description: { type: String, required: true },
6
+ badge: { type: String, required: false, default: void 0 }
7
+ });
8
+ </script>
9
+
10
+ <template>
11
+ <div class="flex flex-col items-center justify-center text-center py-16 px-4 gap-3">
12
+ <div class="size-12 rounded-full bg-(--ui-bg-elevated) flex items-center justify-center">
13
+ <UIcon
14
+ :name="icon"
15
+ class="size-6 text-(--ui-text-muted)"
16
+ />
17
+ </div>
18
+ <h3 class="text-sm font-semibold text-(--ui-text-highlighted)">
19
+ {{ title }}
20
+ </h3>
21
+ <p class="text-xs text-(--ui-text-muted) max-w-sm leading-relaxed">
22
+ {{ description }}
23
+ </p>
24
+ <UBadge
25
+ v-if="badge"
26
+ color="neutral"
27
+ variant="subtle"
28
+ size="sm"
29
+ :label="badge"
30
+ />
31
+ </div>
32
+ </template>