@abraca/nuxt 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +7 -0
  3. package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
  4. package/dist/runtime/components/ADocPickerModal.vue +191 -0
  5. package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
  6. package/dist/runtime/components/ADocumentTree.vue +65 -0
  7. package/dist/runtime/components/AEditor.d.vue.ts +17 -10
  8. package/dist/runtime/components/AEditor.vue +232 -164
  9. package/dist/runtime/components/AEditor.vue.d.ts +17 -10
  10. package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
  11. package/dist/runtime/components/ANodePanel.vue +547 -473
  12. package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
  13. package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
  14. package/dist/runtime/components/ATagsEditor.vue +60 -0
  15. package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
  16. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  17. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  18. package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
  19. package/dist/runtime/components/chat/AChatInput.vue +33 -2
  20. package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
  21. package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
  22. package/dist/runtime/components/chat/AChatList.vue +76 -32
  23. package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
  24. package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
  25. package/dist/runtime/components/chat/AChatMessages.vue +57 -4
  26. package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
  27. package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
  28. package/dist/runtime/components/chat/AChatPanel.vue +17 -1
  29. package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
  30. package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
  31. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
  32. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
  33. package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
  34. package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
  35. package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
  36. package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
  37. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
  38. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
  39. package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
  40. package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
  41. package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
  42. package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
  43. package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
  44. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
  45. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
  46. package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
  47. package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
  48. package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
  49. package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
  50. package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
  51. package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
  52. package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
  53. package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
  54. package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
  55. package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
  56. package/dist/runtime/components/settings/sections.d.ts +37 -0
  57. package/dist/runtime/components/settings/sections.js +45 -0
  58. package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
  59. package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
  60. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  61. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  62. package/dist/runtime/composables/useChat.d.ts +22 -1
  63. package/dist/runtime/composables/useChat.js +79 -8
  64. package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
  65. package/dist/runtime/composables/useNodeContextMenu.js +18 -0
  66. package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
  67. package/dist/runtime/locale.d.ts +8 -0
  68. package/dist/runtime/locale.js +9 -1
  69. package/dist/runtime/utils/chatContent.d.ts +20 -2
  70. package/dist/runtime/utils/chatContent.js +20 -1
  71. package/dist/runtime/utils/docTypes.js +43 -0
  72. package/dist/runtime/utils/titleSync.d.ts +130 -0
  73. package/dist/runtime/utils/titleSync.js +53 -0
  74. package/package.json +11 -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);
@@ -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",
@@ -9,7 +9,7 @@
9
9
  * Usage:
10
10
  * const { isOpen, activeTab, openSettings, closeSettings } = useSettingsModal()
11
11
  */
12
- export type SettingsTab = 'profile' | 'connection' | 'spaces' | 'security' | 'invites' | 'members' | 'trash' | 'offline' | 'plugins' | 'admin';
12
+ export type SettingsTab = 'profile' | 'appearance' | 'connection' | 'spaces' | 'security' | 'invites' | 'members' | 'trash' | 'offline' | 'plugins' | 'admin';
13
13
  export declare function useSettingsModal(): {
14
14
  isOpen: import("vue").WritableComputedRef<boolean, boolean>;
15
15
  activeTab: import("vue").WritableComputedRef<SettingsTab, SettingsTab>;
@@ -61,6 +61,10 @@ export interface AbracadabraLocale {
61
61
  nodePanel: {
62
62
  editorTab: string;
63
63
  propertiesTab: string;
64
+ chatTab: string;
65
+ settingsTab: string;
66
+ serverTab: string;
67
+ openAsFullPage: string;
64
68
  close: string;
65
69
  icon: string;
66
70
  color: string;
@@ -251,6 +255,10 @@ export interface AbracadabraLocale {
251
255
  edgeNormal: string;
252
256
  edgeThick: string;
253
257
  filter: string;
258
+ scrollBehavior: string;
259
+ personalSetting: string;
260
+ scrollPan: string;
261
+ scrollZoom: string;
254
262
  };
255
263
  dashboard: {
256
264
  addItem: string;
@@ -47,6 +47,10 @@ export const DEFAULT_LOCALE = {
47
47
  nodePanel: {
48
48
  editorTab: "Editor",
49
49
  propertiesTab: "Properties",
50
+ chatTab: "Chat",
51
+ settingsTab: "Settings",
52
+ serverTab: "Server",
53
+ openAsFullPage: "Open as full page",
50
54
  close: "Close",
51
55
  icon: "Icon",
52
56
  color: "Color",
@@ -236,7 +240,11 @@ export const DEFAULT_LOCALE = {
236
240
  edgeThin: "Thin",
237
241
  edgeNormal: "Normal",
238
242
  edgeThick: "Thick",
239
- filter: "Filter"
243
+ filter: "Filter",
244
+ scrollBehavior: "Scroll behavior",
245
+ personalSetting: "Personal \u2014 this device only",
246
+ scrollPan: "Pan",
247
+ scrollZoom: "Zoom"
240
248
  },
241
249
  dashboard: {
242
250
  addItem: "Add item",
@@ -24,7 +24,11 @@ export type ParsedMessage = {
24
24
  icon?: string;
25
25
  };
26
26
  export declare function parseMessageContent(content: string): ParsedMessage;
27
- /** Segment types for rich text rendering */
27
+ /**
28
+ * Segment types for rich text rendering. Markdown spans (bold/italic/strike/
29
+ * code) carry their inner `value` as plain text — rendered as styled spans, so
30
+ * there is NO HTML injection (no `v-html`); the renderer just picks a tag/class.
31
+ */
28
32
  export type TextSegment = {
29
33
  type: 'text';
30
34
  value: string;
@@ -34,8 +38,22 @@ export type TextSegment = {
34
38
  } | {
35
39
  type: 'mention';
36
40
  name: string;
41
+ } | {
42
+ type: 'bold';
43
+ value: string;
44
+ } | {
45
+ type: 'italic';
46
+ value: string;
47
+ } | {
48
+ type: 'strike';
49
+ value: string;
50
+ } | {
51
+ type: 'code';
52
+ value: string;
37
53
  };
38
- /** Split plain text into segments for rich rendering (links, @mentions) */
54
+ /** Split plain text into segments for rich rendering (markdown, links, @mentions). */
39
55
  export declare function renderMessageSegments(text: string): TextSegment[];
56
+ /** Whether a plain-text message contains any rich span (markdown/link/mention). */
57
+ export declare function hasRichText(text: string): boolean;
40
58
  /** Return a short plain-text preview for any message content string. */
41
59
  export declare function previewMessageContent(content: string): string;
@@ -28,16 +28,32 @@ export function parseMessageContent(content) {
28
28
  }
29
29
  const URL_RE = /https?:\/\/[^\s<>)"']+/g;
30
30
  const MENTION_RE = /@(\w+)/g;
31
+ const CODE_RE = /`([^`\n]+)`/g;
32
+ const BOLD_RE = /\*\*([^*\n]+)\*\*/g;
33
+ const STRIKE_RE = /~~([^~\n]+)~~/g;
34
+ const ITALIC_RE = /\*([^*\n]+)\*|_([^_\n]+)_/g;
31
35
  export function renderMessageSegments(text) {
32
36
  const segments = [];
33
37
  const matches = [];
38
+ for (const m of text.matchAll(CODE_RE)) {
39
+ matches.push({ index: m.index, length: m[0].length, segment: { type: "code", value: m[1] } });
40
+ }
41
+ for (const m of text.matchAll(BOLD_RE)) {
42
+ matches.push({ index: m.index, length: m[0].length, segment: { type: "bold", value: m[1] } });
43
+ }
44
+ for (const m of text.matchAll(STRIKE_RE)) {
45
+ matches.push({ index: m.index, length: m[0].length, segment: { type: "strike", value: m[1] } });
46
+ }
47
+ for (const m of text.matchAll(ITALIC_RE)) {
48
+ matches.push({ index: m.index, length: m[0].length, segment: { type: "italic", value: m[1] ?? m[2] } });
49
+ }
34
50
  for (const m of text.matchAll(URL_RE)) {
35
51
  matches.push({ index: m.index, length: m[0].length, segment: { type: "link", url: m[0] } });
36
52
  }
37
53
  for (const m of text.matchAll(MENTION_RE)) {
38
54
  matches.push({ index: m.index, length: m[0].length, segment: { type: "mention", name: m[1] } });
39
55
  }
40
- matches.sort((a, b) => a.index - b.index);
56
+ matches.sort((a, b) => a.index - b.index || b.length - a.length);
41
57
  let cursor = 0;
42
58
  for (const m of matches) {
43
59
  if (m.index < cursor) continue;
@@ -52,6 +68,9 @@ export function renderMessageSegments(text) {
52
68
  }
53
69
  return segments.length > 0 ? segments : [{ type: "text", value: text }];
54
70
  }
71
+ export function hasRichText(text) {
72
+ return /@\w+|https?:\/\/|\*\*[^*\n]+\*\*|~~[^~\n]+~~|`[^`\n]+`|\*[^*\n]+\*|_[^_\n]+_/.test(text);
73
+ }
55
74
  export function previewMessageContent(content) {
56
75
  const parsed = parseMessageContent(content);
57
76
  if (parsed.kind === "doc-quote") {