@rs-dsh/dsh-core 0.1.0 → 0.1.1
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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/client.js +67 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- **Chinese wording** reviewed: messages follow DSH's own terms (指令 for commands) and end with 。.
|
|
6
|
+
- **Triple-click selections** now get the Comment button. A triple-click on a message's last paragraph runs on to the start of the next message, so the selection belonged to no message; it is now trimmed to the text it covers.
|
|
7
|
+
- **Cards focus their text field** when they open, so you can type a comment or a quote note right away.
|
|
8
|
+
- **The selected passage stays highlighted** while its comment or quote card is open, though focusing the field clears the page selection.
|
|
9
|
+
|
|
3
10
|
## 0.1.0
|
|
4
11
|
|
|
5
12
|
The first release. dsh-threads 0.2.0 and dsh-quote 0.2.0 are built on it.
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The shared base of the rs-dsh plugins for DeepSeek Harness. It gives them one se
|
|
|
9
9
|
Install the plugins you want. Each one brings this package and adds it to the profile:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
dsh plugin --profile <name> add @rs-dsh/dsh-threads@0.
|
|
12
|
+
dsh plugin --profile <name> add @rs-dsh/dsh-threads@0.3.0 @rs-dsh/dsh-quote@0.2.1
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Then reload the DSH web page. However many rs-dsh plugins you install, the core runs once.
|
package/client.js
CHANGED
|
@@ -143,15 +143,17 @@ window.__ModuleLoader__.load({
|
|
|
143
143
|
}
|
|
144
144
|
const zh = {
|
|
145
145
|
actions: '选中文本操作',
|
|
146
|
-
busy: '
|
|
147
|
-
command: '
|
|
148
|
-
noSession: '
|
|
146
|
+
busy: '输入框暂时无法编辑,请稍后再试。',
|
|
147
|
+
command: '输入指令时无法插入内容。',
|
|
148
|
+
noSession: '找不到该会话的输入框。',
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// The only stylesheet the core owns: positioning of its two floating hosts.
|
|
152
152
|
// Everything visible inside them is DSH's own components and tokens. The
|
|
153
153
|
// toolbar keeps the menu's translucent material; text-heavy cards take the
|
|
154
154
|
// solid layer of DSH's preview cards, so the page never shows through.
|
|
155
|
+
// The passage a toolbar action was taken on, painted while its card is open.
|
|
156
|
+
const HELD = 'dsh-core-held'
|
|
155
157
|
const STYLE = `
|
|
156
158
|
[${ROOT}="dsh-core"][data-part]{position:fixed;z-index:1200;max-width:calc(100vw - 16px)}
|
|
157
159
|
[${ROOT}="dsh-core"][data-part][hidden]{display:none}
|
|
@@ -162,6 +164,7 @@ window.__ModuleLoader__.load({
|
|
|
162
164
|
[${ROOT}="dsh-core"][data-part="popover"] [data-part-card]{padding:10px;display:flex;flex-direction:column;max-height:min(70vh,560px);
|
|
163
165
|
background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-panel)}
|
|
164
166
|
[${ROOT}="dsh-core"][data-part="popover"] [data-part-card]>[aria-hidden="true"]:first-child{display:none}
|
|
167
|
+
::highlight(${HELD}){background-color:Highlight;color:HighlightText}
|
|
165
168
|
`
|
|
166
169
|
// ------------------------------------------------------------ plugin body
|
|
167
170
|
|
|
@@ -248,10 +251,36 @@ window.__ModuleLoader__.load({
|
|
|
248
251
|
* in, the chat row, the range and its rectangle, and the cleaned text.
|
|
249
252
|
* Selections in the composer, in inputs or in rs-dsh's own UI are none.
|
|
250
253
|
*/
|
|
254
|
+
/**
|
|
255
|
+
* The range shrunk to the text it actually covers. A triple-click on
|
|
256
|
+
* the last paragraph of a message ends the range at offset 0 of the
|
|
257
|
+
* next message, so its common ancestor would be the whole chat and the
|
|
258
|
+
* selection would belong to no row.
|
|
259
|
+
*/
|
|
260
|
+
function trimRange(range) {
|
|
261
|
+
const root = range.commonAncestorContainer
|
|
262
|
+
if (root.nodeType === 3 || typeof document.createTreeWalker !== 'function') return range
|
|
263
|
+
const walker = document.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */)
|
|
264
|
+
let first = null, last = null
|
|
265
|
+
for (let n = walker.nextNode(); n !== null; n = walker.nextNode()) {
|
|
266
|
+
if (!range.intersectsNode(n)) continue
|
|
267
|
+
const start = n === range.startContainer ? range.startOffset : 0
|
|
268
|
+
const end = n === range.endContainer ? range.endOffset : n.data.length
|
|
269
|
+
if (start >= end || n.data.slice(start, end).trim() === '') continue
|
|
270
|
+
if (first === null) first = { n, start }
|
|
271
|
+
last = { n, end }
|
|
272
|
+
}
|
|
273
|
+
if (first === null) return range
|
|
274
|
+
const trimmed = range.cloneRange()
|
|
275
|
+
trimmed.setStart(first.n, first.start)
|
|
276
|
+
trimmed.setEnd(last.n, last.end)
|
|
277
|
+
return trimmed
|
|
278
|
+
}
|
|
279
|
+
|
|
251
280
|
function selectionContext() {
|
|
252
281
|
const sel = window.getSelection()
|
|
253
282
|
if (sel === null || sel.isCollapsed || sel.rangeCount === 0) return null
|
|
254
|
-
const range = sel.getRangeAt(0)
|
|
283
|
+
const range = trimRange(sel.getRangeAt(0))
|
|
255
284
|
const node = range.commonAncestorContainer
|
|
256
285
|
const el = node instanceof Element ? node : node.parentElement
|
|
257
286
|
if (el === null) return null
|
|
@@ -283,6 +312,7 @@ window.__ModuleLoader__.load({
|
|
|
283
312
|
const open = current
|
|
284
313
|
if (open === null) return
|
|
285
314
|
current = null
|
|
315
|
+
if (open.held) releaseSelection()
|
|
286
316
|
// Close is often called from inside the card's own React event (Enter
|
|
287
317
|
// in an editor, a button). Detach now; unmount once React has finished
|
|
288
318
|
// that event, never during it.
|
|
@@ -321,6 +351,18 @@ window.__ModuleLoader__.load({
|
|
|
321
351
|
host.style.left = at.left + 'px'
|
|
322
352
|
host.style.top = at.top + 'px'
|
|
323
353
|
host.style.visibility = ''
|
|
354
|
+
// The card mounts hidden until placed, so a field's autoFocus fires
|
|
355
|
+
// while it cannot take focus. Focus its first field once it shows.
|
|
356
|
+
if (!entry.focused) {
|
|
357
|
+
entry.focused = true
|
|
358
|
+
if (!host.contains(document.activeElement)) {
|
|
359
|
+
const field = host.querySelector('textarea,input:not([type="hidden"]),[contenteditable="true"]')
|
|
360
|
+
if (field !== null) {
|
|
361
|
+
field.focus({ preventScroll: true })
|
|
362
|
+
if (typeof field.setSelectionRange === 'function') field.setSelectionRange(field.value.length, field.value.length)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
324
366
|
}
|
|
325
367
|
const draw = () => {
|
|
326
368
|
root.render(jsx(ui.MenuSurface, { 'data-part-card': '', children: jsx(PopoverBody, { render, close, place }) }))
|
|
@@ -335,6 +377,24 @@ window.__ModuleLoader__.load({
|
|
|
335
377
|
return render(close)
|
|
336
378
|
}
|
|
337
379
|
|
|
380
|
+
// -------------------------------------------------- held selection
|
|
381
|
+
//
|
|
382
|
+
// Focusing the card's field takes the page selection away. So when a
|
|
383
|
+
// toolbar action opens a card, the passage stays painted with a CSS
|
|
384
|
+
// highlight, in the selection's colours, until the card closes.
|
|
385
|
+
|
|
386
|
+
const canHold = typeof Highlight === 'function' && window.CSS !== undefined && window.CSS.highlights !== undefined
|
|
387
|
+
|
|
388
|
+
function holdSelection(range) {
|
|
389
|
+
if (!canHold || !range) return false
|
|
390
|
+
window.CSS.highlights.set(HELD, new Highlight(range))
|
|
391
|
+
return true
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function releaseSelection() {
|
|
395
|
+
if (canHold) window.CSS.highlights.delete(HELD)
|
|
396
|
+
}
|
|
397
|
+
|
|
338
398
|
// -------------------------------------------------- selection toolbar
|
|
339
399
|
|
|
340
400
|
const buttons = [] // { id, plugin, label, icon, order, seq, when, onSelect }
|
|
@@ -391,7 +451,10 @@ window.__ModuleLoader__.load({
|
|
|
391
451
|
onMouseDown: (e) => e.preventDefault(),
|
|
392
452
|
onClick: () => {
|
|
393
453
|
hideToolbar()
|
|
454
|
+
const before = current
|
|
394
455
|
try { b.onSelect(target) } catch (error) { console.error(error) }
|
|
456
|
+
// The action opened a card: keep its passage painted while it is open.
|
|
457
|
+
if (current !== null && current !== before) current.held = holdSelection(target.range)
|
|
395
458
|
},
|
|
396
459
|
children: typeof b.label === 'function' ? b.label() : b.label,
|
|
397
460
|
}, b.plugin + '/' + b.id)),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rs-dsh/dsh-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Shared UI layer for the rs-dsh DSH plugins: one selection toolbar, anchored cards and DSH's own components, so the plugins look like DSH and stay small.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|