@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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +7 -0
- package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
- package/dist/runtime/components/ADocPickerModal.vue +191 -0
- package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
- package/dist/runtime/components/ADocumentTree.vue +65 -0
- package/dist/runtime/components/AEditor.d.vue.ts +17 -10
- package/dist/runtime/components/AEditor.vue +232 -164
- package/dist/runtime/components/AEditor.vue.d.ts +17 -10
- package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
- package/dist/runtime/components/ANodePanel.vue +547 -473
- package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
- package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
- package/dist/runtime/components/ATagsEditor.vue +60 -0
- package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
- package/dist/runtime/components/chat/AChatInput.vue +33 -2
- package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
- package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
- package/dist/runtime/components/chat/AChatList.vue +76 -32
- package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
- package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
- package/dist/runtime/components/chat/AChatMessages.vue +57 -4
- package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
- package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
- package/dist/runtime/components/chat/AChatPanel.vue +17 -1
- package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
- package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
- package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
- package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
- package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
- package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
- package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
- package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
- package/dist/runtime/components/settings/sections.d.ts +37 -0
- package/dist/runtime/components/settings/sections.js +45 -0
- package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
- package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
- package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
- package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
- package/dist/runtime/composables/useChat.d.ts +22 -1
- package/dist/runtime/composables/useChat.js +79 -8
- package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
- package/dist/runtime/composables/useNodeContextMenu.js +18 -0
- package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
- package/dist/runtime/locale.d.ts +8 -0
- package/dist/runtime/locale.js +9 -1
- package/dist/runtime/utils/chatContent.d.ts +20 -2
- package/dist/runtime/utils/chatContent.js +20 -1
- package/dist/runtime/utils/docTypes.js +43 -0
- package/dist/runtime/utils/titleSync.d.ts +130 -0
- package/dist/runtime/utils/titleSync.js +53 -0
- 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
|
|
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:
|
|
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(
|
|
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>;
|
package/dist/runtime/locale.d.ts
CHANGED
|
@@ -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;
|
package/dist/runtime/locale.js
CHANGED
|
@@ -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
|
-
/**
|
|
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") {
|