@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.
- package/dist/module.d.mts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -0
- package/dist/runtime/components/ACodeEditor.vue +123 -22
- 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/ADocViewToggle.d.vue.ts +40 -0
- package/dist/runtime/components/ADocViewToggle.vue +234 -0
- package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
- package/dist/runtime/components/ADocumentTree.vue +66 -1
- package/dist/runtime/components/AEditor.d.vue.ts +17 -10
- package/dist/runtime/components/AEditor.vue +403 -167
- 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 +553 -481
- 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/editor/ADocSuggestMenu.d.vue.ts +7 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
- 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/useDocLinkPick.d.ts +9 -8
- package/dist/runtime/composables/useDocLinkPick.js +7 -18
- package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
- package/dist/runtime/composables/useDocSuggest.js +56 -0
- 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/extensions/doc-link-drop.js +2 -2
- package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
- package/dist/runtime/extensions/doc-suggest.js +85 -0
- 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/codeHighlightStyle.d.ts +15 -0
- package/dist/runtime/utils/codeHighlightStyle.js +34 -0
- package/dist/runtime/utils/docTypes.js +43 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
- package/dist/runtime/utils/loadCodeMirror.js +6 -3
- package/dist/runtime/utils/titleSync.d.ts +130 -0
- package/dist/runtime/utils/titleSync.js +53 -0
- 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
|
|
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);
|
|
@@ -3,16 +3,17 @@ export interface DocLinkPickResult {
|
|
|
3
3
|
label: string;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Used by doc-embed extension and inline link insertion.
|
|
6
|
+
* Imperative command-palette document picker.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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 {
|
|
1
|
+
import { useOverlay } from "#imports";
|
|
2
|
+
import ADocPickModal from "../components/ADocPickModal.vue";
|
|
2
3
|
export function useDocLinkPick() {
|
|
3
|
-
const
|
|
4
|
-
|
|
4
|
+
const overlay = useOverlay();
|
|
5
|
+
const modal = overlay.create(ADocPickModal);
|
|
5
6
|
function pickDoc() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
isOpen.value = true;
|
|
9
|
-
});
|
|
7
|
+
const { result } = modal.open();
|
|
8
|
+
return result;
|
|
10
9
|
}
|
|
11
|
-
|
|
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",
|