dsh-better-workspace 0.11.0 → 0.11.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/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +60 -26
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区层级树:名称中的 / 即虚拟分组(web/前端 · web/后端),添加工作区的目录流附所属分组弹窗,重命名即时重排层级,可新建空分组。Hierarchy tree for the DSH sidebar workspace list.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "DSH web plugin: a hierarchical workspace tree for the sidebar. Workspace titles containing \"/\" group into virtual folders (web/前端 · web/后端); the add-workspace directory flow gains a parent-group popup; renames re-derive the tree; a new-folder button creates empty levels.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -2699,13 +2699,16 @@ window.__ModuleLoader__.load({
|
|
|
2699
2699
|
'.bw-row-icon{flex:none;display:grid;place-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
|
|
2700
2700
|
'.bw-chevron{flex:none;display:grid;place-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a);transition:transform .15s ease}',
|
|
2701
2701
|
'.bw-chevron-open{transform:rotate(90deg)}',
|
|
2702
|
-
|
|
2702
|
+
// The outline is a text-shadow now, and overflow:hidden clips shadows just
|
|
2703
|
+
// as it clipped the stroke: 3px of padding covers the diagonal reach of the
|
|
2704
|
+
// widest rim. margin cancels it, so text starts and truncates where it did.
|
|
2705
|
+
'.bw-row-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px;margin:-3px}',
|
|
2703
2706
|
'.bw-row-count{flex:none;font-size:11px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
|
|
2704
2707
|
'.bw-row-time{flex:none;font-size:11px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
|
|
2705
|
-
// The
|
|
2706
|
-
// count / relative time) opts out — stroked meta text
|
|
2707
|
-
// it
|
|
2708
|
-
'.bw-row-count,.bw-row-time{-
|
|
2708
|
+
// The rim is inherited from the row through text-shadow, and the 11px meta
|
|
2709
|
+
// column (session count / relative time) opts out of it — stroked meta text
|
|
2710
|
+
// outshines the title it supports — while keeping the halo the row carries.
|
|
2711
|
+
'.bw-row-count,.bw-row-time{text-shadow:var(--bw-glow-shadow,none)}',
|
|
2709
2712
|
'.bw-schedule-badge{flex:none;display:inline-flex;align-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a);margin:0 6px}',
|
|
2710
2713
|
'.bw-row-actions{flex:none;display:none;align-items:center;gap:2px}',
|
|
2711
2714
|
'.bw-row:hover .bw-row-actions{display:flex}',
|
|
@@ -2783,11 +2786,14 @@ window.__ModuleLoader__.load({
|
|
|
2783
2786
|
'.bw-preview{display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px dashed var(--dsw-alias-border-l2,rgba(127,127,127,.3));border-radius:6px;min-height:28px}',
|
|
2784
2787
|
'.bw-preview-icon{flex:none;display:grid;place-items:center;width:20px;height:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2785
2788
|
'.bw-preview-icon svg{width:18px;height:18px}',
|
|
2786
|
-
'.bw-preview-label{font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:18px}',
|
|
2789
|
+
'.bw-preview-label{font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:18px;padding:3px;margin:-3px}',
|
|
2787
2790
|
'.bw-pulse{animation:bw-breathe 1.8s ease-in-out infinite}',
|
|
2788
2791
|
'@keyframes bw-breathe{0%,100%{filter:drop-shadow(0 0 1px var(--bw-pulse-color));opacity:.55}50%{filter:drop-shadow(0 0 6px var(--bw-pulse-color));opacity:1}}',
|
|
2789
2792
|
'.bw-pulse-text{animation:bw-breathe-text 1.8s ease-in-out infinite}',
|
|
2790
|
-
|
|
2793
|
+
// The keyframes replace text-shadow wholesale, so the row's rim rides along
|
|
2794
|
+
// in --bw-stroke-shadow (set by rowStyleOf, comma-terminated) and the empty
|
|
2795
|
+
// fallback keeps a row without an outline breathing exactly as before.
|
|
2796
|
+
'@keyframes bw-breathe-text{0%,100%{text-shadow:var(--bw-stroke-shadow,) 0 0 1px var(--bw-pulse-color);opacity:.65}50%{text-shadow:var(--bw-stroke-shadow,) 0 0 7px var(--bw-pulse-color);opacity:1}}',
|
|
2791
2797
|
'.bw-sync-modes{display:flex;gap:6px;margin-top:8px}',
|
|
2792
2798
|
'.bw-sync-mode{flex:1;padding:5px 10px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;font-size:12px}',
|
|
2793
2799
|
'.bw-sync-mode-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12))}',
|
|
@@ -3328,15 +3334,33 @@ window.__ModuleLoader__.load({
|
|
|
3328
3334
|
}
|
|
3329
3335
|
return colorToRgb(picked) ? picked : DEFAULT_APPEARANCE.strokeColor
|
|
3330
3336
|
}
|
|
3331
|
-
//
|
|
3332
|
-
//
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3337
|
+
// The outline is painted as eight offset copies of the glyph mask instead of
|
|
3338
|
+
// with -webkit-text-stroke. That stroke is a GEOMETRIC band centred on the
|
|
3339
|
+
// glyph outline: the fill (paint-order: stroke fill) covers its inner half,
|
|
3340
|
+
// and the surviving outer half lands wherever the outline happens to sit —
|
|
3341
|
+
// usually between device pixels. The rasteriser then spreads that half pixel
|
|
3342
|
+
// over two or three of them, the rim never reaches its own colour, and the
|
|
3343
|
+
// edge reads as pale and uneven — the "jagged" outline.
|
|
3344
|
+
//
|
|
3345
|
+
// An offset glyph copy snaps to whole device pixels instead (0.5px at 2x is
|
|
3346
|
+
// exactly one device pixel), so the rim is solid. Measured on the same 13px
|
|
3347
|
+
// row at 2x, the share of rim pixels reaching the picked colour goes
|
|
3348
|
+
// 0.19 -> 0.69 while the painted area grows by only ~10%: the outline keeps
|
|
3349
|
+
// its width and simply stops being blended away. Cost is nil — a 400-row
|
|
3350
|
+
// scroll repaints at the same 16.7ms median either way.
|
|
3351
|
+
const STROKE_DIRECTIONS = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [-1, 1], [1, -1], [-1, -1]]
|
|
3352
|
+
// One device pixel expressed in CSS pixels. An offset below it is blended
|
|
3353
|
+
// away again, so a thin setting floors here instead of fading out.
|
|
3354
|
+
const strokeOffsetOf = (appearance) => {
|
|
3355
|
+
const width = clampStrokeWidth(appearance.strokeWidth)
|
|
3356
|
+
const dpr = typeof window !== 'undefined' && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1
|
|
3357
|
+
return Math.max(width / 2, 1 / dpr)
|
|
3358
|
+
}
|
|
3359
|
+
const strokeShadowsOf = (appearance) => {
|
|
3360
|
+
if (!appearance || appearance.stroke === false) return []
|
|
3361
|
+
const color = strokeColorOf(appearance)
|
|
3362
|
+
const offset = strokeOffsetOf(appearance)
|
|
3363
|
+
return STROKE_DIRECTIONS.map(([dx, dy]) => (dx * offset) + 'px ' + (dy * offset) + 'px ' + color)
|
|
3340
3364
|
}
|
|
3341
3365
|
|
|
3342
3366
|
function FolderRow({ node, depth, expanded, onToggle, onContextMenu, dropInto, dragEvents, custStyle, iconMode, pulse, t }) {
|
|
@@ -3359,7 +3383,7 @@ window.__ModuleLoader__.load({
|
|
|
3359
3383
|
E('span', { className: cls('bw-chevron', expanded && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
|
|
3360
3384
|
E('span', { className: 'bw-row-icon' }, iconChild),
|
|
3361
3385
|
// A row that already carries a custom label glow breathes in sync.
|
|
3362
|
-
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle
|
|
3386
|
+
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle['--bw-text-glow'] && 'bw-pulse-text') }, node.name),
|
|
3363
3387
|
total > 0 ? E('span', { className: 'bw-row-count' }, String(total)) : null,
|
|
3364
3388
|
)
|
|
3365
3389
|
}
|
|
@@ -3380,7 +3404,7 @@ window.__ModuleLoader__.load({
|
|
|
3380
3404
|
},
|
|
3381
3405
|
E('span', { className: cls('bw-chevron', sessionsOpen && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
|
|
3382
3406
|
E('span', { className: 'bw-row-icon' }, iconChild),
|
|
3383
|
-
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle
|
|
3407
|
+
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle['--bw-text-glow'] && 'bw-pulse-text'), title: workspace.title || workspace.leaf }, workspace.leaf),
|
|
3384
3408
|
count > 0 ? E('span', { className: 'bw-row-count' }, String(count)) : null,
|
|
3385
3409
|
E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
|
|
3386
3410
|
E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('session.new'), onClick: (e) => { e.stopPropagation(); onStart() } }, icon('IconPlusOutline16')),
|
|
@@ -3567,7 +3591,7 @@ window.__ModuleLoader__.load({
|
|
|
3567
3591
|
next[index] = n
|
|
3568
3592
|
patch({ color: rgbToHex(next[0], next[1], next[2]) })
|
|
3569
3593
|
}
|
|
3570
|
-
const previewShadows =
|
|
3594
|
+
const previewShadows = strokeShadowsOf(appearance)
|
|
3571
3595
|
if (glow > 0 && color) previewShadows.push('0 0 ' + glow + 'px ' + color)
|
|
3572
3596
|
if (shadow) previewShadows.push('1px 1px 2px rgba(0,0,0,.85)')
|
|
3573
3597
|
// The preview must show the REAL outline: with no custom color the pole
|
|
@@ -3587,7 +3611,6 @@ window.__ModuleLoader__.load({
|
|
|
3587
3611
|
const poll = setInterval(sample, 1500)
|
|
3588
3612
|
return () => clearInterval(poll)
|
|
3589
3613
|
}, [color, stroke])
|
|
3590
|
-
const previewStroke = strokeStyleOf(appearance)
|
|
3591
3614
|
return E('div', { className: 'bw-appearance' },
|
|
3592
3615
|
E('div', { className: 'bw-field' },
|
|
3593
3616
|
t('custom.color'),
|
|
@@ -3728,7 +3751,6 @@ window.__ModuleLoader__.load({
|
|
|
3728
3751
|
color: color || undefined,
|
|
3729
3752
|
fontWeight: weight > 0 ? weight : undefined,
|
|
3730
3753
|
textShadow: previewShadows.length > 0 ? previewShadows.join(',') : undefined,
|
|
3731
|
-
...(previewStroke || null),
|
|
3732
3754
|
},
|
|
3733
3755
|
},
|
|
3734
3756
|
allowIcon ? E('span', { className: 'bw-preview-icon' }, iconOf(iconMode, true)) : null,
|
|
@@ -4155,15 +4177,27 @@ window.__ModuleLoader__.load({
|
|
|
4155
4177
|
const glow = Number(appearance.glow) || 0
|
|
4156
4178
|
const weight = Number(appearance.weight) || 0
|
|
4157
4179
|
const shadow = appearance.shadow === true
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4180
|
+
// text-shadow paints its first entry on top, so the rim goes in front of
|
|
4181
|
+
// the halo and the drop shadow that sit outside it.
|
|
4182
|
+
const outline = strokeShadowsOf(appearance)
|
|
4183
|
+
const halos = []
|
|
4184
|
+
if (glow > 0 && color) halos.push('0 0 ' + glow + 'px ' + color)
|
|
4185
|
+
if (shadow) halos.push('1px 1px 2px rgba(0,0,0,.85)')
|
|
4186
|
+
const shadows = outline.concat(halos)
|
|
4161
4187
|
const style = {}
|
|
4162
4188
|
if (color) style.color = color
|
|
4163
4189
|
if (weight > 0) style.fontWeight = weight
|
|
4164
4190
|
if (shadows.length > 0) style.textShadow = shadows.join(',')
|
|
4165
|
-
|
|
4166
|
-
|
|
4191
|
+
// The rim is published twice: inside the row's own text-shadow, and as a
|
|
4192
|
+
// variable for the places that cannot inherit it. The trailing comma is
|
|
4193
|
+
// load-bearing — the pulse keyframes append the halo after it.
|
|
4194
|
+
if (outline.length > 0) style['--bw-stroke-shadow'] = outline.join(',') + ','
|
|
4195
|
+
// Meta text (session count / relative time) opts out of the rim but keeps
|
|
4196
|
+
// the halo, which it can only read from here.
|
|
4197
|
+
if (halos.length > 0) style['--bw-glow-shadow'] = halos.join(',')
|
|
4198
|
+
// The label breathes its text only when the row actually carries a halo;
|
|
4199
|
+
// a rim alone is not a reason to pulse.
|
|
4200
|
+
if (halos.length > 0) style['--bw-text-glow'] = '1'
|
|
4167
4201
|
return Object.keys(style).length > 0 ? style : null
|
|
4168
4202
|
}
|
|
4169
4203
|
const keyOf = (kind, payload) => {
|