@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
@@ -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,34 @@
1
+ /**
2
+ * <ASettingsRow>
3
+ *
4
+ * One labelled setting row: label + optional description on the left, the
5
+ * control (default slot) on the right. Bottom-bordered so rows stack into a
6
+ * clean list inside an <ASettingsGroup>. Use `stacked` for wide controls
7
+ * (color grids, lists) that need their own row beneath the label.
8
+ *
9
+ * Shared building block for settings panels — ported from cou-sh
10
+ * SettingsV2/Row.vue so module panels share one consistent row look.
11
+ */
12
+ type __VLS_Props = {
13
+ label: string;
14
+ description?: string;
15
+ /** Stack label + description above a full-width control row (wide widgets). */
16
+ stacked?: boolean;
17
+ /** Vertical alignment of the control against the label. */
18
+ align?: 'center' | 'start';
19
+ };
20
+ declare var __VLS_1: {};
21
+ type __VLS_Slots = {} & {
22
+ default?: (props: typeof __VLS_1) => any;
23
+ };
24
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
25
+ align: "center" | "start";
26
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
28
+ declare const _default: typeof __VLS_export;
29
+ export default _default;
30
+ type __VLS_WithSlots<T, S> = T & {
31
+ new (): {
32
+ $slots: S;
33
+ };
34
+ };
@@ -0,0 +1,34 @@
1
+ <script setup>
2
+ defineProps({
3
+ label: { type: String, required: true },
4
+ description: { type: String, required: false },
5
+ stacked: { type: Boolean, required: false },
6
+ align: { type: String, required: false, default: "center" }
7
+ });
8
+ </script>
9
+
10
+ <template>
11
+ <div
12
+ class="flex gap-4 py-3.5 border-b border-(--ui-border) last:border-b-0"
13
+ :class="[
14
+ stacked ? 'flex-col' : 'flex-col sm:flex-row sm:justify-between',
15
+ !stacked && align === 'center' ? 'sm:items-center' : 'sm:items-start'
16
+ ]"
17
+ >
18
+ <div class="flex flex-col gap-0.5 min-w-0">
19
+ <span class="text-sm text-(--ui-text-highlighted)">{{ label }}</span>
20
+ <span
21
+ v-if="description"
22
+ class="text-xs text-(--ui-text-muted) leading-relaxed"
23
+ >
24
+ {{ description }}
25
+ </span>
26
+ </div>
27
+ <div
28
+ class="flex items-center gap-2 shrink-0"
29
+ :class="stacked && 'w-full justify-start'"
30
+ >
31
+ <slot />
32
+ </div>
33
+ </div>
34
+ </template>
@@ -0,0 +1,34 @@
1
+ /**
2
+ * <ASettingsRow>
3
+ *
4
+ * One labelled setting row: label + optional description on the left, the
5
+ * control (default slot) on the right. Bottom-bordered so rows stack into a
6
+ * clean list inside an <ASettingsGroup>. Use `stacked` for wide controls
7
+ * (color grids, lists) that need their own row beneath the label.
8
+ *
9
+ * Shared building block for settings panels — ported from cou-sh
10
+ * SettingsV2/Row.vue so module panels share one consistent row look.
11
+ */
12
+ type __VLS_Props = {
13
+ label: string;
14
+ description?: string;
15
+ /** Stack label + description above a full-width control row (wide widgets). */
16
+ stacked?: boolean;
17
+ /** Vertical alignment of the control against the label. */
18
+ align?: 'center' | 'start';
19
+ };
20
+ declare var __VLS_1: {};
21
+ type __VLS_Slots = {} & {
22
+ default?: (props: typeof __VLS_1) => any;
23
+ };
24
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
25
+ align: "center" | "start";
26
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
28
+ declare const _default: typeof __VLS_export;
29
+ export default _default;
30
+ type __VLS_WithSlots<T, S> = T & {
31
+ new (): {
32
+ $slots: S;
33
+ };
34
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Settings section registry.
3
+ *
4
+ * The single source of truth for which settings sections exist, how they're
5
+ * labelled/grouped/searched, and which component renders each. <ASettingsModal>
6
+ * builds its nav + content from this list instead of hard-coding them, so a new
7
+ * section is one entry here (no template edits), and the nav gets search +
8
+ * grouping for free.
9
+ *
10
+ * Grouping maps onto the shell's two modes:
11
+ * - 'user' → "User settings"
12
+ * - 'workspace' → "Administration" (workspace-level)
13
+ * - 'admin' → "Administration" (admin/service only; `adminOnly`)
14
+ */
15
+ import { type Component } from 'vue';
16
+ import type { SettingsTab } from '../../composables/useSettingsModal.js';
17
+ export type SettingsGroup = 'user' | 'workspace' | 'admin';
18
+ export interface SettingsSectionDef {
19
+ key: SettingsTab;
20
+ label: string;
21
+ icon: string;
22
+ group: SettingsGroup;
23
+ /** Extra terms matched by the nav search box (besides the label). */
24
+ keywords?: string[];
25
+ /** Only shown to admin/service roles. */
26
+ adminOnly?: boolean;
27
+ /** The panel component to render for this section. */
28
+ component: Component;
29
+ }
30
+ /** Which shell "mode" each group belongs to. */
31
+ export declare const GROUP_MODE: Record<SettingsGroup, 'user' | 'admin'>;
32
+ export declare const GROUP_LABELS: Record<SettingsGroup, string>;
33
+ export declare const BUILTIN_SETTINGS_SECTIONS: SettingsSectionDef[];
34
+ /** Filter to the sections visible for the current role. */
35
+ export declare function visibleSections(sections: SettingsSectionDef[], isAdmin: boolean): SettingsSectionDef[];
36
+ /** Case-insensitive search over label + keywords. */
37
+ export declare function searchSections(query: string, sections: SettingsSectionDef[]): SettingsSectionDef[];
@@ -0,0 +1,45 @@
1
+ import { markRaw } from "vue";
2
+ import ASettingsProfilePanel from "./ASettingsProfilePanel.vue";
3
+ import ASettingsAppearancePanel from "./ASettingsAppearancePanel.vue";
4
+ import ASettingsSecurityPanel from "./ASettingsSecurityPanel.vue";
5
+ import ASettingsOfflinePanel from "./ASettingsOfflinePanel.vue";
6
+ import ASettingsSpacesPanel from "./ASettingsSpacesPanel.vue";
7
+ import ASettingsMembersPanel from "./ASettingsMembersPanel.vue";
8
+ import ASettingsConnectionPanel from "./ASettingsConnectionPanel.vue";
9
+ import ASettingsTrashPanel from "./ASettingsTrashPanel.vue";
10
+ import ASettingsPluginsPanel from "./ASettingsPluginsPanel.vue";
11
+ import ASettingsInvitesPanel from "./ASettingsInvitesPanel.vue";
12
+ import ASettingsAdminPanel from "./ASettingsAdminPanel.vue";
13
+ export const GROUP_MODE = {
14
+ user: "user",
15
+ workspace: "admin",
16
+ admin: "admin"
17
+ };
18
+ export const GROUP_LABELS = {
19
+ user: "Account",
20
+ workspace: "Workspace",
21
+ admin: "Administration"
22
+ };
23
+ export const BUILTIN_SETTINGS_SECTIONS = [
24
+ { key: "profile", label: "Profile", icon: "i-lucide-user", group: "user", keywords: ["name", "avatar", "identity", "colour", "color"], component: markRaw(ASettingsProfilePanel) },
25
+ { key: "appearance", label: "Appearance", icon: "i-lucide-palette", group: "user", keywords: ["theme", "dark", "light", "motion", "font"], component: markRaw(ASettingsAppearancePanel) },
26
+ { key: "security", label: "Security", icon: "i-lucide-shield", group: "user", keywords: ["passkey", "password", "account", "logout", "wipe"], component: markRaw(ASettingsSecurityPanel) },
27
+ { key: "offline", label: "Offline", icon: "i-lucide-database", group: "user", keywords: ["sync", "storage", "cache"], component: markRaw(ASettingsOfflinePanel) },
28
+ { key: "spaces", label: "Spaces", icon: "i-lucide-layers", group: "workspace", keywords: ["workspace", "create", "switch"], component: markRaw(ASettingsSpacesPanel) },
29
+ { key: "members", label: "Members", icon: "i-lucide-users", group: "workspace", keywords: ["people", "roles", "online"], component: markRaw(ASettingsMembersPanel) },
30
+ { key: "connection", label: "Connection", icon: "i-lucide-wifi", group: "workspace", keywords: ["server", "p2p", "webrtc", "reconnect"], component: markRaw(ASettingsConnectionPanel) },
31
+ { key: "trash", label: "Trash", icon: "i-lucide-trash-2", group: "workspace", keywords: ["deleted", "restore", "purge"], component: markRaw(ASettingsTrashPanel) },
32
+ { key: "plugins", label: "Plugins", icon: "i-lucide-plug", group: "workspace", keywords: ["extensions", "page types"], component: markRaw(ASettingsPluginsPanel) },
33
+ { key: "invites", label: "Invites", icon: "i-lucide-ticket", group: "admin", adminOnly: true, keywords: ["invite", "code", "join"], component: markRaw(ASettingsInvitesPanel) },
34
+ { key: "admin", label: "Admin", icon: "i-lucide-shield-alert", group: "admin", adminOnly: true, keywords: ["users", "server", "manage"], component: markRaw(ASettingsAdminPanel) }
35
+ ];
36
+ export function visibleSections(sections, isAdmin) {
37
+ return sections.filter((s) => !s.adminOnly || isAdmin);
38
+ }
39
+ export function searchSections(query, sections) {
40
+ const q = query.trim().toLowerCase();
41
+ if (!q) return sections;
42
+ return sections.filter(
43
+ (s) => s.label.toLowerCase().includes(q) || s.keywords?.some((k) => k.toLowerCase().includes(q))
44
+ );
45
+ }
@@ -14,13 +14,13 @@ type __VLS_Props = {
14
14
  extraItems?: DropdownMenuItem[][];
15
15
  };
16
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
- logout: () => any;
18
17
  "open-settings": () => any;
19
18
  "open-account": () => any;
19
+ logout: () => any;
20
20
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
21
- onLogout?: (() => any) | undefined;
22
21
  "onOpen-settings"?: (() => any) | undefined;
23
22
  "onOpen-account"?: (() => any) | undefined;
23
+ onLogout?: (() => any) | undefined;
24
24
  }>, {
25
25
  color: string;
26
26
  collapsed: boolean;
@@ -14,13 +14,13 @@ type __VLS_Props = {
14
14
  extraItems?: DropdownMenuItem[][];
15
15
  };
16
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
17
- logout: () => any;
18
17
  "open-settings": () => any;
19
18
  "open-account": () => any;
19
+ logout: () => any;
20
20
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
21
- onLogout?: (() => any) | undefined;
22
21
  "onOpen-settings"?: (() => any) | undefined;
23
22
  "onOpen-account"?: (() => any) | undefined;
23
+ onLogout?: (() => any) | undefined;
24
24
  }>, {
25
25
  color: string;
26
26
  collapsed: boolean;
@@ -37,8 +37,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
37
37
  name: string;
38
38
  online: boolean | null;
39
39
  currentDocId: string | null;
40
- avatarStyle: string;
41
40
  isSelf: boolean;
41
+ avatarStyle: string;
42
42
  isFollowing: boolean;
43
43
  showFollow: boolean;
44
44
  currentDocLabel: string | null;
@@ -37,8 +37,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
37
37
  name: string;
38
38
  online: boolean | null;
39
39
  currentDocId: string | null;
40
- avatarStyle: string;
41
40
  isSelf: boolean;
41
+ avatarStyle: string;
42
42
  isFollowing: boolean;
43
43
  showFollow: boolean;
44
44
  currentDocLabel: string | null;
@@ -16,6 +16,8 @@ export interface ChatMessage {
16
16
  senderName: string;
17
17
  content: string;
18
18
  createdAt: number;
19
+ /** Id of the message this one replies to, if any (threaded reply). */
20
+ replyTo?: string;
19
21
  }
20
22
  export interface ChatChannel {
21
23
  id: string;
@@ -25,6 +27,12 @@ export interface ChatChannel {
25
27
  unreadCount: number;
26
28
  }
27
29
  export declare function buildDmChannelId(a: string, b: string): string;
30
+ declare function setMuted(id: string, on: boolean): void;
31
+ declare function setPinned(id: string, on: boolean): void;
32
+ declare function setHidden(id: string, on: boolean): void;
33
+ declare function isMuted(id: string): boolean;
34
+ declare function isPinned(id: string): boolean;
35
+ declare function isHidden(id: string): boolean;
28
36
  declare let _publicKeyB64: import("vue").Ref<string, string>;
29
37
  declare let _userName: import("vue").Ref<string, string>;
30
38
  export declare function _initChat(publicKeyB64Ref: typeof _publicKeyB64, userNameRef: typeof _userName): void;
@@ -53,6 +61,7 @@ declare function sendTyping(channelId: string): void;
53
61
  declare function setActiveChannel(channelId: string | null): void;
54
62
  export declare function useChat(): {
55
63
  channels: import("vue").Ref<Record<string, ChatChannel>, Record<string, ChatChannel>>;
64
+ channelList: import("vue").ComputedRef<ChatChannel[]>;
56
65
  activeChannel: import("vue").Ref<string | null, string | null>;
57
66
  messagesByChannel: import("vue").Ref<Record<string, ChatMessage[]>, Record<string, ChatMessage[]>>;
58
67
  typingByChannel: import("vue").Ref<Record<string, Map<string, string>>, Record<string, Map<string, string>>>;
@@ -62,13 +71,25 @@ export declare function useChat(): {
62
71
  sendTyping: typeof sendTyping;
63
72
  setActiveChannel: typeof setActiveChannel;
64
73
  buildDmChannelId: typeof buildDmChannelId;
74
+ mutedChannels: import("vue").Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
75
+ pinnedChannels: import("vue").Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
76
+ hiddenChannels: import("vue").Ref<Set<string> & Omit<Set<string>, keyof Set<any>>, Set<string> | (Set<string> & Omit<Set<string>, keyof Set<any>>)>;
77
+ setMuted: typeof setMuted;
78
+ setPinned: typeof setPinned;
79
+ setHidden: typeof setHidden;
80
+ isMuted: typeof isMuted;
81
+ isPinned: typeof isPinned;
82
+ isHidden: typeof isHidden;
65
83
  };
66
84
  export declare function useChatChannel(channelId: string): {
67
85
  messages: import("vue").ComputedRef<ChatMessage[]>;
68
86
  channel: import("vue").ComputedRef<ChatChannel | undefined>;
69
87
  typingUsers: import("vue").ComputedRef<string[]>;
70
88
  unreadCount: import("vue").ComputedRef<number>;
71
- send: (content: string) => void;
89
+ send: (content: string, opts?: {
90
+ mentions?: string[];
91
+ replyTo?: string;
92
+ }) => void;
72
93
  loadHistory: (opts?: {
73
94
  before?: number;
74
95
  limit?: number;
@@ -33,6 +33,58 @@ const channels = ref({});
33
33
  const typingByChannel = ref({});
34
34
  const activeChannel = ref(null);
35
35
  const typingTimers = /* @__PURE__ */ new Map();
36
+ function _loadIdSet(key) {
37
+ if (typeof localStorage === "undefined") return /* @__PURE__ */ new Set();
38
+ try {
39
+ const raw = localStorage.getItem(key);
40
+ return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
41
+ } catch {
42
+ return /* @__PURE__ */ new Set();
43
+ }
44
+ }
45
+ function _saveIdSet(key, set) {
46
+ if (typeof localStorage === "undefined") return;
47
+ try {
48
+ localStorage.setItem(key, JSON.stringify([...set]));
49
+ } catch {
50
+ }
51
+ }
52
+ const mutedChannels = ref(_loadIdSet("abracadabra_chat_muted"));
53
+ const pinnedChannels = ref(_loadIdSet("abracadabra_chat_pinned"));
54
+ const hiddenChannels = ref(_loadIdSet("abracadabra_chat_hidden"));
55
+ function _setPref(ref_, key, id, on) {
56
+ const next = new Set(ref_.value);
57
+ if (on) next.add(id);
58
+ else next.delete(id);
59
+ ref_.value = next;
60
+ _saveIdSet(key, next);
61
+ }
62
+ function setMuted(id, on) {
63
+ _setPref(mutedChannels, "abracadabra_chat_muted", id, on);
64
+ }
65
+ function setPinned(id, on) {
66
+ _setPref(pinnedChannels, "abracadabra_chat_pinned", id, on);
67
+ }
68
+ function setHidden(id, on) {
69
+ _setPref(hiddenChannels, "abracadabra_chat_hidden", id, on);
70
+ }
71
+ function isMuted(id) {
72
+ return mutedChannels.value.has(id);
73
+ }
74
+ function isPinned(id) {
75
+ return pinnedChannels.value.has(id);
76
+ }
77
+ function isHidden(id) {
78
+ return hiddenChannels.value.has(id);
79
+ }
80
+ const channelList = computed(
81
+ () => Object.values(channels.value).filter((c) => !hiddenChannels.value.has(c.id)).sort((a, b) => {
82
+ const pa = pinnedChannels.value.has(a.id) ? 1 : 0;
83
+ const pb = pinnedChannels.value.has(b.id) ? 1 : 0;
84
+ if (pa !== pb) return pb - pa;
85
+ return (b.lastMessage?.createdAt ?? 0) - (a.lastMessage?.createdAt ?? 0);
86
+ })
87
+ );
36
88
  let _publicKeyB64 = ref("");
37
89
  let _userName = ref("");
38
90
  export function _initChat(publicKeyB64Ref, userNameRef) {
@@ -112,12 +164,13 @@ function addMessage(msg) {
112
164
  if (chan) {
113
165
  const isActive = activeChannel.value === msg.channel;
114
166
  const isOwn = msg.senderId === _publicKeyB64.value;
167
+ const suppress = isActive || isOwn || mutedChannels.value.has(msg.channel);
115
168
  channels.value = {
116
169
  ...channels.value,
117
170
  [msg.channel]: {
118
171
  ...chan,
119
172
  lastMessage: msg,
120
- unreadCount: isActive || isOwn ? chan.unreadCount : chan.unreadCount + 1
173
+ unreadCount: suppress ? chan.unreadCount : chan.unreadCount + 1
121
174
  }
122
175
  };
123
176
  }
@@ -148,7 +201,8 @@ export function _handleStatelessChat(payload) {
148
201
  // as "chat is broken / messages aren't syncing".
149
202
  senderName: rec.sender_name ?? resolveSenderName(senderId),
150
203
  content: rec.content ?? "",
151
- createdAt: tsMs
204
+ createdAt: tsMs,
205
+ ...rec.reply_to ? { replyTo: rec.reply_to } : {}
152
206
  };
153
207
  ensureChannel(msg.channel, resolveChannelLabel(msg.channel, msg.senderName, msg.senderId));
154
208
  addMessage(msg);
@@ -234,7 +288,8 @@ function sendMessage(channelId, content, opts) {
234
288
  senderId: _publicKeyB64.value,
235
289
  senderName: userName?.value ?? _userName.value,
236
290
  content: trimmed,
237
- createdAt: Date.now()
291
+ createdAt: Date.now(),
292
+ ...opts?.replyTo ? { replyTo: opts.replyTo } : {}
238
293
  });
239
294
  provider.value.sendStateless(JSON.stringify({
240
295
  type: "messages:send",
@@ -284,7 +339,9 @@ async function _attachPeriodProvider(channel, rootProv, periodId) {
284
339
  senderId: f.senderId,
285
340
  senderName: "",
286
341
  content,
287
- createdAt: f.createdAt
342
+ createdAt: f.createdAt,
343
+ // Best-effort: the SDK's folded record may carry reply linkage.
344
+ ...f.reply_to || f.replyTo ? { replyTo: f.reply_to ?? f.replyTo } : {}
288
345
  });
289
346
  }
290
347
  };
@@ -360,11 +417,15 @@ function setActiveChannel(channelId) {
360
417
  }
361
418
  }
362
419
  const totalUnreadCount = computed(
363
- () => Object.values(channels.value).reduce((sum, ch) => sum + ch.unreadCount, 0)
420
+ () => Object.values(channels.value).reduce(
421
+ (sum, ch) => sum + (mutedChannels.value.has(ch.id) ? 0 : ch.unreadCount),
422
+ 0
423
+ )
364
424
  );
365
425
  export function useChat() {
366
426
  return {
367
427
  channels,
428
+ channelList,
368
429
  activeChannel,
369
430
  messagesByChannel,
370
431
  typingByChannel,
@@ -373,7 +434,17 @@ export function useChat() {
373
434
  fetchHistory,
374
435
  sendTyping,
375
436
  setActiveChannel,
376
- buildDmChannelId
437
+ buildDmChannelId,
438
+ // Channel preferences
439
+ mutedChannels,
440
+ pinnedChannels,
441
+ hiddenChannels,
442
+ setMuted,
443
+ setPinned,
444
+ setHidden,
445
+ isMuted,
446
+ isPinned,
447
+ isHidden
377
448
  };
378
449
  }
379
450
  export function useChatChannel(channelId) {
@@ -392,8 +463,8 @@ export function useChatChannel(channelId) {
392
463
  stop();
393
464
  });
394
465
  }
395
- function send(content) {
396
- sendMessage(key, content);
466
+ function send(content, opts) {
467
+ sendMessage(key, content, opts);
397
468
  }
398
469
  function loadHistory(opts) {
399
470
  fetchHistory(key, opts);
@@ -3,16 +3,17 @@ export interface DocLinkPickResult {
3
3
  label: string;
4
4
  }
5
5
  /**
6
- * Manages the state for the document picker dialog.
7
- * Used by doc-embed extension and inline link insertion.
6
+ * Imperative command-palette document picker.
8
7
  *
9
- * The module does not ship a modal component the consuming app provides
10
- * one and wires it up to the `pending` state. When the app calls `resolve()`
11
- * or `cancel()`, the promise returned by `pickDoc()` settles.
8
+ * Opens `<ADocPickModal>` (a `UCommandPalette` in a `UModal`) via Nuxt UI's
9
+ * overlay manager and resolves with the chosen doc, or `null` if dismissed.
10
+ * Self-contained — callers (the slash doc-embed/doc-link handlers, the
11
+ * `<ADocLinkPopover>` toolbar button, the drag handle) just `await pickDoc()`;
12
+ * no host wiring required. This is the picker used everywhere EXCEPT the inline
13
+ * `[[` / `![[` typing triggers (those commit directly via `<ADocSuggestMenu>`).
14
+ *
15
+ * Mirrors cou-sh/app/composables/useDocLinkPick.ts.
12
16
  */
13
17
  export declare function useDocLinkPick(): {
14
- isOpen: import("vue").Ref<boolean, boolean>;
15
18
  pickDoc: () => Promise<DocLinkPickResult | null>;
16
- resolve: (result: DocLinkPickResult) => void;
17
- cancel: () => void;
18
19
  };
@@ -1,22 +1,11 @@
1
- import { ref } from "vue";
1
+ import { useOverlay } from "#imports";
2
+ import ADocPickModal from "../components/ADocPickModal.vue";
2
3
  export function useDocLinkPick() {
3
- const isOpen = ref(false);
4
- let _resolve = null;
4
+ const overlay = useOverlay();
5
+ const modal = overlay.create(ADocPickModal);
5
6
  function pickDoc() {
6
- return new Promise((resolve2) => {
7
- _resolve = resolve2;
8
- isOpen.value = true;
9
- });
7
+ const { result } = modal.open();
8
+ return result;
10
9
  }
11
- function resolve(result) {
12
- isOpen.value = false;
13
- _resolve?.(result);
14
- _resolve = null;
15
- }
16
- function cancel() {
17
- isOpen.value = false;
18
- _resolve?.(null);
19
- _resolve = null;
20
- }
21
- return { isOpen, pickDoc, resolve, cancel };
10
+ return { pickDoc };
22
11
  }
@@ -0,0 +1,34 @@
1
+ import type { useChildTree } from './useChildTree.js';
2
+ type Tree = ReturnType<typeof useChildTree>;
3
+ export interface DocSuggestItem {
4
+ id: string;
5
+ label: string;
6
+ icon: string;
7
+ /** Dimmed ancestor path (e.g. "Kanban Board / Todo") for nesting context. */
8
+ prefix?: string;
9
+ /** When true, selecting this item creates a new child doc named `label`. */
10
+ isCreate?: boolean;
11
+ }
12
+ /** Reactive state shared between the DocSuggest extension and its popup. */
13
+ export interface DocSuggestPopupState {
14
+ active: boolean;
15
+ mode: 'link' | 'embed';
16
+ query: string;
17
+ items: DocSuggestItem[];
18
+ index: number;
19
+ rect: {
20
+ left: number;
21
+ top: number;
22
+ bottom: number;
23
+ } | null;
24
+ onSelect: ((item: DocSuggestItem) => void) | null;
25
+ }
26
+ export declare function emptyDocSuggestState(): DocSuggestPopupState;
27
+ /**
28
+ * Build a whole-space doc search for the `[[` / `![[` popups. Ranks entries by
29
+ * label match and appends a "Create …" item when the query has no exact match.
30
+ * Each result carries a dimmed ancestor-path `prefix` for nesting context — the
31
+ * popup stays a flat, searchable list (no tree drill-down).
32
+ */
33
+ export declare function makeDocSearch(tree: Tree, currentDocId: string): (rawQuery: string) => DocSuggestItem[];
34
+ export {};
@@ -0,0 +1,56 @@
1
+ import { resolveDocType } from "../utils/docTypes.js";
2
+ export function emptyDocSuggestState() {
3
+ return { active: false, mode: "link", query: "", items: [], index: 0, rect: null, onSelect: null };
4
+ }
5
+ const MAX_RESULTS = 20;
6
+ export function makeDocSearch(tree, currentDocId) {
7
+ return (rawQuery) => {
8
+ const query = rawQuery.trim();
9
+ const q = query.toLowerCase();
10
+ const entries = tree.entries.value;
11
+ const byId = new Map(entries.map((e) => [e.id, e]));
12
+ const pathOf = (parentId) => {
13
+ const out = [];
14
+ const seen = /* @__PURE__ */ new Set();
15
+ let pid = parentId;
16
+ while (pid && byId.has(pid) && !seen.has(pid)) {
17
+ seen.add(pid);
18
+ const e = byId.get(pid);
19
+ out.unshift(e.label || "Untitled");
20
+ pid = e.parentId;
21
+ }
22
+ return out.join(" / ");
23
+ };
24
+ const scored = [];
25
+ for (const e of entries) {
26
+ if (e.id === currentDocId) continue;
27
+ const label = (e.label || "").trim();
28
+ if (!label) continue;
29
+ const lower = label.toLowerCase();
30
+ let score = -1;
31
+ if (!q) score = 0;
32
+ else if (lower === q) score = 3;
33
+ else if (lower.startsWith(q)) score = 2;
34
+ else if (lower.includes(q)) score = 1;
35
+ if (score < 0) continue;
36
+ scored.push({ entry: e, score });
37
+ }
38
+ scored.sort((a, b) => b.score - a.score || a.entry.label.localeCompare(b.entry.label));
39
+ const items = scored.slice(0, MAX_RESULTS).map(({ entry }) => ({
40
+ id: entry.id,
41
+ label: entry.label,
42
+ icon: resolveDocType(entry.type).icon,
43
+ prefix: pathOf(entry.parentId) || void 0
44
+ }));
45
+ const hasExact = items.some((i) => i.label.toLowerCase() === q);
46
+ if (query && !hasExact) {
47
+ items.push({
48
+ id: "__create__",
49
+ label: `Create "${query}"`,
50
+ icon: "i-lucide-file-plus",
51
+ isCreate: true
52
+ });
53
+ }
54
+ return items;
55
+ };
56
+ }
@@ -20,6 +20,10 @@ interface NodeContextMenuOptions {
20
20
  onOpenInWindow?: (id: string) => void;
21
21
  /** Create a child page under this node — renders "Add child page". */
22
22
  onAddChild?: (parentId: string) => void;
23
+ /** Reparent this node — renders "Move to…" (e.g. open an <ADocPickerModal>). */
24
+ onMoveTo?: (id: string) => void;
25
+ /** Edit this node's tags — renders "Edit tags…" (e.g. open an <ATagsEditor>). */
26
+ onEditTags?: (id: string) => void;
23
27
  /** Export the doc — renders an "Export" submenu (Markdown / HTML). Wire to
24
28
  * `useDocExport().exportDoc` (kept out of this composable so it stays
25
29
  * decoupled from the export pipeline + its lazy jszip import). */
@@ -67,6 +67,15 @@ export function useNodeContextMenu(opts) {
67
67
  }
68
68
  });
69
69
  }
70
+ if (opts.onMoveTo) {
71
+ editGroup.push({
72
+ label: "Move to\u2026",
73
+ icon: "i-lucide-corner-down-right",
74
+ onSelect() {
75
+ opts.onMoveTo(nodeId);
76
+ }
77
+ });
78
+ }
70
79
  const colorItems = UI_COLORS.slice(0, 12).map((colorName) => ({
71
80
  label: colorName.charAt(0).toUpperCase() + colorName.slice(1),
72
81
  icon: "i-lucide-circle",
@@ -110,6 +119,15 @@ export function useNodeContextMenu(opts) {
110
119
  }
111
120
  });
112
121
  }
122
+ if (opts.onEditTags) {
123
+ actionsGroup.push({
124
+ label: "Edit tags\u2026",
125
+ icon: "i-lucide-tags",
126
+ onSelect() {
127
+ opts.onEditTags(nodeId);
128
+ }
129
+ });
130
+ }
113
131
  if (opts.onExport) {
114
132
  actionsGroup.push({
115
133
  label: "Export",