@abraca/nuxt 2.10.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.d.mts +14 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -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/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 +19 -12
- package/dist/runtime/components/AEditor.vue +243 -165
- package/dist/runtime/components/AEditor.vue.d.ts +19 -12
- 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 +17 -7
- package/dist/runtime/components/ANodePanel.vue +550 -451
- package/dist/runtime/components/ANodePanel.vue.d.ts +17 -7
- 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/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/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/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/AProseRenderer.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/AProseRenderer.vue.d.ts +2 -2
- 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/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/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/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 +14 -0
- package/dist/runtime/composables/useNodeContextMenu.js +59 -1
- package/dist/runtime/composables/useSettingsModal.d.ts +1 -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/locale.d.ts +8 -0
- package/dist/runtime/locale.js +9 -1
- 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/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 -4
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import * as Y from "yjs";
|
|
4
|
+
import { yjsToMarkdown, yjsToHtml } from "../utils/yjsConvert";
|
|
5
|
+
import { triggerBadgeColor } from "../composables/useDocSnapshots";
|
|
6
|
+
import AModalShell from "./AModalShell.vue";
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
version: { type: [Number, null], required: true },
|
|
9
|
+
snapshots: { type: Array, required: false, default: () => [] },
|
|
10
|
+
getSnapshot: { type: Function, required: true },
|
|
11
|
+
docLabel: { type: String, required: false },
|
|
12
|
+
docType: { type: String, required: false },
|
|
13
|
+
docMeta: { type: [Object, null], required: false },
|
|
14
|
+
isOwner: { type: Boolean, required: false }
|
|
15
|
+
});
|
|
16
|
+
const emit = defineEmits(["update:version", "restore", "fork", "delete"]);
|
|
17
|
+
const open = computed({
|
|
18
|
+
get: () => props.version != null,
|
|
19
|
+
set: (v) => {
|
|
20
|
+
if (!v) emit("update:version", null);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const tab = ref("preview");
|
|
24
|
+
const loading = ref(false);
|
|
25
|
+
const error = ref(null);
|
|
26
|
+
const snap = ref(null);
|
|
27
|
+
const mdCache = /* @__PURE__ */ new Map();
|
|
28
|
+
const previewHtml = ref("");
|
|
29
|
+
const selectedMd = ref("");
|
|
30
|
+
function b64ToBytes(b64) {
|
|
31
|
+
const norm = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
32
|
+
const bin = atob(norm);
|
|
33
|
+
const bytes = new Uint8Array(bin.length);
|
|
34
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
35
|
+
return bytes;
|
|
36
|
+
}
|
|
37
|
+
function renderSnapshot(data) {
|
|
38
|
+
const ydoc = new Y.Doc();
|
|
39
|
+
try {
|
|
40
|
+
Y.applyUpdate(ydoc, b64ToBytes(data));
|
|
41
|
+
const fragment = ydoc.getXmlFragment("default");
|
|
42
|
+
const label = props.docLabel || "Document";
|
|
43
|
+
return {
|
|
44
|
+
html: yjsToHtml(fragment, label),
|
|
45
|
+
md: yjsToMarkdown(fragment, label, props.docMeta ?? void 0, props.docType)
|
|
46
|
+
};
|
|
47
|
+
} finally {
|
|
48
|
+
ydoc.destroy();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const compareVersions = computed(() => {
|
|
52
|
+
const v = props.version;
|
|
53
|
+
if (v == null) return [];
|
|
54
|
+
return props.snapshots.map((s) => s.version).filter((x) => x !== v).sort((a, b) => b - a);
|
|
55
|
+
});
|
|
56
|
+
const cmpVersion = ref(null);
|
|
57
|
+
const cmpMd = ref("");
|
|
58
|
+
function pickDefaultCompare() {
|
|
59
|
+
const v = props.version;
|
|
60
|
+
if (v == null) {
|
|
61
|
+
cmpVersion.value = null;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const older = compareVersions.value.filter((x) => x < v);
|
|
65
|
+
cmpVersion.value = older.length ? older[0] : null;
|
|
66
|
+
}
|
|
67
|
+
const cmpIndex = computed(() => {
|
|
68
|
+
const list = compareVersions.value;
|
|
69
|
+
return cmpVersion.value == null ? -1 : list.indexOf(cmpVersion.value);
|
|
70
|
+
});
|
|
71
|
+
function stepCompare(delta) {
|
|
72
|
+
const list = compareVersions.value;
|
|
73
|
+
if (!list.length) return;
|
|
74
|
+
const i = cmpIndex.value < 0 ? 0 : cmpIndex.value + delta;
|
|
75
|
+
if (i < 0 || i >= list.length) return;
|
|
76
|
+
cmpVersion.value = list[i];
|
|
77
|
+
}
|
|
78
|
+
async function mdFor(version) {
|
|
79
|
+
const cached = mdCache.get(version);
|
|
80
|
+
if (cached != null) return cached;
|
|
81
|
+
const s = await props.getSnapshot(version);
|
|
82
|
+
const md = s?.data ? renderSnapshot(s.data).md : "";
|
|
83
|
+
mdCache.set(version, md);
|
|
84
|
+
return md;
|
|
85
|
+
}
|
|
86
|
+
const DIFF_LINE_CAP = 2e3;
|
|
87
|
+
const DIFF_CELL_CAP = 2e6;
|
|
88
|
+
function lineDiff(oldStr, newStr) {
|
|
89
|
+
const a = oldStr.length ? oldStr.split("\n") : [];
|
|
90
|
+
const b = newStr.length ? newStr.split("\n") : [];
|
|
91
|
+
const n = a.length;
|
|
92
|
+
const m = b.length;
|
|
93
|
+
const dp = Array.from({ length: n + 1 }, () => new Int32Array(m + 1));
|
|
94
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
95
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
96
|
+
dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const rows = [];
|
|
100
|
+
let i = 0;
|
|
101
|
+
let j = 0;
|
|
102
|
+
while (i < n && j < m) {
|
|
103
|
+
if (a[i] === b[j]) {
|
|
104
|
+
rows.push({ t: "ctx", text: a[i] });
|
|
105
|
+
i++;
|
|
106
|
+
j++;
|
|
107
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
108
|
+
rows.push({ t: "del", text: a[i] });
|
|
109
|
+
i++;
|
|
110
|
+
} else {
|
|
111
|
+
rows.push({ t: "add", text: b[j] });
|
|
112
|
+
j++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
while (i < n) rows.push({ t: "del", text: a[i++] });
|
|
116
|
+
while (j < m) rows.push({ t: "add", text: b[j++] });
|
|
117
|
+
return rows;
|
|
118
|
+
}
|
|
119
|
+
const diffTooLarge = computed(() => {
|
|
120
|
+
const a = cmpMd.value ? cmpMd.value.split("\n").length : 0;
|
|
121
|
+
const b = selectedMd.value ? selectedMd.value.split("\n").length : 0;
|
|
122
|
+
return a > DIFF_LINE_CAP || b > DIFF_LINE_CAP || a * b > DIFF_CELL_CAP;
|
|
123
|
+
});
|
|
124
|
+
const diffRows = computed(() => {
|
|
125
|
+
if (diffTooLarge.value) return [];
|
|
126
|
+
return lineDiff(cmpMd.value, selectedMd.value);
|
|
127
|
+
});
|
|
128
|
+
const diffHasChanges = computed(() => diffRows.value.some((r) => r.t !== "ctx"));
|
|
129
|
+
async function load() {
|
|
130
|
+
const v = props.version;
|
|
131
|
+
if (v == null) return;
|
|
132
|
+
loading.value = true;
|
|
133
|
+
error.value = null;
|
|
134
|
+
snap.value = null;
|
|
135
|
+
previewHtml.value = "";
|
|
136
|
+
selectedMd.value = "";
|
|
137
|
+
cmpMd.value = "";
|
|
138
|
+
tab.value = "preview";
|
|
139
|
+
try {
|
|
140
|
+
const s = await props.getSnapshot(v);
|
|
141
|
+
if (!s) throw new Error("Snapshot not found");
|
|
142
|
+
snap.value = s;
|
|
143
|
+
const { html, md } = s.data ? renderSnapshot(s.data) : { html: "", md: "" };
|
|
144
|
+
previewHtml.value = html;
|
|
145
|
+
selectedMd.value = md;
|
|
146
|
+
mdCache.set(v, md);
|
|
147
|
+
pickDefaultCompare();
|
|
148
|
+
} catch (e) {
|
|
149
|
+
error.value = e instanceof Error ? e.message : String(e);
|
|
150
|
+
} finally {
|
|
151
|
+
loading.value = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
watch(cmpVersion, async (v) => {
|
|
155
|
+
cmpMd.value = v == null ? "" : await mdFor(v);
|
|
156
|
+
});
|
|
157
|
+
watch(() => props.version, (v) => {
|
|
158
|
+
mdCache.clear();
|
|
159
|
+
if (v != null) load();
|
|
160
|
+
}, { immediate: true });
|
|
161
|
+
function authorName(publicKey) {
|
|
162
|
+
if (!publicKey) return "Unknown";
|
|
163
|
+
return publicKey.length > 12 ? `${publicKey.slice(0, 8)}\u2026${publicKey.slice(-4)}` : publicKey;
|
|
164
|
+
}
|
|
165
|
+
const absFmt = new Intl.DateTimeFormat(void 0, { dateStyle: "medium", timeStyle: "short" });
|
|
166
|
+
const relFmt = typeof Intl.RelativeTimeFormat === "function" ? new Intl.RelativeTimeFormat(void 0, { numeric: "auto" }) : null;
|
|
167
|
+
function fmtAbs(unixSeconds) {
|
|
168
|
+
return absFmt.format(new Date(unixSeconds * 1e3));
|
|
169
|
+
}
|
|
170
|
+
function fmtRel(unixSeconds) {
|
|
171
|
+
if (!relFmt) return "";
|
|
172
|
+
const diffS = unixSeconds - Date.now() / 1e3;
|
|
173
|
+
const abs = Math.abs(diffS);
|
|
174
|
+
if (abs < 60) return relFmt.format(Math.round(diffS), "second");
|
|
175
|
+
if (abs < 3600) return relFmt.format(Math.round(diffS / 60), "minute");
|
|
176
|
+
if (abs < 86400) return relFmt.format(Math.round(diffS / 3600), "hour");
|
|
177
|
+
return relFmt.format(Math.round(diffS / 86400), "day");
|
|
178
|
+
}
|
|
179
|
+
function fmtSize(bytes) {
|
|
180
|
+
if (bytes == null) return "\u2014";
|
|
181
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
182
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
183
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
184
|
+
}
|
|
185
|
+
function fileIcon(mime) {
|
|
186
|
+
if (!mime) return "i-lucide-file";
|
|
187
|
+
if (mime.startsWith("image/")) return "i-lucide-image";
|
|
188
|
+
if (mime.startsWith("video/")) return "i-lucide-film";
|
|
189
|
+
if (mime.startsWith("audio/")) return "i-lucide-music";
|
|
190
|
+
if (mime === "application/pdf") return "i-lucide-file-text";
|
|
191
|
+
if (/zip|tar|gzip/.test(mime)) return "i-lucide-file-archive";
|
|
192
|
+
return "i-lucide-file";
|
|
193
|
+
}
|
|
194
|
+
function act(kind) {
|
|
195
|
+
const v = props.version;
|
|
196
|
+
if (v == null) return;
|
|
197
|
+
if (kind === "restore") emit("restore", v);
|
|
198
|
+
else if (kind === "fork") emit("fork", v);
|
|
199
|
+
else emit("delete", v);
|
|
200
|
+
emit("update:version", null);
|
|
201
|
+
}
|
|
202
|
+
</script>
|
|
203
|
+
|
|
204
|
+
<template>
|
|
205
|
+
<AModalShell
|
|
206
|
+
:open="open"
|
|
207
|
+
max-width="sm:max-w-3xl"
|
|
208
|
+
@update:open="open = $event"
|
|
209
|
+
>
|
|
210
|
+
<div class="flex flex-col gap-4">
|
|
211
|
+
<h2 class="text-base font-semibold text-(--ui-text-highlighted)">
|
|
212
|
+
{{ snap ? `Snapshot v${snap.version}` : "Snapshot details" }}
|
|
213
|
+
</h2>
|
|
214
|
+
|
|
215
|
+
<!-- Loading -->
|
|
216
|
+
<div
|
|
217
|
+
v-if="loading"
|
|
218
|
+
class="space-y-3"
|
|
219
|
+
>
|
|
220
|
+
<USkeleton class="h-8 w-1/2" />
|
|
221
|
+
<USkeleton class="h-24 w-full" />
|
|
222
|
+
<USkeleton class="h-40 w-full" />
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<!-- Error -->
|
|
226
|
+
<UAlert
|
|
227
|
+
v-else-if="error"
|
|
228
|
+
color="error"
|
|
229
|
+
variant="soft"
|
|
230
|
+
title="Couldn't load snapshot"
|
|
231
|
+
:description="error"
|
|
232
|
+
icon="i-lucide-alert-circle"
|
|
233
|
+
:actions="[{
|
|
234
|
+
label: 'Retry',
|
|
235
|
+
color: 'neutral',
|
|
236
|
+
variant: 'outline',
|
|
237
|
+
onClick: load
|
|
238
|
+
}]"
|
|
239
|
+
/>
|
|
240
|
+
|
|
241
|
+
<!-- Detail -->
|
|
242
|
+
<template v-else-if="snap">
|
|
243
|
+
<!-- Metadata header -->
|
|
244
|
+
<div class="flex flex-wrap items-center gap-2 mb-1">
|
|
245
|
+
<UBadge
|
|
246
|
+
:label="snap.trigger"
|
|
247
|
+
:color="triggerBadgeColor(snap.trigger)"
|
|
248
|
+
variant="subtle"
|
|
249
|
+
size="sm"
|
|
250
|
+
/>
|
|
251
|
+
<span
|
|
252
|
+
v-if="snap.label"
|
|
253
|
+
class="text-sm text-(--ui-text) truncate"
|
|
254
|
+
>{{ snap.label }}</span>
|
|
255
|
+
<span class="text-xs text-(--ui-text-dimmed)">
|
|
256
|
+
{{ fmtAbs(snap.created_at) }} · {{ fmtRel(snap.created_at) }}
|
|
257
|
+
</span>
|
|
258
|
+
<div class="flex items-center gap-1.5 ms-auto">
|
|
259
|
+
<span class="text-xs text-(--ui-text-muted)">{{ authorName(snap.created_by) }}</span>
|
|
260
|
+
<span class="text-xs text-(--ui-text-dimmed)">· {{ fmtSize(snap.size_bytes) }}</span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<!-- Preview / Diff toggle -->
|
|
265
|
+
<div class="flex items-center gap-1 mb-1">
|
|
266
|
+
<UButton
|
|
267
|
+
label="Preview"
|
|
268
|
+
icon="i-lucide-eye"
|
|
269
|
+
size="xs"
|
|
270
|
+
:color="tab === 'preview' ? 'primary' : 'neutral'"
|
|
271
|
+
:variant="tab === 'preview' ? 'solid' : 'ghost'"
|
|
272
|
+
@click="tab = 'preview'"
|
|
273
|
+
/>
|
|
274
|
+
<UButton
|
|
275
|
+
label="Changes"
|
|
276
|
+
icon="i-lucide-git-compare"
|
|
277
|
+
size="xs"
|
|
278
|
+
:color="tab === 'diff' ? 'primary' : 'neutral'"
|
|
279
|
+
:variant="tab === 'diff' ? 'solid' : 'ghost'"
|
|
280
|
+
@click="tab = 'diff'"
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<!-- Preview tab -->
|
|
285
|
+
<div
|
|
286
|
+
v-show="tab === 'preview'"
|
|
287
|
+
class="snapshot-prose max-h-[55vh] overflow-y-auto rounded-md border border-(--ui-border) bg-(--ui-bg) p-4"
|
|
288
|
+
>
|
|
289
|
+
<!-- eslint-disable-next-line vue/no-v-html -- serialized from our own convert pipeline -->
|
|
290
|
+
<div
|
|
291
|
+
v-if="previewHtml"
|
|
292
|
+
v-html="previewHtml"
|
|
293
|
+
/>
|
|
294
|
+
<p
|
|
295
|
+
v-else
|
|
296
|
+
class="text-sm text-(--ui-text-dimmed) text-center py-6"
|
|
297
|
+
>
|
|
298
|
+
This snapshot has no readable content.
|
|
299
|
+
</p>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<!-- Diff tab -->
|
|
303
|
+
<div v-show="tab === 'diff'">
|
|
304
|
+
<div class="flex items-center gap-2 mb-2 text-xs text-(--ui-text-muted)">
|
|
305
|
+
<span>Compared with</span>
|
|
306
|
+
<template v-if="cmpVersion != null">
|
|
307
|
+
<UButton
|
|
308
|
+
icon="i-lucide-chevron-left"
|
|
309
|
+
size="xs"
|
|
310
|
+
color="neutral"
|
|
311
|
+
variant="ghost"
|
|
312
|
+
:disabled="cmpIndex <= 0"
|
|
313
|
+
@click="stepCompare(-1)"
|
|
314
|
+
/>
|
|
315
|
+
<span class="tabular-nums font-medium text-(--ui-text)">v{{ cmpVersion }}</span>
|
|
316
|
+
<UButton
|
|
317
|
+
icon="i-lucide-chevron-right"
|
|
318
|
+
size="xs"
|
|
319
|
+
color="neutral"
|
|
320
|
+
variant="ghost"
|
|
321
|
+
:disabled="cmpIndex < 0 || cmpIndex >= compareVersions.length - 1"
|
|
322
|
+
@click="stepCompare(1)"
|
|
323
|
+
/>
|
|
324
|
+
</template>
|
|
325
|
+
<span
|
|
326
|
+
v-else
|
|
327
|
+
class="italic"
|
|
328
|
+
>the earliest version (nothing older to compare)</span>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<p
|
|
332
|
+
v-if="diffTooLarge"
|
|
333
|
+
class="text-sm text-(--ui-text-dimmed) text-center py-6"
|
|
334
|
+
>
|
|
335
|
+
This document is too large to diff.
|
|
336
|
+
</p>
|
|
337
|
+
<p
|
|
338
|
+
v-else-if="!diffHasChanges"
|
|
339
|
+
class="text-sm text-(--ui-text-dimmed) text-center py-6"
|
|
340
|
+
>
|
|
341
|
+
No textual changes between these versions.
|
|
342
|
+
</p>
|
|
343
|
+
<div
|
|
344
|
+
v-else
|
|
345
|
+
class="max-h-[55vh] overflow-y-auto rounded-md border border-(--ui-border) bg-(--ui-bg) font-mono text-xs"
|
|
346
|
+
>
|
|
347
|
+
<div
|
|
348
|
+
v-for="(row, idx) in diffRows"
|
|
349
|
+
:key="idx"
|
|
350
|
+
class="flex gap-2 px-3 py-0.5 whitespace-pre-wrap break-words"
|
|
351
|
+
:class="{
|
|
352
|
+
'bg-(--ui-color-success-500)/10 text-(--ui-color-success-600)': row.t === 'add',
|
|
353
|
+
'bg-(--ui-color-error-500)/10 text-(--ui-color-error-600)': row.t === 'del',
|
|
354
|
+
'text-(--ui-text-dimmed)': row.t === 'ctx'
|
|
355
|
+
}"
|
|
356
|
+
>
|
|
357
|
+
<span class="select-none w-3 shrink-0 opacity-70">{{ row.t === "add" ? "+" : row.t === "del" ? "\u2212" : "" }}</span>
|
|
358
|
+
<span class="flex-1">{{ row.text || " " }}</span>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<!-- Referenced files -->
|
|
364
|
+
<div
|
|
365
|
+
v-if="snap.files && snap.files.length"
|
|
366
|
+
class="mt-1"
|
|
367
|
+
>
|
|
368
|
+
<p class="text-xs font-medium text-(--ui-text-muted) mb-1.5">
|
|
369
|
+
Referenced files
|
|
370
|
+
</p>
|
|
371
|
+
<ul class="rounded-md border border-(--ui-border) divide-y divide-(--ui-border)">
|
|
372
|
+
<li
|
|
373
|
+
v-for="f in snap.files"
|
|
374
|
+
:key="f.id"
|
|
375
|
+
class="flex items-center gap-2.5 px-3 py-2"
|
|
376
|
+
>
|
|
377
|
+
<UIcon
|
|
378
|
+
:name="fileIcon(f.mime_type)"
|
|
379
|
+
class="size-4 text-(--ui-text-muted) shrink-0"
|
|
380
|
+
/>
|
|
381
|
+
<span class="text-sm truncate flex-1">{{ f.filename }}</span>
|
|
382
|
+
<span class="text-xs text-(--ui-text-dimmed) shrink-0">{{ fmtSize(f.size) }}</span>
|
|
383
|
+
</li>
|
|
384
|
+
</ul>
|
|
385
|
+
</div>
|
|
386
|
+
</template>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<template #footer>
|
|
390
|
+
<div class="flex items-center gap-2 w-full">
|
|
391
|
+
<UButton
|
|
392
|
+
label="Cancel"
|
|
393
|
+
color="neutral"
|
|
394
|
+
variant="ghost"
|
|
395
|
+
@click="emit('update:version', null)"
|
|
396
|
+
/>
|
|
397
|
+
<div class="ms-auto flex items-center gap-2">
|
|
398
|
+
<UButton
|
|
399
|
+
v-if="isOwner"
|
|
400
|
+
label="Delete"
|
|
401
|
+
icon="i-lucide-trash-2"
|
|
402
|
+
color="error"
|
|
403
|
+
variant="ghost"
|
|
404
|
+
:disabled="!snap"
|
|
405
|
+
@click="act('delete')"
|
|
406
|
+
/>
|
|
407
|
+
<UButton
|
|
408
|
+
label="Fork"
|
|
409
|
+
icon="i-lucide-git-branch"
|
|
410
|
+
color="neutral"
|
|
411
|
+
variant="outline"
|
|
412
|
+
:disabled="!snap"
|
|
413
|
+
@click="act('fork')"
|
|
414
|
+
/>
|
|
415
|
+
<UButton
|
|
416
|
+
label="Restore"
|
|
417
|
+
icon="i-lucide-history"
|
|
418
|
+
color="primary"
|
|
419
|
+
:disabled="!snap"
|
|
420
|
+
@click="act('restore')"
|
|
421
|
+
/>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
</template>
|
|
425
|
+
</AModalShell>
|
|
426
|
+
</template>
|
|
427
|
+
|
|
428
|
+
<style scoped>
|
|
429
|
+
.snapshot-prose :deep(h1){font-size:1.25rem;font-weight:600;margin:.75rem 0 .5rem}.snapshot-prose :deep(h2){font-size:1.1rem;font-weight:600;margin:.75rem 0 .4rem}.snapshot-prose :deep(h3){font-size:1rem;font-weight:600;margin:.6rem 0 .3rem}.snapshot-prose :deep(p){line-height:1.6;margin:.4rem 0}.snapshot-prose :deep(ul){list-style:disc;margin:.4rem 0;padding-inline-start:1.4rem}.snapshot-prose :deep(ol){list-style:decimal;margin:.4rem 0;padding-inline-start:1.4rem}.snapshot-prose :deep(li){margin:.15rem 0}.snapshot-prose :deep(blockquote){border-inline-start:3px solid var(--ui-border);color:var(--ui-text-muted);margin:.5rem 0;padding-inline-start:.75rem}.snapshot-prose :deep(pre){background:var(--ui-bg-elevated);border-radius:.375rem;font-size:.8rem;margin:.5rem 0;overflow-x:auto;padding:.6rem .8rem}.snapshot-prose :deep(code){font-family:ui-monospace,monospace;font-size:.85em}.snapshot-prose :deep(a){color:var(--ui-primary);text-decoration:underline}.snapshot-prose :deep(img){border-radius:.375rem;height:auto;max-width:100%}.snapshot-prose :deep(table){border-collapse:collapse;margin:.5rem 0}.snapshot-prose :deep(td),.snapshot-prose :deep(th){border:1px solid var(--ui-border);padding:.3rem .5rem}
|
|
430
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SnapshotData, SnapshotMeta } from '@abraca/dabra';
|
|
2
|
+
import type { DocPageMeta } from '../types.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
/** Selected version — null closes the modal. The parent owns this. */
|
|
5
|
+
version: number | null;
|
|
6
|
+
/** The loaded snapshot metadata list (compare-baseline candidates). */
|
|
7
|
+
snapshots?: SnapshotMeta[];
|
|
8
|
+
/** Fetch a single snapshot's data blob (e.g. `useDocSnapshots().getSnapshot`). */
|
|
9
|
+
getSnapshot: (version: number) => Promise<SnapshotData | null>;
|
|
10
|
+
/** Document label — used as the title when serializing. */
|
|
11
|
+
docLabel?: string;
|
|
12
|
+
/** Document page type — passed to the markdown serializer. */
|
|
13
|
+
docType?: string;
|
|
14
|
+
/** Document meta — passed to the markdown serializer. */
|
|
15
|
+
docMeta?: DocPageMeta | null;
|
|
16
|
+
/** Whether the current user may delete snapshots. */
|
|
17
|
+
isOwner?: boolean;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
|
+
delete: (version: number) => any;
|
|
21
|
+
restore: (version: number) => any;
|
|
22
|
+
fork: (version: number) => any;
|
|
23
|
+
"update:version": (value: number | null) => any;
|
|
24
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
25
|
+
onDelete?: ((version: number) => any) | undefined;
|
|
26
|
+
onRestore?: ((version: number) => any) | undefined;
|
|
27
|
+
onFork?: ((version: number) => any) | undefined;
|
|
28
|
+
"onUpdate:version"?: ((value: number | null) => any) | undefined;
|
|
29
|
+
}>, {
|
|
30
|
+
snapshots: SnapshotMeta[];
|
|
31
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
32
|
+
declare const _default: typeof __VLS_export;
|
|
33
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
/** Current tags for the document. */
|
|
4
|
+
tags?: string[];
|
|
5
|
+
/** Optional modal heading. */
|
|
6
|
+
title?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (v: boolean) => any;
|
|
10
|
+
save: (tags: string[]) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:open"?: ((v: boolean) => any) | undefined;
|
|
13
|
+
onSave?: ((tags: string[]) => any) | undefined;
|
|
14
|
+
}>, {
|
|
15
|
+
tags: string[];
|
|
16
|
+
title: string;
|
|
17
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, watch } from "vue";
|
|
3
|
+
import AModalShell from "./AModalShell.vue";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
open: { type: Boolean, required: true },
|
|
6
|
+
tags: { type: Array, required: false, default: () => [] },
|
|
7
|
+
title: { type: String, required: false, default: "Edit tags" }
|
|
8
|
+
});
|
|
9
|
+
const emit = defineEmits(["update:open", "save"]);
|
|
10
|
+
const draft = ref([]);
|
|
11
|
+
watch(() => props.open, (isOpen) => {
|
|
12
|
+
if (isOpen) draft.value = [...props.tags ?? []];
|
|
13
|
+
}, { immediate: true });
|
|
14
|
+
function save() {
|
|
15
|
+
const cleaned = Array.from(
|
|
16
|
+
new Set(draft.value.map((t) => t.trim()).filter(Boolean))
|
|
17
|
+
);
|
|
18
|
+
emit("save", cleaned);
|
|
19
|
+
emit("update:open", false);
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<AModalShell
|
|
25
|
+
:open="open"
|
|
26
|
+
:title="title"
|
|
27
|
+
max-width="sm:max-w-md"
|
|
28
|
+
@update:open="emit('update:open', $event)"
|
|
29
|
+
>
|
|
30
|
+
<UFormField
|
|
31
|
+
label="Tags"
|
|
32
|
+
hint="Press Enter to add"
|
|
33
|
+
>
|
|
34
|
+
<UInputTags
|
|
35
|
+
v-model="draft"
|
|
36
|
+
placeholder="Add a tag…"
|
|
37
|
+
size="md"
|
|
38
|
+
class="w-full"
|
|
39
|
+
:ui="{ root: 'w-full' }"
|
|
40
|
+
/>
|
|
41
|
+
</UFormField>
|
|
42
|
+
|
|
43
|
+
<template #footer>
|
|
44
|
+
<div class="flex items-center justify-end gap-2 w-full">
|
|
45
|
+
<UButton
|
|
46
|
+
label="Cancel"
|
|
47
|
+
color="neutral"
|
|
48
|
+
variant="ghost"
|
|
49
|
+
@click="emit('update:open', false)"
|
|
50
|
+
/>
|
|
51
|
+
<UButton
|
|
52
|
+
label="Save"
|
|
53
|
+
color="primary"
|
|
54
|
+
icon="i-lucide-check"
|
|
55
|
+
@click="save"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</AModalShell>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
/** Current tags for the document. */
|
|
4
|
+
tags?: string[];
|
|
5
|
+
/** Optional modal heading. */
|
|
6
|
+
title?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:open": (v: boolean) => any;
|
|
10
|
+
save: (tags: string[]) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:open"?: ((v: boolean) => any) | undefined;
|
|
13
|
+
onSave?: ((tags: string[]) => any) | undefined;
|
|
14
|
+
}>, {
|
|
15
|
+
tags: string[];
|
|
16
|
+
title: string;
|
|
17
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
12
12
|
awareness: boolean;
|
|
13
13
|
tag: "video" | "audio";
|
|
14
14
|
live: boolean;
|
|
15
|
-
total: boolean;
|
|
16
15
|
controls: boolean;
|
|
16
|
+
total: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
19
19
|
export default _default;
|
|
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
12
12
|
awareness: boolean;
|
|
13
13
|
tag: "video" | "audio";
|
|
14
14
|
live: boolean;
|
|
15
|
-
total: boolean;
|
|
16
15
|
controls: boolean;
|
|
16
|
+
total: boolean;
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
18
|
declare const _default: typeof __VLS_export;
|
|
19
19
|
export default _default;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat message input with @mention autocomplete and document drop support.
|
|
3
|
-
*
|
|
4
|
-
* Ported from cou-sh/app/components/chat/ChatInput.vue — decoupled from
|
|
5
|
-
* useChat/useChatUsers/usePermissions. App provides handlers via emits.
|
|
6
|
-
*/
|
|
7
1
|
export interface ChatMentionUser {
|
|
8
2
|
id: string;
|
|
9
3
|
name: string;
|
|
10
4
|
isAgent?: boolean;
|
|
11
5
|
}
|
|
6
|
+
/** Minimal shape of the message being replied to (for the draft chip). */
|
|
7
|
+
export interface ReplyTarget {
|
|
8
|
+
id: string;
|
|
9
|
+
senderName?: string;
|
|
10
|
+
content?: string;
|
|
11
|
+
}
|
|
12
12
|
type __VLS_Props = {
|
|
13
13
|
/** Whether to auto-focus the input on mount */
|
|
14
14
|
autofocus?: boolean;
|
|
@@ -17,6 +17,8 @@ type __VLS_Props = {
|
|
|
17
17
|
canSend?: boolean;
|
|
18
18
|
/** Online users for @mention autocomplete */
|
|
19
19
|
mentionUsers?: ChatMentionUser[];
|
|
20
|
+
/** Message being replied to — shows a draft chip above the composer. */
|
|
21
|
+
replyTo?: ReplyTarget | null;
|
|
20
22
|
};
|
|
21
23
|
declare function focus(): void;
|
|
22
24
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
@@ -28,6 +30,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
28
30
|
docId: string;
|
|
29
31
|
label: string;
|
|
30
32
|
}) => any;
|
|
33
|
+
"cancel-reply": () => any;
|
|
31
34
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
32
35
|
onSend?: ((content: string) => any) | undefined;
|
|
33
36
|
onTyping?: (() => any) | undefined;
|
|
@@ -35,7 +38,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
|
35
38
|
docId: string;
|
|
36
39
|
label: string;
|
|
37
40
|
}) => any) | undefined;
|
|
41
|
+
"onCancel-reply"?: (() => any) | undefined;
|
|
38
42
|
}>, {
|
|
43
|
+
replyTo: ReplyTarget | null;
|
|
39
44
|
placeholder: string;
|
|
40
45
|
autofocus: boolean;
|
|
41
46
|
canSend: boolean;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, nextTick, onMounted } from "vue";
|
|
3
|
+
import { previewMessageContent } from "../../utils/chatContent";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
autofocus: { type: Boolean, required: false, default: false },
|
|
5
6
|
placeholder: { type: String, required: false, default: "Type a message\u2026" },
|
|
6
7
|
canSend: { type: Boolean, required: false, default: true },
|
|
7
|
-
mentionUsers: { type: Array, required: false, default: () => [] }
|
|
8
|
+
mentionUsers: { type: Array, required: false, default: () => [] },
|
|
9
|
+
replyTo: { type: [Object, null], required: false, default: null }
|
|
8
10
|
});
|
|
9
|
-
const emit = defineEmits(["send", "typing", "doc-drop"]);
|
|
11
|
+
const emit = defineEmits(["send", "typing", "doc-drop", "cancel-reply"]);
|
|
12
|
+
const replyPreview = computed(
|
|
13
|
+
() => props.replyTo?.content ? previewMessageContent(props.replyTo.content) : ""
|
|
14
|
+
);
|
|
10
15
|
const messageInput = ref("");
|
|
11
16
|
const inputRef = ref(null);
|
|
12
17
|
const isDraggingDoc = ref(false);
|
|
@@ -192,6 +197,32 @@ defineExpose({ focus });
|
|
|
192
197
|
</div>
|
|
193
198
|
</Transition>
|
|
194
199
|
|
|
200
|
+
<!-- Reply draft chip -->
|
|
201
|
+
<div
|
|
202
|
+
v-if="replyTo"
|
|
203
|
+
class="flex items-center gap-2 mb-2 px-2.5 py-1.5 rounded-lg bg-(--ui-bg-elevated) border-l-2 border-(--ui-primary) text-xs"
|
|
204
|
+
>
|
|
205
|
+
<UIcon
|
|
206
|
+
name="i-lucide-reply"
|
|
207
|
+
class="size-3.5 shrink-0 text-(--ui-text-muted)"
|
|
208
|
+
/>
|
|
209
|
+
<div class="min-w-0 flex-1">
|
|
210
|
+
<span class="text-(--ui-text-muted)">Replying to </span>
|
|
211
|
+
<span class="font-medium text-(--ui-text-highlighted)">{{ replyTo.senderName || "message" }}</span>
|
|
212
|
+
<span
|
|
213
|
+
v-if="replyPreview"
|
|
214
|
+
class="text-(--ui-text-dimmed)"
|
|
215
|
+
> · {{ replyPreview }}</span>
|
|
216
|
+
</div>
|
|
217
|
+
<UButton
|
|
218
|
+
icon="i-lucide-x"
|
|
219
|
+
size="xs"
|
|
220
|
+
color="neutral"
|
|
221
|
+
variant="ghost"
|
|
222
|
+
@click="emit('cancel-reply')"
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
195
226
|
<div class="flex items-end gap-2">
|
|
196
227
|
<textarea
|
|
197
228
|
ref="inputRef"
|