dsh-reference-anything 0.2.1 → 0.2.3
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/README.md +25 -13
- package/lib/client.js +334 -140
- package/lib/update.js +4 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
One `@` to reference them all.
|
|
10
10
|
|
|
11
|
-
**English** · [简体中文](./README_zh-CN.md) · [News](#news) · [Roadmap](#
|
|
11
|
+
**English** · [简体中文](./README_zh-CN.md) · [News](#news) · [Roadmap](#roadmap) · [Installation](#installation) · [Usage](#usage) · [Report Bug][github-issues-link]
|
|
12
12
|
|
|
13
13
|
<!-- SHIELD GROUP -->
|
|
14
14
|
|
|
@@ -24,11 +24,15 @@ One `@` to reference them all.
|
|
|
24
24
|
|
|
25
25
|
Within the unified `@` menu of DeepSeek Harness (DSH), reference commands, Skills, workspace files/folders, DSH sessions, and historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi.
|
|
26
26
|
|
|
27
|
-
This plugin uses OpenCLI to access historical conversations through AI chat sessions that are already logged in.
|
|
27
|
+
This plugin uses OpenCLI to access historical conversations through AI chat sessions that are already logged in. By default, only conversation titles are stored locally, and the agent fetches remote content on demand. An optional offline-mirror mode stores the latest complete conversation bodies locally.
|
|
28
|
+
|
|
29
|
+
> [!NOTE]
|
|
30
|
+
> DSH is currently in Beta, so its underlying capabilities and interfaces may change as it evolves. This plugin will adapt alongside those changes. Because of some current DSH limitations, parts of the implementation may not yet be ideal; we will continue to follow DSH updates and improve the plugin over time. See the relevant sections below for specific limitations and usage notes.
|
|
28
31
|
|
|
29
32
|
## News
|
|
30
33
|
|
|
31
|
-
- **2026-08-
|
|
34
|
+
- **2026-08-19 · v0.2.3** — Added automatic version checks and in-settings updates, Pill/Raw text input rendering modes, and reusable background browser sessions for more reliable OpenCLI synchronization and input interactions.
|
|
35
|
+
- **2026-08-18 · v0.2.0** — A redesigned Reference Anything settings page with local session statistics, paginated management, Provider/Profile selection, and sync status checks.
|
|
32
36
|
- **2026-08-18** — Introduced on-demand read protocol: references default to safe pointers, and the agent reads the body and attachments only after authorization.
|
|
33
37
|
- **2026-08-17** — Unified ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi under the DSH `@` menu.
|
|
34
38
|
|
|
@@ -57,33 +61,38 @@ dsh plugin --profile web add dsh-reference-anything
|
|
|
57
61
|
For development, the repository can still be installed from a local path:
|
|
58
62
|
|
|
59
63
|
```powershell
|
|
60
|
-
|
|
64
|
+
# Run from the repository root
|
|
65
|
+
dsh plugin --profile web add .
|
|
61
66
|
```
|
|
62
67
|
|
|
63
|
-
After installing the DSH plugin, open `Settings →
|
|
68
|
+
After installing the DSH plugin, open `Settings → Reference Anything → Availability check` in DSH Web (restart DSH first if this settings entry is not yet visible), then click **One-click setup**. It discovers OpenCLI; installs or upgrades it globally through npm when it is missing or older than `1.8.6`; installs the bundled adapters for all six platforms; starts or refreshes Browser Bridge; and opens the OpenCLI Browser Bridge page in the [Chrome Web Store](https://chromewebstore.google.com/detail/opencli/ildkmabpimmkaediidaifkhjpohdnifk). Confirm the extension installation, then return and click **Recheck setup**. Any remaining failed check displays its own recovery action.
|
|
64
69
|
|
|
65
|
-
You can also
|
|
70
|
+
You can also install OpenCLI and the conversation adapter manually, then start Browser Bridge:
|
|
66
71
|
|
|
67
72
|
```powershell
|
|
68
|
-
|
|
73
|
+
npm install --global "@jackwener/opencli@>=1.8.6"
|
|
74
|
+
opencli plugin install file:///C:/path/to/dsh-reference-anything/opencli-plugin
|
|
75
|
+
opencli daemon restart
|
|
69
76
|
```
|
|
70
77
|
|
|
71
|
-
Browser extensions cannot be silently installed from a webpage; confirm the installation in the Chrome Web Store or
|
|
78
|
+
Replace `C:/path/to/dsh-reference-anything` with the repository location. Browser extensions cannot be silently installed from a webpage; confirm the installation in the Chrome Web Store, or download an extension package from [OpenCLI Releases](https://github.com/jackwener/opencli/releases) and use “Load unpacked.” If the browser blocks the store popup, the settings page keeps a normal fallback link. When multiple browser profiles are connected, select and apply one directly in the failed check. Global npm installation remains subject to OS permissions; failures retain their original diagnostic in the settings page.
|
|
72
79
|
|
|
73
80
|
## Usage
|
|
74
81
|
|
|
75
|
-
1. Open `Settings →
|
|
76
|
-
2.
|
|
77
|
-
3.
|
|
82
|
+
1. Open `Settings → Reference Anything` in DSH Web.
|
|
83
|
+
2. Under **Availability check**, confirm that OpenCLI, Browser Bridge, the browser extension, and the conversation adapter are ready.
|
|
84
|
+
3. Under **External conversation sync settings**, choose a connected browser Profile, history storage mode, and sync mode. Then click **Sync enabled sources now**, or sync an individual Provider from its card.
|
|
78
85
|
4. Type `@` in the input box and choose from the `Files and folders`, `DSH sessions`, or `External conversations` groups.
|
|
79
86
|
5. Type a keyword to filter candidates, for example `@cache-design`.
|
|
80
87
|
|
|
88
|
+
The default **Read bodies on demand** mode stores only the title index locally and uses the browser when an agent reads a reference. Choose **Store full bodies locally** for offline reading and full-text search; this mode keeps only the latest version of each conversation. The settings page also lets you enable or disable each `@` group, reorder groups, set their maximum result counts (six per group by default), and switch between Pill and Raw text input rendering. The plugin checks npm for updates when it loads; restart DSH after installing an update from the settings page.
|
|
89
|
+
|
|
81
90
|
> [!WARNING]
|
|
82
91
|
> To protect your account and conversation data, external conversations are imported and synchronized through OpenCLI using your existing logged-in browser session. A browser window may temporarily open during use or synchronization, and it may display OpenCLI debugging information. This is expected—please do not be alarmed or close the window manually; wait for the operation to finish.
|
|
83
92
|
|
|
84
93
|
### Search
|
|
85
94
|
|
|
86
|
-
The `@` menu contains five groups: `Commands`, `Skills`, `Files and folders`, `DSH sessions`, and `External conversations`. The first two appear only when `@` is at the beginning of the draft. Each group shows up to
|
|
95
|
+
The `@` menu contains five groups: `Commands`, `Skills`, `Files and folders`, `DSH sessions`, and `External conversations`. The first two appear only when `@` is at the beginning of the draft. Each group shows up to six results by default. Under `Settings → Reference Anything → General`, you can enable or disable groups, reorder them, and set each limit from 1 to 50.
|
|
87
96
|
|
|
88
97
|
#### @Commands — DSH native commands
|
|
89
98
|
|
|
@@ -124,7 +133,7 @@ Supports historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok,
|
|
|
124
133
|
|
|
125
134
|
**Search capabilities:**
|
|
126
135
|
- **Title match:** fuzzy search on conversation titles
|
|
127
|
-
- **Content search:** if title matches are insufficient,
|
|
136
|
+
- **Content search:** available only in **Store full bodies locally** mode; if title matches are insufficient, synchronized bodies are searched and matching excerpts are shown in the candidate list
|
|
128
137
|
- **Provider and account isolation:** history is maintained separately by Provider and account scope
|
|
129
138
|
- `@` search uses the account scope cached by the latest sync and never probes the browser; after a sync observes an account switch, it exposes only that account while older rows remain available in conversation management for cleanup
|
|
130
139
|
|
|
@@ -135,6 +144,9 @@ Supports historical conversations from ChatGPT, Claude, Gemini, DeepSeek, Grok,
|
|
|
135
144
|
|
|
136
145
|
Opening the source URL happens only in the UI; the URL is never injected into model context. The initial reference contains only a safe pointer; if the model needs the body, it calls `reference_read` on demand.
|
|
137
146
|
|
|
147
|
+
> [!NOTE]
|
|
148
|
+
> Due to DSH's current underlying length limit for References, external-conversation references temporarily use a compatibility layer implemented by this plugin instead of relying entirely on DSH's native reference presentation. As a result, some interactions may behave differently or encounter issues in certain scenarios. We have raised this limitation in DSH Discussions; when a future DSH release provides the necessary support, we will update the plugin promptly and migrate to a more native implementation.
|
|
149
|
+
|
|
138
150
|
---
|
|
139
151
|
|
|
140
152
|
**General notes:**
|
package/lib/client.js
CHANGED
|
@@ -17074,6 +17074,7 @@ function adoptAdaptiveComposerHeight() {
|
|
|
17074
17074
|
document.querySelectorAll("[data-dsh-ref-height-ruler]").forEach((node) => node.remove());
|
|
17075
17075
|
};
|
|
17076
17076
|
}
|
|
17077
|
+
var caretVisualAffinity = /* @__PURE__ */ new WeakMap();
|
|
17077
17078
|
function logicalOffsetFromPoint(input, backdrop, clientX, clientY) {
|
|
17078
17079
|
const previousVisibility = input.style.getPropertyValue("visibility");
|
|
17079
17080
|
const previousVisibilityPriority = input.style.getPropertyPriority("visibility");
|
|
@@ -17100,24 +17101,85 @@ function logicalOffsetFromPoint(input, backdrop, clientX, clientY) {
|
|
|
17100
17101
|
const lineHeight = Number.parseFloat(style.lineHeight);
|
|
17101
17102
|
const fontSize = Number.parseFloat(style.fontSize);
|
|
17102
17103
|
const resolvedLineHeight = lineHeight > 0 ? lineHeight : fontSize > 0 ? fontSize * 1.2 : 20;
|
|
17103
|
-
if (clamped !== void 0 && preciseNode)
|
|
17104
|
-
|
|
17104
|
+
if (clamped !== void 0 && preciseNode) {
|
|
17105
|
+
rememberCaretAffinityAtPoint(input, backdrop, clamped, clientX, clientY);
|
|
17106
|
+
return clamped;
|
|
17107
|
+
}
|
|
17108
|
+
if (clamped !== void 0 && node === backdrop && logicalEndpointMatchesPoint(backdrop, clamped, input.value.length, clientX, clientY, resolvedLineHeight)) {
|
|
17109
|
+
rememberCaretAffinityAtPoint(input, backdrop, clamped, clientX, clientY);
|
|
17110
|
+
return clamped;
|
|
17111
|
+
}
|
|
17112
|
+
const fallback2 = nearestVisualLogicalOffset(backdrop, input.value.length, clientX, clientY, resolvedLineHeight) ?? clamped;
|
|
17113
|
+
if (fallback2 !== void 0) rememberCaretAffinityAtPoint(input, backdrop, fallback2, clientX, clientY);
|
|
17114
|
+
return fallback2;
|
|
17105
17115
|
}
|
|
17106
17116
|
function restoreInlineProperty(style, name, value, priority) {
|
|
17107
17117
|
if (value === "") style.removeProperty(name);
|
|
17108
17118
|
else style.setProperty(name, value, priority);
|
|
17109
17119
|
}
|
|
17110
17120
|
function nearestVisualLogicalOffset(root, maxLogical, clientX, clientY, lineHeight) {
|
|
17111
|
-
const boundaries = visualLogicalBoundaries(root, maxLogical, lineHeight);
|
|
17112
|
-
if (boundaries.length === 0) return void 0;
|
|
17113
17121
|
const style = getComputedStyle(root);
|
|
17114
17122
|
const paddingTop = Number.parseFloat(style.paddingTop) || 0;
|
|
17115
17123
|
const contentTop = root.getBoundingClientRect().top + paddingTop;
|
|
17116
17124
|
const targetLine = Math.floor((clientY - contentTop) / lineHeight);
|
|
17117
17125
|
const lineOf = (boundary) => Math.round((boundary.top - contentTop) / lineHeight);
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17126
|
+
const cache = /* @__PURE__ */ new Map();
|
|
17127
|
+
const boundaryAt = (offset, affinity = "forward") => {
|
|
17128
|
+
const at = Math.min(Math.max(offset, 0), maxLogical);
|
|
17129
|
+
const key = `${at}:${affinity}`;
|
|
17130
|
+
if (cache.has(key)) return cache.get(key);
|
|
17131
|
+
const rect = visualCaretRectAtLogicalOffset(root, at, affinity);
|
|
17132
|
+
const boundary = rect === void 0 ? void 0 : { offset: at, left: rect.left, top: rect.top, height: rect.height ?? lineHeight };
|
|
17133
|
+
cache.set(key, boundary);
|
|
17134
|
+
return boundary;
|
|
17135
|
+
};
|
|
17136
|
+
const first = boundaryAt(0);
|
|
17137
|
+
const last = boundaryAt(maxLogical);
|
|
17138
|
+
if (first === void 0 || last === void 0) return void 0;
|
|
17139
|
+
const firstLine = lineOf(first);
|
|
17140
|
+
const lastLine = lineOf(last);
|
|
17141
|
+
const wantedLine = Math.min(Math.max(targetLine, Math.min(firstLine, lastLine)), Math.max(firstLine, lastLine));
|
|
17142
|
+
const firstOffsetOnOrAfterLine = (line) => {
|
|
17143
|
+
let low = 0;
|
|
17144
|
+
let high = maxLogical;
|
|
17145
|
+
while (low < high) {
|
|
17146
|
+
const middle = Math.floor((low + high) / 2);
|
|
17147
|
+
const boundary = boundaryAt(middle);
|
|
17148
|
+
if (boundary === void 0 || lineOf(boundary) >= line) high = middle;
|
|
17149
|
+
else low = middle + 1;
|
|
17150
|
+
}
|
|
17151
|
+
return low;
|
|
17152
|
+
};
|
|
17153
|
+
const start = firstOffsetOnOrAfterLine(wantedLine);
|
|
17154
|
+
const after = firstOffsetOnOrAfterLine(wantedLine + 1);
|
|
17155
|
+
const end = after === start && lineOf(last) === wantedLine ? maxLogical : Math.max(start, after - 1);
|
|
17156
|
+
const candidates = /* @__PURE__ */ new Set([start, end]);
|
|
17157
|
+
const span = end - start;
|
|
17158
|
+
if (span <= 512) {
|
|
17159
|
+
for (let offset = start; offset <= end; offset++) candidates.add(offset);
|
|
17160
|
+
} else {
|
|
17161
|
+
const rtl = style.direction === "rtl";
|
|
17162
|
+
let low = start;
|
|
17163
|
+
let high = end;
|
|
17164
|
+
while (low <= high) {
|
|
17165
|
+
const middle = Math.floor((low + high) / 2);
|
|
17166
|
+
const boundary = boundaryAt(middle);
|
|
17167
|
+
if (boundary === void 0) {
|
|
17168
|
+
high = middle - 1;
|
|
17169
|
+
continue;
|
|
17170
|
+
}
|
|
17171
|
+
candidates.add(middle);
|
|
17172
|
+
const beforePoint = rtl ? boundary.left > clientX : boundary.left < clientX;
|
|
17173
|
+
if (beforePoint) low = middle + 1;
|
|
17174
|
+
else high = middle - 1;
|
|
17175
|
+
}
|
|
17176
|
+
for (const offset of [low - 2, low - 1, low, low + 1, low + 2]) {
|
|
17177
|
+
if (offset >= start && offset <= end) candidates.add(offset);
|
|
17178
|
+
}
|
|
17179
|
+
}
|
|
17180
|
+
candidates.add(after);
|
|
17181
|
+
const onLine = Array.from(candidates).flatMap((offset) => [boundaryAt(offset, "backward"), boundaryAt(offset, "forward")]).filter((boundary) => boundary !== void 0 && lineOf(boundary) === wantedLine);
|
|
17182
|
+
if (onLine.length === 0) return Math.abs(targetLine - firstLine) <= Math.abs(targetLine - lastLine) ? first.offset : last.offset;
|
|
17121
17183
|
return onLine.reduce((best, boundary) => {
|
|
17122
17184
|
const distance = Math.abs(boundary.left - clientX);
|
|
17123
17185
|
const bestDistance = Math.abs(best.left - clientX);
|
|
@@ -17125,51 +17187,27 @@ function nearestVisualLogicalOffset(root, maxLogical, clientX, clientY, lineHeig
|
|
|
17125
17187
|
return boundary.offset > best.offset ? boundary : best;
|
|
17126
17188
|
}).offset;
|
|
17127
17189
|
}
|
|
17128
|
-
function
|
|
17129
|
-
|
|
17130
|
-
const
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
if (
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
const text = child.data;
|
|
17150
|
-
let local = 0;
|
|
17151
|
-
for (const symbol2 of text) {
|
|
17152
|
-
const next = local + symbol2.length;
|
|
17153
|
-
const range = document.createRange();
|
|
17154
|
-
range.setStart(child, local);
|
|
17155
|
-
range.setEnd(child, next);
|
|
17156
|
-
const rect = range.getBoundingClientRect();
|
|
17157
|
-
if (usableCaretRect(rect)) {
|
|
17158
|
-
const start = direction === "rtl" ? rect.right : rect.left;
|
|
17159
|
-
const end = direction === "rtl" ? rect.left : rect.right;
|
|
17160
|
-
add(logical + local, start, rect.top, rect.height);
|
|
17161
|
-
add(logical + next, end, rect.top, rect.height);
|
|
17162
|
-
}
|
|
17163
|
-
local = next;
|
|
17164
|
-
}
|
|
17165
|
-
logical += text.length;
|
|
17166
|
-
continue;
|
|
17167
|
-
}
|
|
17168
|
-
visit(child);
|
|
17169
|
-
}
|
|
17170
|
-
};
|
|
17171
|
-
visit(root);
|
|
17172
|
-
return boundaries;
|
|
17190
|
+
function logicalEndpointMatchesPoint(root, offset, maxLogical, clientX, clientY, lineHeight) {
|
|
17191
|
+
if (offset !== 0 && offset !== maxLogical) return false;
|
|
17192
|
+
const rtl = getComputedStyle(root).direction === "rtl";
|
|
17193
|
+
return ["backward", "forward"].some((affinity) => {
|
|
17194
|
+
const boundary = visualCaretRectAtLogicalOffset(root, offset, affinity);
|
|
17195
|
+
if (boundary === void 0) return false;
|
|
17196
|
+
const sameLine = clientY >= boundary.top - lineHeight / 2 && clientY <= boundary.top + lineHeight * 1.5;
|
|
17197
|
+
if (!sameLine) return false;
|
|
17198
|
+
if (offset === 0) return rtl ? clientX >= boundary.left - 1 : clientX <= boundary.left + 1;
|
|
17199
|
+
return rtl ? clientX <= boundary.left + 1 : clientX >= boundary.left - 1;
|
|
17200
|
+
});
|
|
17201
|
+
}
|
|
17202
|
+
function rememberCaretAffinityAtPoint(input, root, offset, clientX, clientY) {
|
|
17203
|
+
const choices = ["backward", "forward"].map((affinity) => ({ affinity, rect: visualCaretRectAtLogicalOffset(root, offset, affinity) })).filter((choice) => choice.rect !== void 0);
|
|
17204
|
+
if (choices.length === 0) return;
|
|
17205
|
+
const best = choices.reduce((nearest, choice) => {
|
|
17206
|
+
const distance = Math.abs(choice.rect.top + (choice.rect.height ?? 0) / 2 - clientY) * 1e3 + Math.abs(choice.rect.left - clientX);
|
|
17207
|
+
const nearestDistance = Math.abs(nearest.rect.top + (nearest.rect.height ?? 0) / 2 - clientY) * 1e3 + Math.abs(nearest.rect.left - clientX);
|
|
17208
|
+
return distance < nearestDistance ? choice : nearest;
|
|
17209
|
+
});
|
|
17210
|
+
caretVisualAffinity.set(input, best.affinity);
|
|
17173
17211
|
}
|
|
17174
17212
|
function logicalOffsetAtDomPoint(root, node, nodeOffset, clientX) {
|
|
17175
17213
|
if (!root.contains(node) && node !== root) return void 0;
|
|
@@ -17194,56 +17232,120 @@ function logicalOffsetAtDomPoint(root, node, nodeOffset, clientX) {
|
|
|
17194
17232
|
}
|
|
17195
17233
|
function adoptAdaptiveChipHitTesting() {
|
|
17196
17234
|
let press;
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
input.setSelectionRange(
|
|
17235
|
+
let clickSequence;
|
|
17236
|
+
const applyRange = (input, start, end, direction) => {
|
|
17237
|
+
input.setSelectionRange(start, end, direction);
|
|
17200
17238
|
input.dispatchEvent(new Event("select", { bubbles: true }));
|
|
17201
17239
|
};
|
|
17240
|
+
const applyTarget = (current, target, clientY) => {
|
|
17241
|
+
if (current.unit === "character") {
|
|
17242
|
+
applyRange(current.input, Math.min(current.anchor, target), Math.max(current.anchor, target), target < current.anchor ? "backward" : "forward");
|
|
17243
|
+
return;
|
|
17244
|
+
}
|
|
17245
|
+
const targetRange = current.unit === "word" ? wordRangeAtLogicalOffset(current.input.value, target) : visualLineRangeAtPoint(current.input, current.backdrop, clientY);
|
|
17246
|
+
if (targetRange === void 0) return;
|
|
17247
|
+
if (targetRange.end <= current.anchorStart) applyRange(current.input, targetRange.start, current.anchorEnd, "backward");
|
|
17248
|
+
else applyRange(current.input, current.anchorStart, targetRange.end, "forward");
|
|
17249
|
+
};
|
|
17250
|
+
const scrollDelta = (current) => {
|
|
17251
|
+
const scrollport = current.input.closest("[data-input-scroll]");
|
|
17252
|
+
if (!(scrollport instanceof HTMLElement) || scrollport.scrollHeight <= scrollport.clientHeight) return 0;
|
|
17253
|
+
const viewport = scrollport.getBoundingClientRect();
|
|
17254
|
+
const edge = Math.min(32, viewport.height / 4);
|
|
17255
|
+
if (!(edge > 0)) return 0;
|
|
17256
|
+
if (current.clientY < viewport.top + edge) return -Math.max(2, Math.min(24, (viewport.top + edge - current.clientY) / 2));
|
|
17257
|
+
if (current.clientY > viewport.bottom - edge) return Math.max(2, Math.min(24, (current.clientY - (viewport.bottom - edge)) / 2));
|
|
17258
|
+
return 0;
|
|
17259
|
+
};
|
|
17260
|
+
const scheduleDragScroll = (current) => {
|
|
17261
|
+
if (current.scrollFrame !== 0 || scrollDelta(current) === 0) return;
|
|
17262
|
+
current.scrollFrame = requestAnimationFrame(() => {
|
|
17263
|
+
current.scrollFrame = 0;
|
|
17264
|
+
if (press !== current) return;
|
|
17265
|
+
const scrollport = current.input.closest("[data-input-scroll]");
|
|
17266
|
+
if (!(scrollport instanceof HTMLElement)) return;
|
|
17267
|
+
const before = scrollport.scrollTop;
|
|
17268
|
+
scrollport.scrollTop += scrollDelta(current);
|
|
17269
|
+
if (scrollport.scrollTop !== before) {
|
|
17270
|
+
const target = logicalOffsetFromPoint(current.input, current.backdrop, current.clientX, current.clientY);
|
|
17271
|
+
if (target !== void 0) applyTarget(current, target, current.clientY);
|
|
17272
|
+
scheduleDragScroll(current);
|
|
17273
|
+
}
|
|
17274
|
+
});
|
|
17275
|
+
};
|
|
17276
|
+
const finishPress = () => {
|
|
17277
|
+
const current = press;
|
|
17278
|
+
press = void 0;
|
|
17279
|
+
if (current === void 0) return void 0;
|
|
17280
|
+
cancelAnimationFrame(current.scrollFrame);
|
|
17281
|
+
if (Math.hypot(current.clientX - current.startX, current.clientY - current.startY) > 5) clickSequence = void 0;
|
|
17282
|
+
try {
|
|
17283
|
+
if (current.input.hasPointerCapture?.(current.pointerId)) current.input.releasePointerCapture(current.pointerId);
|
|
17284
|
+
} catch {
|
|
17285
|
+
}
|
|
17286
|
+
return current;
|
|
17287
|
+
};
|
|
17202
17288
|
const onPointerDown = (event) => {
|
|
17203
17289
|
const input = event.target;
|
|
17204
|
-
if (event.button !== 0 || !(input instanceof HTMLTextAreaElement)) return;
|
|
17290
|
+
if (event.button !== 0 || event.pointerType === "touch" || !(input instanceof HTMLTextAreaElement)) return;
|
|
17205
17291
|
const backdrop = input.closest("[data-composer-card]")?.querySelector("[data-input-backdrop]:not([data-dsh-ref-height-ruler])");
|
|
17206
17292
|
if (!(backdrop instanceof HTMLElement) || backdrop.querySelector('[data-decoration="chip"]') === null) return;
|
|
17207
17293
|
const target = logicalOffsetFromPoint(input, backdrop, event.clientX, event.clientY);
|
|
17208
17294
|
if (target === void 0) return;
|
|
17209
17295
|
const anchor = event.shiftKey ? input.selectionDirection === "backward" ? input.selectionEnd : input.selectionStart : target;
|
|
17210
|
-
|
|
17211
|
-
|
|
17296
|
+
const previousClick = clickSequence;
|
|
17297
|
+
const continuedClick = previousClick !== void 0 && previousClick.input === input && event.timeStamp - previousClick.time <= 500 && Math.hypot(event.clientX - previousClick.x, event.clientY - previousClick.y) <= 5;
|
|
17298
|
+
const sequenceCount = continuedClick ? previousClick.count + 1 : 1;
|
|
17299
|
+
clickSequence = { input, x: event.clientX, y: event.clientY, time: event.timeStamp, count: sequenceCount };
|
|
17300
|
+
const clickCount = Math.max(sequenceCount, event.detail);
|
|
17301
|
+
const unit = clickCount >= 3 ? "line" : clickCount === 2 ? "word" : "character";
|
|
17302
|
+
const unitRange = unit === "word" ? wordRangeAtLogicalOffset(input.value, target) : unit === "line" ? visualLineRangeAtPoint(input, backdrop, event.clientY) : void 0;
|
|
17303
|
+
const anchorStart = unitRange?.start ?? anchor;
|
|
17304
|
+
const anchorEnd = unitRange?.end ?? anchor;
|
|
17305
|
+
finishPress();
|
|
17306
|
+
input.focus({ preventScroll: true });
|
|
17307
|
+
press = { input, backdrop, anchor, anchorStart, anchorEnd, unit, clientX: event.clientX, clientY: event.clientY, startX: event.clientX, startY: event.clientY, pointerId: event.pointerId, scrollFrame: 0 };
|
|
17308
|
+
try {
|
|
17309
|
+
input.setPointerCapture?.(event.pointerId);
|
|
17310
|
+
} catch {
|
|
17311
|
+
}
|
|
17312
|
+
if (press !== void 0) applyTarget(press, target, event.clientY);
|
|
17212
17313
|
event.preventDefault();
|
|
17213
17314
|
};
|
|
17214
17315
|
const onPointerMove = (event) => {
|
|
17215
17316
|
const current = press;
|
|
17216
17317
|
if (current === void 0) return;
|
|
17318
|
+
current.clientX = event.clientX;
|
|
17319
|
+
current.clientY = event.clientY;
|
|
17320
|
+
if (Math.hypot(current.clientX - current.startX, current.clientY - current.startY) > 5) clickSequence = void 0;
|
|
17217
17321
|
const target = logicalOffsetFromPoint(current.input, current.backdrop, event.clientX, event.clientY);
|
|
17218
17322
|
if (target === void 0) return;
|
|
17219
|
-
|
|
17323
|
+
applyTarget(current, target, event.clientY);
|
|
17324
|
+
scheduleDragScroll(current);
|
|
17220
17325
|
event.preventDefault();
|
|
17221
17326
|
};
|
|
17222
17327
|
const onPointerUp = (event) => {
|
|
17223
|
-
const current =
|
|
17224
|
-
press = void 0;
|
|
17328
|
+
const current = finishPress();
|
|
17225
17329
|
if (current === void 0) return;
|
|
17226
17330
|
const target = logicalOffsetFromPoint(current.input, current.backdrop, event.clientX, event.clientY);
|
|
17227
|
-
if (target !== void 0)
|
|
17331
|
+
if (target !== void 0) applyTarget(current, target, event.clientY);
|
|
17228
17332
|
event.preventDefault();
|
|
17229
17333
|
};
|
|
17230
17334
|
const onPointerCancel = () => {
|
|
17231
|
-
|
|
17335
|
+
finishPress();
|
|
17232
17336
|
};
|
|
17233
17337
|
const onDoubleClick = (event) => {
|
|
17234
17338
|
const input = event.target;
|
|
17235
17339
|
if (!(input instanceof HTMLTextAreaElement)) return;
|
|
17236
17340
|
const backdrop = input.closest("[data-composer-card]")?.querySelector("[data-input-backdrop]:not([data-dsh-ref-height-ruler])");
|
|
17237
17341
|
if (!(backdrop instanceof HTMLElement) || backdrop.querySelector('[data-decoration="chip"]') === null) return;
|
|
17238
|
-
const
|
|
17239
|
-
|
|
17240
|
-
|
|
17241
|
-
const applyWord = () => {
|
|
17242
|
-
input.setSelectionRange(selected.start, selected.end);
|
|
17342
|
+
const selected = { start: input.selectionStart, end: input.selectionEnd, direction: input.selectionDirection };
|
|
17343
|
+
const restoreWordSelection = () => {
|
|
17344
|
+
input.setSelectionRange(selected.start, selected.end, selected.direction);
|
|
17243
17345
|
input.dispatchEvent(new Event("select", { bubbles: true }));
|
|
17244
17346
|
};
|
|
17245
|
-
|
|
17246
|
-
queueMicrotask(
|
|
17347
|
+
restoreWordSelection();
|
|
17348
|
+
queueMicrotask(restoreWordSelection);
|
|
17247
17349
|
event.preventDefault();
|
|
17248
17350
|
event.stopImmediatePropagation();
|
|
17249
17351
|
};
|
|
@@ -17252,18 +17354,13 @@ function adoptAdaptiveChipHitTesting() {
|
|
|
17252
17354
|
const input = event.target;
|
|
17253
17355
|
const backdrop = input.closest("[data-composer-card]")?.querySelector("[data-input-backdrop]:not([data-dsh-ref-height-ruler])");
|
|
17254
17356
|
if (!(backdrop instanceof HTMLElement) || backdrop.querySelector('[data-decoration="chip"]') === null) return;
|
|
17255
|
-
const
|
|
17256
|
-
const
|
|
17257
|
-
|
|
17258
|
-
const start = logicalOffsetFromPoint(input, backdrop, left, event.clientY);
|
|
17259
|
-
const end = logicalOffsetFromPoint(input, backdrop, right, event.clientY);
|
|
17260
|
-
if (start === void 0 || end === void 0) return;
|
|
17261
|
-
const applyLine = () => {
|
|
17262
|
-
input.setSelectionRange(Math.min(start, end), Math.max(start, end));
|
|
17357
|
+
const selected = { start: input.selectionStart, end: input.selectionEnd, direction: input.selectionDirection };
|
|
17358
|
+
const restoreLineSelection = () => {
|
|
17359
|
+
input.setSelectionRange(selected.start, selected.end, selected.direction);
|
|
17263
17360
|
input.dispatchEvent(new Event("select", { bubbles: true }));
|
|
17264
17361
|
};
|
|
17265
|
-
|
|
17266
|
-
queueMicrotask(
|
|
17362
|
+
restoreLineSelection();
|
|
17363
|
+
queueMicrotask(restoreLineSelection);
|
|
17267
17364
|
event.preventDefault();
|
|
17268
17365
|
event.stopImmediatePropagation();
|
|
17269
17366
|
};
|
|
@@ -17271,16 +17368,21 @@ function adoptAdaptiveChipHitTesting() {
|
|
|
17271
17368
|
document.addEventListener("pointermove", onPointerMove, true);
|
|
17272
17369
|
document.addEventListener("pointerup", onPointerUp, true);
|
|
17273
17370
|
document.addEventListener("pointercancel", onPointerCancel, true);
|
|
17371
|
+
document.addEventListener("lostpointercapture", onPointerCancel, true);
|
|
17274
17372
|
document.addEventListener("dblclick", onDoubleClick, true);
|
|
17275
17373
|
document.addEventListener("click", onClick, true);
|
|
17374
|
+
window.addEventListener("blur", onPointerCancel);
|
|
17276
17375
|
return () => {
|
|
17277
|
-
|
|
17376
|
+
finishPress();
|
|
17377
|
+
clickSequence = void 0;
|
|
17278
17378
|
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
17279
17379
|
document.removeEventListener("pointermove", onPointerMove, true);
|
|
17280
17380
|
document.removeEventListener("pointerup", onPointerUp, true);
|
|
17281
17381
|
document.removeEventListener("pointercancel", onPointerCancel, true);
|
|
17382
|
+
document.removeEventListener("lostpointercapture", onPointerCancel, true);
|
|
17282
17383
|
document.removeEventListener("dblclick", onDoubleClick, true);
|
|
17283
17384
|
document.removeEventListener("click", onClick, true);
|
|
17385
|
+
window.removeEventListener("blur", onPointerCancel);
|
|
17284
17386
|
};
|
|
17285
17387
|
}
|
|
17286
17388
|
function wordRangeAtLogicalOffset(value, offset) {
|
|
@@ -17289,6 +17391,13 @@ function wordRangeAtLogicalOffset(value, offset) {
|
|
|
17289
17391
|
if (chip >= 0) return { start: chip, end: chip + 1 };
|
|
17290
17392
|
const pivot = at === value.length && at > 0 ? at - 1 : at;
|
|
17291
17393
|
if (pivot < 0 || pivot >= value.length) return { start: at, end: at };
|
|
17394
|
+
if (typeof Intl.Segmenter === "function") {
|
|
17395
|
+
for (const part of new Intl.Segmenter(void 0, { granularity: "word" }).segment(value)) {
|
|
17396
|
+
const start2 = part.index;
|
|
17397
|
+
const end2 = start2 + part.segment.length;
|
|
17398
|
+
if (pivot >= start2 && pivot < end2) return { start: start2, end: end2 };
|
|
17399
|
+
}
|
|
17400
|
+
}
|
|
17292
17401
|
const kind = (char) => /[\p{L}\p{N}\p{M}_]/u.test(char) ? "word" : /\s/u.test(char) ? "space" : "punctuation";
|
|
17293
17402
|
const selectedKind = kind(value[pivot] ?? "");
|
|
17294
17403
|
let start = pivot;
|
|
@@ -17297,16 +17406,43 @@ function wordRangeAtLogicalOffset(value, offset) {
|
|
|
17297
17406
|
while (end < value.length && kind(value[end] ?? "") === selectedKind && value[end] !== "\uFFFC") end++;
|
|
17298
17407
|
return { start, end };
|
|
17299
17408
|
}
|
|
17409
|
+
function visualLineRangeAtPoint(input, backdrop, clientY) {
|
|
17410
|
+
const style = getComputedStyle(input);
|
|
17411
|
+
const rect = backdrop.getBoundingClientRect();
|
|
17412
|
+
const left = rect.left + (Number.parseFloat(style.paddingLeft) || 0) + 1;
|
|
17413
|
+
const right = rect.right - (Number.parseFloat(style.paddingRight) || 0) - 1;
|
|
17414
|
+
const parsedLineHeight = Number.parseFloat(style.lineHeight);
|
|
17415
|
+
const fontSize = Number.parseFloat(style.fontSize);
|
|
17416
|
+
const lineHeight = parsedLineHeight > 0 ? parsedLineHeight : fontSize > 0 ? fontSize * 1.2 : 20;
|
|
17417
|
+
const first = nearestVisualLogicalOffset(backdrop, input.value.length, left, clientY, lineHeight) ?? logicalOffsetFromPoint(input, backdrop, left, clientY);
|
|
17418
|
+
const last = nearestVisualLogicalOffset(backdrop, input.value.length, right, clientY, lineHeight) ?? logicalOffsetFromPoint(input, backdrop, right, clientY);
|
|
17419
|
+
if (first === void 0 || last === void 0) return void 0;
|
|
17420
|
+
const start = Math.min(first, last);
|
|
17421
|
+
let end = Math.max(first, last);
|
|
17422
|
+
if (input.value[end] === "\n") end++;
|
|
17423
|
+
return { start, end };
|
|
17424
|
+
}
|
|
17300
17425
|
function adoptAdaptiveChipKeyboardNavigation() {
|
|
17301
17426
|
let preferredX;
|
|
17427
|
+
let preferredFocus;
|
|
17302
17428
|
const reset = () => {
|
|
17303
17429
|
preferredX = void 0;
|
|
17430
|
+
preferredFocus = void 0;
|
|
17304
17431
|
};
|
|
17305
17432
|
const onKeyDown = (event) => {
|
|
17306
17433
|
const vertical = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "PageUp" || event.key === "PageDown";
|
|
17307
17434
|
const lineEdge = event.key === "Home" || event.key === "End";
|
|
17308
17435
|
if (!vertical && !lineEdge) {
|
|
17309
17436
|
reset();
|
|
17437
|
+
if ((event.key === "ArrowLeft" || event.key === "ArrowRight") && event.target instanceof HTMLTextAreaElement) {
|
|
17438
|
+
const input2 = event.target;
|
|
17439
|
+
const chip = input2.closest("[data-composer-card]")?.querySelector('[data-decoration="chip"]');
|
|
17440
|
+
if (chip instanceof Element) {
|
|
17441
|
+
queueMicrotask(() => {
|
|
17442
|
+
caretVisualAffinity.set(input2, event.key === "ArrowLeft" ? "backward" : "forward");
|
|
17443
|
+
});
|
|
17444
|
+
}
|
|
17445
|
+
}
|
|
17310
17446
|
return;
|
|
17311
17447
|
}
|
|
17312
17448
|
if (event.defaultPrevented || event.isComposing || event.keyCode === 229 || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
@@ -17314,8 +17450,9 @@ function adoptAdaptiveChipKeyboardNavigation() {
|
|
|
17314
17450
|
const input = event.target;
|
|
17315
17451
|
const backdrop = input.closest("[data-composer-card]")?.querySelector("[data-input-backdrop]:not([data-dsh-ref-height-ruler])");
|
|
17316
17452
|
if (!(backdrop instanceof HTMLElement) || backdrop.querySelector('[data-decoration="chip"]') === null) return;
|
|
17317
|
-
const
|
|
17318
|
-
|
|
17453
|
+
const pageKey = event.key === "PageUp" || event.key === "PageDown";
|
|
17454
|
+
const focus = pageKey && !event.shiftKey && input.selectionStart !== input.selectionEnd ? event.key === "PageUp" ? input.selectionStart : input.selectionEnd : input.selectionDirection === "backward" ? input.selectionStart : input.selectionEnd;
|
|
17455
|
+
let anchorRect = visualCaretRectAtLogicalOffset(backdrop, focus, caretVisualAffinity.get(input));
|
|
17319
17456
|
if (anchorRect === void 0) return;
|
|
17320
17457
|
const style = getComputedStyle(input);
|
|
17321
17458
|
const lineHeight = Number.parseFloat(style.lineHeight);
|
|
@@ -17327,6 +17464,7 @@ function adoptAdaptiveChipKeyboardNavigation() {
|
|
|
17327
17464
|
let targetX = anchorRect.left;
|
|
17328
17465
|
let targetY = anchorRect.top + lineHeight / 2;
|
|
17329
17466
|
if (vertical) {
|
|
17467
|
+
if (preferredFocus?.input !== input || preferredFocus.offset !== focus || preferredFocus.value !== input.value) preferredX = void 0;
|
|
17330
17468
|
preferredX ??= anchorRect.left;
|
|
17331
17469
|
targetX = preferredX;
|
|
17332
17470
|
const direction = event.key === "ArrowUp" || event.key === "PageUp" ? -1 : 1;
|
|
@@ -17336,7 +17474,7 @@ function adoptAdaptiveChipKeyboardNavigation() {
|
|
|
17336
17474
|
const viewport = scrollport.getBoundingClientRect();
|
|
17337
17475
|
if (targetY < viewport.top || targetY > viewport.bottom) {
|
|
17338
17476
|
scrollport.scrollTop += targetY < viewport.top ? targetY - viewport.top : targetY - viewport.bottom;
|
|
17339
|
-
anchorRect = visualCaretRectAtLogicalOffset(backdrop, focus) ?? anchorRect;
|
|
17477
|
+
anchorRect = visualCaretRectAtLogicalOffset(backdrop, focus, caretVisualAffinity.get(input)) ?? anchorRect;
|
|
17340
17478
|
targetY = anchorRect.top + lineHeight / 2 + direction * distance;
|
|
17341
17479
|
}
|
|
17342
17480
|
}
|
|
@@ -17353,15 +17491,12 @@ function adoptAdaptiveChipKeyboardNavigation() {
|
|
|
17353
17491
|
const selectionAnchor = event.shiftKey ? input.selectionDirection === "backward" ? input.selectionEnd : input.selectionStart : target;
|
|
17354
17492
|
input.setSelectionRange(Math.min(selectionAnchor, target), Math.max(selectionAnchor, target), target < selectionAnchor ? "backward" : "forward");
|
|
17355
17493
|
input.dispatchEvent(new Event("select", { bubbles: true }));
|
|
17494
|
+
if (vertical) preferredFocus = { input, offset: target, value: input.value };
|
|
17356
17495
|
event.preventDefault();
|
|
17357
17496
|
};
|
|
17358
17497
|
document.addEventListener("keydown", onKeyDown);
|
|
17359
|
-
document.addEventListener("input", reset, true);
|
|
17360
|
-
document.addEventListener("pointerdown", reset, true);
|
|
17361
17498
|
return () => {
|
|
17362
17499
|
document.removeEventListener("keydown", onKeyDown);
|
|
17363
|
-
document.removeEventListener("input", reset, true);
|
|
17364
|
-
document.removeEventListener("pointerdown", reset, true);
|
|
17365
17500
|
};
|
|
17366
17501
|
}
|
|
17367
17502
|
function logicalOffsetBeforeChip(root, target) {
|
|
@@ -17528,7 +17663,7 @@ function adoptAdaptiveChipCaret() {
|
|
|
17528
17663
|
const card = input.closest("[data-composer-card]");
|
|
17529
17664
|
const backdrop = card?.querySelector("[data-input-backdrop]:not([data-dsh-ref-height-ruler])");
|
|
17530
17665
|
if (!(backdrop instanceof HTMLElement) || backdrop.querySelector('[data-decoration="chip"]') === null) return hide();
|
|
17531
|
-
const rect = visualCaretRectAtLogicalOffset(backdrop, input.selectionEnd);
|
|
17666
|
+
const rect = visualCaretRectAtLogicalOffset(backdrop, input.selectionEnd, caretVisualAffinity.get(input));
|
|
17532
17667
|
if (rect === void 0 || !Number.isFinite(rect.left) || !Number.isFinite(rect.top)) return hide();
|
|
17533
17668
|
input.classList.remove("dsh_ref_native_caret_hidden");
|
|
17534
17669
|
const style = getComputedStyle(input);
|
|
@@ -17583,6 +17718,12 @@ function adoptAdaptiveChipCaret() {
|
|
|
17583
17718
|
const onActive = () => {
|
|
17584
17719
|
schedule(true, true);
|
|
17585
17720
|
};
|
|
17721
|
+
const onSelect = () => {
|
|
17722
|
+
restartBlink = true;
|
|
17723
|
+
revealPending = true;
|
|
17724
|
+
cancelAnimationFrame(frame);
|
|
17725
|
+
update();
|
|
17726
|
+
};
|
|
17586
17727
|
const onCompositionStart = () => {
|
|
17587
17728
|
composing = true;
|
|
17588
17729
|
schedule(true, true);
|
|
@@ -17593,6 +17734,7 @@ function adoptAdaptiveChipCaret() {
|
|
|
17593
17734
|
};
|
|
17594
17735
|
for (const event of passiveEvents) document.addEventListener(event, onPassive, true);
|
|
17595
17736
|
for (const event of activeEvents) document.addEventListener(event, onActive, true);
|
|
17737
|
+
document.addEventListener("select", onSelect, true);
|
|
17596
17738
|
document.addEventListener("compositionstart", onCompositionStart, true);
|
|
17597
17739
|
document.addEventListener("compositionend", onCompositionEnd, true);
|
|
17598
17740
|
schedule();
|
|
@@ -17601,6 +17743,7 @@ function adoptAdaptiveChipCaret() {
|
|
|
17601
17743
|
observer.disconnect();
|
|
17602
17744
|
for (const event of passiveEvents) document.removeEventListener(event, onPassive, true);
|
|
17603
17745
|
for (const event of activeEvents) document.removeEventListener(event, onActive, true);
|
|
17746
|
+
document.removeEventListener("select", onSelect, true);
|
|
17604
17747
|
document.removeEventListener("compositionstart", onCompositionStart, true);
|
|
17605
17748
|
document.removeEventListener("compositionend", onCompositionEnd, true);
|
|
17606
17749
|
hide();
|
|
@@ -17663,59 +17806,103 @@ function projectPickerIcon(node, text, marker, kind) {
|
|
|
17663
17806
|
function usableCaretRect(rect) {
|
|
17664
17807
|
return rect.height > 0 || rect.top !== 0 || rect.left !== 0;
|
|
17665
17808
|
}
|
|
17666
|
-
function
|
|
17809
|
+
function measuredRangeRect(range) {
|
|
17810
|
+
const measure = range.getBoundingClientRect;
|
|
17811
|
+
return typeof measure === "function" ? measure.call(range) : void 0;
|
|
17812
|
+
}
|
|
17813
|
+
function visualCaretRectAtLogicalOffset(root, target, affinity = "forward") {
|
|
17667
17814
|
const range = rangeAtLogicalOffset(root, target);
|
|
17668
17815
|
if (range === void 0) return void 0;
|
|
17669
|
-
const
|
|
17670
|
-
const boundary =
|
|
17671
|
-
|
|
17816
|
+
const boundaries = caretBoundaryRectsAtLogicalOffset(root, target);
|
|
17817
|
+
const boundary = affinity === "backward" ? boundaries.before ?? boundaries.after : boundaries.after ?? boundaries.before;
|
|
17818
|
+
if (boundary !== void 0) return boundary;
|
|
17819
|
+
const measured = measuredRangeRect(range);
|
|
17820
|
+
if (measured !== void 0 && usableCaretRect(measured)) return { left: measured.left, top: measured.top, source: "text", height: measured.height };
|
|
17821
|
+
return trailingEmptyLineCaretRect(root, target);
|
|
17672
17822
|
}
|
|
17673
17823
|
function visualCaretTop(backdropTop, paddingTop, anchorTop, lineHeight, caretHeight) {
|
|
17674
17824
|
const contentTop = backdropTop + paddingTop;
|
|
17675
17825
|
const lineIndex = Math.max(0, Math.round((anchorTop - contentTop) / lineHeight));
|
|
17676
17826
|
return contentTop + lineIndex * lineHeight + Math.max(0, (lineHeight - caretHeight) / 2);
|
|
17677
17827
|
}
|
|
17678
|
-
function
|
|
17828
|
+
function caretBoundaryRectsAtLogicalOffset(root, target) {
|
|
17679
17829
|
let logical = 0;
|
|
17680
|
-
let
|
|
17830
|
+
let before;
|
|
17831
|
+
let after;
|
|
17681
17832
|
const visit = (parent) => {
|
|
17682
17833
|
for (const child of Array.from(parent.childNodes)) {
|
|
17683
17834
|
if (child instanceof Element && child.matches('[data-decoration="chip"]')) {
|
|
17684
17835
|
const chip = child.getBoundingClientRect();
|
|
17685
|
-
if (target === logical)
|
|
17836
|
+
if (target === logical) after ??= { left: chip.left, top: chip.top, height: chip.height, source: "chip" };
|
|
17686
17837
|
logical += 1;
|
|
17687
|
-
if (target === logical)
|
|
17838
|
+
if (target === logical) before = { left: chip.right, top: chip.top, height: chip.height, source: "chip" };
|
|
17688
17839
|
continue;
|
|
17689
17840
|
}
|
|
17690
17841
|
if (child instanceof Text) {
|
|
17691
17842
|
const start = logical;
|
|
17692
17843
|
const end = start + child.data.length;
|
|
17693
17844
|
if (target >= start && target <= end && child.data.length > 0) {
|
|
17694
|
-
const range = document.createRange();
|
|
17695
17845
|
if (target < end) {
|
|
17846
|
+
const range = document.createRange();
|
|
17696
17847
|
const at = Math.max(0, target - start);
|
|
17697
17848
|
range.setStart(child, at);
|
|
17698
17849
|
range.setEnd(child, at + 1);
|
|
17699
|
-
const rect = range
|
|
17700
|
-
if (usableCaretRect(rect))
|
|
17850
|
+
const rect = measuredRangeRect(range);
|
|
17851
|
+
if (rect !== void 0 && usableCaretRect(rect)) after ??= { left: rect.left, top: rect.top, height: rect.height, source: "text" };
|
|
17701
17852
|
}
|
|
17702
17853
|
if (target > start) {
|
|
17854
|
+
const range = document.createRange();
|
|
17703
17855
|
const at = Math.min(child.data.length, target - start);
|
|
17704
17856
|
range.setStart(child, at - 1);
|
|
17705
17857
|
range.setEnd(child, at);
|
|
17706
|
-
const rect = range
|
|
17707
|
-
if (usableCaretRect(rect))
|
|
17858
|
+
const rect = measuredRangeRect(range);
|
|
17859
|
+
if (rect !== void 0 && usableCaretRect(rect)) before = { left: rect.right, top: rect.top, height: rect.height, source: "text" };
|
|
17708
17860
|
}
|
|
17709
17861
|
}
|
|
17710
17862
|
logical = end;
|
|
17711
17863
|
continue;
|
|
17712
17864
|
}
|
|
17713
|
-
|
|
17714
|
-
|
|
17865
|
+
visit(child);
|
|
17866
|
+
}
|
|
17867
|
+
};
|
|
17868
|
+
visit(root);
|
|
17869
|
+
return { before, after };
|
|
17870
|
+
}
|
|
17871
|
+
function trailingEmptyLineCaretRect(root, target) {
|
|
17872
|
+
let logical = 0;
|
|
17873
|
+
let previous = "";
|
|
17874
|
+
const visit = (parent) => {
|
|
17875
|
+
for (const child of Array.from(parent.childNodes)) {
|
|
17876
|
+
if (child instanceof Element && child.matches('[data-decoration="chip"]')) {
|
|
17877
|
+
if (logical < target) previous = "\uFFFC";
|
|
17878
|
+
logical += 1;
|
|
17879
|
+
continue;
|
|
17880
|
+
}
|
|
17881
|
+
if (child instanceof Text) {
|
|
17882
|
+
const end = logical + child.data.length;
|
|
17883
|
+
if (target > logical && target <= end) previous = child.data[target - logical - 1] ?? previous;
|
|
17884
|
+
logical = end;
|
|
17885
|
+
continue;
|
|
17886
|
+
}
|
|
17887
|
+
visit(child);
|
|
17715
17888
|
}
|
|
17716
|
-
return trailingChip;
|
|
17717
17889
|
};
|
|
17718
|
-
|
|
17890
|
+
visit(root);
|
|
17891
|
+
if (target !== logical || previous !== "\n") return void 0;
|
|
17892
|
+
const style = getComputedStyle(root);
|
|
17893
|
+
const lineHeight = Number.parseFloat(style.lineHeight);
|
|
17894
|
+
const fontSize = Number.parseFloat(style.fontSize);
|
|
17895
|
+
const height = lineHeight > 0 ? lineHeight : fontSize > 0 ? fontSize * 1.2 : 20;
|
|
17896
|
+
const paddingLeft = Number.parseFloat(style.paddingLeft) || 0;
|
|
17897
|
+
const paddingRight = Number.parseFloat(style.paddingRight) || 0;
|
|
17898
|
+
const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
|
|
17899
|
+
const rect = root.getBoundingClientRect();
|
|
17900
|
+
return {
|
|
17901
|
+
left: style.direction === "rtl" ? rect.right - paddingRight : rect.left + paddingLeft,
|
|
17902
|
+
top: Math.max(rect.top, rect.bottom - paddingBottom - height),
|
|
17903
|
+
height,
|
|
17904
|
+
source: "text"
|
|
17905
|
+
};
|
|
17719
17906
|
}
|
|
17720
17907
|
function rangeAtLogicalOffset(root, target) {
|
|
17721
17908
|
const range = document.createRange();
|
|
@@ -17765,6 +17952,27 @@ function rangeBetweenLogicalOffsets(root, start, end) {
|
|
|
17765
17952
|
return range;
|
|
17766
17953
|
}
|
|
17767
17954
|
|
|
17955
|
+
// src/client/notice.ts
|
|
17956
|
+
var NOTICE_DURATION_MS = 5e3;
|
|
17957
|
+
function createAutoDismissNotice(read, write, durationMs = NOTICE_DURATION_MS) {
|
|
17958
|
+
let timer;
|
|
17959
|
+
return {
|
|
17960
|
+
show(notice, patch = {}) {
|
|
17961
|
+
if (timer) clearTimeout(timer);
|
|
17962
|
+
write({ ...read(), ...patch, notice });
|
|
17963
|
+
timer = setTimeout(() => {
|
|
17964
|
+
const current = read();
|
|
17965
|
+
if (current.notice === notice) write({ ...current, notice: void 0 });
|
|
17966
|
+
timer = void 0;
|
|
17967
|
+
}, durationMs);
|
|
17968
|
+
},
|
|
17969
|
+
dispose() {
|
|
17970
|
+
if (timer) clearTimeout(timer);
|
|
17971
|
+
timer = void 0;
|
|
17972
|
+
}
|
|
17973
|
+
};
|
|
17974
|
+
}
|
|
17975
|
+
|
|
17768
17976
|
// src/client/index.ts
|
|
17769
17977
|
var inject = ["inputTriggers", "remote", "remote.commands", "slots", "connection", "locale"];
|
|
17770
17978
|
function apply(ctx) {
|
|
@@ -17784,9 +17992,11 @@ function apply(ctx) {
|
|
|
17784
17992
|
});
|
|
17785
17993
|
let currentJob = "";
|
|
17786
17994
|
let poll;
|
|
17787
|
-
let settingsNoticeTimer;
|
|
17788
17995
|
let refreshGeneration = 0;
|
|
17789
17996
|
const t = ctx.locale.bind(REFERENCE_ANYTHING_NS);
|
|
17997
|
+
const notices = createAutoDismissNotice(() => scope.getSnapshot(), (value) => {
|
|
17998
|
+
scope.set(value);
|
|
17999
|
+
});
|
|
17790
18000
|
let applySources;
|
|
17791
18001
|
ctx.effect(() => ctx.locale.register(REFERENCE_ANYTHING_NS, { zh, en }), "reference-anything.client.dictionaries");
|
|
17792
18002
|
ctx.effect(() => adoptMenuGroupTitleProjection(t), "reference-anything.client.menu-group-localization");
|
|
@@ -17917,12 +18127,8 @@ function apply(ctx) {
|
|
|
17917
18127
|
try {
|
|
17918
18128
|
const update = unwrap(await activeRemote.updateStatus());
|
|
17919
18129
|
if (remote === activeRemote) {
|
|
17920
|
-
|
|
17921
|
-
scope.set({
|
|
17922
|
-
...current,
|
|
17923
|
-
update,
|
|
17924
|
-
notice: update.updateAvailable ? t("notice.updateAvailable", { version: update.latestVersion }) : current.notice
|
|
17925
|
-
});
|
|
18130
|
+
if (update.updateAvailable) notices.show(t("notice.updateAvailable", { version: update.latestVersion }), { update });
|
|
18131
|
+
else scope.set({ ...scope.getSnapshot(), update });
|
|
17926
18132
|
}
|
|
17927
18133
|
} catch {
|
|
17928
18134
|
}
|
|
@@ -17983,7 +18189,7 @@ function apply(ctx) {
|
|
|
17983
18189
|
await Promise.all([refreshStats(), refreshStorage()]);
|
|
17984
18190
|
const current = scope.getSnapshot().browse;
|
|
17985
18191
|
if (current) await browse(current.query, current.provider, 0);
|
|
17986
|
-
|
|
18192
|
+
notices.show(t("notice.cleared", { count }), { error: void 0 });
|
|
17987
18193
|
} catch (error51) {
|
|
17988
18194
|
scope.set({ ...scope.getSnapshot(), error: message(error51) });
|
|
17989
18195
|
}
|
|
@@ -17995,7 +18201,7 @@ function apply(ctx) {
|
|
|
17995
18201
|
await Promise.all([refreshStats(), refreshStorage()]);
|
|
17996
18202
|
const current = scope.getSnapshot().browse;
|
|
17997
18203
|
if (current) await browse(current.query, current.provider, 0);
|
|
17998
|
-
|
|
18204
|
+
notices.show(t("notice.cleared", { count }), { error: void 0 });
|
|
17999
18205
|
} catch (error51) {
|
|
18000
18206
|
scope.set({ ...scope.getSnapshot(), error: message(error51) });
|
|
18001
18207
|
}
|
|
@@ -18017,14 +18223,7 @@ function apply(ctx) {
|
|
|
18017
18223
|
try {
|
|
18018
18224
|
const value = unwrap(await remote.settingsUpdate(settings));
|
|
18019
18225
|
applySources?.(value.picker, value.inputRenderMode);
|
|
18020
|
-
|
|
18021
|
-
if (settingsNoticeTimer) clearTimeout(settingsNoticeTimer);
|
|
18022
|
-
scope.set({ ...scope.getSnapshot(), settings: value, error: void 0, notice });
|
|
18023
|
-
settingsNoticeTimer = setTimeout(() => {
|
|
18024
|
-
const current = scope.getSnapshot();
|
|
18025
|
-
if (current.notice === notice) scope.set({ ...current, notice: void 0 });
|
|
18026
|
-
settingsNoticeTimer = void 0;
|
|
18027
|
-
}, 2400);
|
|
18226
|
+
notices.show(t("notice.settingsSaved"), { settings: value, error: void 0 });
|
|
18028
18227
|
} catch (error51) {
|
|
18029
18228
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18030
18229
|
}
|
|
@@ -18039,7 +18238,7 @@ function apply(ctx) {
|
|
|
18039
18238
|
await Promise.all([refreshStats(), refreshStorage()]);
|
|
18040
18239
|
const current = scope.getSnapshot().browse;
|
|
18041
18240
|
if (current) await browse(current.query, current.provider, 0);
|
|
18042
|
-
|
|
18241
|
+
notices.show(t("notice.cleared", { count }), { error: void 0 });
|
|
18043
18242
|
} catch (error51) {
|
|
18044
18243
|
scope.set({ ...scope.getSnapshot(), error: message(error51) });
|
|
18045
18244
|
}
|
|
@@ -18051,13 +18250,13 @@ function apply(ctx) {
|
|
|
18051
18250
|
await Promise.all([refreshStats(), refreshStorage()]);
|
|
18052
18251
|
const current = scope.getSnapshot().browse;
|
|
18053
18252
|
if (current) await browse(current.query, current.provider, 0);
|
|
18054
|
-
|
|
18253
|
+
notices.show(t("notice.cleared", { count }), { error: void 0 });
|
|
18055
18254
|
} catch (error51) {
|
|
18056
18255
|
scope.set({ ...scope.getSnapshot(), error: message(error51) });
|
|
18057
18256
|
}
|
|
18058
18257
|
};
|
|
18059
18258
|
ctx.effect(() => () => {
|
|
18060
|
-
|
|
18259
|
+
notices.dispose();
|
|
18061
18260
|
}, "reference-anything.client.settings-notice");
|
|
18062
18261
|
const startSync = async (providers, mode) => {
|
|
18063
18262
|
if (!remote) return;
|
|
@@ -18130,7 +18329,7 @@ function apply(ctx) {
|
|
|
18130
18329
|
try {
|
|
18131
18330
|
if (!remote) return;
|
|
18132
18331
|
const update = unwrap(await remote.checkUpdate());
|
|
18133
|
-
|
|
18332
|
+
notices.show(update.updateAvailable ? t("notice.updateAvailable", { version: update.latestVersion }) : t("notice.upToDate", { version: update.currentVersion }), { update, error: void 0 });
|
|
18134
18333
|
} catch (error51) {
|
|
18135
18334
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18136
18335
|
}
|
|
@@ -18146,12 +18345,7 @@ function apply(ctx) {
|
|
|
18146
18345
|
updateAvailable: false,
|
|
18147
18346
|
checkedAt: Date.now()
|
|
18148
18347
|
};
|
|
18149
|
-
|
|
18150
|
-
...scope.getSnapshot(),
|
|
18151
|
-
update,
|
|
18152
|
-
error: void 0,
|
|
18153
|
-
notice: result.restartRequired ? t("notice.updateInstalled", { version: result.version }) : t("notice.upToDate", { version: previous?.currentVersion || result.version })
|
|
18154
|
-
});
|
|
18348
|
+
notices.show(result.restartRequired ? t("notice.updateInstalled", { version: result.version }) : t("notice.upToDate", { version: previous?.currentVersion || result.version }), { update, error: void 0 });
|
|
18155
18349
|
} catch (error51) {
|
|
18156
18350
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18157
18351
|
}
|
|
@@ -18182,11 +18376,11 @@ function apply(ctx) {
|
|
|
18182
18376
|
});
|
|
18183
18377
|
if (!health?.version || !health.opencliCompatible) throw new Error(t("settings.opencliStillUnavailable"));
|
|
18184
18378
|
if (setupReady(health)) {
|
|
18185
|
-
|
|
18379
|
+
notices.show(t("notice.setupComplete"), { error: void 0, setupStep: "complete" });
|
|
18186
18380
|
} else if (health?.extensionConnected && !health.connectivityOk) {
|
|
18187
18381
|
scope.set({ ...scope.getSnapshot(), error: t("settings.connectivityStillUnavailable"), notice: void 0, setupStep: void 0 });
|
|
18188
18382
|
} else if (!health?.extensionConnected) {
|
|
18189
|
-
|
|
18383
|
+
notices.show(extensionPageOpened ? t("notice.oneClickSetup") : t("notice.extensionStoreBlocked"), { error: void 0, setupStep: "extension" });
|
|
18190
18384
|
} else {
|
|
18191
18385
|
scope.set({ ...scope.getSnapshot(), error: t("settings.setupIncomplete"), notice: void 0, setupStep: void 0 });
|
|
18192
18386
|
}
|
|
@@ -18202,7 +18396,7 @@ function apply(ctx) {
|
|
|
18202
18396
|
const settings = scope.getSnapshot().settings;
|
|
18203
18397
|
unwrap(await remote.settingsUpdate({ ...settings, opencliPath: discovery.executable }));
|
|
18204
18398
|
await refresh();
|
|
18205
|
-
|
|
18399
|
+
notices.show(t("notice.opencliFound", { version: discovery.version }), { error: void 0 });
|
|
18206
18400
|
} catch (error51) {
|
|
18207
18401
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18208
18402
|
}
|
|
@@ -18214,7 +18408,7 @@ function apply(ctx) {
|
|
|
18214
18408
|
await refresh();
|
|
18215
18409
|
const health = scope.getSnapshot().health;
|
|
18216
18410
|
if (!health?.version || !health.opencliCompatible) throw new Error(t("settings.opencliStillUnavailable"));
|
|
18217
|
-
|
|
18411
|
+
notices.show(t("notice.opencliInstalled", { version: discovery.version }), { error: void 0 });
|
|
18218
18412
|
} catch (error51) {
|
|
18219
18413
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18220
18414
|
}
|
|
@@ -18227,7 +18421,7 @@ function apply(ctx) {
|
|
|
18227
18421
|
await refresh();
|
|
18228
18422
|
const health = scope.getSnapshot().health;
|
|
18229
18423
|
if (!health?.extensionConnected) throw new Error(t("settings.profileStillUnavailable"));
|
|
18230
|
-
|
|
18424
|
+
notices.show(t("notice.profileSelected"), { error: void 0 });
|
|
18231
18425
|
} catch (error51) {
|
|
18232
18426
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18233
18427
|
}
|
|
@@ -18239,7 +18433,7 @@ function apply(ctx) {
|
|
|
18239
18433
|
await refresh();
|
|
18240
18434
|
const health = scope.getSnapshot().health;
|
|
18241
18435
|
if (!health?.pluginInstalled || !health.adapterCompatible) throw new Error(t("settings.adapterStillUnavailable"));
|
|
18242
|
-
|
|
18436
|
+
notices.show(t("notice.adapterInstalled"), { error: void 0 });
|
|
18243
18437
|
} catch (error51) {
|
|
18244
18438
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18245
18439
|
}
|
|
@@ -18251,7 +18445,7 @@ function apply(ctx) {
|
|
|
18251
18445
|
await refresh();
|
|
18252
18446
|
const health = scope.getSnapshot().health;
|
|
18253
18447
|
if (!health?.daemonRunning || health.daemonStale) throw new Error(t("settings.daemonStillUnavailable"));
|
|
18254
|
-
|
|
18448
|
+
notices.show(t("notice.daemonRestarted"), { error: void 0 });
|
|
18255
18449
|
} catch (error51) {
|
|
18256
18450
|
scope.set({ ...scope.getSnapshot(), error: message(error51), notice: void 0 });
|
|
18257
18451
|
}
|
package/lib/update.js
CHANGED
|
@@ -91,7 +91,10 @@ export class PackageUpdateManager {
|
|
|
91
91
|
export async function fetchLatestVersion(signal) {
|
|
92
92
|
const timeout = AbortSignal.timeout(CHECK_TIMEOUT_MS);
|
|
93
93
|
const response = await fetch(NPM_LATEST_URL, {
|
|
94
|
-
|
|
94
|
+
// The registry's package metadata endpoint accepts the install-v1 media
|
|
95
|
+
// type, but its `/latest` dist-tag endpoint responds 406 to that same
|
|
96
|
+
// header. Request ordinary JSON because this endpoint returns one manifest.
|
|
97
|
+
headers: { accept: 'application/json' },
|
|
95
98
|
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
96
99
|
});
|
|
97
100
|
if (!response.ok)
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-reference-anything",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Reference locally mirrored ChatGPT, Claude, Gemini, DeepSeek, Grok, and Kimi conversations from DeepSeek Harness",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"publishConfig": {
|
|
7
|
-
"access": "public"
|
|
8
|
-
},
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"opencli-plugin/*.js",
|
|
54
54
|
"opencli-plugin/opencli-plugin.json",
|
|
55
55
|
"opencli-plugin/package.json",
|
|
56
|
-
"cordis.patch.yml",
|
|
57
|
-
"images/logo.png",
|
|
58
|
-
"NOTICE.md",
|
|
56
|
+
"cordis.patch.yml",
|
|
57
|
+
"images/logo.png",
|
|
58
|
+
"NOTICE.md",
|
|
59
59
|
"README.md"
|
|
60
60
|
],
|
|
61
61
|
"dsh": {
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"build": "node build.mjs",
|
|
79
79
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
80
80
|
"test": "vitest run",
|
|
81
|
-
"check": "pnpm run typecheck && pnpm run test && pnpm run build",
|
|
82
|
-
"prepublishOnly": "pnpm run check",
|
|
81
|
+
"check": "pnpm run typecheck && pnpm run test && pnpm run build",
|
|
82
|
+
"prepublishOnly": "pnpm run check",
|
|
83
83
|
"prepare": "node build.mjs"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|