agentation-vue 0.2.6 → 0.2.10
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/AgentationVue.vue +206 -52
- package/dist/components/AgentationToolbar.vue +1 -2
- package/dist/components/AnnotationInput.d.vue.ts +2 -0
- package/dist/components/AnnotationInput.vue +87 -12
- package/dist/components/AnnotationInput.vue.d.ts +2 -0
- package/dist/components/MentionDropdown.d.vue.ts +16 -0
- package/dist/components/MentionDropdown.vue +41 -0
- package/dist/components/MentionDropdown.vue.d.ts +16 -0
- package/dist/components/SettingsPanel.vue +2 -0
- package/dist/components/SettingsPopover.vue +3 -5
- package/dist/composables/useAnnotations.d.ts +2 -0
- package/dist/composables/useAnnotations.mjs +6 -1
- package/dist/composables/useKeyboardShortcuts.mjs +22 -8
- package/dist/composables/useMentionDropdown.d.ts +21 -0
- package/dist/composables/useMentionDropdown.mjs +162 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.mjs +1 -0
- package/dist/styles/agentation.css +1 -1
- package/dist/utils/mention.d.ts +13 -0
- package/dist/utils/mention.mjs +46 -0
- package/package.json +1 -1
package/dist/AgentationVue.vue
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isVue2 as _isVue2,
|
|
4
|
+
computed,
|
|
5
|
+
defineComponent,
|
|
6
|
+
onBeforeUnmount,
|
|
7
|
+
onMounted,
|
|
8
|
+
ref,
|
|
9
|
+
watch
|
|
10
|
+
} from "vue-demi";
|
|
3
11
|
import AgentationToolbar from "./components/AgentationToolbar.vue";
|
|
4
12
|
import AnnotationInput from "./components/AnnotationInput.vue";
|
|
5
13
|
import AnnotationMarker from "./components/AnnotationMarker.vue";
|
|
@@ -10,7 +18,10 @@ import { useAnnotations } from "./composables/useAnnotations.mjs";
|
|
|
10
18
|
import { useAreaSelect } from "./composables/useAreaSelect.mjs";
|
|
11
19
|
import { useElementDetection } from "./composables/useElementDetection.mjs";
|
|
12
20
|
import { useInteractionMode } from "./composables/useInteractionMode.mjs";
|
|
13
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_SHORTCUT_CONFIG,
|
|
23
|
+
useKeyboardShortcuts
|
|
24
|
+
} from "./composables/useKeyboardShortcuts.mjs";
|
|
14
25
|
import { useMarkerPositions } from "./composables/useMarkerPositions.mjs";
|
|
15
26
|
import { useMultiSelect } from "./composables/useMultiSelect.mjs";
|
|
16
27
|
import { useOutputFormatter } from "./composables/useOutputFormatter.mjs";
|
|
@@ -18,7 +29,15 @@ import { useSettings } from "./composables/useSettings.mjs";
|
|
|
18
29
|
import { useTextSelection } from "./composables/useTextSelection.mjs";
|
|
19
30
|
import { isInsideAgentationTree } from "./utils/agentation-tree.mjs";
|
|
20
31
|
import { copyToClipboard } from "./utils/clipboard.mjs";
|
|
21
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
isFixed as checkIsFixed,
|
|
34
|
+
detectVueComponents,
|
|
35
|
+
getAccessibilityInfo,
|
|
36
|
+
getComputedStylesSummary,
|
|
37
|
+
getNearbyElements,
|
|
38
|
+
getNearbyText,
|
|
39
|
+
getRelevantComputedStyles
|
|
40
|
+
} from "./utils/dom-inspector.mjs";
|
|
22
41
|
import { createPortalContainer, destroyPortalContainer } from "./utils/portal.mjs";
|
|
23
42
|
import { getElementName, getElementPath } from "./utils/selectors.mjs";
|
|
24
43
|
import { boundingBoxToStyle } from "./utils/style.mjs";
|
|
@@ -60,8 +79,24 @@ const toolbarRef = ref(null);
|
|
|
60
79
|
const currentUrl = ref(props.pageUrl || getCurrentUrl());
|
|
61
80
|
const { settings } = useSettings();
|
|
62
81
|
const { mode, transition } = useInteractionMode();
|
|
63
|
-
const {
|
|
64
|
-
|
|
82
|
+
const {
|
|
83
|
+
annotations,
|
|
84
|
+
addAnnotation,
|
|
85
|
+
removeAnnotation,
|
|
86
|
+
updateAnnotation,
|
|
87
|
+
clearAnnotations,
|
|
88
|
+
restoreAnnotations,
|
|
89
|
+
setScopeUrl
|
|
90
|
+
} = useAnnotations(currentUrl.value);
|
|
91
|
+
const {
|
|
92
|
+
hoveredRect,
|
|
93
|
+
hoveredName,
|
|
94
|
+
hoveredComponentChain,
|
|
95
|
+
onMouseMove,
|
|
96
|
+
clearHighlight,
|
|
97
|
+
getElementUnderOverlay,
|
|
98
|
+
cleanup: cleanupDetection
|
|
99
|
+
} = useElementDetection(overlayEl, () => settings.showComponentTree);
|
|
65
100
|
const textSelection = useTextSelection(mode);
|
|
66
101
|
const multiSelect = useMultiSelect(mode, transition);
|
|
67
102
|
const areaSelect = useAreaSelect(mode, transition);
|
|
@@ -78,11 +113,17 @@ const editingAnnotation = ref(null);
|
|
|
78
113
|
const settingsOpen = ref(false);
|
|
79
114
|
const settingsAnchorEl = ref(null);
|
|
80
115
|
const copyFeedback = ref(false);
|
|
116
|
+
const undoFeedback = ref(false);
|
|
117
|
+
const undoSnapshot = ref([]);
|
|
118
|
+
let undoTimer = null;
|
|
119
|
+
const UNDO_TIMEOUT_MS = 5e3;
|
|
81
120
|
const toolbarDragging = ref(false);
|
|
82
121
|
const DRAG_END_SUPPRESSION_MS = 500;
|
|
83
122
|
const SETTINGS_CLOSE_SUPPRESSION_MS = 220;
|
|
84
123
|
let suppressInteractionsUntil = 0;
|
|
85
|
-
const effectiveBlockPageInteractions = computed(
|
|
124
|
+
const effectiveBlockPageInteractions = computed(
|
|
125
|
+
() => props.blockPageInteractions ?? settings.blockPageInteractions
|
|
126
|
+
);
|
|
86
127
|
const rootStyle = computed(() => {
|
|
87
128
|
const hex = settings.markerColor;
|
|
88
129
|
if (!hex)
|
|
@@ -106,7 +147,16 @@ const pendingMarkerY = computed(() => {
|
|
|
106
147
|
return 0;
|
|
107
148
|
return pendingPosition.value.y + (window.scrollY || document.documentElement.scrollTop);
|
|
108
149
|
});
|
|
109
|
-
const pendingIsSelection = computed(
|
|
150
|
+
const pendingIsSelection = computed(
|
|
151
|
+
() => mode.value === "input-open" && !editingAnnotation.value && (multiSelect.selectedElements.value.length > 0 || !!areaSelect.areaRect.value)
|
|
152
|
+
);
|
|
153
|
+
const mentionCandidates = computed(
|
|
154
|
+
() => annotations.value.map((ann, i) => ({
|
|
155
|
+
id: ann.id,
|
|
156
|
+
displayNumber: i + 1,
|
|
157
|
+
commentPreview: ann.comment.replace(/@\[\d+\]/g, "@\u2026").slice(0, 40) + (ann.comment.length > 40 ? "\u2026" : "")
|
|
158
|
+
})).filter((c) => !editingAnnotation.value || c.id !== editingAnnotation.value.id)
|
|
159
|
+
);
|
|
110
160
|
let portalContainer = null;
|
|
111
161
|
const isVue2 = _isVue2;
|
|
112
162
|
const PassThrough = defineComponent({
|
|
@@ -115,8 +165,12 @@ const PassThrough = defineComponent({
|
|
|
115
165
|
return (typeof slot === "function" ? slot() : slot?.[0]) || null;
|
|
116
166
|
}
|
|
117
167
|
});
|
|
118
|
-
const portalWrapper = computed(
|
|
119
|
-
|
|
168
|
+
const portalWrapper = computed(
|
|
169
|
+
() => props.disablePortal || isVue2 ? PassThrough : "Teleport"
|
|
170
|
+
);
|
|
171
|
+
const portalProps = computed(
|
|
172
|
+
() => props.disablePortal || isVue2 ? {} : { to: "body" }
|
|
173
|
+
);
|
|
120
174
|
onMounted(() => {
|
|
121
175
|
if (!props.disablePortal && isVue2 && rootEl.value) {
|
|
122
176
|
portalContainer = createPortalContainer();
|
|
@@ -124,39 +178,68 @@ onMounted(() => {
|
|
|
124
178
|
}
|
|
125
179
|
});
|
|
126
180
|
onBeforeUnmount(() => {
|
|
181
|
+
dismissUndo();
|
|
127
182
|
animPause.cleanup();
|
|
128
183
|
cleanupDetection();
|
|
129
184
|
if (portalContainer) {
|
|
130
185
|
destroyPortalContainer(portalContainer);
|
|
131
186
|
}
|
|
132
187
|
});
|
|
133
|
-
watch(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
watch(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
watch(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
188
|
+
watch(
|
|
189
|
+
() => props.outputDetail,
|
|
190
|
+
(v) => {
|
|
191
|
+
if (v)
|
|
192
|
+
settings.outputDetail = v;
|
|
193
|
+
},
|
|
194
|
+
{ immediate: true }
|
|
195
|
+
);
|
|
196
|
+
watch(
|
|
197
|
+
() => props.markerColor,
|
|
198
|
+
(v) => {
|
|
199
|
+
if (v)
|
|
200
|
+
settings.markerColor = v;
|
|
201
|
+
},
|
|
202
|
+
{ immediate: true }
|
|
203
|
+
);
|
|
204
|
+
watch(
|
|
205
|
+
() => props.theme,
|
|
206
|
+
(v) => {
|
|
207
|
+
if (v)
|
|
208
|
+
settings.theme = v;
|
|
209
|
+
},
|
|
210
|
+
{ immediate: true }
|
|
211
|
+
);
|
|
212
|
+
watch(
|
|
213
|
+
() => props.blockPageInteractions,
|
|
214
|
+
(v) => {
|
|
215
|
+
if (v)
|
|
216
|
+
settings.blockPageInteractions = v;
|
|
217
|
+
},
|
|
218
|
+
{ immediate: true }
|
|
219
|
+
);
|
|
220
|
+
watch(
|
|
221
|
+
() => props.autoHideToolbar,
|
|
222
|
+
(v) => {
|
|
223
|
+
if (v)
|
|
224
|
+
settings.autoHideToolbar = v;
|
|
225
|
+
},
|
|
226
|
+
{ immediate: true }
|
|
227
|
+
);
|
|
228
|
+
watch(
|
|
229
|
+
() => props.pageUrl,
|
|
230
|
+
(url) => {
|
|
231
|
+
syncUrlScope(url || getCurrentUrl());
|
|
232
|
+
},
|
|
233
|
+
{ immediate: true }
|
|
234
|
+
);
|
|
235
|
+
watch(
|
|
236
|
+
() => props.activationKey,
|
|
237
|
+
(v) => {
|
|
238
|
+
if (v !== void 0)
|
|
239
|
+
settings.activationKey = v;
|
|
240
|
+
},
|
|
241
|
+
{ immediate: true }
|
|
242
|
+
);
|
|
160
243
|
function onActivate() {
|
|
161
244
|
transition("inspect");
|
|
162
245
|
}
|
|
@@ -207,7 +290,10 @@ function onOverlayMouseUp(e) {
|
|
|
207
290
|
areaSelect.onMouseUp();
|
|
208
291
|
const rect = areaSelect.areaRect.value;
|
|
209
292
|
if (rect && rect.width > 10 && rect.height > 10) {
|
|
210
|
-
pendingPosition.value = {
|
|
293
|
+
pendingPosition.value = {
|
|
294
|
+
x: rect.x + rect.width / 2,
|
|
295
|
+
y: rect.y + rect.height / 2
|
|
296
|
+
};
|
|
211
297
|
pendingElementName.value = "Area selection";
|
|
212
298
|
pendingComponentChain.value = void 0;
|
|
213
299
|
pendingComputedStyles.value = void 0;
|
|
@@ -229,7 +315,9 @@ function onOverlayMouseUp(e) {
|
|
|
229
315
|
};
|
|
230
316
|
pendingElementName.value = `"${textResult.selectedText.slice(0, 30)}"`;
|
|
231
317
|
pendingComponentChain.value = getVueComponents(textResult.anchorElement);
|
|
232
|
-
pendingComputedStyles.value = getRelevantComputedStyles(
|
|
318
|
+
pendingComputedStyles.value = getRelevantComputedStyles(
|
|
319
|
+
textResult.anchorElement
|
|
320
|
+
);
|
|
233
321
|
pendingTarget.value = textResult.anchorElement;
|
|
234
322
|
pendingTextSelection.value = {
|
|
235
323
|
text: textResult.selectedText,
|
|
@@ -282,7 +370,10 @@ function shouldUseDocumentFallbackEvents() {
|
|
|
282
370
|
return mode.value === "inspect" && !effectiveBlockPageInteractions.value && !isInteractionLocked();
|
|
283
371
|
}
|
|
284
372
|
function lockInteractionsTemporarily(durationMs) {
|
|
285
|
-
suppressInteractionsUntil = Math.max(
|
|
373
|
+
suppressInteractionsUntil = Math.max(
|
|
374
|
+
suppressInteractionsUntil,
|
|
375
|
+
Date.now() + durationMs
|
|
376
|
+
);
|
|
286
377
|
}
|
|
287
378
|
function isInteractionLocked() {
|
|
288
379
|
return settingsOpen.value || toolbarDragging.value || Date.now() < suppressInteractionsUntil;
|
|
@@ -297,6 +388,7 @@ function closeSettings(lockInteractions = true) {
|
|
|
297
388
|
function syncUrlScope(nextUrl) {
|
|
298
389
|
if (!nextUrl || currentUrl.value === nextUrl)
|
|
299
390
|
return;
|
|
391
|
+
dismissUndo();
|
|
300
392
|
currentUrl.value = nextUrl;
|
|
301
393
|
setScopeUrl(nextUrl);
|
|
302
394
|
}
|
|
@@ -395,7 +487,9 @@ function onInputAdd(comment) {
|
|
|
395
487
|
comment,
|
|
396
488
|
url,
|
|
397
489
|
element: "multi",
|
|
398
|
-
elementPath: `region at (${Math.round(boundingBox.x)}, ${Math.round(
|
|
490
|
+
elementPath: `region at (${Math.round(boundingBox.x)}, ${Math.round(
|
|
491
|
+
boundingBox.y
|
|
492
|
+
)})`,
|
|
399
493
|
isMultiSelect: true,
|
|
400
494
|
elements,
|
|
401
495
|
boundingBox,
|
|
@@ -471,7 +565,12 @@ function onInputAdd(comment) {
|
|
|
471
565
|
vueComponents: getVueComponents(el),
|
|
472
566
|
nearbyElements: getNearbyElements(el),
|
|
473
567
|
nearbyText: getNearbyText(el),
|
|
474
|
-
boundingBox: {
|
|
568
|
+
boundingBox: {
|
|
569
|
+
x: rect.x,
|
|
570
|
+
y: rect.y,
|
|
571
|
+
width: rect.width,
|
|
572
|
+
height: rect.height
|
|
573
|
+
},
|
|
475
574
|
cssClasses: detail === "forensic" ? Array.from(el.classList).join(" ") : void 0,
|
|
476
575
|
fullPath: detail === "forensic" ? getElementPath(el) : void 0,
|
|
477
576
|
computedStyles: detail === "forensic" ? getComputedStylesSummary(el) : void 0,
|
|
@@ -489,7 +588,11 @@ function onInputCancel() {
|
|
|
489
588
|
transition("inspect");
|
|
490
589
|
}
|
|
491
590
|
async function onCopy() {
|
|
492
|
-
const markdown = formatAnnotations(
|
|
591
|
+
const markdown = formatAnnotations(
|
|
592
|
+
annotations.value,
|
|
593
|
+
settings.outputDetail,
|
|
594
|
+
resolvedUrl.value
|
|
595
|
+
);
|
|
493
596
|
if (props.copyToClipboard !== false) {
|
|
494
597
|
const success = await copyToClipboard(markdown);
|
|
495
598
|
if (!success)
|
|
@@ -501,13 +604,37 @@ async function onCopy() {
|
|
|
501
604
|
}
|
|
502
605
|
emit("copy", markdown);
|
|
503
606
|
if (settings.clearAfterCopy) {
|
|
504
|
-
|
|
505
|
-
|
|
607
|
+
onClear();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function dismissUndo() {
|
|
611
|
+
undoFeedback.value = false;
|
|
612
|
+
undoSnapshot.value = [];
|
|
613
|
+
if (undoTimer) {
|
|
614
|
+
clearTimeout(undoTimer);
|
|
615
|
+
undoTimer = null;
|
|
506
616
|
}
|
|
507
617
|
}
|
|
618
|
+
function startUndoTimer() {
|
|
619
|
+
if (undoTimer)
|
|
620
|
+
clearTimeout(undoTimer);
|
|
621
|
+
undoTimer = setTimeout(() => dismissUndo(), UNDO_TIMEOUT_MS);
|
|
622
|
+
}
|
|
623
|
+
function onUndo() {
|
|
624
|
+
const snapshot = undoSnapshot.value;
|
|
625
|
+
dismissUndo();
|
|
626
|
+
if (snapshot.length > 0)
|
|
627
|
+
restoreAnnotations(snapshot);
|
|
628
|
+
}
|
|
508
629
|
function onClear() {
|
|
630
|
+
dismissUndo();
|
|
509
631
|
const cleared = clearAnnotations();
|
|
632
|
+
if (cleared.length === 0)
|
|
633
|
+
return;
|
|
510
634
|
emit("annotations-clear", cleared);
|
|
635
|
+
undoSnapshot.value = cleared;
|
|
636
|
+
undoFeedback.value = true;
|
|
637
|
+
startUndoTimer();
|
|
511
638
|
}
|
|
512
639
|
function onMarkerClick(ann) {
|
|
513
640
|
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
@@ -515,12 +642,16 @@ function onMarkerClick(ann) {
|
|
|
515
642
|
const markerY = ann.isFixed ? ann.y : ann.y - scrollTop;
|
|
516
643
|
editingAnnotation.value = ann;
|
|
517
644
|
pendingPosition.value = { x: markerX, y: markerY };
|
|
518
|
-
pendingElementName.value = getElementName(
|
|
645
|
+
pendingElementName.value = getElementName(
|
|
646
|
+
ann._targetRef?.deref() || document.createElement(ann.element)
|
|
647
|
+
);
|
|
519
648
|
pendingComponentChain.value = ann.vueComponents;
|
|
520
|
-
pendingComputedStyles.value = ann.computedStyles ? Object.fromEntries(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
649
|
+
pendingComputedStyles.value = ann.computedStyles ? Object.fromEntries(
|
|
650
|
+
ann.computedStyles.split("\n").filter(Boolean).map((line) => {
|
|
651
|
+
const idx = line.indexOf(":");
|
|
652
|
+
return idx > -1 ? [line.slice(0, idx).trim(), line.slice(idx + 1).trim()] : [line, ""];
|
|
653
|
+
})
|
|
654
|
+
) : ann._targetRef?.deref() ? getRelevantComputedStyles(ann._targetRef.deref()) : void 0;
|
|
524
655
|
pendingTextSelection.value = null;
|
|
525
656
|
pendingTarget.value = ann._targetRef?.deref() || null;
|
|
526
657
|
transition("input-open");
|
|
@@ -596,7 +727,10 @@ onMounted(() => {
|
|
|
596
727
|
document.addEventListener("mousemove", onDocumentMouseMove, true);
|
|
597
728
|
document.addEventListener("mousedown", onDocumentMouseDown, true);
|
|
598
729
|
document.addEventListener("mouseup", onDocumentMouseUp, true);
|
|
599
|
-
document.addEventListener("wheel", onDocumentWheel, {
|
|
730
|
+
document.addEventListener("wheel", onDocumentWheel, {
|
|
731
|
+
passive: true,
|
|
732
|
+
capture: true
|
|
733
|
+
});
|
|
600
734
|
document.addEventListener("click", onDocumentClick, true);
|
|
601
735
|
});
|
|
602
736
|
onBeforeUnmount(() => {
|
|
@@ -613,14 +747,21 @@ onBeforeUnmount(() => {
|
|
|
613
747
|
|
|
614
748
|
<template>
|
|
615
749
|
<component :is="portalWrapper" v-bind="portalProps">
|
|
616
|
-
<div
|
|
750
|
+
<div
|
|
751
|
+
ref="rootEl"
|
|
752
|
+
data-agentation-vue
|
|
753
|
+
:data-va-theme="settings.theme !== 'auto' ? settings.theme : void 0"
|
|
754
|
+
:style="rootStyle"
|
|
755
|
+
>
|
|
617
756
|
<!-- Intercept overlay -->
|
|
618
757
|
<div
|
|
619
758
|
v-if="mode !== 'idle'"
|
|
620
759
|
ref="overlayEl"
|
|
621
760
|
class="__va-intercept"
|
|
622
761
|
:class="{ '__va-intercept--input-open': mode === 'input-open' }"
|
|
623
|
-
:style="
|
|
762
|
+
:style="
|
|
763
|
+
mode === 'inspect' && !effectiveBlockPageInteractions ? { pointerEvents: 'none' } : void 0
|
|
764
|
+
"
|
|
624
765
|
@mousemove="onOverlayMouseMove"
|
|
625
766
|
@mousedown="onOverlayMouseDown"
|
|
626
767
|
@mouseup="onOverlayMouseUp"
|
|
@@ -679,6 +820,7 @@ onBeforeUnmount(() => {
|
|
|
679
820
|
:computed-styles="pendingComputedStyles"
|
|
680
821
|
:initial-comment="editingAnnotation?.comment"
|
|
681
822
|
:is-editing="!!editingAnnotation"
|
|
823
|
+
:mention-candidates="mentionCandidates"
|
|
682
824
|
@add="onInputAdd"
|
|
683
825
|
@cancel="onInputCancel"
|
|
684
826
|
@delete="onInputDelete"
|
|
@@ -698,6 +840,18 @@ onBeforeUnmount(() => {
|
|
|
698
840
|
Copied!
|
|
699
841
|
</div>
|
|
700
842
|
|
|
843
|
+
<!-- Undo clear feedback -->
|
|
844
|
+
<div
|
|
845
|
+
v-if="undoFeedback"
|
|
846
|
+
class="__va-undo-feedback"
|
|
847
|
+
:class="{ '__va-undo-feedback--shifted': copyFeedback }"
|
|
848
|
+
>
|
|
849
|
+
<span>Annotations cleared</span>
|
|
850
|
+
<button type="button" class="__va-undo-btn" @click="onUndo">
|
|
851
|
+
Undo
|
|
852
|
+
</button>
|
|
853
|
+
</div>
|
|
854
|
+
|
|
701
855
|
<!-- Toolbar -->
|
|
702
856
|
<AgentationToolbar
|
|
703
857
|
ref="toolbarRef"
|
|
@@ -77,8 +77,7 @@ function onDeactivate() {
|
|
|
77
77
|
emit("deactivate");
|
|
78
78
|
}
|
|
79
79
|
function onClear() {
|
|
80
|
-
|
|
81
|
-
emit("clear");
|
|
80
|
+
emit("clear");
|
|
82
81
|
}
|
|
83
82
|
function onOpenSettings(e) {
|
|
84
83
|
const anchorEl = e.currentTarget instanceof HTMLElement ? e.currentTarget : null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MentionCandidate } from '../utils/mention';
|
|
1
2
|
type __VLS_Props = {
|
|
2
3
|
position: {
|
|
3
4
|
x: number;
|
|
@@ -8,6 +9,7 @@ type __VLS_Props = {
|
|
|
8
9
|
computedStyles?: Record<string, string>;
|
|
9
10
|
initialComment?: string;
|
|
10
11
|
isEditing?: boolean;
|
|
12
|
+
mentionCandidates?: MentionCandidate[];
|
|
11
13
|
};
|
|
12
14
|
declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
|
|
13
15
|
cancel: () => any;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, onMounted, ref } from "vue-demi";
|
|
2
|
+
import { computed, onMounted, ref, toRef } from "vue-demi";
|
|
3
|
+
import { useMentionDropdown } from "../composables/useMentionDropdown.mjs";
|
|
4
|
+
import { hydrateMentions, serializeMentions } from "../utils/mention.mjs";
|
|
3
5
|
import ComponentChain from "./ComponentChain.vue";
|
|
6
|
+
import MentionDropdown from "./MentionDropdown.vue";
|
|
4
7
|
import VaButton from "./VaButton.vue";
|
|
5
8
|
import VaIcon from "./VaIcon.vue";
|
|
6
9
|
const props = defineProps({
|
|
@@ -9,12 +12,16 @@ const props = defineProps({
|
|
|
9
12
|
componentChain: { type: String, required: false },
|
|
10
13
|
computedStyles: { type: Object, required: false },
|
|
11
14
|
initialComment: { type: String, required: false },
|
|
12
|
-
isEditing: { type: Boolean, required: false }
|
|
15
|
+
isEditing: { type: Boolean, required: false },
|
|
16
|
+
mentionCandidates: { type: Array, required: false }
|
|
13
17
|
});
|
|
14
18
|
const emit = defineEmits(["add", "cancel", "delete"]);
|
|
15
|
-
const comment = ref(props.initialComment || "");
|
|
16
19
|
const inputEl = ref(null);
|
|
20
|
+
const commentText = ref(props.initialComment || "");
|
|
17
21
|
const computedStyleEntries = computed(() => Object.entries(props.computedStyles || {}));
|
|
22
|
+
const candidates = toRef(props, "mentionCandidates");
|
|
23
|
+
const safeCandidates = computed(() => candidates.value || []);
|
|
24
|
+
const mention = useMentionDropdown(inputEl, safeCandidates);
|
|
18
25
|
const inputStyle = computed(() => {
|
|
19
26
|
const x = Math.min(props.position.x, window.innerWidth - 380);
|
|
20
27
|
const y = Math.min(props.position.y + 20, window.innerHeight - 150);
|
|
@@ -23,14 +30,71 @@ const inputStyle = computed(() => {
|
|
|
23
30
|
top: `${Math.max(10, y)}px`
|
|
24
31
|
};
|
|
25
32
|
});
|
|
33
|
+
function autoResize() {
|
|
34
|
+
const el = inputEl.value;
|
|
35
|
+
if (!el)
|
|
36
|
+
return;
|
|
37
|
+
el.style.height = "auto";
|
|
38
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
39
|
+
}
|
|
40
|
+
function getComment() {
|
|
41
|
+
const el = inputEl.value;
|
|
42
|
+
if (!el)
|
|
43
|
+
return "";
|
|
44
|
+
return serializeMentions(el);
|
|
45
|
+
}
|
|
46
|
+
function onInput() {
|
|
47
|
+
commentText.value = getComment();
|
|
48
|
+
autoResize();
|
|
49
|
+
mention.checkForTrigger();
|
|
50
|
+
}
|
|
51
|
+
function onKeyDown(e) {
|
|
52
|
+
if (mention.onKeyDown(e))
|
|
53
|
+
return;
|
|
54
|
+
if (e.key === "Enter" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
onAdd();
|
|
57
|
+
} else if (e.key === "Escape") {
|
|
58
|
+
if (mention.isOpen.value) {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
mention.close();
|
|
61
|
+
} else {
|
|
62
|
+
emit("cancel");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
26
66
|
function onAdd() {
|
|
27
|
-
const text =
|
|
67
|
+
const text = getComment().trim();
|
|
28
68
|
if (!text)
|
|
29
69
|
return;
|
|
30
70
|
emit("add", text);
|
|
31
71
|
}
|
|
72
|
+
function onPaste(e) {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
const text = e.clipboardData?.getData("text/plain") || "";
|
|
75
|
+
document.execCommand("insertText", false, text);
|
|
76
|
+
}
|
|
77
|
+
function onSelectCandidate(candidate) {
|
|
78
|
+
mention.selectCandidate(candidate);
|
|
79
|
+
}
|
|
32
80
|
onMounted(() => {
|
|
33
|
-
inputEl.value
|
|
81
|
+
const el = inputEl.value;
|
|
82
|
+
if (!el)
|
|
83
|
+
return;
|
|
84
|
+
if (props.initialComment) {
|
|
85
|
+
const html = hydrateMentions(props.initialComment, safeCandidates.value);
|
|
86
|
+
el.innerHTML = html;
|
|
87
|
+
}
|
|
88
|
+
el.focus();
|
|
89
|
+
const sel = window.getSelection();
|
|
90
|
+
if (sel && el.childNodes.length > 0) {
|
|
91
|
+
const range = document.createRange();
|
|
92
|
+
range.selectNodeContents(el);
|
|
93
|
+
range.collapse(false);
|
|
94
|
+
sel.removeAllRanges();
|
|
95
|
+
sel.addRange(range);
|
|
96
|
+
}
|
|
97
|
+
autoResize();
|
|
34
98
|
});
|
|
35
99
|
</script>
|
|
36
100
|
|
|
@@ -66,13 +130,24 @@ onMounted(() => {
|
|
|
66
130
|
<ComponentChain :chain="componentChain" variant="light" truncate="leaf" />
|
|
67
131
|
</div>
|
|
68
132
|
<span v-else class="__va-input-label">{{ elementName || "Annotation" }}</span>
|
|
69
|
-
<
|
|
133
|
+
<div
|
|
70
134
|
ref="inputEl"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
135
|
+
class="__va-input-editable"
|
|
136
|
+
contenteditable="true"
|
|
137
|
+
role="textbox"
|
|
138
|
+
aria-multiline="true"
|
|
139
|
+
data-placeholder="Add a comment..."
|
|
140
|
+
@input="onInput"
|
|
141
|
+
@keydown="onKeyDown"
|
|
142
|
+
@paste="onPaste"
|
|
143
|
+
/>
|
|
144
|
+
<MentionDropdown
|
|
145
|
+
:open="mention.isOpen.value"
|
|
146
|
+
:candidates="mention.filteredCandidates.value"
|
|
147
|
+
:active-index="mention.activeIndex.value"
|
|
148
|
+
:position="mention.dropdownPosition.value"
|
|
149
|
+
@select="onSelectCandidate"
|
|
150
|
+
/>
|
|
76
151
|
<div class="__va-input-actions">
|
|
77
152
|
<button
|
|
78
153
|
v-if="isEditing"
|
|
@@ -86,7 +161,7 @@ onMounted(() => {
|
|
|
86
161
|
<VaButton variant="secondary" @click="$emit('cancel')">
|
|
87
162
|
Cancel
|
|
88
163
|
</VaButton>
|
|
89
|
-
<VaButton :disabled="!
|
|
164
|
+
<VaButton :disabled="!commentText.trim()" @click="onAdd">
|
|
90
165
|
{{ isEditing ? "Save" : "Add" }}
|
|
91
166
|
</VaButton>
|
|
92
167
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MentionCandidate } from '../utils/mention';
|
|
1
2
|
type __VLS_Props = {
|
|
2
3
|
position: {
|
|
3
4
|
x: number;
|
|
@@ -8,6 +9,7 @@ type __VLS_Props = {
|
|
|
8
9
|
computedStyles?: Record<string, string>;
|
|
9
10
|
initialComment?: string;
|
|
10
11
|
isEditing?: boolean;
|
|
12
|
+
mentionCandidates?: MentionCandidate[];
|
|
11
13
|
};
|
|
12
14
|
declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
|
|
13
15
|
cancel: () => any;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MentionCandidate } from '../utils/mention';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
candidates: MentionCandidate[];
|
|
5
|
+
activeIndex: number;
|
|
6
|
+
position: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
|
|
12
|
+
select: (candidate: MentionCandidate) => any;
|
|
13
|
+
}, string, import("vue-demi").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
14
|
+
onSelect?: ((candidate: MentionCandidate) => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue-demi").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue-demi";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
open: { type: Boolean, required: true },
|
|
5
|
+
candidates: { type: Array, required: true },
|
|
6
|
+
activeIndex: { type: Number, required: true },
|
|
7
|
+
position: { type: Object, required: true }
|
|
8
|
+
});
|
|
9
|
+
defineEmits(["select"]);
|
|
10
|
+
const dropdownStyle = computed(() => {
|
|
11
|
+
if (!props.open)
|
|
12
|
+
return { display: "none" };
|
|
13
|
+
return {
|
|
14
|
+
left: `${props.position.x}px`,
|
|
15
|
+
top: `${props.position.y}px`
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div
|
|
22
|
+
v-show="open && candidates.length > 0"
|
|
23
|
+
class="__va-mention-dropdown"
|
|
24
|
+
:style="dropdownStyle"
|
|
25
|
+
role="listbox"
|
|
26
|
+
data-agentation-vue
|
|
27
|
+
>
|
|
28
|
+
<div
|
|
29
|
+
v-for="(candidate, i) in candidates"
|
|
30
|
+
:key="candidate.id"
|
|
31
|
+
class="__va-mention-option"
|
|
32
|
+
:class="{ '__va-mention-option--active': i === activeIndex }"
|
|
33
|
+
role="option"
|
|
34
|
+
:aria-selected="i === activeIndex"
|
|
35
|
+
@mousedown.prevent="$emit('select', candidate)"
|
|
36
|
+
>
|
|
37
|
+
<span class="__va-mention-option-number">{{ candidate.displayNumber }}</span>
|
|
38
|
+
<span class="__va-mention-option-preview">{{ candidate.commentPreview }}</span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MentionCandidate } from '../utils/mention';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
candidates: MentionCandidate[];
|
|
5
|
+
activeIndex: number;
|
|
6
|
+
position: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
|
|
12
|
+
select: (candidate: MentionCandidate) => any;
|
|
13
|
+
}, string, import("vue-demi").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
14
|
+
onSelect?: ((candidate: MentionCandidate) => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue-demi").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, toRef } from "vue-demi";
|
|
3
|
+
import { VA_VERSION } from "../constants.mjs";
|
|
3
4
|
import { vaTooltipDirective } from "../directives/vaTooltip.mjs";
|
|
4
5
|
import VaIcon from "./VaIcon.vue";
|
|
5
6
|
import VaToggle from "./VaToggle.vue";
|
|
@@ -36,6 +37,7 @@ function toggleTheme() {
|
|
|
36
37
|
<template>
|
|
37
38
|
<div class="__va-settings" data-agentation-vue @click.stop>
|
|
38
39
|
<div class="__va-settings-top">
|
|
40
|
+
<span class="__va-settings-title">Agentation vue <span class="__va-settings-version">v{{ VA_VERSION }}</span></span>
|
|
39
41
|
<button v-va-tooltip="'Toggle theme'" type="button" class="__va-theme-toggle" @click="toggleTheme">
|
|
40
42
|
<VaIcon :name="themeIcon" />
|
|
41
43
|
</button>
|
|
@@ -138,12 +138,10 @@ function schedulePositionUpdate() {
|
|
|
138
138
|
function onDocumentPointerDown(e) {
|
|
139
139
|
if (!props.open)
|
|
140
140
|
return;
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
141
|
+
const path = e.composedPath();
|
|
142
|
+
if (panelEl.value && path.includes(panelEl.value))
|
|
143
143
|
return;
|
|
144
|
-
if (
|
|
145
|
-
return;
|
|
146
|
-
if (props.anchorEl?.contains(target))
|
|
144
|
+
if (props.anchorEl && path.includes(props.anchorEl))
|
|
147
145
|
return;
|
|
148
146
|
emit("close");
|
|
149
147
|
}
|
|
@@ -4,6 +4,7 @@ declare function addAnnotation(annotation: Omit<Annotation, 'id' | 'timestamp'>)
|
|
|
4
4
|
declare function removeAnnotation(id: string): Annotation | undefined;
|
|
5
5
|
declare function updateAnnotation(id: string, updates: Partial<Annotation>): Annotation | undefined;
|
|
6
6
|
declare function clearAnnotations(): Annotation[];
|
|
7
|
+
declare function restoreAnnotations(items: Annotation[]): void;
|
|
7
8
|
export declare function setAnnotationStorage(adapter: StorageAdapter): void;
|
|
8
9
|
export declare function resetAnnotationStorage(): void;
|
|
9
10
|
export declare function useAnnotations(initialUrl?: string): {
|
|
@@ -102,6 +103,7 @@ export declare function useAnnotations(initialUrl?: string): {
|
|
|
102
103
|
removeAnnotation: typeof removeAnnotation;
|
|
103
104
|
updateAnnotation: typeof updateAnnotation;
|
|
104
105
|
clearAnnotations: typeof clearAnnotations;
|
|
106
|
+
restoreAnnotations: typeof restoreAnnotations;
|
|
105
107
|
setScopeUrl: typeof setScopeUrl;
|
|
106
108
|
};
|
|
107
109
|
export {};
|
|
@@ -110,6 +110,11 @@ function clearAnnotations() {
|
|
|
110
110
|
save();
|
|
111
111
|
return cleared;
|
|
112
112
|
}
|
|
113
|
+
function restoreAnnotations(items) {
|
|
114
|
+
annotations.value.push(...items);
|
|
115
|
+
counter = getCounterSeed(annotations.value);
|
|
116
|
+
save();
|
|
117
|
+
}
|
|
113
118
|
setScopeUrl(scopedUrl);
|
|
114
119
|
export function setAnnotationStorage(adapter) {
|
|
115
120
|
annotationStorage = adapter;
|
|
@@ -121,5 +126,5 @@ export function resetAnnotationStorage() {
|
|
|
121
126
|
}
|
|
122
127
|
export function useAnnotations(initialUrl = getCurrentUrl()) {
|
|
123
128
|
setScopeUrl(initialUrl);
|
|
124
|
-
return { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, setScopeUrl };
|
|
129
|
+
return { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, restoreAnnotations, setScopeUrl };
|
|
125
130
|
}
|
|
@@ -50,6 +50,7 @@ export function useKeyboardShortcuts(options) {
|
|
|
50
50
|
let lastActivationKeyUpTime = 0;
|
|
51
51
|
let listenerAttached = false;
|
|
52
52
|
let mergedKeymap = { ...DEFAULT_KEYMAP, ...options.config.value.keymap };
|
|
53
|
+
const suppressedKeyUps = /* @__PURE__ */ new Set();
|
|
53
54
|
watch(options.config, (cfg2) => {
|
|
54
55
|
mergedKeymap = { ...DEFAULT_KEYMAP, ...cfg2.keymap };
|
|
55
56
|
});
|
|
@@ -129,9 +130,15 @@ export function useKeyboardShortcuts(options) {
|
|
|
129
130
|
break;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
|
-
function
|
|
133
|
+
function normalizeKey(key) {
|
|
134
|
+
return key.length === 1 ? key.toLowerCase() : key;
|
|
135
|
+
}
|
|
136
|
+
function consume(e, suppressKeyUp = false) {
|
|
133
137
|
e.preventDefault();
|
|
138
|
+
e.stopImmediatePropagation();
|
|
134
139
|
e.stopPropagation();
|
|
140
|
+
if (suppressKeyUp)
|
|
141
|
+
suppressedKeyUps.add(normalizeKey(e.key));
|
|
135
142
|
}
|
|
136
143
|
function onKeyDown(e) {
|
|
137
144
|
if (e.repeat)
|
|
@@ -145,14 +152,14 @@ export function useKeyboardShortcuts(options) {
|
|
|
145
152
|
}
|
|
146
153
|
if (scope === "settings") {
|
|
147
154
|
if (e.key === "Escape") {
|
|
148
|
-
consume(e);
|
|
155
|
+
consume(e, true);
|
|
149
156
|
actions.closeSettings();
|
|
150
157
|
}
|
|
151
158
|
return;
|
|
152
159
|
}
|
|
153
160
|
if (scope === "input") {
|
|
154
161
|
if (e.key === "Escape") {
|
|
155
|
-
consume(e);
|
|
162
|
+
consume(e, true);
|
|
156
163
|
actions.inputCancel();
|
|
157
164
|
}
|
|
158
165
|
return;
|
|
@@ -167,11 +174,17 @@ export function useKeyboardShortcuts(options) {
|
|
|
167
174
|
if (!action)
|
|
168
175
|
return;
|
|
169
176
|
if (cfg().priorityWhenOpen) {
|
|
170
|
-
consume(e);
|
|
177
|
+
consume(e, true);
|
|
171
178
|
}
|
|
172
179
|
executeAction(action);
|
|
173
180
|
}
|
|
174
181
|
function onKeyUp(e) {
|
|
182
|
+
const normalizedKey = normalizeKey(e.key);
|
|
183
|
+
if (suppressedKeyUps.has(normalizedKey)) {
|
|
184
|
+
suppressedKeyUps.delete(normalizedKey);
|
|
185
|
+
consume(e);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
175
188
|
const { doubleTap } = cfg();
|
|
176
189
|
if (!doubleTap.enabled)
|
|
177
190
|
return;
|
|
@@ -198,13 +211,14 @@ export function useKeyboardShortcuts(options) {
|
|
|
198
211
|
}
|
|
199
212
|
function onBlurOrVisibility() {
|
|
200
213
|
lastActivationKeyUpTime = 0;
|
|
214
|
+
suppressedKeyUps.clear();
|
|
201
215
|
}
|
|
202
216
|
function attach() {
|
|
203
217
|
if (listenerAttached)
|
|
204
218
|
return;
|
|
205
219
|
listenerAttached = true;
|
|
206
|
-
|
|
207
|
-
|
|
220
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
221
|
+
window.addEventListener("keyup", onKeyUp, true);
|
|
208
222
|
window.addEventListener("blur", onBlurOrVisibility);
|
|
209
223
|
document.addEventListener("visibilitychange", onBlurOrVisibility);
|
|
210
224
|
}
|
|
@@ -212,8 +226,8 @@ export function useKeyboardShortcuts(options) {
|
|
|
212
226
|
if (!listenerAttached)
|
|
213
227
|
return;
|
|
214
228
|
listenerAttached = false;
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
window.removeEventListener("keydown", onKeyDown, true);
|
|
230
|
+
window.removeEventListener("keyup", onKeyUp, true);
|
|
217
231
|
window.removeEventListener("blur", onBlurOrVisibility);
|
|
218
232
|
document.removeEventListener("visibilitychange", onBlurOrVisibility);
|
|
219
233
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Ref } from 'vue-demi';
|
|
2
|
+
import type { MentionCandidate } from '../utils/mention';
|
|
3
|
+
export declare function useMentionDropdown(inputEl: Ref<HTMLElement | null>, candidates: Ref<MentionCandidate[]>): {
|
|
4
|
+
isOpen: Ref<boolean, boolean>;
|
|
5
|
+
filteredCandidates: import("vue-demi").ComputedRef<MentionCandidate[]>;
|
|
6
|
+
activeIndex: Ref<number, number>;
|
|
7
|
+
dropdownPosition: Ref<{
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
}, {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
} | {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}>;
|
|
17
|
+
checkForTrigger: () => void;
|
|
18
|
+
selectCandidate: (candidate: MentionCandidate) => void;
|
|
19
|
+
onKeyDown: (e: KeyboardEvent) => boolean;
|
|
20
|
+
close: () => void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { computed, ref } from "vue-demi";
|
|
2
|
+
function getSelection(el) {
|
|
3
|
+
const root = el.getRootNode();
|
|
4
|
+
if (root instanceof ShadowRoot && typeof root.getSelection === "function")
|
|
5
|
+
return root.getSelection();
|
|
6
|
+
return window.getSelection();
|
|
7
|
+
}
|
|
8
|
+
function findTrigger(el) {
|
|
9
|
+
const sel = getSelection(el);
|
|
10
|
+
if (!sel || sel.rangeCount === 0)
|
|
11
|
+
return null;
|
|
12
|
+
const range = sel.getRangeAt(0);
|
|
13
|
+
if (!range.collapsed)
|
|
14
|
+
return null;
|
|
15
|
+
const { startContainer, startOffset } = range;
|
|
16
|
+
if (startContainer.nodeType !== Node.TEXT_NODE)
|
|
17
|
+
return null;
|
|
18
|
+
const textNode = startContainer;
|
|
19
|
+
const text = textNode.textContent || "";
|
|
20
|
+
const beforeCursor = text.slice(0, startOffset);
|
|
21
|
+
for (let i = beforeCursor.length - 1; i >= 0; i--) {
|
|
22
|
+
const ch = beforeCursor[i];
|
|
23
|
+
if (ch === " " || ch === "\n")
|
|
24
|
+
return null;
|
|
25
|
+
if (ch === "@") {
|
|
26
|
+
if (i === 0 || beforeCursor[i - 1] === " " || beforeCursor[i - 1] === "\n") {
|
|
27
|
+
return {
|
|
28
|
+
textNode,
|
|
29
|
+
atIndex: i,
|
|
30
|
+
query: beforeCursor.slice(i + 1)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
export function useMentionDropdown(inputEl, candidates) {
|
|
39
|
+
const isOpen = ref(false);
|
|
40
|
+
const query = ref("");
|
|
41
|
+
const activeIndex = ref(0);
|
|
42
|
+
const dropdownPosition = ref({ x: 0, y: 0 });
|
|
43
|
+
let currentTrigger = null;
|
|
44
|
+
const filteredCandidates = computed(() => {
|
|
45
|
+
if (!isOpen.value)
|
|
46
|
+
return [];
|
|
47
|
+
const q = query.value.toLowerCase();
|
|
48
|
+
return candidates.value.filter((c) => {
|
|
49
|
+
if (!q)
|
|
50
|
+
return true;
|
|
51
|
+
return String(c.displayNumber).startsWith(q) || c.commentPreview.toLowerCase().includes(q);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
function checkForTrigger() {
|
|
55
|
+
const el = inputEl.value;
|
|
56
|
+
if (!el)
|
|
57
|
+
return;
|
|
58
|
+
const trigger = findTrigger(el);
|
|
59
|
+
if (trigger) {
|
|
60
|
+
currentTrigger = trigger;
|
|
61
|
+
query.value = trigger.query;
|
|
62
|
+
activeIndex.value = 0;
|
|
63
|
+
isOpen.value = true;
|
|
64
|
+
updatePosition();
|
|
65
|
+
} else {
|
|
66
|
+
close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function updatePosition() {
|
|
70
|
+
const el = inputEl.value;
|
|
71
|
+
if (!el || !currentTrigger)
|
|
72
|
+
return;
|
|
73
|
+
const sel = getSelection(el);
|
|
74
|
+
if (!sel || sel.rangeCount === 0)
|
|
75
|
+
return;
|
|
76
|
+
const range = sel.getRangeAt(0).cloneRange();
|
|
77
|
+
range.setStart(currentTrigger.textNode, currentTrigger.atIndex);
|
|
78
|
+
const rect = range.getBoundingClientRect();
|
|
79
|
+
const containerRect = el.closest(".__va-input")?.getBoundingClientRect();
|
|
80
|
+
if (!containerRect)
|
|
81
|
+
return;
|
|
82
|
+
const x = rect.left - containerRect.left;
|
|
83
|
+
const y = rect.bottom - containerRect.top + 4;
|
|
84
|
+
const viewportBottom = window.innerHeight;
|
|
85
|
+
const estimatedDropdownHeight = Math.min(filteredCandidates.value.length * 36 + 8, 200);
|
|
86
|
+
if (rect.bottom + estimatedDropdownHeight > viewportBottom) {
|
|
87
|
+
dropdownPosition.value = { x, y: rect.top - containerRect.top - estimatedDropdownHeight - 4 };
|
|
88
|
+
} else {
|
|
89
|
+
dropdownPosition.value = { x, y };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function selectCandidate(candidate) {
|
|
93
|
+
const el = inputEl.value;
|
|
94
|
+
if (!el || !currentTrigger)
|
|
95
|
+
return;
|
|
96
|
+
const sel = getSelection(el);
|
|
97
|
+
if (!sel || sel.rangeCount === 0)
|
|
98
|
+
return;
|
|
99
|
+
const { textNode, atIndex } = currentTrigger;
|
|
100
|
+
const cursorOffset = sel.getRangeAt(0).startOffset;
|
|
101
|
+
const range = document.createRange();
|
|
102
|
+
range.setStart(textNode, atIndex);
|
|
103
|
+
range.setEnd(textNode, cursorOffset);
|
|
104
|
+
range.deleteContents();
|
|
105
|
+
const chip = document.createElement("span");
|
|
106
|
+
chip.contentEditable = "false";
|
|
107
|
+
chip.className = "__va-mention";
|
|
108
|
+
chip.dataset.mentionId = candidate.id;
|
|
109
|
+
chip.textContent = `@${candidate.displayNumber}`;
|
|
110
|
+
range.insertNode(chip);
|
|
111
|
+
const space = document.createTextNode("\xA0");
|
|
112
|
+
chip.after(space);
|
|
113
|
+
const newRange = document.createRange();
|
|
114
|
+
newRange.setStartAfter(space);
|
|
115
|
+
newRange.collapse(true);
|
|
116
|
+
sel.removeAllRanges();
|
|
117
|
+
sel.addRange(newRange);
|
|
118
|
+
close();
|
|
119
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
120
|
+
}
|
|
121
|
+
function onKeyDown(e) {
|
|
122
|
+
if (!isOpen.value || filteredCandidates.value.length === 0)
|
|
123
|
+
return false;
|
|
124
|
+
if (e.key === "ArrowDown") {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
activeIndex.value = (activeIndex.value + 1) % filteredCandidates.value.length;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (e.key === "ArrowUp") {
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
activeIndex.value = (activeIndex.value - 1 + filteredCandidates.value.length) % filteredCandidates.value.length;
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (e.key === "Enter") {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
selectCandidate(filteredCandidates.value[activeIndex.value]);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (e.key === "Escape") {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
close();
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
function close() {
|
|
147
|
+
isOpen.value = false;
|
|
148
|
+
query.value = "";
|
|
149
|
+
activeIndex.value = 0;
|
|
150
|
+
currentTrigger = null;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
isOpen,
|
|
154
|
+
filteredCandidates,
|
|
155
|
+
activeIndex,
|
|
156
|
+
dropdownPosition,
|
|
157
|
+
checkForTrigger,
|
|
158
|
+
selectCandidate,
|
|
159
|
+
onKeyDown,
|
|
160
|
+
close
|
|
161
|
+
};
|
|
162
|
+
}
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}[data-agentation-vue]:not([data-agentation-vue] [data-agentation-vue]){--va-accent:#42b883;--va-accent-rgb:66,184,131}@media (prefers-color-scheme:dark){[data-agentation-vue]:not([data-va-theme=light]){--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}}[data-agentation-vue][data-va-theme=dark],[data-agentation-vue][data-va-theme=dark] [data-agentation-vue]{--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}[data-agentation-vue][data-va-theme=light],[data-agentation-vue][data-va-theme=light] [data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}:where([data-agentation-vue],[data-agentation-vue] *){border:none;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.5;list-style:none;margin:0;padding:0;text-decoration:none}.__va-intercept{background:transparent;cursor:crosshair;inset:0;position:fixed;z-index:2147483646}.__va-intercept--input-open{pointer-events:none}.__va-toolbar{position:fixed;--va-toolbar-base-transform:translate(0);--va-toolbar-auto-hide-transform:translate(0);--va-toolbar-size:42px;--va-toolbar-edge-offset:20px;--va-toolbar-auto-hide-peek:8px;--va-toolbar-auto-hide-corner-peek:16px;--va-toolbar-auto-hide-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-peek));--va-toolbar-auto-hide-corner-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-corner-peek));display:block;max-width:42px;min-height:42px;z-index:2147483647;--va-toolbar-resize-delay:0ms;background:var(--va-bg);border:1px solid var(--va-border);border-radius:999px;box-shadow:0 4px 12px rgba(0,0,0,.15);padding:0;transform:var(--va-toolbar-base-transform) var(--va-toolbar-auto-hide-transform);transition:border-radius .12s cubic-bezier(.32,.72,0,1),padding .12s cubic-bezier(.32,.72,0,1),max-width .24s cubic-bezier(.22,1,.36,1) var(--va-toolbar-resize-delay),transform .15s ease,opacity .15s ease,box-shadow .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-toolbar--place-bottom-right{bottom:20px;right:20px}.__va-toolbar--place-bottom-center{bottom:20px;left:50%;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-bottom-left{bottom:20px;left:20px}.__va-toolbar--place-top-right{right:20px;top:20px}.__va-toolbar--place-top-center{left:50%;top:20px;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-top-left{left:20px;top:20px}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-center{--va-toolbar-auto-hide-transform:translateY(calc(var(--va-toolbar-auto-hide-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-center{--va-toolbar-auto-hide-transform:translateY(var(--va-toolbar-auto-hide-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--collapsed{cursor:pointer;max-width:42px;min-height:42px}.__va-toolbar--expanded{--va-toolbar-resize-delay:110ms;border-radius:10px;max-width:520px;padding:4px}.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide):hover{box-shadow:0 6px 16px rgba(0,0,0,.2)}.__va-toolbar--dragging{bottom:auto;left:0;right:auto;top:0;transform:none;transition:none}.__va-toolbar--dragging.__va-toolbar--collapsed:hover{transform:none}.__va-toolbar-stage{align-items:center;display:flex;gap:4px}.__va-toolbar-stage--toggle{inset:0;justify-content:center;opacity:1;pointer-events:auto;position:absolute;transform:scale(1);transition:opacity .11s ease,transform .18s cubic-bezier(.22,1,.36,1),visibility 0s linear 0s;visibility:visible}.__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:8px;filter:blur(4px);opacity:0;overflow:hidden;pointer-events:none;transform:translateX(var(--va-toolbar-menu-enter-x));transition:opacity .14s ease 0s,transform .22s cubic-bezier(.22,1,.36,1) 0s,visibility 0s linear .14s,filter 80ms ease 0s;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--toggle{opacity:0;pointer-events:none;transform:scale(.94);transition:opacity 90ms ease,transform .12s ease,visibility 0s linear 90ms;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--menu{filter:blur(0);opacity:1;pointer-events:auto;transform:translateX(0);transition:opacity .15s ease .15s,transform .24s cubic-bezier(.22,1,.36,1) .12s,visibility 0s linear 0s,filter .15s ease .2s;visibility:visible}.__va-toolbar--place-bottom-right .__va-toolbar-stage--menu,.__va-toolbar--place-top-right .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:-8px}.__va-toolbar--place-bottom-center .__va-toolbar-stage--menu,.__va-toolbar--place-top-center .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:0px}.__va-snap-zones{inset:0;pointer-events:none;position:fixed;z-index:2147483646}.__va-snap-zone{background:rgba(var(--va-accent-rgb),.035);border:1.5px dashed rgba(var(--va-accent-rgb),.18);border-radius:999px;box-shadow:0 0 0 1px rgba(var(--va-accent-rgb),.04);height:42px;position:fixed;transform:translate(-50%,-50%);transition:all .12s ease;width:42px}.__va-snap-zone--active{background:rgba(var(--va-accent-rgb),.09);border-color:rgba(var(--va-accent-rgb),.4)}.__va-snap-zone--rect{border-radius:8px;transform:none}.__va-snap-zone--rect.__va-snap-zone--active{transform:none}.__va-icon-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:background .15s ease,color .15s ease;width:32px}.__va-icon-btn:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-icon-btn--active{background:rgba(var(--va-accent-rgb),.15);color:var(--va-accent)}.__va-icon-btn--active:hover{background:rgba(var(--va-accent-rgb),.25)}.__va-icon-btn:disabled{cursor:not-allowed;opacity:.3}.__va-icon-btn:disabled:hover{background:transparent}.__va-icon-btn svg{height:18px;width:18px}.__va-toolbar-toggle{align-items:center;background:transparent;border:none;color:var(--va-accent);cursor:pointer;display:flex;height:100%;justify-content:center;line-height:0;outline:none;padding:0;position:relative;width:100%}.__va-toolbar-toggle svg{flex-shrink:0;height:22px;width:22px}.__va-toolbar-sep{background:var(--va-border);flex-shrink:0;height:20px;width:1px}.__va-drag-handle{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:grab;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:24px}.__va-drag-handle:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-drag-handle:active{cursor:grabbing}.__va-drag-handle-dots{display:grid;gap:2.5px;grid-template-columns:repeat(2,3px);grid-template-rows:repeat(3,3px)}.__va-drag-handle-dots span{background:currentColor;border-radius:50%;height:3px;opacity:.6;width:3px}.__va-toolbar-badge{align-items:center;background:var(--va-accent);border-radius:9px;color:#fff;display:flex;font-size:11px;font-weight:600;height:18px;justify-content:center;min-width:18px;padding:0 4px;position:absolute;right:-6px;top:-6px}.__va-tooltip{align-items:center;background:#131518;border:1px solid hsla(0,0%,100%,.08);border-radius:10px;box-shadow:0 10px 24px rgba(0,0,0,.32),0 2px 6px rgba(0,0,0,.28);color:#dce1e7;display:inline-flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:11px;font-weight:500;gap:6px;left:0;line-height:1.15;max-width:min(360px,calc(100vw - 16px));opacity:0;padding:6px 9px;pointer-events:none;position:fixed;top:0;transform:translateY(4px) scale(.98);transform-origin:center bottom;transition:opacity .12s ease,transform .12s ease;white-space:nowrap;z-index:2147483647}.__va-tooltip[data-placement=bottom]{transform:translateY(-4px) scale(.98);transform-origin:center top}.__va-tooltip.__va-tooltip--visible{opacity:1}.__va-tooltip.__va-tooltip--visible[data-placement=bottom],.__va-tooltip.__va-tooltip--visible[data-placement=top]{transform:translateY(0) scale(1)}.__va-tooltip-label{overflow:hidden;text-overflow:ellipsis}.__va-tooltip-shortcut{align-items:center;background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.18);border-radius:6px;color:#b5bec8;display:inline-flex;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:11px;font-weight:600;letter-spacing:.03em;line-height:1;min-height:18px;padding:2px 6px}.__va-tooltip-arrow{background:#131518;border:1px solid hsla(0,0%,100%,.08);border-left:none;border-top:none;height:10px;left:50%;position:absolute;transform:translateX(-50%) rotate(45deg);width:10px}.__va-tooltip[data-placement=top] .__va-tooltip-arrow{bottom:-5px}.__va-tooltip[data-placement=bottom] .__va-tooltip-arrow{top:-5px;transform:translateX(-50%) rotate(225deg)}.__va-highlight{background:rgba(var(--va-accent-rgb),.08);border:2px solid var(--va-accent);border-radius:4px;pointer-events:none;position:fixed;transition:all 80ms ease;z-index:2147483645}.__va-highlight-label{background:var(--va-accent);border-radius:4px;color:#fff;font-size:11px;font-weight:500;left:0;max-width:300px;overflow:hidden;padding:2px 8px;position:absolute;text-overflow:ellipsis;top:-28px;white-space:nowrap}.__va-highlight-label--chain{background:#1a1a1a;max-width:500px;overflow:visible;padding:4px 10px}.__va-marker{align-items:center;background:var(--va-accent);border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,.3);color:#fff;cursor:pointer;display:flex;font-size:12px;font-weight:700;height:24px;justify-content:center;position:absolute;transform:translate(-50%,-50%);transition:all .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:24px;z-index:2147483647}.__va-marker:hover{box-shadow:0 3px 10px rgba(0,0,0,.4);transform:translate(-50%,-50%) scale(1.2)}.__va-marker--fixed{position:fixed}.__va-marker--stale{opacity:.5}.__va-marker-plus{height:14px;stroke-width:3;width:14px}.__va-marker-pencil{display:none;height:12px;width:12px}.__va-marker:not(.__va-marker--pending):hover .__va-marker-number{display:none}.__va-marker:not(.__va-marker--pending):hover .__va-marker-pencil{display:block}.__va-marker--pending{animation:__va-pulse 1.5s ease-in-out infinite}.__va-marker--selection{border-radius:6px;height:26px;width:26px}@keyframes __va-pulse{0%,to{box-shadow:0 2px 6px rgba(0,0,0,.3)}50%{box-shadow:0 2px 12px rgba(var(--va-accent-rgb),.5)}}.__va-input{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:360px;min-width:280px;padding:12px;position:fixed;z-index:2147483647}.__va-input-label{color:var(--va-text-secondary);display:block;font-size:12px;margin-bottom:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-chain{margin-bottom:8px}.__va-input-styles{margin-bottom:10px}.__va-input-styles-summary{align-items:center;color:var(--va-text-secondary);cursor:pointer;display:flex;font-size:12px;gap:6px;min-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-input-styles-summary::-webkit-details-marker{display:none}.__va-input-styles-summary:before{content:"▸";flex-shrink:0;font-size:11px;transition:transform .16s ease}.__va-input-styles[open] .__va-input-styles-summary:before{transform:rotate(90deg)}.__va-input-styles-block{background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;line-height:1.55;margin-top:8px;padding:8px 10px}.__va-input-styles-element{color:var(--va-text-secondary);display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-style-line{color:var(--va-text);word-break:break-word}.__va-input-style-prop{color:#a855f7}.__va-input-style-value{color:var(--va-text)}.__va-comp-chain{align-items:center;display:inline-flex;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;gap:6px}.__va-comp,.__va-comp-chain{white-space:nowrap}.__va-comp-bracket{opacity:.4}.__va-comp-chain--dark{color:#fff}.__va-comp-chain--light{color:var(--va-accent)}.__va-comp-ellipsis{font-size:11px;letter-spacing:1px;opacity:.5}.__va-comp-chain--collapsible{cursor:pointer;flex-wrap:wrap}.__va-comp-toggle{flex-shrink:0;font-size:10px;opacity:.6;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:10px}.__va-input input{background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;color:var(--va-text);font-size:13px;outline:none;padding:8px 10px;transition:border-color .15s ease;width:100%}.__va-input input:focus{border-color:var(--va-accent)}.__va-input-actions{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-top:8px}.__va-input-actions-right{display:flex;gap:8px;margin-left:auto}.__va-input-delete-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:32px}.__va-input-delete-btn svg{height:16px;width:16px}.__va-input-delete-btn:hover{background:var(--va-bg-secondary);color:#ef4444}.__va-btn{border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;outline:none;padding:6px 14px;transition:all .15s ease}.__va-btn--secondary{background:var(--va-bg-secondary);color:var(--va-text-secondary)}.__va-btn--secondary:hover{background:var(--va-border)}.__va-btn--primary{background:var(--va-accent);color:#fff}.__va-btn--primary:hover{opacity:.9}.__va-btn:disabled{cursor:not-allowed;opacity:.4}.__va-settings-popover{position:fixed;z-index:2147483647}.__va-settings{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:min(340px,calc(100vw - 16px));min-width:260px;padding:16px;position:relative}.__va-settings-top{border-bottom:1px solid var(--va-border);display:flex;justify-content:flex-end;margin-bottom:12px;padding-bottom:10px}.__va-theme-toggle{align-items:center;background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:999px;color:var(--va-text-secondary);cursor:pointer;display:inline-flex;height:28px;justify-content:center;transition:all .15s ease;width:28px}.__va-theme-toggle:hover{border-color:var(--va-text-secondary);color:var(--va-text)}.__va-theme-toggle svg{height:15px;width:15px}.__va-settings-row{align-items:center;display:flex;gap:12px;justify-content:space-between;margin-bottom:10px}.__va-settings-label{color:var(--va-text-secondary);font-size:12px;white-space:nowrap}.__va-settings-row--stack{display:block}.__va-settings-row--stack .__va-settings-label{display:block;margin-bottom:8px}.__va-settings-row:last-child{margin-bottom:0}.__va-settings-divider{background:var(--va-border);height:1px;margin:8px 0 12px}.__va-settings select{background:var(--va-bg);border:1px solid var(--va-border);border-radius:4px;color:var(--va-text);font-size:12px;outline:none;padding:4px 8px}.__va-color-swatches{align-items:center;display:flex;gap:6px}.__va-color-swatch{border:2px solid var(--va-border);border-radius:50%;cursor:pointer;flex-shrink:0;height:22px;outline:none;padding:0;transition:all .15s ease;width:22px}.__va-color-swatch:hover{transform:scale(1.15)}.__va-color-swatch--active{border-color:var(--va-text);transform:scale(1.15)}.__va-toggle{background:var(--va-border);border:none;border-radius:10px;cursor:pointer;flex-shrink:0;height:20px;outline:none;padding:0;position:relative;transition:background .15s ease;width:36px}.__va-toggle--active{background:var(--va-accent)}.__va-toggle:after{background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2);content:"";height:16px;left:2px;position:absolute;top:2px;transition:transform .15s ease;width:16px}.__va-toggle--active:after{transform:translateX(16px)}.__va-selection-rect{background:rgba(var(--va-accent-rgb),.06);border:2px dashed var(--va-accent);border-radius:2px;pointer-events:none;position:fixed;z-index:2147483645}.__va-copy-feedback{animation:__va-fadeIn .15s ease;background:var(--va-accent);border-radius:6px;bottom:72px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:#fff;font-size:13px;font-weight:500;padding:8px 16px;position:fixed;right:20px;z-index:2147483647}@keyframes __va-fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
|
|
1
|
+
[data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}[data-agentation-vue]:not([data-agentation-vue] [data-agentation-vue]){--va-accent:#42b883;--va-accent-rgb:66,184,131}@media (prefers-color-scheme:dark){[data-agentation-vue]:not([data-va-theme=light]){--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}}[data-agentation-vue][data-va-theme=dark],[data-agentation-vue][data-va-theme=dark] [data-agentation-vue]{--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}[data-agentation-vue][data-va-theme=light],[data-agentation-vue][data-va-theme=light] [data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}:where([data-agentation-vue],[data-agentation-vue] *){border:none;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.5;list-style:none;margin:0;padding:0;text-decoration:none}.__va-intercept{background:transparent;cursor:crosshair;inset:0;position:fixed;z-index:2147483646}.__va-intercept--input-open{pointer-events:none}.__va-toolbar{position:fixed;--va-toolbar-base-transform:translate(0);--va-toolbar-auto-hide-transform:translate(0);--va-toolbar-size:42px;--va-toolbar-edge-offset:20px;--va-toolbar-auto-hide-peek:8px;--va-toolbar-auto-hide-corner-peek:16px;--va-toolbar-auto-hide-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-peek));--va-toolbar-auto-hide-corner-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-corner-peek));display:block;max-width:42px;min-height:42px;z-index:2147483647;--va-toolbar-resize-delay:0ms;background:var(--va-bg);border:1px solid var(--va-border);border-radius:999px;box-shadow:0 4px 12px rgba(0,0,0,.15);padding:0;transform:var(--va-toolbar-base-transform) var(--va-toolbar-auto-hide-transform);transition:border-radius .12s cubic-bezier(.32,.72,0,1),padding .12s cubic-bezier(.32,.72,0,1),max-width .24s cubic-bezier(.22,1,.36,1) var(--va-toolbar-resize-delay),transform .15s ease,opacity .15s ease,box-shadow .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-toolbar--place-bottom-right{bottom:20px;right:20px}.__va-toolbar--place-bottom-center{bottom:20px;left:50%;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-bottom-left{bottom:20px;left:20px}.__va-toolbar--place-top-right{right:20px;top:20px}.__va-toolbar--place-top-center{left:50%;top:20px;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-top-left{left:20px;top:20px}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-center{--va-toolbar-auto-hide-transform:translateY(calc(var(--va-toolbar-auto-hide-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-center{--va-toolbar-auto-hide-transform:translateY(var(--va-toolbar-auto-hide-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--collapsed{cursor:pointer;max-width:42px;min-height:42px}.__va-toolbar--expanded{--va-toolbar-resize-delay:110ms;border-radius:10px;max-width:520px;padding:4px}.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide):hover{box-shadow:0 6px 16px rgba(0,0,0,.2)}.__va-toolbar--dragging{bottom:auto;left:0;right:auto;top:0;transform:none;transition:none}.__va-toolbar--dragging.__va-toolbar--collapsed:hover{transform:none}.__va-toolbar-stage{align-items:center;display:flex;gap:4px}.__va-toolbar-stage--toggle{inset:0;justify-content:center;opacity:1;pointer-events:auto;position:absolute;transform:scale(1);transition:opacity .11s ease,transform .18s cubic-bezier(.22,1,.36,1),visibility 0s linear 0s;visibility:visible}.__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:8px;filter:blur(4px);opacity:0;overflow:hidden;pointer-events:none;transform:translateX(var(--va-toolbar-menu-enter-x));transition:opacity .14s ease 0s,transform .22s cubic-bezier(.22,1,.36,1) 0s,visibility 0s linear .14s,filter 80ms ease 0s;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--toggle{opacity:0;pointer-events:none;transform:scale(.94);transition:opacity 90ms ease,transform .12s ease,visibility 0s linear 90ms;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--menu{filter:blur(0);opacity:1;pointer-events:auto;transform:translateX(0);transition:opacity .15s ease .15s,transform .24s cubic-bezier(.22,1,.36,1) .12s,visibility 0s linear 0s,filter .15s ease .2s;visibility:visible}.__va-toolbar--place-bottom-right .__va-toolbar-stage--menu,.__va-toolbar--place-top-right .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:-8px}.__va-toolbar--place-bottom-center .__va-toolbar-stage--menu,.__va-toolbar--place-top-center .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:0px}.__va-snap-zones{inset:0;pointer-events:none;position:fixed;z-index:2147483646}.__va-snap-zone{background:rgba(var(--va-accent-rgb),.035);border:1.5px dashed rgba(var(--va-accent-rgb),.18);border-radius:999px;box-shadow:0 0 0 1px rgba(var(--va-accent-rgb),.04);height:42px;position:fixed;transform:translate(-50%,-50%);transition:all .12s ease;width:42px}.__va-snap-zone--active{background:rgba(var(--va-accent-rgb),.09);border-color:rgba(var(--va-accent-rgb),.4)}.__va-snap-zone--rect{border-radius:8px;transform:none}.__va-snap-zone--rect.__va-snap-zone--active{transform:none}.__va-icon-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:background .15s ease,color .15s ease;width:32px}.__va-icon-btn:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-icon-btn--active{background:rgba(var(--va-accent-rgb),.15);color:var(--va-accent)}.__va-icon-btn--active:hover{background:rgba(var(--va-accent-rgb),.25)}.__va-icon-btn:disabled{cursor:not-allowed;opacity:.3}.__va-icon-btn:disabled:hover{background:transparent}.__va-icon-btn svg{height:18px;width:18px}.__va-toolbar-toggle{align-items:center;background:transparent;border:none;color:var(--va-accent);cursor:pointer;display:flex;height:100%;justify-content:center;line-height:0;outline:none;padding:0;position:relative;width:100%}.__va-toolbar-toggle svg{flex-shrink:0;height:22px;width:22px}.__va-toolbar-sep{background:var(--va-border);flex-shrink:0;height:20px;width:1px}.__va-drag-handle{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:grab;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:24px}.__va-drag-handle:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-drag-handle:active{cursor:grabbing}.__va-drag-handle-dots{display:grid;gap:2.5px;grid-template-columns:repeat(2,3px);grid-template-rows:repeat(3,3px)}.__va-drag-handle-dots span{background:currentColor;border-radius:50%;height:3px;opacity:.6;width:3px}.__va-toolbar-badge{align-items:center;background:var(--va-accent);border-radius:9px;color:#fff;display:flex;font-size:11px;font-weight:600;height:18px;justify-content:center;min-width:18px;padding:0 4px;position:absolute;right:-6px;top:-6px}.__va-tooltip{align-items:center;background:#131518;border:1px solid hsla(0,0%,100%,.08);border-radius:10px;box-shadow:0 10px 24px rgba(0,0,0,.32),0 2px 6px rgba(0,0,0,.28);color:#dce1e7;display:inline-flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:11px;font-weight:500;gap:6px;left:0;line-height:1.15;max-width:min(360px,calc(100vw - 16px));opacity:0;padding:6px 9px;pointer-events:none;position:fixed;top:0;transform:translateY(4px) scale(.98);transform-origin:center bottom;transition:opacity .12s ease,transform .12s ease;white-space:nowrap;z-index:2147483647}.__va-tooltip[data-placement=bottom]{transform:translateY(-4px) scale(.98);transform-origin:center top}.__va-tooltip.__va-tooltip--visible{opacity:1}.__va-tooltip.__va-tooltip--visible[data-placement=bottom],.__va-tooltip.__va-tooltip--visible[data-placement=top]{transform:translateY(0) scale(1)}.__va-tooltip-label{overflow:hidden;text-overflow:ellipsis}.__va-tooltip-shortcut{align-items:center;background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.18);border-radius:6px;color:#b5bec8;display:inline-flex;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:11px;font-weight:600;letter-spacing:.03em;line-height:1;min-height:18px;padding:2px 6px}.__va-tooltip-arrow{background:#131518;border:1px solid hsla(0,0%,100%,.08);border-left:none;border-top:none;height:10px;left:50%;position:absolute;transform:translateX(-50%) rotate(45deg);width:10px}.__va-tooltip[data-placement=top] .__va-tooltip-arrow{bottom:-5px}.__va-tooltip[data-placement=bottom] .__va-tooltip-arrow{top:-5px;transform:translateX(-50%) rotate(225deg)}.__va-highlight{background:rgba(var(--va-accent-rgb),.08);border:2px solid var(--va-accent);border-radius:4px;pointer-events:none;position:fixed;transition:all 80ms ease;z-index:2147483645}.__va-highlight-label{background:var(--va-accent);border-radius:4px;color:#fff;font-size:11px;font-weight:500;left:0;max-width:300px;overflow:hidden;padding:2px 8px;position:absolute;text-overflow:ellipsis;top:-28px;white-space:nowrap}.__va-highlight-label--chain{background:#1a1a1a;max-width:500px;overflow:visible;padding:4px 10px}.__va-marker{align-items:center;background:var(--va-accent);border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,.3);color:#fff;cursor:pointer;display:flex;font-size:12px;font-weight:700;height:24px;justify-content:center;position:absolute;transform:translate(-50%,-50%);transition:all .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:24px;z-index:2147483647}.__va-marker:hover{box-shadow:0 3px 10px rgba(0,0,0,.4);transform:translate(-50%,-50%) scale(1.2)}.__va-marker--fixed{position:fixed}.__va-marker--stale{opacity:.5}.__va-marker-plus{height:14px;stroke-width:3;width:14px}.__va-marker-pencil{display:none;height:12px;width:12px}.__va-marker:not(.__va-marker--pending):hover .__va-marker-number{display:none}.__va-marker:not(.__va-marker--pending):hover .__va-marker-pencil{display:block}.__va-marker--pending{animation:__va-pulse 1.5s ease-in-out infinite}.__va-marker--selection{border-radius:6px;height:26px;width:26px}@keyframes __va-pulse{0%,to{box-shadow:0 2px 6px rgba(0,0,0,.3)}50%{box-shadow:0 2px 12px rgba(var(--va-accent-rgb),.5)}}.__va-input{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:360px;min-width:280px;padding:12px;position:fixed;z-index:2147483647}.__va-input-label{color:var(--va-text-secondary);display:block;font-size:12px;margin-bottom:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-chain{margin-bottom:8px}.__va-input-styles{margin-bottom:10px}.__va-input-styles-summary{align-items:center;color:var(--va-text-secondary);cursor:pointer;display:flex;font-size:12px;gap:6px;min-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-input-styles-summary::-webkit-details-marker{display:none}.__va-input-styles-summary:before{content:"▸";flex-shrink:0;font-size:11px;transition:transform .16s ease}.__va-input-styles[open] .__va-input-styles-summary:before{transform:rotate(90deg)}.__va-input-styles-block{background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;line-height:1.55;margin-top:8px;padding:8px 10px}.__va-input-styles-element{color:var(--va-text-secondary);display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-style-line{color:var(--va-text);word-break:break-word}.__va-input-style-prop{color:#a855f7}.__va-input-style-value{color:var(--va-text)}.__va-comp-chain{align-items:center;display:inline-flex;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;gap:6px}.__va-comp,.__va-comp-chain{white-space:nowrap}.__va-comp-bracket{opacity:.4}.__va-comp-chain--dark{color:#fff}.__va-comp-chain--light{color:var(--va-accent)}.__va-comp-ellipsis{font-size:11px;letter-spacing:1px;opacity:.5}.__va-comp-chain--collapsible{cursor:pointer;flex-wrap:wrap}.__va-comp-toggle{flex-shrink:0;font-size:10px;opacity:.6;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:10px}.__va-input-editable{background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;color:var(--va-text);font-family:inherit;font-size:13px;line-height:1.4;min-height:1.4em;outline:none;overflow:hidden;padding:8px 10px;transition:border-color .15s ease;white-space:pre-wrap;width:100%;word-break:break-word}.__va-input-editable:focus{border-color:var(--va-accent)}.__va-input-editable:empty:before{color:var(--va-text-secondary);content:attr(data-placeholder);pointer-events:none}.__va-mention{background:rgba(var(--va-accent-rgb),.15);border-radius:4px;color:var(--va-accent);cursor:default;display:inline;font-size:12px;font-weight:600;padding:1px 6px;-webkit-user-select:all;-moz-user-select:all;user-select:all;white-space:nowrap}.__va-mention--deleted{opacity:.45;text-decoration:line-through}.__va-mention-dropdown{background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-height:200px;max-width:280px;min-width:180px;overflow-y:auto;padding:4px;position:absolute;z-index:1}.__va-mention-option{align-items:center;border-radius:4px;color:var(--va-text);cursor:pointer;display:flex;font-size:12px;gap:8px;padding:6px 8px}.__va-mention-option--active,.__va-mention-option:hover{background:var(--va-bg-secondary)}.__va-mention-option-number{align-items:center;background:var(--va-accent);border-radius:50%;color:#fff;display:inline-flex;flex-shrink:0;font-size:11px;font-weight:700;height:20px;justify-content:center;width:20px}.__va-mention-option-preview{color:var(--va-text-secondary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-actions{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-top:8px}.__va-input-actions-right{display:flex;gap:8px;margin-left:auto}.__va-input-delete-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:32px}.__va-input-delete-btn svg{height:16px;width:16px}.__va-input-delete-btn:hover{background:var(--va-bg-secondary);color:#ef4444}.__va-btn{border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;outline:none;padding:6px 14px;transition:all .15s ease}.__va-btn--secondary{background:var(--va-bg-secondary);color:var(--va-text-secondary)}.__va-btn--secondary:hover{background:var(--va-border)}.__va-btn--primary{background:var(--va-accent);color:#fff}.__va-btn--primary:hover{opacity:.9}.__va-btn:disabled{cursor:not-allowed;opacity:.4}.__va-settings-popover{position:fixed;z-index:2147483647}.__va-settings{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:min(340px,calc(100vw - 16px));min-width:260px;padding:16px;position:relative}.__va-settings-top{align-items:center;border-bottom:1px solid var(--va-border);display:flex;justify-content:space-between;margin-bottom:12px;padding-bottom:10px}.__va-settings-title{color:var(--va-text);font-size:13px;font-weight:600}.__va-settings-version{color:var(--va-text-secondary);font-size:11px;font-weight:400}.__va-theme-toggle{align-items:center;background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:999px;color:var(--va-text-secondary);cursor:pointer;display:inline-flex;height:28px;justify-content:center;transition:all .15s ease;width:28px}.__va-theme-toggle:hover{border-color:var(--va-text-secondary);color:var(--va-text)}.__va-theme-toggle svg{height:15px;width:15px}.__va-settings-row{align-items:center;display:flex;gap:12px;justify-content:space-between;margin-bottom:10px}.__va-settings-label{color:var(--va-text-secondary);font-size:12px;white-space:nowrap}.__va-settings-row--stack{display:block}.__va-settings-row--stack .__va-settings-label{display:block;margin-bottom:8px}.__va-settings-row:last-child{margin-bottom:0}.__va-settings-divider{background:var(--va-border);height:1px;margin:8px 0 12px}.__va-settings select{background:var(--va-bg);border:1px solid var(--va-border);border-radius:4px;color:var(--va-text);font-size:12px;outline:none;padding:4px 8px}.__va-color-swatches{align-items:center;display:flex;gap:6px}.__va-color-swatch{border:2px solid var(--va-border);border-radius:50%;cursor:pointer;flex-shrink:0;height:22px;outline:none;padding:0;transition:all .15s ease;width:22px}.__va-color-swatch:hover{transform:scale(1.15)}.__va-color-swatch--active{border-color:var(--va-text);transform:scale(1.15)}.__va-toggle{background:var(--va-border);border:none;border-radius:10px;cursor:pointer;flex-shrink:0;height:20px;outline:none;padding:0;position:relative;transition:background .15s ease;width:36px}.__va-toggle--active{background:var(--va-accent)}.__va-toggle:after{background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2);content:"";height:16px;left:2px;position:absolute;top:2px;transition:transform .15s ease;width:16px}.__va-toggle--active:after{transform:translateX(16px)}.__va-selection-rect{background:rgba(var(--va-accent-rgb),.06);border:2px dashed var(--va-accent);border-radius:2px;pointer-events:none;position:fixed;z-index:2147483645}.__va-copy-feedback{animation:__va-fadeIn .15s ease;background:var(--va-accent);border-radius:6px;bottom:72px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:#fff;font-size:13px;font-weight:500;padding:8px 16px;position:fixed;right:20px;z-index:2147483647}@keyframes __va-fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.__va-undo-feedback{align-items:center;animation:__va-fadeIn .15s ease;background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;bottom:72px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:var(--va-text);display:flex;font-size:13px;font-weight:500;gap:12px;padding:8px 12px 8px 16px;position:fixed;right:20px;z-index:2147483647}.__va-undo-feedback--shifted{bottom:112px}.__va-undo-btn{background:var(--va-accent);border:none;border-radius:4px;color:#fff;cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;outline:none;padding:4px 10px;transition:opacity .15s ease}.__va-undo-btn:hover{opacity:.85}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MentionCandidate {
|
|
2
|
+
id: string;
|
|
3
|
+
displayNumber: number;
|
|
4
|
+
commentPreview: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function createMentionChipHTML(id: string, displayNumber: number): string;
|
|
7
|
+
export declare function createDeletedMentionChipHTML(id: string): string;
|
|
8
|
+
export declare function serializeMentions(el: HTMLElement): string;
|
|
9
|
+
export declare function hydrateMentions(text: string, annotations: {
|
|
10
|
+
id: string;
|
|
11
|
+
displayNumber: number;
|
|
12
|
+
}[]): string;
|
|
13
|
+
export declare function extractMentionIds(text: string): string[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const MENTION_REGEX = /@\[(\d+)\]/g;
|
|
2
|
+
export function createMentionChipHTML(id, displayNumber) {
|
|
3
|
+
return `<span contenteditable="false" class="__va-mention" data-mention-id="${id}">@${displayNumber}</span>`;
|
|
4
|
+
}
|
|
5
|
+
export function createDeletedMentionChipHTML(id) {
|
|
6
|
+
return `<span contenteditable="false" class="__va-mention __va-mention--deleted" data-mention-id="${id}">@?</span>`;
|
|
7
|
+
}
|
|
8
|
+
export function serializeMentions(el) {
|
|
9
|
+
let result = "";
|
|
10
|
+
for (const node of el.childNodes) {
|
|
11
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
12
|
+
result += node.textContent || "";
|
|
13
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
14
|
+
const element = node;
|
|
15
|
+
if (element.classList.contains("__va-mention") && element.dataset.mentionId) {
|
|
16
|
+
result += `@[${element.dataset.mentionId}]`;
|
|
17
|
+
} else if (element.tagName === "BR") {
|
|
18
|
+
result += "\n";
|
|
19
|
+
} else {
|
|
20
|
+
const inner = serializeMentions(element);
|
|
21
|
+
if (element.tagName === "DIV" && result.length > 0 && !result.endsWith("\n"))
|
|
22
|
+
result += "\n";
|
|
23
|
+
result += inner;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
export function hydrateMentions(text, annotations) {
|
|
30
|
+
const lookup = new Map(annotations.map((a) => [a.id, a.displayNumber]));
|
|
31
|
+
return escapeHTML(text).replace(
|
|
32
|
+
/@\[(\d+)\]/g,
|
|
33
|
+
(_, id) => {
|
|
34
|
+
const displayNumber = lookup.get(id);
|
|
35
|
+
if (displayNumber != null)
|
|
36
|
+
return createMentionChipHTML(id, displayNumber);
|
|
37
|
+
return createDeletedMentionChipHTML(id);
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
export function extractMentionIds(text) {
|
|
42
|
+
return [...new Set(Array.from(text.matchAll(MENTION_REGEX), (m) => m[1]))];
|
|
43
|
+
}
|
|
44
|
+
function escapeHTML(str) {
|
|
45
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
46
|
+
}
|