agentation-vue 0.2.5 → 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.d.vue.ts +1 -0
- package/dist/AgentationVue.vue +212 -57
- package/dist/AgentationVue.vue.d.ts +1 -0
- 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 +5 -1
- package/dist/composables/useAnnotations.mjs +26 -4
- package/dist/composables/useElementDetection.mjs +2 -2
- package/dist/composables/useKeyboardShortcuts.mjs +25 -11
- package/dist/composables/useMentionDropdown.d.ts +21 -0
- package/dist/composables/useMentionDropdown.mjs +162 -0
- package/dist/composables/useMultiSelect.mjs +2 -2
- package/dist/composables/useSettings.d.ts +3 -0
- package/dist/composables/useSettings.mjs +19 -2
- package/dist/constants.d.ts +1 -0
- package/dist/constants.mjs +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +11 -2
- package/dist/styles/agentation.css +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/utils/agentation-tree.d.ts +2 -0
- package/dist/utils/agentation-tree.mjs +21 -0
- package/dist/utils/dom-inspector.d.ts +3 -0
- package/dist/utils/dom-inspector.mjs +11 -0
- package/dist/utils/mention.d.ts +13 -0
- package/dist/utils/mention.mjs +46 -0
- package/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { Annotation } from '../types';
|
|
1
|
+
import type { Annotation, StorageAdapter } from '../types';
|
|
2
2
|
declare function setScopeUrl(url: string): void;
|
|
3
3
|
declare function addAnnotation(annotation: Omit<Annotation, 'id' | 'timestamp'>): Annotation;
|
|
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;
|
|
8
|
+
export declare function setAnnotationStorage(adapter: StorageAdapter): void;
|
|
9
|
+
export declare function resetAnnotationStorage(): void;
|
|
7
10
|
export declare function useAnnotations(initialUrl?: string): {
|
|
8
11
|
annotations: import("vue-demi").Ref<{
|
|
9
12
|
id: string;
|
|
@@ -100,6 +103,7 @@ export declare function useAnnotations(initialUrl?: string): {
|
|
|
100
103
|
removeAnnotation: typeof removeAnnotation;
|
|
101
104
|
updateAnnotation: typeof updateAnnotation;
|
|
102
105
|
clearAnnotations: typeof clearAnnotations;
|
|
106
|
+
restoreAnnotations: typeof restoreAnnotations;
|
|
103
107
|
setScopeUrl: typeof setScopeUrl;
|
|
104
108
|
};
|
|
105
109
|
export {};
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { ref } from "vue-demi";
|
|
2
2
|
const STORAGE_KEY = "agentation-vue-annotations";
|
|
3
|
+
const fallbackAnnotationStorage = {
|
|
4
|
+
getItem(key) {
|
|
5
|
+
return sessionStorage.getItem(key);
|
|
6
|
+
},
|
|
7
|
+
setItem(key, value) {
|
|
8
|
+
sessionStorage.setItem(key, value);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
let annotationStorage = fallbackAnnotationStorage;
|
|
3
12
|
function serializeAnnotations(annotations2) {
|
|
4
13
|
return JSON.stringify(annotations2.map(({ _targetRef, ...rest }) => rest));
|
|
5
14
|
}
|
|
@@ -24,7 +33,7 @@ function parseStore(raw, currentUrl) {
|
|
|
24
33
|
}
|
|
25
34
|
function loadAnnotations(url) {
|
|
26
35
|
try {
|
|
27
|
-
const stored =
|
|
36
|
+
const stored = annotationStorage.getItem(STORAGE_KEY);
|
|
28
37
|
const store = parseStore(stored, url);
|
|
29
38
|
const annotations2 = store[url];
|
|
30
39
|
return Array.isArray(annotations2) ? annotations2 : [];
|
|
@@ -48,7 +57,7 @@ function setScopeUrl(url) {
|
|
|
48
57
|
}
|
|
49
58
|
function save() {
|
|
50
59
|
try {
|
|
51
|
-
const stored =
|
|
60
|
+
const stored = annotationStorage.getItem(STORAGE_KEY);
|
|
52
61
|
const store = parseStore(stored, scopedUrl);
|
|
53
62
|
if (annotations.value.length > 0)
|
|
54
63
|
store[scopedUrl] = annotations.value;
|
|
@@ -62,7 +71,7 @@ function save() {
|
|
|
62
71
|
])
|
|
63
72
|
)
|
|
64
73
|
);
|
|
65
|
-
|
|
74
|
+
annotationStorage.setItem(STORAGE_KEY, serialized);
|
|
66
75
|
} catch {
|
|
67
76
|
}
|
|
68
77
|
}
|
|
@@ -101,8 +110,21 @@ function clearAnnotations() {
|
|
|
101
110
|
save();
|
|
102
111
|
return cleared;
|
|
103
112
|
}
|
|
113
|
+
function restoreAnnotations(items) {
|
|
114
|
+
annotations.value.push(...items);
|
|
115
|
+
counter = getCounterSeed(annotations.value);
|
|
116
|
+
save();
|
|
117
|
+
}
|
|
104
118
|
setScopeUrl(scopedUrl);
|
|
119
|
+
export function setAnnotationStorage(adapter) {
|
|
120
|
+
annotationStorage = adapter;
|
|
121
|
+
setScopeUrl(scopedUrl);
|
|
122
|
+
}
|
|
123
|
+
export function resetAnnotationStorage() {
|
|
124
|
+
annotationStorage = fallbackAnnotationStorage;
|
|
125
|
+
setScopeUrl(scopedUrl);
|
|
126
|
+
}
|
|
105
127
|
export function useAnnotations(initialUrl = getCurrentUrl()) {
|
|
106
128
|
setScopeUrl(initialUrl);
|
|
107
|
-
return { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, setScopeUrl };
|
|
129
|
+
return { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, restoreAnnotations, setScopeUrl };
|
|
108
130
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref } from "vue-demi";
|
|
2
|
-
import {
|
|
2
|
+
import { isInsideAgentationTree } from "../utils/agentation-tree.mjs";
|
|
3
3
|
import { detectVueComponents } from "../utils/dom-inspector.mjs";
|
|
4
4
|
import { getElementName } from "../utils/selectors.mjs";
|
|
5
5
|
export function useElementDetection(overlayRef, showComponentTree) {
|
|
@@ -50,7 +50,7 @@ export function useElementDetection(overlayRef, showComponentTree) {
|
|
|
50
50
|
const el = getElementUnderOverlay(e);
|
|
51
51
|
if (el === lastElement)
|
|
52
52
|
return;
|
|
53
|
-
if (el
|
|
53
|
+
if (el && isInsideAgentationTree(el)) {
|
|
54
54
|
clearHighlight();
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { onBeforeUnmount, onMounted, watch } from "vue-demi";
|
|
2
|
-
import {
|
|
2
|
+
import { getDeepActiveElement, isInsideAgentationTree } from "../utils/agentation-tree.mjs";
|
|
3
3
|
const DEFAULT_KEYMAP = {
|
|
4
4
|
"activate": "",
|
|
5
5
|
"element-select": "v",
|
|
@@ -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
|
});
|
|
@@ -69,14 +70,14 @@ export function useKeyboardShortcuts(options) {
|
|
|
69
70
|
return "open";
|
|
70
71
|
}
|
|
71
72
|
function isForeignEditable() {
|
|
72
|
-
const active =
|
|
73
|
+
const active = getDeepActiveElement();
|
|
73
74
|
if (!active)
|
|
74
75
|
return false;
|
|
75
76
|
const tag = active.tagName.toLowerCase();
|
|
76
77
|
const isEditable = tag === "input" || tag === "textarea" || active.isContentEditable;
|
|
77
78
|
if (!isEditable)
|
|
78
79
|
return false;
|
|
79
|
-
return !active
|
|
80
|
+
return !isInsideAgentationTree(active);
|
|
80
81
|
}
|
|
81
82
|
function isBrowserCombo(e) {
|
|
82
83
|
if (!e.metaKey && !e.ctrlKey)
|
|
@@ -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
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref } from "vue-demi";
|
|
2
|
-
import {
|
|
2
|
+
import { isInsideAgentationTree } from "../utils/agentation-tree.mjs";
|
|
3
3
|
const LEAF_TAGS = /* @__PURE__ */ new Set(["button", "a", "input", "img"]);
|
|
4
4
|
export function useMultiSelect(mode, transitionFn) {
|
|
5
5
|
const selectionRect = ref(null);
|
|
@@ -11,7 +11,7 @@ export function useMultiSelect(mode, transitionFn) {
|
|
|
11
11
|
function cacheElements() {
|
|
12
12
|
cachedElements = [];
|
|
13
13
|
for (const el of document.querySelectorAll("body *")) {
|
|
14
|
-
if (el
|
|
14
|
+
if (isInsideAgentationTree(el))
|
|
15
15
|
continue;
|
|
16
16
|
const rect = el.getBoundingClientRect();
|
|
17
17
|
if (rect.width === 0 || rect.height === 0)
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../types';
|
|
2
|
+
export declare function setSettingsStorage(adapter: StorageAdapter): void;
|
|
3
|
+
export declare function resetSettingsStorage(): void;
|
|
1
4
|
export declare function useSettings(): {
|
|
2
5
|
settings: {
|
|
3
6
|
outputDetail: import("../types").OutputDetail;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { reactive, watch } from "vue-demi";
|
|
2
2
|
const STORAGE_KEY = "agentation-vue-settings";
|
|
3
|
+
const fallbackSettingsStorage = {
|
|
4
|
+
getItem(key) {
|
|
5
|
+
return localStorage.getItem(key);
|
|
6
|
+
},
|
|
7
|
+
setItem(key, value) {
|
|
8
|
+
localStorage.setItem(key, value);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
let settingsStorage = fallbackSettingsStorage;
|
|
3
12
|
const defaults = {
|
|
4
13
|
outputDetail: "standard",
|
|
5
14
|
markerColor: "#42B883",
|
|
@@ -13,7 +22,7 @@ const defaults = {
|
|
|
13
22
|
};
|
|
14
23
|
function loadSettings() {
|
|
15
24
|
try {
|
|
16
|
-
const stored =
|
|
25
|
+
const stored = settingsStorage.getItem(STORAGE_KEY);
|
|
17
26
|
if (stored)
|
|
18
27
|
return { ...defaults, ...JSON.parse(stored) };
|
|
19
28
|
} catch {
|
|
@@ -25,11 +34,19 @@ watch(
|
|
|
25
34
|
() => ({ ...settings }),
|
|
26
35
|
(val) => {
|
|
27
36
|
try {
|
|
28
|
-
|
|
37
|
+
settingsStorage.setItem(STORAGE_KEY, JSON.stringify(val));
|
|
29
38
|
} catch {
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
41
|
);
|
|
42
|
+
export function setSettingsStorage(adapter) {
|
|
43
|
+
settingsStorage = adapter;
|
|
44
|
+
Object.assign(settings, loadSettings());
|
|
45
|
+
}
|
|
46
|
+
export function resetSettingsStorage() {
|
|
47
|
+
settingsStorage = fallbackSettingsStorage;
|
|
48
|
+
Object.assign(settings, loadSettings());
|
|
49
|
+
}
|
|
33
50
|
export function useSettings() {
|
|
34
51
|
function resetSettings() {
|
|
35
52
|
Object.assign(settings, defaults);
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.mjs
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { default as VaIconButton } from './components/VaIconButton.vue';
|
|
|
13
13
|
export { default as VaToggle } from './components/VaToggle.vue';
|
|
14
14
|
export { vaTooltipDirective };
|
|
15
15
|
export { useAnimationPause } from './composables/useAnimationPause';
|
|
16
|
-
export { useAnnotations } from './composables/useAnnotations';
|
|
16
|
+
export { resetAnnotationStorage, setAnnotationStorage, useAnnotations, } from './composables/useAnnotations';
|
|
17
17
|
export { useAreaSelect } from './composables/useAreaSelect';
|
|
18
18
|
export { useElementDetection } from './composables/useElementDetection';
|
|
19
19
|
export { useInteractionMode } from './composables/useInteractionMode';
|
|
@@ -22,10 +22,12 @@ export type { DoubleTapConfig, KeyboardShortcutConfig, KeyboardShortcutState, Sh
|
|
|
22
22
|
export { useMarkerPositions } from './composables/useMarkerPositions';
|
|
23
23
|
export { useMultiSelect } from './composables/useMultiSelect';
|
|
24
24
|
export { formatAnnotations, useOutputFormatter } from './composables/useOutputFormatter';
|
|
25
|
-
export { useSettings } from './composables/useSettings';
|
|
25
|
+
export { resetSettingsStorage, setSettingsStorage, useSettings, } from './composables/useSettings';
|
|
26
26
|
export { useTextSelection } from './composables/useTextSelection';
|
|
27
27
|
export { useToolbarAutoHide } from './composables/useToolbarAutoHide';
|
|
28
28
|
export type { VaTooltipOptions, VaTooltipPlacement, VaTooltipValue } from './directives/vaTooltip';
|
|
29
29
|
export { icons } from './icons';
|
|
30
30
|
export type { IconName } from './icons';
|
|
31
|
-
export type { AgentationEmits, AgentationProps, Annotation, BoundingBox, ElementRef, InteractionMode, OutputDetail, Settings, } from './types';
|
|
31
|
+
export type { AgentationEmits, AgentationProps, Annotation, BoundingBox, ElementRef, InteractionMode, OutputDetail, Settings, StorageAdapter, } from './types';
|
|
32
|
+
export { resetVueDetector, setVueDetector } from './utils/dom-inspector';
|
|
33
|
+
export type { VueDetector } from './utils/dom-inspector';
|
package/dist/index.mjs
CHANGED
|
@@ -16,7 +16,11 @@ export { default as VaIconButton } from "./components/VaIconButton.vue";
|
|
|
16
16
|
export { default as VaToggle } from "./components/VaToggle.vue";
|
|
17
17
|
export { vaTooltipDirective };
|
|
18
18
|
export { useAnimationPause } from "./composables/useAnimationPause.mjs";
|
|
19
|
-
export {
|
|
19
|
+
export {
|
|
20
|
+
resetAnnotationStorage,
|
|
21
|
+
setAnnotationStorage,
|
|
22
|
+
useAnnotations
|
|
23
|
+
} from "./composables/useAnnotations.mjs";
|
|
20
24
|
export { useAreaSelect } from "./composables/useAreaSelect.mjs";
|
|
21
25
|
export { useElementDetection } from "./composables/useElementDetection.mjs";
|
|
22
26
|
export { useInteractionMode } from "./composables/useInteractionMode.mjs";
|
|
@@ -24,7 +28,12 @@ export { DEFAULT_SHORTCUT_CONFIG, useKeyboardShortcuts } from "./composables/use
|
|
|
24
28
|
export { useMarkerPositions } from "./composables/useMarkerPositions.mjs";
|
|
25
29
|
export { useMultiSelect } from "./composables/useMultiSelect.mjs";
|
|
26
30
|
export { formatAnnotations, useOutputFormatter } from "./composables/useOutputFormatter.mjs";
|
|
27
|
-
export {
|
|
31
|
+
export {
|
|
32
|
+
resetSettingsStorage,
|
|
33
|
+
setSettingsStorage,
|
|
34
|
+
useSettings
|
|
35
|
+
} from "./composables/useSettings.mjs";
|
|
28
36
|
export { useTextSelection } from "./composables/useTextSelection.mjs";
|
|
29
37
|
export { useToolbarAutoHide } from "./composables/useToolbarAutoHide.mjs";
|
|
30
38
|
export { icons } from "./icons.mjs";
|
|
39
|
+
export { resetVueDetector, setVueDetector } from "./utils/dom-inspector.mjs";
|