@abraca/nuxt 2.10.0 → 2.11.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 +14 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -0
- package/dist/runtime/assets/editor.css +1 -1
- package/dist/runtime/components/AConnectionBadge.d.vue.ts +29 -0
- package/dist/runtime/components/AConnectionBadge.vue +79 -0
- package/dist/runtime/components/AConnectionBadge.vue.d.ts +29 -0
- package/dist/runtime/components/AEditor.d.vue.ts +2 -2
- package/dist/runtime/components/AEditor.vue +11 -1
- package/dist/runtime/components/AEditor.vue.d.ts +2 -2
- package/dist/runtime/components/AEncryptionModePicker.d.vue.ts +33 -0
- package/dist/runtime/components/AEncryptionModePicker.vue +211 -0
- package/dist/runtime/components/AEncryptionModePicker.vue.d.ts +33 -0
- package/dist/runtime/components/AModalShell.d.vue.ts +48 -0
- package/dist/runtime/components/AModalShell.vue +105 -0
- package/dist/runtime/components/AModalShell.vue.d.ts +48 -0
- package/dist/runtime/components/ANodePanel.d.vue.ts +8 -6
- package/dist/runtime/components/ANodePanel.vue +25 -0
- package/dist/runtime/components/ANodePanel.vue.d.ts +8 -6
- package/dist/runtime/components/ANodePanelHeader.d.vue.ts +20 -10
- package/dist/runtime/components/ANodePanelHeader.vue +17 -3
- package/dist/runtime/components/ANodePanelHeader.vue.d.ts +20 -10
- package/dist/runtime/components/ANodeSettingsPanel.d.vue.ts +2 -0
- package/dist/runtime/components/ANodeSettingsPanel.vue +21 -1
- package/dist/runtime/components/ANodeSettingsPanel.vue.d.ts +2 -0
- package/dist/runtime/components/ASnapshotPreviewModal.d.vue.ts +33 -0
- package/dist/runtime/components/ASnapshotPreviewModal.vue +430 -0
- package/dist/runtime/components/ASnapshotPreviewModal.vue.d.ts +33 -0
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +2 -2
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +2 -2
- package/dist/runtime/components/editor/ALocationPickerPopover.vue +28 -7
- package/dist/runtime/components/registry/APluginDetail.d.vue.ts +2 -2
- package/dist/runtime/components/registry/APluginDetail.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/AProseRenderer.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +2 -2
- package/dist/runtime/components/shell/ABreadcrumbForDoc.d.vue.ts +6 -0
- package/dist/runtime/components/shell/ABreadcrumbForDoc.vue +75 -3
- package/dist/runtime/components/shell/ABreadcrumbForDoc.vue.d.ts +6 -0
- package/dist/runtime/components/shell/ADocPanelServerSettings.d.vue.ts +17 -0
- package/dist/runtime/components/shell/ADocPanelServerSettings.vue +253 -0
- package/dist/runtime/components/shell/ADocPanelServerSettings.vue.d.ts +17 -0
- package/dist/runtime/components/shell/ADocPanelSettings.d.vue.ts +2 -0
- package/dist/runtime/components/shell/ADocPanelSettings.vue +15 -4
- package/dist/runtime/components/shell/ADocPanelSettings.vue.d.ts +2 -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/composables/useDocBreadcrumb.d.ts +17 -2
- package/dist/runtime/composables/useDocBreadcrumb.js +17 -3
- package/dist/runtime/composables/useDocSnapshots.d.ts +2 -1
- package/dist/runtime/composables/useDocSnapshots.js +5 -0
- package/dist/runtime/composables/useEditor.d.ts +1 -1
- package/dist/runtime/composables/useEditor.js +120 -0
- package/dist/runtime/composables/useEditorToolbar.d.ts +12 -4
- package/dist/runtime/composables/useEditorToolbar.js +78 -56
- package/dist/runtime/composables/useNodeContextMenu.d.ts +10 -0
- package/dist/runtime/composables/useNodeContextMenu.js +41 -1
- package/dist/runtime/composables/useSwipeGesture.d.ts +48 -0
- package/dist/runtime/composables/useSwipeGesture.js +140 -0
- package/dist/runtime/extensions/document-header.js +16 -6
- package/dist/runtime/extensions/document-meta.js +344 -19
- package/dist/runtime/extensions/meta-field.js +42 -0
- package/dist/runtime/extensions/views/DocumentMetaView.vue +33 -7
- package/dist/runtime/extensions/views/FieldView.vue +51 -19
- package/dist/runtime/extensions/views/MetaFieldView.vue +30 -4
- package/dist/runtime/middleware/abracadabra-auth.d.ts +1 -1
- package/dist/runtime/plugin-abracadabra.client.d.ts +1 -1
- package/dist/runtime/plugin-abracadabra.client.js +12 -2
- package/dist/runtime/plugin-abracadabra.server.d.ts +1 -1
- package/dist/runtime/plugin-shared-globals.client.d.ts +1 -1
- package/package.json +1 -4
|
@@ -1,15 +1,87 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
2
3
|
import { useDocBreadcrumb } from "../../composables/useDocBreadcrumb";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
docId: { type: null, required: true },
|
|
5
|
-
maxDepth: { type: Number, required: false, default: 8 }
|
|
6
|
+
maxDepth: { type: Number, required: false, default: 8 },
|
|
7
|
+
maxVisible: { type: Number, required: false, default: 0 }
|
|
6
8
|
});
|
|
7
|
-
const { items } = useDocBreadcrumb(() => props.docId, {
|
|
9
|
+
const { items, collapsed } = useDocBreadcrumb(() => props.docId, {
|
|
10
|
+
maxDepth: props.maxDepth,
|
|
11
|
+
maxVisible: props.maxVisible
|
|
12
|
+
});
|
|
13
|
+
const overflowItems = computed(
|
|
14
|
+
() => collapsed.value.hidden.map((a) => ({ label: a.label, icon: a.icon, to: a.to }))
|
|
15
|
+
);
|
|
8
16
|
</script>
|
|
9
17
|
|
|
10
18
|
<template>
|
|
19
|
+
<!-- Collapsed single-line variant (overflow menu for the hidden middle) -->
|
|
20
|
+
<nav
|
|
21
|
+
v-if="collapsed.overflowed"
|
|
22
|
+
aria-label="Breadcrumb"
|
|
23
|
+
class="flex items-center gap-0.5 min-w-0 text-(--ui-text-muted)"
|
|
24
|
+
>
|
|
25
|
+
<template
|
|
26
|
+
v-for="ancestor in collapsed.head"
|
|
27
|
+
:key="ancestor.id"
|
|
28
|
+
>
|
|
29
|
+
<ULink
|
|
30
|
+
:to="ancestor.to"
|
|
31
|
+
class="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs hover:bg-(--ui-bg-elevated)/60 hover:text-(--ui-text) transition-colors min-w-0"
|
|
32
|
+
>
|
|
33
|
+
<UIcon
|
|
34
|
+
:name="ancestor.icon"
|
|
35
|
+
class="size-3.5 shrink-0"
|
|
36
|
+
/>
|
|
37
|
+
<span class="truncate max-w-[10ch]">{{ ancestor.label }}</span>
|
|
38
|
+
</ULink>
|
|
39
|
+
<UIcon
|
|
40
|
+
name="i-lucide-chevron-right"
|
|
41
|
+
class="size-3.5 shrink-0 opacity-60"
|
|
42
|
+
/>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<UDropdownMenu :items="overflowItems">
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
class="flex items-center px-1 py-0.5 rounded text-xs hover:bg-(--ui-bg-elevated)/60 hover:text-(--ui-text) transition-colors"
|
|
49
|
+
:aria-label="`Show ${collapsed.hidden.length} hidden`"
|
|
50
|
+
>
|
|
51
|
+
…
|
|
52
|
+
</button>
|
|
53
|
+
</UDropdownMenu>
|
|
54
|
+
<UIcon
|
|
55
|
+
name="i-lucide-chevron-right"
|
|
56
|
+
class="size-3.5 shrink-0 opacity-60"
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<template
|
|
60
|
+
v-for="(ancestor, idx) in collapsed.tail"
|
|
61
|
+
:key="ancestor.id"
|
|
62
|
+
>
|
|
63
|
+
<ULink
|
|
64
|
+
:to="ancestor.to"
|
|
65
|
+
class="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs hover:bg-(--ui-bg-elevated)/60 hover:text-(--ui-text) transition-colors min-w-0"
|
|
66
|
+
:class="ancestor.to ? '' : 'pointer-events-none text-(--ui-text)'"
|
|
67
|
+
>
|
|
68
|
+
<UIcon
|
|
69
|
+
:name="ancestor.icon"
|
|
70
|
+
class="size-3.5 shrink-0"
|
|
71
|
+
/>
|
|
72
|
+
<span class="truncate max-w-[10ch]">{{ ancestor.label }}</span>
|
|
73
|
+
</ULink>
|
|
74
|
+
<UIcon
|
|
75
|
+
v-if="idx < collapsed.tail.length - 1"
|
|
76
|
+
name="i-lucide-chevron-right"
|
|
77
|
+
class="size-3.5 shrink-0 opacity-60"
|
|
78
|
+
/>
|
|
79
|
+
</template>
|
|
80
|
+
</nav>
|
|
81
|
+
|
|
82
|
+
<!-- Full-trail variant -->
|
|
11
83
|
<UBreadcrumb
|
|
12
|
-
v-if="items.length > 0"
|
|
84
|
+
v-else-if="items.length > 0"
|
|
13
85
|
:items="items"
|
|
14
86
|
:ui="{ root: 'min-w-0', list: 'min-w-0 flex-nowrap', item: 'truncate', link: 'truncate' }"
|
|
15
87
|
/>
|
|
@@ -3,9 +3,15 @@ type __VLS_Props = {
|
|
|
3
3
|
docId: string | null | undefined;
|
|
4
4
|
/** Maximum ancestors to walk (default 8) */
|
|
5
5
|
maxDepth?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Collapse the middle into a "…" overflow menu once the trail exceeds this
|
|
8
|
+
* many crumbs. 0 (default) renders the full trail with no collapsing.
|
|
9
|
+
*/
|
|
10
|
+
maxVisible?: number;
|
|
6
11
|
};
|
|
7
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
8
13
|
maxDepth: number;
|
|
14
|
+
maxVisible: number;
|
|
9
15
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
16
|
declare const _default: typeof __VLS_export;
|
|
11
17
|
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** Hub document id (informational — the panel reflects the active server). */
|
|
3
|
+
docId?: string;
|
|
4
|
+
};
|
|
5
|
+
declare var __VLS_96: "storage" | "users" | "members" | "invites", __VLS_97: {};
|
|
6
|
+
type __VLS_Slots = {} & {
|
|
7
|
+
[K in NonNullable<typeof __VLS_96>]?: (props: typeof __VLS_97) => any;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
11
|
+
declare const _default: typeof __VLS_export;
|
|
12
|
+
export default _default;
|
|
13
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
14
|
+
new (): {
|
|
15
|
+
$slots: S;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onMounted, reactive, ref, useSlots, watch } from "vue";
|
|
3
|
+
import { useAbracadabra } from "../../composables/useAbracadabra";
|
|
4
|
+
defineProps({
|
|
5
|
+
docId: { type: String, required: false }
|
|
6
|
+
});
|
|
7
|
+
const slots = useSlots();
|
|
8
|
+
const { client, status, synced, effectiveRole, currentServerUrl, reconnect } = useAbracadabra();
|
|
9
|
+
const ROLE_LEVELS = {
|
|
10
|
+
observer: 0,
|
|
11
|
+
viewer: 1,
|
|
12
|
+
editor: 2,
|
|
13
|
+
owner: 3,
|
|
14
|
+
admin: 4,
|
|
15
|
+
service: 5
|
|
16
|
+
};
|
|
17
|
+
const roleLevel = computed(() => ROLE_LEVELS[effectiveRole.value ?? ""] ?? 0);
|
|
18
|
+
const canEdit = computed(() => roleLevel.value >= 2);
|
|
19
|
+
const isAdmin = computed(() => roleLevel.value >= 4);
|
|
20
|
+
const roleBadgeColor = computed(() => {
|
|
21
|
+
const r = effectiveRole.value;
|
|
22
|
+
if (r === "admin" || r === "service") return "warning";
|
|
23
|
+
if (r === "owner") return "primary";
|
|
24
|
+
if (r === "editor") return "success";
|
|
25
|
+
return "neutral";
|
|
26
|
+
});
|
|
27
|
+
const statusDotClass = computed(() => {
|
|
28
|
+
if (status.value === "connected") return "bg-(--ui-success)";
|
|
29
|
+
if (status.value === "connecting") return "bg-(--ui-warning)";
|
|
30
|
+
return "bg-(--ui-error)";
|
|
31
|
+
});
|
|
32
|
+
const serverInfo = ref(null);
|
|
33
|
+
async function loadServerInfo() {
|
|
34
|
+
if (!client.value) return;
|
|
35
|
+
try {
|
|
36
|
+
serverInfo.value = await client.value.serverInfo();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
watch(client, loadServerInfo, { immediate: true });
|
|
41
|
+
const healthData = ref(null);
|
|
42
|
+
const healthLoading = ref(false);
|
|
43
|
+
async function checkHealth() {
|
|
44
|
+
if (!client.value) return;
|
|
45
|
+
healthLoading.value = true;
|
|
46
|
+
try {
|
|
47
|
+
healthData.value = await client.value.health();
|
|
48
|
+
} catch {
|
|
49
|
+
} finally {
|
|
50
|
+
healthLoading.value = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const expanded = reactive({ members: false, users: false, invites: false, storage: false });
|
|
54
|
+
const adminSections = computed(() => [
|
|
55
|
+
{ key: "members", label: "Members", icon: "i-lucide-users", show: canEdit.value && !!slots.members },
|
|
56
|
+
{ key: "users", label: "Users", icon: "i-lucide-shield-alert", show: isAdmin.value && !!slots.users },
|
|
57
|
+
{ key: "invites", label: "Invites", icon: "i-lucide-ticket", show: isAdmin.value && !!slots.invites },
|
|
58
|
+
{ key: "storage", label: "Storage", icon: "i-lucide-hard-drive", show: isAdmin.value && !!slots.storage }
|
|
59
|
+
].filter((s) => s.show));
|
|
60
|
+
onMounted(loadServerInfo);
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<div class="flex-1 overflow-y-auto px-4 py-3 space-y-3">
|
|
65
|
+
<!-- Connection — read-only URL (single-server) -->
|
|
66
|
+
<div class="rounded-lg bg-(--ui-bg-elevated) overflow-hidden">
|
|
67
|
+
<div class="flex items-center gap-1.5 px-3 py-2 border-b border-(--ui-border)">
|
|
68
|
+
<UIcon
|
|
69
|
+
name="i-lucide-plug"
|
|
70
|
+
class="size-3.5 text-(--ui-text-muted) shrink-0"
|
|
71
|
+
/>
|
|
72
|
+
<span class="text-xs font-semibold uppercase tracking-wider text-(--ui-text-muted)">Connection</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="px-3 py-3 space-y-1.5">
|
|
75
|
+
<div class="flex items-center gap-2">
|
|
76
|
+
<UIcon
|
|
77
|
+
name="i-lucide-server"
|
|
78
|
+
class="size-3.5 text-(--ui-text-dimmed) shrink-0"
|
|
79
|
+
/>
|
|
80
|
+
<span class="text-sm truncate flex-1 min-w-0 text-(--ui-text)">{{ currentServerUrl || "\u2014" }}</span>
|
|
81
|
+
<UButton
|
|
82
|
+
v-if="status !== 'connected'"
|
|
83
|
+
icon="i-lucide-refresh-cw"
|
|
84
|
+
size="xs"
|
|
85
|
+
variant="ghost"
|
|
86
|
+
color="primary"
|
|
87
|
+
@click="reconnect"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- Status -->
|
|
94
|
+
<div class="rounded-lg bg-(--ui-bg-elevated) overflow-hidden">
|
|
95
|
+
<div class="flex items-center gap-1.5 px-3 py-2 border-b border-(--ui-border)">
|
|
96
|
+
<UIcon
|
|
97
|
+
name="i-lucide-info"
|
|
98
|
+
class="size-3.5 text-(--ui-text-muted) shrink-0"
|
|
99
|
+
/>
|
|
100
|
+
<span class="text-xs font-semibold uppercase tracking-wider text-(--ui-text-muted)">Status</span>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="divide-y divide-(--ui-border)">
|
|
103
|
+
<div class="flex items-center justify-between gap-2 px-3 py-2">
|
|
104
|
+
<div class="flex items-center gap-2 text-sm text-(--ui-text-muted) min-w-0">
|
|
105
|
+
<UIcon
|
|
106
|
+
name="i-lucide-activity"
|
|
107
|
+
class="size-3.5 shrink-0"
|
|
108
|
+
/>
|
|
109
|
+
<span class="truncate">Connection</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="flex items-center gap-1.5 shrink-0">
|
|
112
|
+
<span :class="['size-1.5 rounded-full', statusDotClass]" />
|
|
113
|
+
<span class="text-sm capitalize">{{ status }}</span>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="flex items-center justify-between gap-2 px-3 py-2">
|
|
117
|
+
<div class="flex items-center gap-2 text-sm text-(--ui-text-muted) min-w-0">
|
|
118
|
+
<UIcon
|
|
119
|
+
name="i-lucide-shield"
|
|
120
|
+
class="size-3.5 shrink-0"
|
|
121
|
+
/>
|
|
122
|
+
<span class="truncate">Your role</span>
|
|
123
|
+
</div>
|
|
124
|
+
<UBadge
|
|
125
|
+
v-if="effectiveRole"
|
|
126
|
+
:color="roleBadgeColor"
|
|
127
|
+
variant="subtle"
|
|
128
|
+
:label="effectiveRole"
|
|
129
|
+
size="sm"
|
|
130
|
+
class="capitalize shrink-0"
|
|
131
|
+
/>
|
|
132
|
+
<span
|
|
133
|
+
v-else
|
|
134
|
+
class="text-sm text-(--ui-text-dimmed)"
|
|
135
|
+
>—</span>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="flex items-center justify-between gap-2 px-3 py-2">
|
|
138
|
+
<div class="flex items-center gap-2 text-sm text-(--ui-text-muted) min-w-0">
|
|
139
|
+
<UIcon
|
|
140
|
+
name="i-lucide-refresh-ccw"
|
|
141
|
+
class="size-3.5 shrink-0"
|
|
142
|
+
/>
|
|
143
|
+
<span class="truncate">Synced</span>
|
|
144
|
+
</div>
|
|
145
|
+
<UBadge
|
|
146
|
+
:color="synced ? 'success' : 'neutral'"
|
|
147
|
+
variant="subtle"
|
|
148
|
+
:label="synced ? 'Yes' : 'No'"
|
|
149
|
+
:icon="synced ? 'i-lucide-check' : 'i-lucide-circle'"
|
|
150
|
+
size="sm"
|
|
151
|
+
class="shrink-0"
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
<div
|
|
155
|
+
v-if="serverInfo?.version"
|
|
156
|
+
class="flex items-center justify-between gap-2 px-3 py-2"
|
|
157
|
+
>
|
|
158
|
+
<div class="flex items-center gap-2 text-sm text-(--ui-text-muted) min-w-0">
|
|
159
|
+
<UIcon
|
|
160
|
+
name="i-lucide-tag"
|
|
161
|
+
class="size-3.5 shrink-0"
|
|
162
|
+
/>
|
|
163
|
+
<span class="truncate">Version</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="flex items-center gap-1 shrink-0">
|
|
166
|
+
<UBadge
|
|
167
|
+
color="neutral"
|
|
168
|
+
variant="subtle"
|
|
169
|
+
:label="`v${serverInfo.version}`"
|
|
170
|
+
size="sm"
|
|
171
|
+
/>
|
|
172
|
+
<UBadge
|
|
173
|
+
v-if="serverInfo.protocol_version"
|
|
174
|
+
color="neutral"
|
|
175
|
+
variant="outline"
|
|
176
|
+
:label="`p${serverInfo.protocol_version}`"
|
|
177
|
+
size="sm"
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div
|
|
182
|
+
v-if="serverInfo?.invite_only !== void 0"
|
|
183
|
+
class="flex items-center justify-between gap-2 px-3 py-2"
|
|
184
|
+
>
|
|
185
|
+
<div class="flex items-center gap-2 text-sm text-(--ui-text-muted) min-w-0">
|
|
186
|
+
<UIcon
|
|
187
|
+
:name="serverInfo.invite_only ? 'i-lucide-lock' : 'i-lucide-globe'"
|
|
188
|
+
class="size-3.5 shrink-0"
|
|
189
|
+
/>
|
|
190
|
+
<span class="truncate">Invite only</span>
|
|
191
|
+
</div>
|
|
192
|
+
<UBadge
|
|
193
|
+
:color="serverInfo.invite_only ? 'warning' : 'success'"
|
|
194
|
+
variant="subtle"
|
|
195
|
+
:label="serverInfo.invite_only ? 'Yes' : 'No'"
|
|
196
|
+
size="sm"
|
|
197
|
+
class="shrink-0"
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="flex items-center justify-between gap-2 px-3 py-2">
|
|
201
|
+
<div class="flex items-center gap-2 text-sm text-(--ui-text-muted) min-w-0">
|
|
202
|
+
<UIcon
|
|
203
|
+
name="i-lucide-heart-pulse"
|
|
204
|
+
class="size-3.5 shrink-0"
|
|
205
|
+
/>
|
|
206
|
+
<span class="truncate">Health</span>
|
|
207
|
+
</div>
|
|
208
|
+
<UButton
|
|
209
|
+
:label="healthData ? `v${healthData.version}` : 'Check'"
|
|
210
|
+
color="neutral"
|
|
211
|
+
variant="ghost"
|
|
212
|
+
size="xs"
|
|
213
|
+
class="shrink-0"
|
|
214
|
+
:loading="healthLoading"
|
|
215
|
+
@click="checkHealth"
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<!-- Admin surfaces — opt-in via slots, gated by role -->
|
|
222
|
+
<div
|
|
223
|
+
v-for="section in adminSections"
|
|
224
|
+
:key="section.key"
|
|
225
|
+
class="rounded-lg bg-(--ui-bg-elevated) overflow-hidden"
|
|
226
|
+
>
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
class="w-full flex items-center gap-1.5 px-3 py-2 border-b border-(--ui-border) hover:bg-(--ui-bg-accented)/40 transition-colors"
|
|
230
|
+
:class="{ 'border-b-0': !expanded[section.key] }"
|
|
231
|
+
@click="expanded[section.key] = !expanded[section.key]"
|
|
232
|
+
>
|
|
233
|
+
<UIcon
|
|
234
|
+
:name="section.icon"
|
|
235
|
+
class="size-3.5 text-(--ui-text-muted) shrink-0"
|
|
236
|
+
/>
|
|
237
|
+
<span class="text-xs font-semibold uppercase tracking-wider text-(--ui-text-muted) flex-1 text-left">
|
|
238
|
+
{{ section.label }}
|
|
239
|
+
</span>
|
|
240
|
+
<UIcon
|
|
241
|
+
:name="expanded[section.key] ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
|
|
242
|
+
class="size-3.5 text-(--ui-text-dimmed)"
|
|
243
|
+
/>
|
|
244
|
+
</button>
|
|
245
|
+
<div
|
|
246
|
+
v-if="expanded[section.key]"
|
|
247
|
+
class="px-3 py-3"
|
|
248
|
+
>
|
|
249
|
+
<slot :name="section.key" />
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** Hub document id (informational — the panel reflects the active server). */
|
|
3
|
+
docId?: string;
|
|
4
|
+
};
|
|
5
|
+
declare var __VLS_96: "storage" | "users" | "members" | "invites", __VLS_97: {};
|
|
6
|
+
type __VLS_Slots = {} & {
|
|
7
|
+
[K in NonNullable<typeof __VLS_96>]?: (props: typeof __VLS_97) => any;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
11
|
+
declare const _default: typeof __VLS_export;
|
|
12
|
+
export default _default;
|
|
13
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
14
|
+
new (): {
|
|
15
|
+
$slots: S;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -87,6 +87,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
87
87
|
"delete-snapshot": (version: number) => any;
|
|
88
88
|
"restore-snapshot": (version: number) => any;
|
|
89
89
|
"fork-snapshot": (version: number) => any;
|
|
90
|
+
"preview-snapshot": (version: number) => any;
|
|
90
91
|
"load-more-snapshots": () => any;
|
|
91
92
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
92
93
|
"onUpdate-meta"?: ((patch: Partial<DocPageMeta>) => any) | undefined;
|
|
@@ -105,6 +106,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
105
106
|
"onDelete-snapshot"?: ((version: number) => any) | undefined;
|
|
106
107
|
"onRestore-snapshot"?: ((version: number) => any) | undefined;
|
|
107
108
|
"onFork-snapshot"?: ((version: number) => any) | undefined;
|
|
109
|
+
"onPreview-snapshot"?: ((version: number) => any) | undefined;
|
|
108
110
|
"onLoad-more-snapshots"?: (() => any) | undefined;
|
|
109
111
|
}>, {
|
|
110
112
|
snapshots: SnapshotMeta[];
|
|
@@ -28,7 +28,7 @@ const props = defineProps({
|
|
|
28
28
|
pendingSnapshotVersion: { type: [Number, null], required: false, default: null },
|
|
29
29
|
creatingSnapshot: { type: Boolean, required: false, default: false }
|
|
30
30
|
});
|
|
31
|
-
const emit = defineEmits(["grant-permission", "change-role", "revoke-permission", "create-invite", "sync-doc", "open-encryption", "user-context-menu", "update-meta", "create-snapshot", "delete-snapshot", "restore-snapshot", "fork-snapshot", "load-more-snapshots"]);
|
|
31
|
+
const emit = defineEmits(["grant-permission", "change-role", "revoke-permission", "create-invite", "sync-doc", "open-encryption", "user-context-menu", "update-meta", "create-snapshot", "delete-snapshot", "restore-snapshot", "fork-snapshot", "preview-snapshot", "load-more-snapshots"]);
|
|
32
32
|
const grantUserId = ref("");
|
|
33
33
|
const grantRole = ref("editor");
|
|
34
34
|
const showManualKeyInput = ref(false);
|
|
@@ -114,6 +114,13 @@ function runSnapConfirm() {
|
|
|
114
114
|
else emit("restore-snapshot", c.version);
|
|
115
115
|
}
|
|
116
116
|
function snapshotMenu(version) {
|
|
117
|
+
const view = [
|
|
118
|
+
{
|
|
119
|
+
label: "View / compare",
|
|
120
|
+
icon: "i-lucide-eye",
|
|
121
|
+
onSelect: () => emit("preview-snapshot", version)
|
|
122
|
+
}
|
|
123
|
+
];
|
|
117
124
|
const manage = [
|
|
118
125
|
{
|
|
119
126
|
label: "Restore",
|
|
@@ -134,7 +141,7 @@ function snapshotMenu(version) {
|
|
|
134
141
|
onSelect: () => askSnapDelete(version)
|
|
135
142
|
}
|
|
136
143
|
];
|
|
137
|
-
return props.isOwner ? [manage, destructive] : [manage];
|
|
144
|
+
return props.isOwner ? [view, manage, destructive] : [view, manage];
|
|
138
145
|
}
|
|
139
146
|
function triggerColor(trigger) {
|
|
140
147
|
switch (trigger) {
|
|
@@ -728,7 +735,11 @@ function patchMeta(key, value) {
|
|
|
728
735
|
:key="snap.version"
|
|
729
736
|
class="flex items-center gap-2.5 p-3 rounded-lg bg-(--ui-bg-elevated)"
|
|
730
737
|
>
|
|
731
|
-
<
|
|
738
|
+
<button
|
|
739
|
+
type="button"
|
|
740
|
+
class="flex-1 min-w-0 text-left cursor-pointer"
|
|
741
|
+
@click="emit('preview-snapshot', snap.version)"
|
|
742
|
+
>
|
|
732
743
|
<div class="flex items-center gap-2">
|
|
733
744
|
<span class="text-sm font-medium text-(--ui-text-highlighted) tabular-nums">
|
|
734
745
|
v{{ snap.version }}
|
|
@@ -749,7 +760,7 @@ function patchMeta(key, value) {
|
|
|
749
760
|
<p class="text-xs text-(--ui-text-dimmed) mt-0.5">
|
|
750
761
|
{{ formatSnapTime(snap.created_at) }} · {{ formatSnapSize(snap.size_bytes) }}
|
|
751
762
|
</p>
|
|
752
|
-
</
|
|
763
|
+
</button>
|
|
753
764
|
<UDropdownMenu
|
|
754
765
|
:items="snapshotMenu(snap.version)"
|
|
755
766
|
:content="{ align: 'end' }"
|
|
@@ -87,6 +87,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
87
87
|
"delete-snapshot": (version: number) => any;
|
|
88
88
|
"restore-snapshot": (version: number) => any;
|
|
89
89
|
"fork-snapshot": (version: number) => any;
|
|
90
|
+
"preview-snapshot": (version: number) => any;
|
|
90
91
|
"load-more-snapshots": () => any;
|
|
91
92
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
92
93
|
"onUpdate-meta"?: ((patch: Partial<DocPageMeta>) => any) | undefined;
|
|
@@ -105,6 +106,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
105
106
|
"onDelete-snapshot"?: ((version: number) => any) | undefined;
|
|
106
107
|
"onRestore-snapshot"?: ((version: number) => any) | undefined;
|
|
107
108
|
"onFork-snapshot"?: ((version: number) => any) | undefined;
|
|
109
|
+
"onPreview-snapshot"?: ((version: number) => any) | undefined;
|
|
108
110
|
"onLoad-more-snapshots"?: (() => any) | undefined;
|
|
109
111
|
}>, {
|
|
110
112
|
snapshots: SnapshotMeta[];
|
|
@@ -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;
|
|
17
18
|
"open-settings": () => any;
|
|
18
19
|
"open-account": () => any;
|
|
19
|
-
logout: () => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
|
+
onLogout?: (() => any) | undefined;
|
|
21
22
|
"onOpen-settings"?: (() => any) | undefined;
|
|
22
23
|
"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;
|
|
17
18
|
"open-settings": () => any;
|
|
18
19
|
"open-account": () => any;
|
|
19
|
-
logout: () => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
|
+
onLogout?: (() => any) | undefined;
|
|
21
22
|
"onOpen-settings"?: (() => any) | undefined;
|
|
22
23
|
"onOpen-account"?: (() => any) | undefined;
|
|
23
|
-
onLogout?: (() => any) | undefined;
|
|
24
24
|
}>, {
|
|
25
25
|
color: string;
|
|
26
26
|
collapsed: boolean;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Caps depth at `maxDepth` (default 8) to handle malformed cycles
|
|
7
7
|
* defensively. The last item is the current doc and has no `to`.
|
|
8
8
|
*/
|
|
9
|
-
import { type ComputedRef, type
|
|
9
|
+
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
10
10
|
export interface DocBreadcrumbItem {
|
|
11
11
|
id: string;
|
|
12
12
|
label: string;
|
|
@@ -14,8 +14,23 @@ export interface DocBreadcrumbItem {
|
|
|
14
14
|
/** Navigation target — undefined for the current doc */
|
|
15
15
|
to?: string;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The trail split for single-line rendering: when the full trail is longer
|
|
19
|
+
* than `maxVisible`, the middle collapses into `hidden` (shown behind a "…"
|
|
20
|
+
* overflow menu), keeping the first crumb (`head`) and the last
|
|
21
|
+
* `maxVisible - 2` crumbs (`tail`). `overflowed` is false when no collapse is
|
|
22
|
+
* needed — then `head` holds the whole trail and `hidden`/`tail` are empty.
|
|
23
|
+
*/
|
|
24
|
+
export interface DocBreadcrumbCollapsed {
|
|
25
|
+
head: DocBreadcrumbItem[];
|
|
26
|
+
hidden: DocBreadcrumbItem[];
|
|
27
|
+
tail: DocBreadcrumbItem[];
|
|
28
|
+
overflowed: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function useDocBreadcrumb(docId: MaybeRefOrGetter<string | null | undefined>, options?: {
|
|
18
31
|
maxDepth?: number;
|
|
32
|
+
maxVisible?: number;
|
|
19
33
|
}): {
|
|
20
34
|
items: ComputedRef<DocBreadcrumbItem[]>;
|
|
35
|
+
collapsed: ComputedRef<DocBreadcrumbCollapsed>;
|
|
21
36
|
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { computed,
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
2
|
import { resolveDocType } from "../utils/docTypes.js";
|
|
3
3
|
import { useRuntimeConfig } from "#imports";
|
|
4
4
|
import { useDocTree } from "./useDocTree.js";
|
|
5
5
|
export function useDocBreadcrumb(docId, options = {}) {
|
|
6
6
|
const maxDepth = options.maxDepth ?? 8;
|
|
7
|
+
const maxVisible = options.maxVisible ?? 0;
|
|
7
8
|
const config = useRuntimeConfig();
|
|
8
9
|
const docBasePath = config.public?.abracadabra?.docBasePath ?? "/doc";
|
|
9
10
|
const tree = useDocTree();
|
|
10
11
|
const items = computed(() => {
|
|
11
|
-
const startId =
|
|
12
|
+
const startId = toValue(docId);
|
|
12
13
|
if (!startId) return [];
|
|
13
14
|
const trail = [];
|
|
14
15
|
let id = startId;
|
|
@@ -29,5 +30,18 @@ export function useDocBreadcrumb(docId, options = {}) {
|
|
|
29
30
|
}
|
|
30
31
|
return trail;
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
+
const collapsed = computed(() => {
|
|
34
|
+
const all = items.value;
|
|
35
|
+
if (maxVisible <= 0 || all.length <= maxVisible) {
|
|
36
|
+
return { head: all, hidden: [], tail: [], overflowed: false };
|
|
37
|
+
}
|
|
38
|
+
const tailCount = Math.max(1, maxVisible - 2);
|
|
39
|
+
return {
|
|
40
|
+
head: all.slice(0, 1),
|
|
41
|
+
hidden: all.slice(1, all.length - tailCount),
|
|
42
|
+
tail: all.slice(all.length - tailCount),
|
|
43
|
+
overflowed: true
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return { items, collapsed };
|
|
33
47
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* onMounted(() => snaps.fetchList())
|
|
20
20
|
*/
|
|
21
21
|
import { type ComputedRef, type InjectionKey, type Ref } from 'vue';
|
|
22
|
-
import type { SnapshotMeta } from '@abraca/dabra';
|
|
22
|
+
import type { SnapshotData, SnapshotMeta } from '@abraca/dabra';
|
|
23
23
|
import type { AbracadabraLocale } from '../locale.js';
|
|
24
24
|
export type DocSnapshotsCtx = ReturnType<typeof useDocSnapshots>;
|
|
25
25
|
export declare const DOC_SNAPSHOTS_KEY: InjectionKey<DocSnapshotsCtx>;
|
|
@@ -72,6 +72,7 @@ export declare function useDocSnapshots(docId: Ref<string> | ComputedRef<string>
|
|
|
72
72
|
pending: Ref<Record<number, "delete" | "restore" | "fork" | undefined>, Record<number, "delete" | "restore" | "fork" | undefined>>;
|
|
73
73
|
fetchList: () => Promise<void>;
|
|
74
74
|
loadMore: () => Promise<void>;
|
|
75
|
+
getSnapshot: (version: number) => Promise<SnapshotData | null>;
|
|
75
76
|
create: (label?: string) => Promise<void>;
|
|
76
77
|
remove: (version: number) => Promise<void>;
|
|
77
78
|
restore: (version: number) => Promise<void>;
|
|
@@ -120,6 +120,10 @@ export function useDocSnapshots(docId, overrides) {
|
|
|
120
120
|
loading.value = false;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
async function getSnapshot(version) {
|
|
124
|
+
if (!client.value || !docId.value) return null;
|
|
125
|
+
return client.value.getSnapshot(docId.value, version, { include: "files" });
|
|
126
|
+
}
|
|
123
127
|
async function create(label) {
|
|
124
128
|
if (!client.value || !docId.value) return;
|
|
125
129
|
creating.value = true;
|
|
@@ -226,6 +230,7 @@ export function useDocSnapshots(docId, overrides) {
|
|
|
226
230
|
// Actions
|
|
227
231
|
fetchList,
|
|
228
232
|
loadMore,
|
|
233
|
+
getSnapshot,
|
|
229
234
|
create,
|
|
230
235
|
remove,
|
|
231
236
|
restore,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Ported from cou-sh/app/composables/useEditorCollaboration.ts.
|
|
16
16
|
*/
|
|
17
17
|
import { type Ref, type ShallowRef } from 'vue';
|
|
18
|
-
import type
|
|
18
|
+
import { type Extensions } from '@tiptap/core';
|
|
19
19
|
import type { CollaborationUser } from '../types.js';
|
|
20
20
|
export { type CollaborationUser } from '../types.js';
|
|
21
21
|
export interface UseEditorOptions {
|