dsh-sidebar-organizer 0.4.0 → 0.4.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 +18 -0
- package/README.en.md +2 -2
- package/README.md +3 -2
- package/lib/client.js +71 -18
- package/package.json +1 -1
- package/src/runtime.js +69 -16
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
版本格式遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
本文件的中文条目为默认语言;英文条目见英文段落(同一版本内)。
|
|
5
5
|
|
|
6
|
+
## 0.4.1 — 2026-11-02
|
|
7
|
+
|
|
8
|
+
- 修复反复拖动时排序“抽搐”:落点改为按当前视觉顺序(`layout.order` +
|
|
9
|
+
内联 `style.order`)计算,并把目标位置锚定到具体某一行的 key,而不是页面里
|
|
10
|
+
的 DOM 序号;长按成立时会先强制落一次布局,避免用上一帧的旧几何做判定。
|
|
11
|
+
- 「更多」的投放判定收窄:只有指针真正落在「更多」按钮矩形内、且位于最后一
|
|
12
|
+
行下方时才收纳,拖到列表末尾不再被误判成收纳。
|
|
13
|
+
|
|
14
|
+
### English
|
|
15
|
+
|
|
16
|
+
- Fixed jittery ordering after repeated drags: the drop index is now computed
|
|
17
|
+
from the visual order (`layout.order` plus inline `style.order`) and anchored
|
|
18
|
+
to a row key instead of the DOM index, and a layout pass is forced when the
|
|
19
|
+
long-press succeeds so the frozen geometry is current.
|
|
20
|
+
- Tightened the “More” drop zone: a row is tucked away only when the pointer is
|
|
21
|
+
inside the anchor's own rectangle and below the last row, so dropping at the
|
|
22
|
+
end of the list no longer hides it by mistake.
|
|
23
|
+
|
|
6
24
|
## 0.4.0 — 2026-11-02
|
|
7
25
|
|
|
8
26
|
- 源码拆分为 `src/` 下的模块(constants / styles / storage / icons / slot /
|
package/README.en.md
CHANGED
|
@@ -6,8 +6,8 @@ Adds **custom ordering** and **tucking away** to the "global panels" strip at th
|
|
|
6
6
|
top-left of the DSH (DeepSeek Harness) sidebar — the column of icons for plugins,
|
|
7
7
|
marketplace, skills manager, scheduled tasks, and so on.
|
|
8
8
|
|
|
9
|
-
> Version **0.4.
|
|
10
|
-
>
|
|
9
|
+
> Version **0.4.1**: fixes jittery ordering after repeated drags and tightens
|
|
10
|
+
> the “More” drop zone.
|
|
11
11
|
|
|
12
12
|
## What it does
|
|
13
13
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
给 DSH(DeepSeek Harness)左侧栏左上角那块「全局面板」栏——插件、市场、技能管理、定时任务……那一列图标——加上**自定义排序**与**收纳**能力。
|
|
6
6
|
|
|
7
|
-
> 当前版本 **0.4.
|
|
7
|
+
> 当前版本 **0.4.1**:修复反复拖动时的排序抖动,并收窄「更多」的投放判定。
|
|
8
8
|
|
|
9
9
|
## 现在能做什么
|
|
10
10
|
|
|
@@ -132,7 +132,8 @@ npm run check # 语法检查
|
|
|
132
132
|
- [x] 0.3.4 — 图标内嵌 id 加前缀;「已收纳」内排序
|
|
133
133
|
- [x] 0.3.5 — 指针捕获延后到拖动成立,修复收纳条目无法点击
|
|
134
134
|
- [x] 0.3.6 — 点击收纳条目只打开面板,不再自动放回侧栏
|
|
135
|
-
- [x] 0.4.0 — 源码模块化与构建脚本、多语言文档、AI 使用与代码来源声明
|
|
135
|
+
- [x] 0.4.0 — 源码模块化与构建脚本、多语言文档、AI 使用与代码来源声明
|
|
136
|
+
- [x] 0.4.1 — 修复反复拖动时的排序抖动与「更多」误收纳
|
|
136
137
|
- [ ] 0.5.0 — 无障碍补强(菜单内上移 / 下移按钮、屏幕阅读器朗读顺序变化)
|
|
137
138
|
|
|
138
139
|
## 声明与引用
|
package/lib/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-sidebar-organizer — client half (v0.4.
|
|
2
|
+
* dsh-sidebar-organizer — client half (v0.4.1).
|
|
3
3
|
*
|
|
4
4
|
* GENERATED FILE — do not edit by hand.
|
|
5
5
|
* Sources live in src/; run `npm run build` to regenerate.
|
|
@@ -15,7 +15,7 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
/* Constants, locale strings and the module-level shared handles. */
|
|
16
16
|
|
|
17
17
|
// Replaced at build time from package.json by scripts/build.mjs.
|
|
18
|
-
const VERSION = "0.4.
|
|
18
|
+
const VERSION = "0.4.1";
|
|
19
19
|
|
|
20
20
|
const PANEL_SLOT = "sidebar.panellist";
|
|
21
21
|
const ACTIONS_SLOT = "sidebar.panellist.actions";
|
|
@@ -424,8 +424,24 @@ function createRuntime(ctx) {
|
|
|
424
424
|
return { nav, rows, moreHost, moreButton };
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Keys of the rows currently visible in the strip, in the order the user sees
|
|
429
|
+
* them — i.e. the persisted layout order, NOT the DOM order. The sidebar keeps
|
|
430
|
+
* rendering rows in registration order and we only move them with CSS
|
|
431
|
+
* `order`, so a DOM-order list would shift every time a drag committed and
|
|
432
|
+
* make drops land somewhere other than where the user released the pointer.
|
|
433
|
+
*/
|
|
427
434
|
function visibleKeys(rows) {
|
|
428
|
-
|
|
435
|
+
const layout = readLayout();
|
|
436
|
+
const visible = rows.filter((row) => !row.hidden).map((row) => row.key);
|
|
437
|
+
const ordered = [];
|
|
438
|
+
for (const key of layout.order) {
|
|
439
|
+
if (visible.includes(key) && !ordered.includes(key)) ordered.push(key);
|
|
440
|
+
}
|
|
441
|
+
for (const key of visible) {
|
|
442
|
+
if (!ordered.includes(key)) ordered.push(key);
|
|
443
|
+
}
|
|
444
|
+
return ordered;
|
|
429
445
|
}
|
|
430
446
|
|
|
431
447
|
function applyOrder(nav, rows, moreHost, moreButton) {
|
|
@@ -500,11 +516,13 @@ function createRuntime(ctx) {
|
|
|
500
516
|
}
|
|
501
517
|
}
|
|
502
518
|
|
|
503
|
-
function pass() {
|
|
519
|
+
function pass(force) {
|
|
504
520
|
if (!alive()) return;
|
|
505
521
|
// A pass would fight the drag ghost for layout: rows must not move while a
|
|
506
|
-
// row is being carried. Re-run once the gesture is over.
|
|
507
|
-
|
|
522
|
+
// row is being carried. Re-run once the gesture is over. `force` is used
|
|
523
|
+
// right before a drag starts to make sure the DOM already reflects the
|
|
524
|
+
// persisted order, so frozen rects and the committed order agree.
|
|
525
|
+
if (state.gesture && !force) {
|
|
508
526
|
state.passDeferred = true;
|
|
509
527
|
return;
|
|
510
528
|
}
|
|
@@ -778,6 +796,16 @@ function createRuntime(ctx) {
|
|
|
778
796
|
clearGesture();
|
|
779
797
|
return;
|
|
780
798
|
}
|
|
799
|
+
// Flush any pending layout before freezing rects: after a previous drop the
|
|
800
|
+
// rAF pass may not have run yet, and stale visuals would make the next drop
|
|
801
|
+
// land somewhere else than where the pointer is.
|
|
802
|
+
try {
|
|
803
|
+
pass(true);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
/* a failed flush must not block the drag */
|
|
806
|
+
}
|
|
807
|
+
if (!element.isConnected) element = locateRowButton(gesture.key) || element;
|
|
808
|
+
gesture.element = element;
|
|
781
809
|
const rect = element.getBoundingClientRect();
|
|
782
810
|
if (rect.width === 0 || rect.height === 0) {
|
|
783
811
|
clearGesture();
|
|
@@ -955,16 +983,25 @@ function createRuntime(ctx) {
|
|
|
955
983
|
gesture.target = null;
|
|
956
984
|
}
|
|
957
985
|
|
|
958
|
-
// Dropping a sidebar row on the "More" anchor tucks it away.
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
986
|
+
// Dropping a sidebar row on the "More" anchor tucks it away. The anchor is
|
|
987
|
+
// a small target: require the pointer to be inside its own rectangle
|
|
988
|
+
// (no padding) and strictly below the last row, otherwise a drop meant for
|
|
989
|
+
// "end of the strip" would quietly hide the row instead of reordering it.
|
|
990
|
+
if (gesture.source === "nav" && snapshot.moreRect) {
|
|
991
|
+
const lastBottom = others.length
|
|
992
|
+
? others[others.length - 1].rect.top + others[others.length - 1].rect.height
|
|
993
|
+
: -Infinity;
|
|
994
|
+
const overMore = insideRect(snapshot.moreRect, x, y, 0) && y > lastBottom + 1;
|
|
995
|
+
if (overMore) {
|
|
996
|
+
if (gesture.moreButton !== snapshot.moreButton) {
|
|
997
|
+
if (gesture.moreButton) gesture.moreButton.classList.remove("dso-drop-more");
|
|
998
|
+
gesture.moreButton = snapshot.moreButton;
|
|
999
|
+
if (gesture.moreButton) gesture.moreButton.classList.add("dso-drop-more");
|
|
1000
|
+
}
|
|
1001
|
+
if (indicator.dataset.visible !== "0") indicator.dataset.visible = "0";
|
|
1002
|
+
gesture.target = { kind: "hide" };
|
|
1003
|
+
return;
|
|
964
1004
|
}
|
|
965
|
-
if (indicator.dataset.visible !== "0") indicator.dataset.visible = "0";
|
|
966
|
-
gesture.target = { kind: "hide" };
|
|
967
|
-
return;
|
|
968
1005
|
}
|
|
969
1006
|
if (gesture.moreButton) {
|
|
970
1007
|
gesture.moreButton.classList.remove("dso-drop-more");
|
|
@@ -985,7 +1022,15 @@ function createRuntime(ctx) {
|
|
|
985
1022
|
break;
|
|
986
1023
|
}
|
|
987
1024
|
}
|
|
988
|
-
|
|
1025
|
+
// Remember which row the drop line sits in front of. Indices are computed
|
|
1026
|
+
// from frozen rects (the visual order at drag start) while commits apply to
|
|
1027
|
+
// the persisted order, so carrying the neighbour key keeps the two aligned
|
|
1028
|
+
// even when another drop has just rewritten the layout.
|
|
1029
|
+
gesture.target = {
|
|
1030
|
+
kind: "place",
|
|
1031
|
+
index,
|
|
1032
|
+
beforeKey: index < others.length ? others[index].key : null,
|
|
1033
|
+
};
|
|
989
1034
|
|
|
990
1035
|
if (index === snapshot.lastIndex) {
|
|
991
1036
|
if (indicator.dataset.visible !== "1") indicator.dataset.visible = "1";
|
|
@@ -1018,8 +1063,16 @@ function createRuntime(ctx) {
|
|
|
1018
1063
|
return;
|
|
1019
1064
|
}
|
|
1020
1065
|
if (target.kind === "place") {
|
|
1066
|
+
// Map the drop position onto the persisted order (see visibleKeys): the
|
|
1067
|
+
// anchor row is the key we resolved from the frozen rects.
|
|
1068
|
+
const { rows } = visibleRows();
|
|
1069
|
+
const others = visibleKeys(rows).filter((item) => item !== key);
|
|
1070
|
+
let index = Math.max(0, Math.min(target.index, others.length));
|
|
1071
|
+
if (target.beforeKey && others.includes(target.beforeKey)) {
|
|
1072
|
+
index = others.indexOf(target.beforeKey);
|
|
1073
|
+
}
|
|
1021
1074
|
if (source === "menu") {
|
|
1022
|
-
revealKey(key,
|
|
1075
|
+
revealKey(key, index);
|
|
1023
1076
|
if (state.dismissMenu) {
|
|
1024
1077
|
try {
|
|
1025
1078
|
state.dismissMenu();
|
|
@@ -1028,7 +1081,7 @@ function createRuntime(ctx) {
|
|
|
1028
1081
|
}
|
|
1029
1082
|
}
|
|
1030
1083
|
} else {
|
|
1031
|
-
reorderKey(key,
|
|
1084
|
+
reorderKey(key, index);
|
|
1032
1085
|
}
|
|
1033
1086
|
}
|
|
1034
1087
|
}
|
package/package.json
CHANGED
package/src/runtime.js
CHANGED
|
@@ -129,8 +129,24 @@ function createRuntime(ctx) {
|
|
|
129
129
|
return { nav, rows, moreHost, moreButton };
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Keys of the rows currently visible in the strip, in the order the user sees
|
|
134
|
+
* them — i.e. the persisted layout order, NOT the DOM order. The sidebar keeps
|
|
135
|
+
* rendering rows in registration order and we only move them with CSS
|
|
136
|
+
* `order`, so a DOM-order list would shift every time a drag committed and
|
|
137
|
+
* make drops land somewhere other than where the user released the pointer.
|
|
138
|
+
*/
|
|
132
139
|
function visibleKeys(rows) {
|
|
133
|
-
|
|
140
|
+
const layout = readLayout();
|
|
141
|
+
const visible = rows.filter((row) => !row.hidden).map((row) => row.key);
|
|
142
|
+
const ordered = [];
|
|
143
|
+
for (const key of layout.order) {
|
|
144
|
+
if (visible.includes(key) && !ordered.includes(key)) ordered.push(key);
|
|
145
|
+
}
|
|
146
|
+
for (const key of visible) {
|
|
147
|
+
if (!ordered.includes(key)) ordered.push(key);
|
|
148
|
+
}
|
|
149
|
+
return ordered;
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
function applyOrder(nav, rows, moreHost, moreButton) {
|
|
@@ -205,11 +221,13 @@ function createRuntime(ctx) {
|
|
|
205
221
|
}
|
|
206
222
|
}
|
|
207
223
|
|
|
208
|
-
function pass() {
|
|
224
|
+
function pass(force) {
|
|
209
225
|
if (!alive()) return;
|
|
210
226
|
// A pass would fight the drag ghost for layout: rows must not move while a
|
|
211
|
-
// row is being carried. Re-run once the gesture is over.
|
|
212
|
-
|
|
227
|
+
// row is being carried. Re-run once the gesture is over. `force` is used
|
|
228
|
+
// right before a drag starts to make sure the DOM already reflects the
|
|
229
|
+
// persisted order, so frozen rects and the committed order agree.
|
|
230
|
+
if (state.gesture && !force) {
|
|
213
231
|
state.passDeferred = true;
|
|
214
232
|
return;
|
|
215
233
|
}
|
|
@@ -483,6 +501,16 @@ function createRuntime(ctx) {
|
|
|
483
501
|
clearGesture();
|
|
484
502
|
return;
|
|
485
503
|
}
|
|
504
|
+
// Flush any pending layout before freezing rects: after a previous drop the
|
|
505
|
+
// rAF pass may not have run yet, and stale visuals would make the next drop
|
|
506
|
+
// land somewhere else than where the pointer is.
|
|
507
|
+
try {
|
|
508
|
+
pass(true);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
/* a failed flush must not block the drag */
|
|
511
|
+
}
|
|
512
|
+
if (!element.isConnected) element = locateRowButton(gesture.key) || element;
|
|
513
|
+
gesture.element = element;
|
|
486
514
|
const rect = element.getBoundingClientRect();
|
|
487
515
|
if (rect.width === 0 || rect.height === 0) {
|
|
488
516
|
clearGesture();
|
|
@@ -660,16 +688,25 @@ function createRuntime(ctx) {
|
|
|
660
688
|
gesture.target = null;
|
|
661
689
|
}
|
|
662
690
|
|
|
663
|
-
// Dropping a sidebar row on the "More" anchor tucks it away.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
691
|
+
// Dropping a sidebar row on the "More" anchor tucks it away. The anchor is
|
|
692
|
+
// a small target: require the pointer to be inside its own rectangle
|
|
693
|
+
// (no padding) and strictly below the last row, otherwise a drop meant for
|
|
694
|
+
// "end of the strip" would quietly hide the row instead of reordering it.
|
|
695
|
+
if (gesture.source === "nav" && snapshot.moreRect) {
|
|
696
|
+
const lastBottom = others.length
|
|
697
|
+
? others[others.length - 1].rect.top + others[others.length - 1].rect.height
|
|
698
|
+
: -Infinity;
|
|
699
|
+
const overMore = insideRect(snapshot.moreRect, x, y, 0) && y > lastBottom + 1;
|
|
700
|
+
if (overMore) {
|
|
701
|
+
if (gesture.moreButton !== snapshot.moreButton) {
|
|
702
|
+
if (gesture.moreButton) gesture.moreButton.classList.remove("dso-drop-more");
|
|
703
|
+
gesture.moreButton = snapshot.moreButton;
|
|
704
|
+
if (gesture.moreButton) gesture.moreButton.classList.add("dso-drop-more");
|
|
705
|
+
}
|
|
706
|
+
if (indicator.dataset.visible !== "0") indicator.dataset.visible = "0";
|
|
707
|
+
gesture.target = { kind: "hide" };
|
|
708
|
+
return;
|
|
669
709
|
}
|
|
670
|
-
if (indicator.dataset.visible !== "0") indicator.dataset.visible = "0";
|
|
671
|
-
gesture.target = { kind: "hide" };
|
|
672
|
-
return;
|
|
673
710
|
}
|
|
674
711
|
if (gesture.moreButton) {
|
|
675
712
|
gesture.moreButton.classList.remove("dso-drop-more");
|
|
@@ -690,7 +727,15 @@ function createRuntime(ctx) {
|
|
|
690
727
|
break;
|
|
691
728
|
}
|
|
692
729
|
}
|
|
693
|
-
|
|
730
|
+
// Remember which row the drop line sits in front of. Indices are computed
|
|
731
|
+
// from frozen rects (the visual order at drag start) while commits apply to
|
|
732
|
+
// the persisted order, so carrying the neighbour key keeps the two aligned
|
|
733
|
+
// even when another drop has just rewritten the layout.
|
|
734
|
+
gesture.target = {
|
|
735
|
+
kind: "place",
|
|
736
|
+
index,
|
|
737
|
+
beforeKey: index < others.length ? others[index].key : null,
|
|
738
|
+
};
|
|
694
739
|
|
|
695
740
|
if (index === snapshot.lastIndex) {
|
|
696
741
|
if (indicator.dataset.visible !== "1") indicator.dataset.visible = "1";
|
|
@@ -723,8 +768,16 @@ function createRuntime(ctx) {
|
|
|
723
768
|
return;
|
|
724
769
|
}
|
|
725
770
|
if (target.kind === "place") {
|
|
771
|
+
// Map the drop position onto the persisted order (see visibleKeys): the
|
|
772
|
+
// anchor row is the key we resolved from the frozen rects.
|
|
773
|
+
const { rows } = visibleRows();
|
|
774
|
+
const others = visibleKeys(rows).filter((item) => item !== key);
|
|
775
|
+
let index = Math.max(0, Math.min(target.index, others.length));
|
|
776
|
+
if (target.beforeKey && others.includes(target.beforeKey)) {
|
|
777
|
+
index = others.indexOf(target.beforeKey);
|
|
778
|
+
}
|
|
726
779
|
if (source === "menu") {
|
|
727
|
-
revealKey(key,
|
|
780
|
+
revealKey(key, index);
|
|
728
781
|
if (state.dismissMenu) {
|
|
729
782
|
try {
|
|
730
783
|
state.dismissMenu();
|
|
@@ -733,7 +786,7 @@ function createRuntime(ctx) {
|
|
|
733
786
|
}
|
|
734
787
|
}
|
|
735
788
|
} else {
|
|
736
|
-
reorderKey(key,
|
|
789
|
+
reorderKey(key, index);
|
|
737
790
|
}
|
|
738
791
|
}
|
|
739
792
|
}
|