dsh-sidebar-suite 0.1.3 → 0.2.5
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 +55 -0
- package/DETAIL.md +137 -64
- package/README.md +213 -90
- package/assets/icon-180.png +0 -0
- package/assets/screenshots/screenshot-1.png +0 -0
- package/assets/screenshots/screenshot-2.png +0 -0
- package/assets/screenshots/screenshot-3.png +0 -0
- package/lib/client.js +2285 -1276
- package/lib/index.js +215 -158
- package/lib/temp-session-host.mjs +842 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +72 -60
- package/screenshots.json +5 -0
- package/temp-session-config.txt +53 -0
- package//344/275/277/347/224/250/350/257/264/346/230/216.md +418 -0
package/lib/client.js
CHANGED
|
@@ -1,1276 +1,2285 @@
|
|
|
1
|
-
// dsh-sidebar-suite — Client half.
|
|
2
|
-
//
|
|
3
|
-
// The DSH left sidebar stacks two groups in one column: the panels group
|
|
4
|
-
// (`nav` with a `panelList` class) and the workspaces group
|
|
5
|
-
// (`[data-slot="sidebar.workspaces"]` inside its own region). They compete for
|
|
6
|
-
// the same vertical space, so this plugin offers three layouts for them:
|
|
7
|
-
//
|
|
8
|
-
// a both groups fold independently
|
|
9
|
-
// b the groups share one space and the control bar switches between them
|
|
10
|
-
// c the workspaces stay put and the panels open as an overlay
|
|
11
|
-
//
|
|
12
|
-
// All state lives in attributes on <html>, the host's markup is
|
|
13
|
-
// restructured
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
var
|
|
22
|
-
var
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
var
|
|
38
|
-
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
95
|
-
'
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
'
|
|
99
|
-
'
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
var
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
//
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
if (
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (
|
|
835
|
-
var
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
var
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
if (
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
} catch (error) {
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1
|
+
// dsh-sidebar-suite — Client half.
|
|
2
|
+
//
|
|
3
|
+
// The DSH left sidebar stacks two groups in one column: the panels group
|
|
4
|
+
// (`nav` with a `panelList` class) and the workspaces group
|
|
5
|
+
// (`[data-slot="sidebar.workspaces"]` inside its own region). They compete for
|
|
6
|
+
// the same vertical space, so this plugin offers three layouts for them:
|
|
7
|
+
//
|
|
8
|
+
// a both groups fold independently
|
|
9
|
+
// b the groups share one space and the control bar switches between them
|
|
10
|
+
// c the workspaces stay put and the panels open as an overlay
|
|
11
|
+
//
|
|
12
|
+
// All layout state lives in attributes on <html>, and the host's markup is
|
|
13
|
+
// never restructured or moved: this plugin only ADDS one control bar as a
|
|
14
|
+
// direct child of the sidebar root, one Temp button as a sibling of the host's
|
|
15
|
+
// New Session button, and (on demand) a temporary-chat dialog + scrim on body.
|
|
16
|
+
window.__ModuleLoader__.load({
|
|
17
|
+
id: 'dsh-sidebar-suite',
|
|
18
|
+
factory(require) {
|
|
19
|
+
'use strict'
|
|
20
|
+
|
|
21
|
+
var ENTRY = 'dsh-sidebar-suite'
|
|
22
|
+
var NS = 'sidebar-suite'
|
|
23
|
+
var LS_KEY = 'dsh-sidebar-suite:state'
|
|
24
|
+
var STYLE_ID = 'dsh-sidebar-suite-style'
|
|
25
|
+
// Where feedback goes. A plugin bundle cannot open a mail client or post a
|
|
26
|
+
// form without a server, so the entry point assembles a ready-to-send
|
|
27
|
+
// report and offers the three channels that need no backend:
|
|
28
|
+
// 1. a pre-filled GitHub issue in THIS repository (the desktop shell opens
|
|
29
|
+
// http(s) links in the system browser — app.asar lib/main.js
|
|
30
|
+
// setWindowOpenHandler),
|
|
31
|
+
// 2. the marketplace's own comment thread. dsh-market keys every plugin's
|
|
32
|
+
// thread on `plugin:<owner/repo>` inside the public catalog repo
|
|
33
|
+
// (verified in dshmarket/client/client.js: GISCUS.repo, commentsTerm).
|
|
34
|
+
// It is the feedback entry the plugin market actually defines, so the
|
|
35
|
+
// detail page links to it instead of inventing a fourth channel,
|
|
36
|
+
// 3. copy to clipboard, for anyone who does not use GitHub.
|
|
37
|
+
var FEEDBACK_REPO = 'https://github.com/YONAN1269/dsh-sidebar-suite'
|
|
38
|
+
var FEEDBACK_DRAFT_KEY = 'dsh-sidebar-suite:feedback-draft'
|
|
39
|
+
var FEEDBACK_MARKET_TERM = 'plugin:YONAN1269/dsh-sidebar-suite'
|
|
40
|
+
var FEEDBACK_MARKET_URL = 'https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/discussions'
|
|
41
|
+
+ '?discussions_q=' + encodeURIComponent('"' + FEEDBACK_MARKET_TERM + '"')
|
|
42
|
+
// The plugin detail page is a React surface. Load React defensively: if the
|
|
43
|
+
// module loader does not hand us `require`, the sidebar still works and we
|
|
44
|
+
// simply skip registering the graphical settings page.
|
|
45
|
+
var React = null
|
|
46
|
+
try {
|
|
47
|
+
React = typeof require === 'function' ? require('react') : null
|
|
48
|
+
} catch (error) {
|
|
49
|
+
React = null
|
|
50
|
+
}
|
|
51
|
+
// A client bundle can be applied more than once in the same page (a hot
|
|
52
|
+
// client reload after the plugin's files change, or a second Loader entry
|
|
53
|
+
// for the same package). Both runs would insert a bar and fight over the
|
|
54
|
+
// same <html> attributes, so the first live run owns the sidebar.
|
|
55
|
+
var OWNER_KEY = '__dshSidebarSuiteOwner'
|
|
56
|
+
|
|
57
|
+
var DEFAULTS = { mode: 'b', showQuickSwitch: true, tab: 'ext', foldExt: false, foldWs: false }
|
|
58
|
+
var MODES = ['a', 'b', 'c']
|
|
59
|
+
|
|
60
|
+
var DICTS = {
|
|
61
|
+
en: {
|
|
62
|
+
'bar.label': 'Sidebar layout',
|
|
63
|
+
'bar.local': 'Settings are stored on this device only (the official config layer is unavailable).',
|
|
64
|
+
'mode.a': 'Fold',
|
|
65
|
+
'mode.a.hint': 'Each group folds on its own.',
|
|
66
|
+
'mode.b': 'Toggle',
|
|
67
|
+
'mode.b.hint': 'The two groups share one space; choose which one is shown.',
|
|
68
|
+
'mode.c': 'Overlay',
|
|
69
|
+
'mode.c.hint': 'Workspaces stay visible; panels open as an overlay.',
|
|
70
|
+
'quick.label': 'Cycle',
|
|
71
|
+
'quick.hint': 'Cycle the sidebar layout.',
|
|
72
|
+
'fold.ext.collapse': 'Hide panels',
|
|
73
|
+
'fold.ext.expand': 'Show panels',
|
|
74
|
+
'fold.ext.hint': 'Hide or show the panels group.',
|
|
75
|
+
'fold.ws.collapse': 'Hide workspaces',
|
|
76
|
+
'fold.ws.expand': 'Show workspaces',
|
|
77
|
+
'fold.ws.hint': 'Hide or show the workspaces group.',
|
|
78
|
+
'tab.to.ext': 'Show panels',
|
|
79
|
+
'tab.to.ext.hint': 'Switch the shared space to the panels group.',
|
|
80
|
+
'tab.to.ws': 'Show workspaces',
|
|
81
|
+
'tab.to.ws.hint': 'Switch the shared space to the workspaces group.',
|
|
82
|
+
'pop.open': 'Open panels',
|
|
83
|
+
'pop.close': 'Close panels',
|
|
84
|
+
'pop.hint': 'Open or close the panels overlay.',
|
|
85
|
+
'settings.title': 'Default layout on startup',
|
|
86
|
+
'settings.help': 'Pick the layout DSH starts with. This writes the plugin config; switching from the sidebar bar is session-only.',
|
|
87
|
+
'settings.unavailable': 'The config service is unavailable right now, so the default cannot be changed here.',
|
|
88
|
+
'settings.current': 'Current default',
|
|
89
|
+
'temp.label': 'Temp',
|
|
90
|
+
'temp.hint': 'Open a temporary session: a real DSH session in this sidebar that is deleted when you quit.',
|
|
91
|
+
'temp.creating': 'Creating…',
|
|
92
|
+
'temp.created': 'Temporary session ready. It lives in the sidebar and is deleted when you quit DSH.',
|
|
93
|
+
'temp.noNative': 'This DSH build did not expose its session navigation service.',
|
|
94
|
+
'temp.failed': 'Could not create the temporary session: ',
|
|
95
|
+
'temp.feedbackHint': 'Report it on the plugin detail page.',
|
|
96
|
+
'temp.restartHint': 'Fully quit DSH (tray icon → Quit) and reopen it — a hot reload does not reload the plugin host.',
|
|
97
|
+
'temp.badge': 'TEMP',
|
|
98
|
+
'policy.title': 'Temporary session cleanup',
|
|
99
|
+
'policy.help': 'Choose when temporary sessions are removed. Saved to the plugin settings file and applied immediately — no restart.',
|
|
100
|
+
'policy.onQuit': 'Delete when DSH quits',
|
|
101
|
+
'policy.schedule': 'Clean up every day at',
|
|
102
|
+
'policy.on': 'On',
|
|
103
|
+
'policy.off': 'Off',
|
|
104
|
+
'policy.note': 'A session in use right now is skipped and retried 5 minutes later.',
|
|
105
|
+
'policy.saving': 'Saving…',
|
|
106
|
+
'policy.saved': 'Saved',
|
|
107
|
+
'policy.failed': 'Could not save: ',
|
|
108
|
+
'policy.loading': 'Loading current settings…',
|
|
109
|
+
'policy.unavailable': 'Cleanup settings are unavailable — the plugin host did not answer. Fully quit DSH and reopen it.',
|
|
110
|
+
},
|
|
111
|
+
zh: {
|
|
112
|
+
'bar.label': '侧栏布局',
|
|
113
|
+
'bar.local': '设置仅保存在本机(官方配置层不可用)。',
|
|
114
|
+
'mode.a': '折叠',
|
|
115
|
+
'mode.a.hint': '两组各自收起。',
|
|
116
|
+
'mode.b': '标签页',
|
|
117
|
+
'mode.b.hint': '两组共用一栏,用标签页切换显示。',
|
|
118
|
+
'mode.c': '浮层',
|
|
119
|
+
'mode.c.hint': '工作区常驻,扩展以浮层展开。',
|
|
120
|
+
'quick.label': '换布局',
|
|
121
|
+
'quick.hint': '循环切换侧栏布局。',
|
|
122
|
+
'fold.ext.collapse': '收起扩展',
|
|
123
|
+
'fold.ext.expand': '展开扩展',
|
|
124
|
+
'fold.ext.hint': '收起或展开扩展区。',
|
|
125
|
+
'fold.ws.collapse': '收起工作区',
|
|
126
|
+
'fold.ws.expand': '展开工作区',
|
|
127
|
+
'fold.ws.hint': '收起或展开工作区。',
|
|
128
|
+
'tab.to.ext': '显示扩展',
|
|
129
|
+
'tab.to.ext.hint': '把共用的一栏切换到扩展区。',
|
|
130
|
+
'tab.to.ws': '显示工作区',
|
|
131
|
+
'tab.to.ws.hint': '把共用的一栏切换到工作区。',
|
|
132
|
+
'pop.open': '展开扩展',
|
|
133
|
+
'pop.close': '收起扩展',
|
|
134
|
+
'pop.hint': '打开或收起扩展浮层。',
|
|
135
|
+
'settings.title': '启动时的默认布局',
|
|
136
|
+
'settings.help': '选择 DSH 启动时使用的布局。这里会写入插件配置;在侧栏上临时切换不会改动它。',
|
|
137
|
+
'settings.unavailable': '配置服务暂时不可用,无法在这里修改默认布局。',
|
|
138
|
+
'settings.current': '当前默认',
|
|
139
|
+
'temp.label': '临时',
|
|
140
|
+
'temp.hint': '开一个临时会话:就是左侧栏里的真实会话,退出 DSH 时删除。',
|
|
141
|
+
'temp.creating': '正在创建…',
|
|
142
|
+
'temp.created': '临时会话已就绪:它在左侧栏里,退出 DSH 时自动删除。',
|
|
143
|
+
'temp.noNative': '当前 DSH 版本没有暴露会话导航服务。',
|
|
144
|
+
'temp.failed': '创建临时会话失败:',
|
|
145
|
+
'temp.feedbackHint': '可在插件详情页反馈。',
|
|
146
|
+
'temp.restartHint': '请从托盘图标彻底退出 DSH 再打开 —— 热重载不会重载插件后端。',
|
|
147
|
+
'temp.badge': '临时',
|
|
148
|
+
'policy.title': '临时会话自动删除',
|
|
149
|
+
'policy.help': '决定临时会话什么时候被清掉。写入插件设置文件,改完立即生效,不用重启。',
|
|
150
|
+
'policy.onQuit': '退出 DSH 时删除',
|
|
151
|
+
'policy.schedule': '每天定时清理,时间',
|
|
152
|
+
'policy.on': '开',
|
|
153
|
+
'policy.off': '关',
|
|
154
|
+
'policy.note': '正在使用的那个会话会跳过,5 分钟后再试一次。',
|
|
155
|
+
'policy.saving': '保存中…',
|
|
156
|
+
'policy.saved': '已保存',
|
|
157
|
+
'policy.failed': '保存失败:',
|
|
158
|
+
'policy.loading': '正在读取当前设置…',
|
|
159
|
+
'policy.unavailable': '读取不到清理设置 —— 插件后端没有响应。请从托盘彻底退出 DSH 再打开。',
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// The plugin detail page must read well in both languages regardless of
|
|
164
|
+
// the UI locale, so the settings copy is deliberately bilingual in both
|
|
165
|
+
// dictionaries. The long detail document (DETAIL.md) does the same.
|
|
166
|
+
var BILINGUAL_SETTINGS = {
|
|
167
|
+
// Pinned at the top of the plugin detail page: the one-line positioning
|
|
168
|
+
// statement. The Host renders locale/meta.description above the sections,
|
|
169
|
+
// but the settings card is lifted to order:-1, so without this line the
|
|
170
|
+
// first thing a reader sees would be a control with no context.
|
|
171
|
+
'settings.intro': '一款专门针对改进 DSH 左侧栏体验的插件 —— 强实用性 + 美观性:沿用宿主原生观感,不另起一套皮肤。 / A plugin dedicated to the DSH sidebar experience — seriously practical and good-looking, built from native host design tokens.',
|
|
172
|
+
'settings.title': '启动时的默认布局 · Default layout on startup',
|
|
173
|
+
'settings.help': '选择 DSH 启动时使用的布局,这里会写入插件配置;在侧栏上临时切换只影响本次会话。 / Pick the layout DSH starts with — this writes the plugin config, while switching from the sidebar bar stays session-only.',
|
|
174
|
+
'settings.unavailable': '配置服务暂时不可用,无法在这里修改默认布局。 / The config service is unavailable right now, so the default cannot be changed here.',
|
|
175
|
+
'settings.current': '当前默认 / Current',
|
|
176
|
+
'settings.support': '喜欢这个插件?请作者喝杯咖啡 · Enjoy it? Buy me a coffee',
|
|
177
|
+
'settings.supportHint': '完全自愿,无任何回报承诺。用微信扫一扫即可;「设置 → 支持贡献者」里也登记了同一个码。 / Purely voluntary, no rewards promised. Scan with WeChat Pay; the same QR is registered under Settings → Support contributors.',
|
|
178
|
+
'settings.qrAlt': '微信支付收款码 / WeChat Pay QR',
|
|
179
|
+
'feedback.title': '反馈 · Feedback',
|
|
180
|
+
'feedback.help': '遇到问题,或者想要某个功能?写在下面,然后选一个入口。 / Hit a bug or want a feature? Write it below, then pick a channel.',
|
|
181
|
+
'feedback.placeholder': '例如:点了「临时」之后,侧栏没有出现新会话…… / e.g. clicking Temp adds no session to the sidebar…',
|
|
182
|
+
'feedback.copy': '复制反馈内容 / Copy report',
|
|
183
|
+
'feedback.issue': '在 GitHub 提 issue / Open an issue',
|
|
184
|
+
'feedback.market': '插件市场评论区 / Marketplace comments',
|
|
185
|
+
'feedback.working': '正在准备… / Preparing…',
|
|
186
|
+
'feedback.copied': '已复制,粘贴发送即可 / Copied — paste and send',
|
|
187
|
+
'feedback.issueOpened': '已在浏览器打开 / Opened in your browser',
|
|
188
|
+
'feedback.failed': '复制被浏览器拒绝了,请手动选中再复制 / The browser blocked the copy — select and copy manually',
|
|
189
|
+
'feedback.hint': '报告会自动带上版本、系统、布局和最近一次错误,不用再补一遍环境信息。 / The report already includes your version, OS, layout and last error.',
|
|
190
|
+
'feedback.where': '作者在哪收到:issue 进本仓库的 Issues(仓库所有者默认会收到通知);市场评论进 awesome-dsh-plugin 仓库的 Discussions(作者需要 watch 那条讨论);「复制反馈内容」不会自动发送,粘到微信 / QQ / 邮件都行。 / Where the author sees it: issues land in this repo (the owner is normally notified); marketplace comments land in the awesome-dsh-plugin Discussions (the author must watch that thread); copied text is never sent automatically — paste it anywhere you can reach me.',
|
|
191
|
+
'feedback.marketHint': '市场评论区如果显示「没有匹配的讨论」,说明还没人留言:回 DSH 的插件市场,找到 dsh-sidebar-suite 卡片点「评论」,第一条留言会自动建帖。 / If GitHub says there are no matching discussions yet, open the plugin market in DSH, find the dsh-sidebar-suite card and press "评论 / Comments" — the first comment creates that thread.',
|
|
192
|
+
}
|
|
193
|
+
for (var lang in DICTS) {
|
|
194
|
+
if (!Object.prototype.hasOwnProperty.call(DICTS, lang)) continue
|
|
195
|
+
for (var key in BILINGUAL_SETTINGS) {
|
|
196
|
+
if (Object.prototype.hasOwnProperty.call(BILINGUAL_SETTINGS, key)) DICTS[lang][key] = BILINGUAL_SETTINGS[key]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
var CSS = [
|
|
201
|
+
'[data-dsbs-bar]{display:flex;flex-wrap:wrap;align-items:center;gap:5px;padding:2px 0 6px;flex:none;min-width:0}',
|
|
202
|
+
'[data-dsbs-bar] [hidden]{display:none!important}',
|
|
203
|
+
'[data-dsbs-sep]{width:1px;height:12px;flex:none;border-radius:1px;margin:0 1px;background:var(--dsw-alias-border-l1,rgba(127,127,127,.3))}',
|
|
204
|
+
'[data-dsbs-bar] button{appearance:none;-webkit-appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:11px;line-height:15px;padding:2px 6px;border-radius:6px;cursor:pointer;user-select:none;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}',
|
|
205
|
+
'[data-dsbs-bar] button:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.14));color:var(--dsw-alias-label-primary,inherit)}',
|
|
206
|
+
'[data-dsbs-bar] button[aria-pressed="true"]{background:var(--dsw-alias-interactive-bg-active,rgba(127,127,127,.22));color:var(--dsw-alias-label-primary,inherit);font-weight:500}',
|
|
207
|
+
'[data-dsbs-bar] button:focus-visible{outline:2px solid var(--dsw-alias-label-primary,currentColor);outline-offset:1px}',
|
|
208
|
+
'[data-dsbs-bar] button[data-dsbs-btn="quick"]{margin-left:auto;color:var(--dsw-alias-label-tertiary,inherit);padding:2px 6px}',
|
|
209
|
+
'html[data-dsbs-narrow="true"] [data-dsbs-btn="quick"]{display:none}',
|
|
210
|
+
'html[data-dsbs-rail="true"] [data-dsbs-bar]{display:none}',
|
|
211
|
+
// a — each group folds on its own
|
|
212
|
+
'html[data-dsbs-mode="a"][data-dsbs-fold-ext="true"] nav[data-dsbs-ext]{display:none}',
|
|
213
|
+
'html[data-dsbs-mode="a"][data-dsbs-fold-ws="true"] div:has(> [data-slot="sidebar.workspaces"]){display:none}',
|
|
214
|
+
// b — the two groups are mutually exclusive
|
|
215
|
+
'html[data-dsbs-mode="b"][data-dsbs-tab="ext"] div:has(> [data-slot="sidebar.workspaces"]){display:none}',
|
|
216
|
+
'html[data-dsbs-mode="b"][data-dsbs-tab="ws"] nav[data-dsbs-ext]{display:none}',
|
|
217
|
+
// c — panels become an overlay above the workspaces
|
|
218
|
+
'html[data-dsbs-mode="c"]:not([data-dsbs-pop="true"]) nav[data-dsbs-ext]{display:none}',
|
|
219
|
+
'html[data-dsbs-mode="c"][data-dsbs-pop="true"] nav[data-dsbs-ext]{display:flex;flex-direction:column;gap:2px;position:absolute;z-index:60;left:4px;right:4px;top:var(--dsbs-pop-top,64px);max-height:60vh;overflow:auto;padding:6px;border-radius:10px;background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-base,#fff));border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3));box-shadow:0 12px 32px rgba(0,0,0,.24)}',
|
|
220
|
+
// the overlay needs a positioned containing block; the sidebar root is the
|
|
221
|
+
// only element with the panels nav as a direct child
|
|
222
|
+
'div:has(> nav[data-dsbs-ext]){position:relative}',
|
|
223
|
+
// Plugin Manager detail page: `.detailSections` is already a column flex,
|
|
224
|
+
// so `order:-1` lifts this bundle's configuration section above the long
|
|
225
|
+
// detail document. Scoped by data-plugin-detail, no other bundle is hit.
|
|
226
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-plugin-config]{order:-1;box-sizing:border-box;padding:14px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.28));border-radius:12px;background:var(--dsw-alias-bg-subtle,rgba(127,127,127,.05))}',
|
|
227
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-dsbs-support]{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:2px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.2))}',
|
|
228
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-dsbs-support] img{width:96px;height:96px;object-fit:contain;box-sizing:border-box;padding:4px;border-radius:8px;background:#fff;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.2))}',
|
|
229
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-dsbs-support] [data-dsbs-support-text]{flex:1 1 200px;min-width:0;display:flex;flex-direction:column;gap:4px}',
|
|
230
|
+
// Feedback box (0.2.3). The element itself is styled inline so it still
|
|
231
|
+
// looks right if this stylesheet is ever blocked; these three rules only
|
|
232
|
+
// cover what inline styles cannot express.
|
|
233
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-dsbs-feedback]{width:100%;box-sizing:border-box}',
|
|
234
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-dsbs-feedback] textarea::placeholder{color:var(--dsw-alias-label-tertiary,rgba(127,127,127,.75))}',
|
|
235
|
+
'[data-plugin-detail="dsh-sidebar-suite"] [data-dsbs-feedback] textarea:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,currentColor);outline-offset:1px}',
|
|
236
|
+
// ---- always-expanded session list (0.2.4) -----------------------
|
|
237
|
+
// The host workspace browser renders one overflow button per group as
|
|
238
|
+
// <button data-row-key="overflow:<group>"> when its 5-row watermark
|
|
239
|
+
// hides the rest (verified in @deepseek-ai/dsh-client-ui-workspace:
|
|
240
|
+
// "sessions.expand" / "展开其余 {n} 个会话"). We drive that button until
|
|
241
|
+
// every group is expanded, then hide it: with all sessions visible the
|
|
242
|
+
// "收起" half of the control is pointless too. Only active while the
|
|
243
|
+
// config says on, so EXPAND_ALL_SESSIONS=off restores the host's UI.
|
|
244
|
+
'html[data-dsbs-expand-all="true"] button[data-row-key^="overflow:"]{display:none!important}',
|
|
245
|
+
// ---- temporary chat (0.1.7) --------------------------------------
|
|
246
|
+
// The Temp button sits on the same row as the host's "New Session"
|
|
247
|
+
// button. Host markup is never moved: we add one sibling and pull it back
|
|
248
|
+
// onto the previous row with a negative margin, so the net flow height is
|
|
249
|
+
// zero and every following sidebar group keeps its original position.
|
|
250
|
+
// The whole two-button row only applies in HARNESS mode. dsh-deepseek-chat
|
|
251
|
+
// swaps the sidebar for its own website navigation in CHAT mode, where
|
|
252
|
+
// shrinking "new chat" would break the host's own layout, so every rule
|
|
253
|
+
// here is gated on html:not([data-dsbs-chat-mode]).
|
|
254
|
+
'html:not([data-dsbs-chat-mode]) [data-dsbs-newchat] button[class*="newSession"]{height:34px!important;width:calc(100% - 92px)!important;min-width:0!important;flex:0 0 auto!important;margin:0 0 10px!important;font-size:12px!important;line-height:20px!important}',
|
|
255
|
+
'html:not([data-dsbs-chat-mode]) [data-dsbs-newchat] button[class*="newSession"] > span[class*="newSessionShortcut"]{display:none!important}',
|
|
256
|
+
// Escape hatch for layouts that fight the two-button row (e.g. another
|
|
257
|
+
// plugin's header row above the sidebar). TEMP_BUTTON_STYLE=own puts the
|
|
258
|
+
// Temp button on its own full-width row and leaves New Session untouched.
|
|
259
|
+
'html[data-dsbs-temp-style="own"] [data-dsbs-newchat] button[class*="newSession"]{height:38px!important;width:auto!important;font-size:14px!important;line-height:22px!important;margin:0 calc(var(--dsh-sidebar-paint-inset,3px) + 1px) 10px!important}',
|
|
260
|
+
'html[data-dsbs-temp-style="own"] [data-dsbs-newchat] > [data-dsbs-temp-slot]{width:auto!important;align-self:stretch!important;margin:0 calc(var(--dsh-sidebar-paint-inset,3px) + 1px) 10px!important}',
|
|
261
|
+
'html[data-dsbs-temp-style="own"] [data-dsbs-temp-slot] > [data-dsbs-btn="temp"]{width:auto!important;height:32px!important;text-align:center!important}',
|
|
262
|
+
// The Temp button is never a direct child of the sidebar root: it sits in
|
|
263
|
+
// its own <div>. dsh-deepseek-chat walks that root and requires exactly
|
|
264
|
+
// ONE direct <button> child (the host's New Session); a second one makes
|
|
265
|
+
// its sidebar adapter bail out and it paints the "当前侧栏不兼容" notice
|
|
266
|
+
// above the CHAT / HARNESS switcher. The wrapper keeps that count at one.
|
|
267
|
+
'[data-dsbs-newchat] > [data-dsbs-temp-slot]{display:flex;flex:none;align-self:flex-end;box-sizing:border-box;margin:-44px 4px 10px 0;width:80px;z-index:2}',
|
|
268
|
+
'[data-dsbs-temp-slot][hidden]{display:none!important}',
|
|
269
|
+
'[data-dsbs-temp-slot] > [data-dsbs-btn="temp"]{appearance:none;-webkit-appearance:none;box-sizing:border-box;width:100%;height:34px;flex:none;border:.5px solid var(--dsw-alias-border-l3,rgba(127,127,127,.35));border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-button-elevated-fill,transparent);color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:12px;font-weight:500;line-height:20px;cursor:pointer;user-select:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:6px 8px}',
|
|
270
|
+
'[data-dsbs-temp-slot] > [data-dsbs-btn="temp"]:hover{background:var(--dsw-alias-button-floating-hover,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)))}',
|
|
271
|
+
'[data-dsbs-temp-slot] > [data-dsbs-btn="temp"]:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,currentColor);outline-offset:1px}',
|
|
272
|
+
'[data-dsbs-temp-slot] > [data-dsbs-btn="temp"][hidden]{display:none!important}',
|
|
273
|
+
// dsh-deepseek-chat is in CHAT (official website) mode: its sidebar and
|
|
274
|
+
// its own "new chat" button own that space, so the Temp button steps
|
|
275
|
+
// aside instead of fighting it. Switchable via temp-session-config.txt.
|
|
276
|
+
'html[data-dsbs-chat-mode] [data-dsbs-temp-slot]{display:none!important}',
|
|
277
|
+
// In the collapsed rail there is no horizontal room for two buttons.
|
|
278
|
+
'html[data-dsbs-temp-solo="true"] [data-dsbs-temp-slot]{display:none!important}',
|
|
279
|
+
'html[data-dsbs-temp-solo="true"]:not([data-dsbs-chat-mode]) [data-dsbs-newchat] button[class*="newSession"]{width:auto!important;height:38px!important;font-size:14px!important;line-height:22px!important;margin:0 calc(var(--dsh-sidebar-paint-inset,3px) + 1px) 10px!important}',
|
|
280
|
+
// A temporary session is a NORMAL DSH session, so its sidebar row is the
|
|
281
|
+
// host's own row. Nothing is added to the DOM: we tag the row and let CSS
|
|
282
|
+
// draw the badge, which survives every React re-render of the list.
|
|
283
|
+
'[data-row-key^="session:"][data-dsbs-temp-row]::after{content:attr(data-dsbs-temp-badge);margin-left:6px;flex:none;box-sizing:border-box;padding:0 5px;border:1px solid currentColor;border-radius:5px;font-size:10px;line-height:14px;font-weight:500;white-space:nowrap;opacity:.6}',
|
|
284
|
+
// The collapsed rail has no room for a word; the row keeps its own look.
|
|
285
|
+
'html[data-dsbs-rail="true"] [data-dsbs-temp-row]::after{display:none}',
|
|
286
|
+
'[data-dsbs-temp-toast]{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:9500;max-width:min(560px,calc(100vw - 40px));box-sizing:border-box;padding:9px 14px;border-radius:10px;background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-base,#1f1f1f));color:var(--dsw-alias-label-primary,inherit);border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));box-shadow:0 12px 34px rgba(0,0,0,.34);font:inherit;font-size:12px;line-height:1.6;word-break:break-word}',
|
|
287
|
+
'[data-dsbs-temp-toast][data-dsbs-temp-toast-error="true"]{color:var(--dsw-alias-state-business-danger,#d9534f);border-color:rgba(217,83,79,.45)}',
|
|
288
|
+
].join('\n')
|
|
289
|
+
|
|
290
|
+
function assign(target) {
|
|
291
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
292
|
+
var source = arguments[i]
|
|
293
|
+
if (!source || typeof source !== 'object') continue
|
|
294
|
+
for (var key in source) {
|
|
295
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key]
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return target
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function safeGet(ctx, name) {
|
|
302
|
+
if (!ctx) return undefined
|
|
303
|
+
try {
|
|
304
|
+
if (typeof ctx.get === 'function') return ctx.get(name)
|
|
305
|
+
} catch (error) {
|
|
306
|
+
void error
|
|
307
|
+
}
|
|
308
|
+
return undefined
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isExtNav(node) {
|
|
312
|
+
return !!node && node.tagName === 'NAV' && /panelList/.test(node.className || '')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Resolved on every sync: the sidebar is rebuilt by React on session
|
|
316
|
+
// changes, so nothing here may be cached.
|
|
317
|
+
function resolveSidebar() {
|
|
318
|
+
if (typeof document === 'undefined') return null
|
|
319
|
+
var workspaces = document.querySelector('[data-slot="sidebar.workspaces"]')
|
|
320
|
+
if (!workspaces) return null
|
|
321
|
+
var regionArea = workspaces.parentElement
|
|
322
|
+
if (!regionArea) return null
|
|
323
|
+
var node = regionArea
|
|
324
|
+
var root = null
|
|
325
|
+
var nav = null
|
|
326
|
+
while (node && node !== document.body && node !== document.documentElement) {
|
|
327
|
+
var children = node.children
|
|
328
|
+
for (var i = 0; i < children.length; i++) {
|
|
329
|
+
var child = children[i]
|
|
330
|
+
if (child === regionArea) continue
|
|
331
|
+
if (isExtNav(child)) {
|
|
332
|
+
nav = child
|
|
333
|
+
root = node
|
|
334
|
+
break
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (root) break
|
|
338
|
+
node = node.parentElement
|
|
339
|
+
}
|
|
340
|
+
if (!root) {
|
|
341
|
+
root = regionArea.parentElement
|
|
342
|
+
if (!root) return null
|
|
343
|
+
for (var j = 0; j < root.children.length; j++) {
|
|
344
|
+
if (isExtNav(root.children[j])) {
|
|
345
|
+
nav = root.children[j]
|
|
346
|
+
break
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return { root: root, nav: nav, regionArea: regionArea, workspaces: workspaces }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function readLocal() {
|
|
354
|
+
try {
|
|
355
|
+
var raw = localStorage.getItem(LS_KEY)
|
|
356
|
+
if (!raw) return {}
|
|
357
|
+
var parsed = JSON.parse(raw)
|
|
358
|
+
return parsed && typeof parsed === 'object' ? parsed : {}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
return {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function writeLocal(value) {
|
|
365
|
+
try {
|
|
366
|
+
localStorage.setItem(LS_KEY, JSON.stringify(value))
|
|
367
|
+
} catch (error) {
|
|
368
|
+
void error
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function adoptValues(raw) {
|
|
373
|
+
var out = assign({}, DEFAULTS)
|
|
374
|
+
if (!raw || typeof raw !== 'object') return out
|
|
375
|
+
if (MODES.indexOf(raw.mode) >= 0) out.mode = raw.mode
|
|
376
|
+
if (typeof raw.showQuickSwitch === 'boolean') out.showQuickSwitch = raw.showQuickSwitch
|
|
377
|
+
if (raw.tab === 'ext' || raw.tab === 'ws') out.tab = raw.tab
|
|
378
|
+
if (typeof raw.foldExt === 'boolean') out.foldExt = raw.foldExt
|
|
379
|
+
if (typeof raw.foldWs === 'boolean') out.foldWs = raw.foldWs
|
|
380
|
+
return out
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// The locale service merges dictionaries per namespace and keeps entries
|
|
384
|
+
// registered by earlier client generations, so a bound translator can mix
|
|
385
|
+
// stale and current strings (that is how a page ends up with Chinese modes
|
|
386
|
+
// and English captions). The bar's own labels only need zh/en, so they are
|
|
387
|
+
// resolved from the active locale id and this plugin's dictionaries.
|
|
388
|
+
function detectLang(localeService) {
|
|
389
|
+
var candidates = []
|
|
390
|
+
try {
|
|
391
|
+
if (localeService && typeof localeService.getSnapshot === 'function') {
|
|
392
|
+
var snapshot = localeService.getSnapshot()
|
|
393
|
+
if (snapshot && snapshot.active) candidates.push(snapshot.active)
|
|
394
|
+
}
|
|
395
|
+
if (localeService && typeof localeService.getLocale === 'function') {
|
|
396
|
+
var active = localeService.getLocale()
|
|
397
|
+
if (typeof active === 'string') candidates.push(active)
|
|
398
|
+
else if (active && active.active) candidates.push(active.active)
|
|
399
|
+
}
|
|
400
|
+
} catch (error) {
|
|
401
|
+
void error
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
candidates.push(document.documentElement.lang)
|
|
405
|
+
} catch (error) {
|
|
406
|
+
void error
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
candidates.push(navigator.language)
|
|
410
|
+
} catch (error) {
|
|
411
|
+
void error
|
|
412
|
+
}
|
|
413
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
414
|
+
if (/^zh/i.test(String(candidates[i] || ''))) return 'zh'
|
|
415
|
+
}
|
|
416
|
+
return 'en'
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function createTranslator(localeService) {
|
|
420
|
+
var lang = 'en'
|
|
421
|
+
try {
|
|
422
|
+
if (localeService && typeof localeService.register === 'function') {
|
|
423
|
+
localeService.register(NS, DICTS)
|
|
424
|
+
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
void error
|
|
427
|
+
}
|
|
428
|
+
function refresh() {
|
|
429
|
+
lang = detectLang(localeService)
|
|
430
|
+
}
|
|
431
|
+
refresh()
|
|
432
|
+
var t = function (key) {
|
|
433
|
+
return DICTS[lang][key] || DICTS.en[key] || key
|
|
434
|
+
}
|
|
435
|
+
t.refresh = refresh
|
|
436
|
+
return t
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function apply(ctx) {
|
|
440
|
+
if (typeof document === 'undefined' || typeof window === 'undefined') return
|
|
441
|
+
|
|
442
|
+
// Claim the sidebar before touching the DOM. A second apply takes over
|
|
443
|
+
// instead of stacking: the run that already owns the sidebar is torn down
|
|
444
|
+
// first, so a hot reload swaps the bar in place and never leaves stale
|
|
445
|
+
// buttons or html attributes behind.
|
|
446
|
+
var owner = { alive: true, teardown: null }
|
|
447
|
+
try {
|
|
448
|
+
var current = window[OWNER_KEY]
|
|
449
|
+
if (current && current.alive) {
|
|
450
|
+
try {
|
|
451
|
+
if (typeof current.teardown === 'function') current.teardown()
|
|
452
|
+
} catch (error) {
|
|
453
|
+
void error
|
|
454
|
+
}
|
|
455
|
+
current.alive = false
|
|
456
|
+
}
|
|
457
|
+
window[OWNER_KEY] = owner
|
|
458
|
+
} catch (error) {
|
|
459
|
+
void error
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
var html = document.documentElement
|
|
463
|
+
var localeService = null
|
|
464
|
+
try {
|
|
465
|
+
localeService = ctx.locale
|
|
466
|
+
} catch (error) {
|
|
467
|
+
localeService = null
|
|
468
|
+
}
|
|
469
|
+
if (!localeService) localeService = safeGet(ctx, 'locale')
|
|
470
|
+
var t = createTranslator(localeService)
|
|
471
|
+
|
|
472
|
+
var config = { controller: null, values: assign({}, DEFAULTS), local: false }
|
|
473
|
+
var runtime = {
|
|
474
|
+
disposed: false,
|
|
475
|
+
// `mode` in the profile is the startup default. Picking a layout from
|
|
476
|
+
// the bar is session-only and must never rewrite the config form.
|
|
477
|
+
sessionMode: null,
|
|
478
|
+
configMode: null,
|
|
479
|
+
bar: null,
|
|
480
|
+
root: null,
|
|
481
|
+
nav: null,
|
|
482
|
+
regionArea: null,
|
|
483
|
+
popOpen: false,
|
|
484
|
+
rail: false,
|
|
485
|
+
railSaved: null,
|
|
486
|
+
hidden: { ext: false, ws: false },
|
|
487
|
+
observedRoot: null,
|
|
488
|
+
resizeObserver: null,
|
|
489
|
+
mutationObserver: null,
|
|
490
|
+
raf: 0,
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
var manualCleanups = []
|
|
494
|
+
var tempButton = null
|
|
495
|
+
var tempSlot = null // wrapper that keeps the root's top-level <button> count at one
|
|
496
|
+
var tempNewChatHost = null
|
|
497
|
+
var tempChatPolicy = 'hide' // TEMP_IN_CHAT_MODE in temp-session-config.txt
|
|
498
|
+
var tempChatGuard = null
|
|
499
|
+
var tempIds = [] // session ids the Host half will delete on quit
|
|
500
|
+
var tempBusy = false
|
|
501
|
+
var tempToastTimer = 0
|
|
502
|
+
|
|
503
|
+
// 0.2.4 — always-expanded session list. `expandAllKnown` gates the
|
|
504
|
+
// clicker until the Host half has answered (or clearly cannot answer),
|
|
505
|
+
// so EXPAND_ALL_SESSIONS=off is never briefly overridden by the default.
|
|
506
|
+
var expandAll = true
|
|
507
|
+
var expandAllKnown = false
|
|
508
|
+
var expandCooldown = Object.create(null)
|
|
509
|
+
var expandRounds = 0
|
|
510
|
+
var expandTimer = 0
|
|
511
|
+
var expandGateTimer = 0
|
|
512
|
+
|
|
513
|
+
// Register a resource and its disposal through the host context when it
|
|
514
|
+
// offers one; otherwise keep the disposer for the end of this run.
|
|
515
|
+
function effect(fn, label) {
|
|
516
|
+
var disposer = null
|
|
517
|
+
try {
|
|
518
|
+
if (typeof ctx.effect === 'function') {
|
|
519
|
+
ctx.effect(fn, label)
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
void error
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
disposer = fn()
|
|
527
|
+
} catch (error) {
|
|
528
|
+
void error
|
|
529
|
+
}
|
|
530
|
+
if (typeof disposer === 'function') manualCleanups.push(disposer)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function setFlag(element, name, on) {
|
|
534
|
+
if (!element) return
|
|
535
|
+
if (on) element.setAttribute(name, 'true')
|
|
536
|
+
else element.removeAttribute(name)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ---- markup -------------------------------------------------------
|
|
540
|
+
|
|
541
|
+
function makeButton(key, labelKey, hintKey, onClick) {
|
|
542
|
+
var button = document.createElement('button')
|
|
543
|
+
button.type = 'button'
|
|
544
|
+
button.setAttribute('data-dsbs-btn', key)
|
|
545
|
+
if (labelKey) button.setAttribute('data-dsbs-label', labelKey)
|
|
546
|
+
if (hintKey) button.setAttribute('data-dsbs-hint', hintKey)
|
|
547
|
+
if (labelKey) button.textContent = t(labelKey)
|
|
548
|
+
if (hintKey) button.title = t(hintKey)
|
|
549
|
+
button.addEventListener('click', function (event) {
|
|
550
|
+
event.preventDefault()
|
|
551
|
+
event.stopPropagation()
|
|
552
|
+
onClick(event)
|
|
553
|
+
})
|
|
554
|
+
return button
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function makeSeparator() {
|
|
558
|
+
var separator = document.createElement('span')
|
|
559
|
+
separator.setAttribute('data-dsbs-sep', '')
|
|
560
|
+
separator.setAttribute('aria-hidden', 'true')
|
|
561
|
+
return separator
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ---- temporary sessions (0.2.0) -----------------------------------
|
|
565
|
+
// A temporary session IS a real DSH session. It is created through the
|
|
566
|
+
// host's own navigation service — the exact call the native "New Session"
|
|
567
|
+
// button makes — so it appears in the left sidebar, uses the native
|
|
568
|
+
// conversation window, keeps its own model selection and tool loop, and
|
|
569
|
+
// can be switched away from like any other task.
|
|
570
|
+
//
|
|
571
|
+
// Only deletion is ours. Verified in app.asar: DSH has no session-delete
|
|
572
|
+
// API (its own UI action is archive/hide), so the Host half records the
|
|
573
|
+
// ids and removes the session directories from disk at quit and again on
|
|
574
|
+
// the next launch. Nothing is kept "in memory only" any more.
|
|
575
|
+
|
|
576
|
+
function tempUrl(path) {
|
|
577
|
+
try { return new URL(path, document.baseURI).href } catch (error) { void error; return path }
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Surface the real failure: a 404 here means the Host half never mounted
|
|
581
|
+
// its /api routes (usually DSH was hot-reloaded, not restarted).
|
|
582
|
+
function tempCall(path, options) {
|
|
583
|
+
var init = { credentials: 'same-origin', cache: 'no-store' }
|
|
584
|
+
if (options && options.method) init.method = options.method
|
|
585
|
+
if (options && options.body !== undefined) {
|
|
586
|
+
init.body = options.body
|
|
587
|
+
init.headers = { 'content-type': 'application/json' }
|
|
588
|
+
}
|
|
589
|
+
return fetch(tempUrl(path), init)
|
|
590
|
+
.then(function (response) {
|
|
591
|
+
return response.text().then(function (body) {
|
|
592
|
+
var detail = (body || '').replace(/\s+/g, ' ').slice(0, 180)
|
|
593
|
+
if (!response.ok) throw new Error('HTTP ' + response.status + (detail ? ' · ' + detail : ''))
|
|
594
|
+
try { return JSON.parse(body) } catch (error) {
|
|
595
|
+
throw new Error('non-JSON response · ' + detail)
|
|
596
|
+
}
|
|
597
|
+
})
|
|
598
|
+
})
|
|
599
|
+
.catch(function (error) {
|
|
600
|
+
if (error instanceof TypeError) throw new Error('network error · ' + String(error.message))
|
|
601
|
+
throw error
|
|
602
|
+
})
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function toast(text, isError) {
|
|
606
|
+
var node = null
|
|
607
|
+
try { node = document.querySelector('[data-dsbs-temp-toast]') } catch (error) { void error }
|
|
608
|
+
if (!node) {
|
|
609
|
+
node = document.createElement('div')
|
|
610
|
+
node.setAttribute('data-dsbs-temp-toast', '')
|
|
611
|
+
node.setAttribute('role', 'status')
|
|
612
|
+
document.body.appendChild(node)
|
|
613
|
+
}
|
|
614
|
+
node.setAttribute('data-dsbs-temp-toast-error', isError ? 'true' : 'false')
|
|
615
|
+
node.textContent = text
|
|
616
|
+
clearTimeout(tempToastTimer)
|
|
617
|
+
tempToastTimer = setTimeout(function () {
|
|
618
|
+
try { if (node.parentNode) node.parentNode.removeChild(node) } catch (error) { void error }
|
|
619
|
+
}, isError ? 10000 : 3500)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// `uiWorkspace` is the client service the sidebar itself uses for "New
|
|
623
|
+
// Session" (dsh-client-ui-workspace). Read through ctx.get() so a host
|
|
624
|
+
// build without it leaves the three sidebar layouts untouched.
|
|
625
|
+
function uiWorkspace() {
|
|
626
|
+
try {
|
|
627
|
+
if (typeof ctx.get !== 'function') return null
|
|
628
|
+
return ctx.get('uiWorkspace') || null
|
|
629
|
+
} catch (error) {
|
|
630
|
+
void error
|
|
631
|
+
return null
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function listOf(store) {
|
|
636
|
+
try {
|
|
637
|
+
var snap = store && typeof store.getSnapshot === 'function' ? store.getSnapshot() : null
|
|
638
|
+
return snap || null
|
|
639
|
+
} catch (error) {
|
|
640
|
+
void error
|
|
641
|
+
return null
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Same workspace the native button would pick: current, else the first. */
|
|
646
|
+
function newSessionWorkspace(ui) {
|
|
647
|
+
try {
|
|
648
|
+
var snap = listOf(ui.workspaces && ui.workspaces.list)
|
|
649
|
+
var items = (snap && snap.items) || []
|
|
650
|
+
var current = null
|
|
651
|
+
try { current = ui.mainReference && ui.mainReference.sessionId } catch (error) { void error }
|
|
652
|
+
if (current) {
|
|
653
|
+
for (var i = 0; i < items.length; i++) {
|
|
654
|
+
if ((items[i].sessionIds || []).indexOf(current) >= 0) return items[i].workspaceId
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (items.length > 0) return items[0].workspaceId
|
|
658
|
+
} catch (error) {
|
|
659
|
+
void error
|
|
660
|
+
}
|
|
661
|
+
return undefined
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function knownSessionIds(ui) {
|
|
665
|
+
try {
|
|
666
|
+
var snap = listOf(ui.sessions && ui.sessions.list)
|
|
667
|
+
return (snap && snap.ids ? snap.ids.slice() : [])
|
|
668
|
+
} catch (error) {
|
|
669
|
+
void error
|
|
670
|
+
return []
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Fallback for builds without createNamed: watch the list for a new id. */
|
|
675
|
+
function waitForNewSession(ui, before) {
|
|
676
|
+
return new Promise(function (resolve) {
|
|
677
|
+
var tries = 0
|
|
678
|
+
var tick = function () {
|
|
679
|
+
var ids = knownSessionIds(ui)
|
|
680
|
+
for (var i = 0; i < ids.length; i++) {
|
|
681
|
+
if (before.indexOf(ids[i]) < 0) { resolve(ids[i]); return }
|
|
682
|
+
}
|
|
683
|
+
tries += 1
|
|
684
|
+
if (tries > 50) { resolve(''); return }
|
|
685
|
+
setTimeout(tick, 100)
|
|
686
|
+
}
|
|
687
|
+
tick()
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function createTempSession() {
|
|
692
|
+
if (tempBusy) return
|
|
693
|
+
var ui = uiWorkspace()
|
|
694
|
+
if (!ui) {
|
|
695
|
+
toast(t('temp.noNative') + ' ' + t('temp.restartHint'), true)
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
tempBusy = true
|
|
699
|
+
if (tempButton) {
|
|
700
|
+
tempButton.disabled = true
|
|
701
|
+
tempButton.textContent = t('temp.creating')
|
|
702
|
+
}
|
|
703
|
+
var workspaceId = newSessionWorkspace(ui)
|
|
704
|
+
var before = knownSessionIds(ui)
|
|
705
|
+
Promise.resolve()
|
|
706
|
+
.then(function () {
|
|
707
|
+
// No workspace yet (fresh install, nothing opened): let the host's
|
|
708
|
+
// own button logic decide, then read the id it settles on.
|
|
709
|
+
if (typeof ui.createNamed === 'function' && workspaceId !== undefined) {
|
|
710
|
+
return ui.createNamed({ workspaceId: workspaceId })
|
|
711
|
+
}
|
|
712
|
+
if (typeof ui.startSession === 'function') {
|
|
713
|
+
ui.startSession(workspaceId)
|
|
714
|
+
return waitForNewSession(ui, before)
|
|
715
|
+
}
|
|
716
|
+
throw new Error('uiWorkspace.createNamed / startSession missing')
|
|
717
|
+
})
|
|
718
|
+
.then(function (id) {
|
|
719
|
+
if (!id) throw new Error('the host returned no session id')
|
|
720
|
+
try {
|
|
721
|
+
if (typeof ui.openSession === 'function') ui.openSession(id)
|
|
722
|
+
} catch (error) {
|
|
723
|
+
void error
|
|
724
|
+
}
|
|
725
|
+
if (tempIds.indexOf(id) < 0) tempIds.push(id)
|
|
726
|
+
applyTempRows()
|
|
727
|
+
return tempCall('api/dsh-temp-session.track?sessionId=' + encodeURIComponent(id), { method: 'POST' })
|
|
728
|
+
})
|
|
729
|
+
.then(function (data) {
|
|
730
|
+
if (!data || data.ok !== true) throw new Error((data && data.error) || 'tracking failed')
|
|
731
|
+
toast(t('temp.created'), false)
|
|
732
|
+
})
|
|
733
|
+
.catch(function (error) {
|
|
734
|
+
toast(t('temp.failed') + String((error && error.message) || error) + ' · ' + t('temp.restartHint') + ' ' + t('temp.feedbackHint'), true)
|
|
735
|
+
})
|
|
736
|
+
.then(function () {
|
|
737
|
+
tempBusy = false
|
|
738
|
+
if (tempButton) {
|
|
739
|
+
tempButton.disabled = false
|
|
740
|
+
tempButton.textContent = t('temp.label')
|
|
741
|
+
}
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Tag every sidebar row whose session id is in the temporary set. */
|
|
746
|
+
function applyTempRows() {
|
|
747
|
+
var rows = null
|
|
748
|
+
try { rows = document.querySelectorAll('[data-row-key^="session:"]') } catch (error) {
|
|
749
|
+
void error
|
|
750
|
+
return
|
|
751
|
+
}
|
|
752
|
+
if (!rows) return
|
|
753
|
+
var badge = t('temp.badge') || 'TEMP'
|
|
754
|
+
for (var i = 0; i < rows.length; i++) {
|
|
755
|
+
var row = rows[i]
|
|
756
|
+
var key = row.getAttribute('data-row-key') || ''
|
|
757
|
+
var id = key.slice('session:'.length)
|
|
758
|
+
if (tempIds.indexOf(id) >= 0) {
|
|
759
|
+
if (row.getAttribute('data-dsbs-temp-row') !== '1') row.setAttribute('data-dsbs-temp-row', '1')
|
|
760
|
+
if (row.getAttribute('data-dsbs-temp-badge') !== badge) row.setAttribute('data-dsbs-temp-badge', badge)
|
|
761
|
+
} else if (row.hasAttribute('data-dsbs-temp-row')) {
|
|
762
|
+
row.removeAttribute('data-dsbs-temp-row')
|
|
763
|
+
row.removeAttribute('data-dsbs-temp-badge')
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function refreshTempIds() {
|
|
769
|
+
return tempCall('api/dsh-temp-session.list')
|
|
770
|
+
.then(function (data) {
|
|
771
|
+
if (data && Array.isArray(data.ids)) {
|
|
772
|
+
tempIds = data.ids.slice()
|
|
773
|
+
applyTempRows()
|
|
774
|
+
}
|
|
775
|
+
})
|
|
776
|
+
.catch(function (error) { void error })
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// ---- Temp button beside the host's "New Session" button (0.1.6) ----
|
|
780
|
+
// The button is a plain sibling of the host button, never a replacement
|
|
781
|
+
// or a moved node: React keeps owning its own DOM and we only add ours.
|
|
782
|
+
function makeTempButton() {
|
|
783
|
+
var button = document.createElement('button')
|
|
784
|
+
button.type = 'button'
|
|
785
|
+
button.setAttribute('data-dsbs-btn', 'temp')
|
|
786
|
+
button.textContent = t('temp.label')
|
|
787
|
+
button.title = t('temp.hint')
|
|
788
|
+
button.addEventListener('click', function (event) {
|
|
789
|
+
event.preventDefault()
|
|
790
|
+
event.stopPropagation()
|
|
791
|
+
createTempSession()
|
|
792
|
+
})
|
|
793
|
+
return button
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function makeTempSlot() {
|
|
797
|
+
var slot = document.createElement('div')
|
|
798
|
+
slot.setAttribute('data-dsbs-temp-slot', '')
|
|
799
|
+
return slot
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function hideTempButton() {
|
|
803
|
+
if (tempButton) tempButton.setAttribute('hidden', '')
|
|
804
|
+
if (tempSlot) tempSlot.setAttribute('hidden', '')
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function placeTempButton() {
|
|
808
|
+
if (!tempButton) tempButton = makeTempButton()
|
|
809
|
+
if (!tempSlot) tempSlot = makeTempSlot()
|
|
810
|
+
ensureChatGuard()
|
|
811
|
+
var native = null
|
|
812
|
+
try { native = document.querySelector('button[class*="newSession"]') } catch (error) { void error }
|
|
813
|
+
if (!native || !native.parentNode) return false
|
|
814
|
+
var host = native.parentNode
|
|
815
|
+
// The host's "New Session" button is a direct child of the sidebar root,
|
|
816
|
+
// and dsh-deepseek-chat validates that root: it needs exactly one direct
|
|
817
|
+
// <button> child. Adding a second one there makes its sidebar adapter
|
|
818
|
+
// give up and show "当前侧栏不兼容:顶部新会话仍使用 HARNESS". Our button
|
|
819
|
+
// therefore lives inside [data-dsbs-temp-slot], which the adapter ignores.
|
|
820
|
+
if (tempButton.parentNode !== tempSlot) tempSlot.appendChild(tempButton)
|
|
821
|
+
if (tempSlot.parentNode !== host) host.insertBefore(tempSlot, native.nextSibling)
|
|
822
|
+
if (host !== tempNewChatHost) {
|
|
823
|
+
if (tempNewChatHost) tempNewChatHost.removeAttribute('data-dsbs-newchat')
|
|
824
|
+
tempNewChatHost = host
|
|
825
|
+
host.setAttribute('data-dsbs-newchat', '')
|
|
826
|
+
}
|
|
827
|
+
var width = 0
|
|
828
|
+
try { width = host.getBoundingClientRect().width } catch (error) { void error }
|
|
829
|
+
var fixed = false
|
|
830
|
+
try { fixed = !!(window.getComputedStyle && window.getComputedStyle(native).position === 'fixed') } catch (error) { void error }
|
|
831
|
+
// The collapsed rail has no horizontal room for a second button.
|
|
832
|
+
setFlag(html, 'data-dsbs-temp-solo', fixed || runtime.rail === true || (width > 0 && width < 170))
|
|
833
|
+
var label = t('temp.label')
|
|
834
|
+
if (tempButton.textContent !== label) tempButton.textContent = label
|
|
835
|
+
var hint = t('temp.hint')
|
|
836
|
+
if (tempButton.title !== hint) tempButton.title = hint
|
|
837
|
+
return true
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
var tempStatusLoaded = false
|
|
841
|
+
var tempStatusTries = 0
|
|
842
|
+
var tempStatusPending = false
|
|
843
|
+
|
|
844
|
+
/** Publish the CSS flag only when the setting is known and enabled. */
|
|
845
|
+
function applyExpandFlag() {
|
|
846
|
+
setFlag(html, 'data-dsbs-expand-all', expandAllKnown && expandAll)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* The host keeps five session rows per workspace and renders one overflow
|
|
851
|
+
* button for the rest: <button data-row-key="overflow:<group>">.
|
|
852
|
+
* Pressing it raises that group's watermark by five (or to Infinity on
|
|
853
|
+
* the last batch) and flips aria-expanded to true once nothing is hidden.
|
|
854
|
+
* React owns that state, so the honest way to expand is to press the host
|
|
855
|
+
* button — never to rewrite or fake host DOM.
|
|
856
|
+
*
|
|
857
|
+
* A short cooldown per group prevents the same button from being pressed
|
|
858
|
+
* twice before React re-renders (the second press would collapse it).
|
|
859
|
+
*/
|
|
860
|
+
function expandAllSessions() {
|
|
861
|
+
if (runtime.disposed || !expandAllKnown || !expandAll) return
|
|
862
|
+
var buttons = null
|
|
863
|
+
try {
|
|
864
|
+
buttons = document.querySelectorAll('button[data-row-key^="overflow:"]')
|
|
865
|
+
} catch (error) {
|
|
866
|
+
void error
|
|
867
|
+
return
|
|
868
|
+
}
|
|
869
|
+
if (!buttons || !buttons.length) {
|
|
870
|
+
expandRounds = 0
|
|
871
|
+
return
|
|
872
|
+
}
|
|
873
|
+
var now = Date.now()
|
|
874
|
+
var clicked = 0
|
|
875
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
876
|
+
var button = buttons[i]
|
|
877
|
+
if (!button || button.getAttribute('aria-expanded') === 'true') continue
|
|
878
|
+
var key = button.getAttribute('data-row-key') || ('row-' + i)
|
|
879
|
+
if (now - (expandCooldown[key] || 0) < 120) continue
|
|
880
|
+
expandCooldown[key] = now
|
|
881
|
+
try {
|
|
882
|
+
button.click()
|
|
883
|
+
clicked += 1
|
|
884
|
+
} catch (error) {
|
|
885
|
+
void error
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (clicked === 0 || expandRounds >= 40) {
|
|
889
|
+
expandRounds = 0
|
|
890
|
+
return
|
|
891
|
+
}
|
|
892
|
+
expandRounds += 1
|
|
893
|
+
if (expandTimer) return
|
|
894
|
+
expandTimer = setTimeout(function () {
|
|
895
|
+
expandTimer = 0
|
|
896
|
+
expandAllSessions()
|
|
897
|
+
}, 150)
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function loadTempStatus() {
|
|
901
|
+
if (tempStatusLoaded || tempStatusPending) return
|
|
902
|
+
if (tempStatusTries >= 3) {
|
|
903
|
+
// The Host half never answered. Fall back to the file's default
|
|
904
|
+
// rather than leaving the session list half-folded forever.
|
|
905
|
+
if (!expandAllKnown) {
|
|
906
|
+
expandAllKnown = true
|
|
907
|
+
applyExpandFlag()
|
|
908
|
+
scheduleSync()
|
|
909
|
+
}
|
|
910
|
+
return
|
|
911
|
+
}
|
|
912
|
+
tempStatusTries += 1
|
|
913
|
+
tempStatusPending = true
|
|
914
|
+
// A hanging request must not leave the session list folded forever.
|
|
915
|
+
// Two seconds is well below the first heartbeat after a cold start.
|
|
916
|
+
if (!expandGateTimer) {
|
|
917
|
+
expandGateTimer = setTimeout(function () {
|
|
918
|
+
expandGateTimer = 0
|
|
919
|
+
if (expandAllKnown) return
|
|
920
|
+
expandAllKnown = true
|
|
921
|
+
expandAll = true
|
|
922
|
+
applyExpandFlag()
|
|
923
|
+
scheduleSync()
|
|
924
|
+
}, 2000)
|
|
925
|
+
}
|
|
926
|
+
tempCall('api/dsh-temp-session.status').then(function (data) {
|
|
927
|
+
tempStatusPending = false
|
|
928
|
+
tempStatusLoaded = true
|
|
929
|
+
if (expandGateTimer) { clearTimeout(expandGateTimer); expandGateTimer = 0 }
|
|
930
|
+
if (data && data.showButton === false) hideTempButton()
|
|
931
|
+
if (data && data.chatModePolicy) tempChatPolicy = String(data.chatModePolicy)
|
|
932
|
+
if (data && data.buttonStyle) setFlag(html, 'data-dsbs-temp-style', String(data.buttonStyle) === 'own')
|
|
933
|
+
// Older Host halves (<= 0.2.3) do not send this key; default to on.
|
|
934
|
+
expandAll = !data || data.expandAllSessions !== false
|
|
935
|
+
expandAllKnown = true
|
|
936
|
+
applyExpandFlag()
|
|
937
|
+
syncChatMode()
|
|
938
|
+
refreshTempIds()
|
|
939
|
+
scheduleSync()
|
|
940
|
+
}).catch(function (error) {
|
|
941
|
+
void error
|
|
942
|
+
tempStatusPending = false
|
|
943
|
+
if (expandGateTimer) { clearTimeout(expandGateTimer); expandGateTimer = 0 }
|
|
944
|
+
if (!expandAllKnown) {
|
|
945
|
+
expandAllKnown = true
|
|
946
|
+
expandAll = true
|
|
947
|
+
applyExpandFlag()
|
|
948
|
+
scheduleSync()
|
|
949
|
+
}
|
|
950
|
+
})
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ---- coexistence with dsh-deepseek-chat (CHAT / HARNESS switcher) ----
|
|
954
|
+
// That plugin swaps the sidebar and the whole main area for DeepSeek's
|
|
955
|
+
// official website when the user is in CHAT mode. Its "new chat" button
|
|
956
|
+
// shares the same row as the host's "New Session" button, so a second
|
|
957
|
+
// button there (ours) both breaks its layout and offers a harness model
|
|
958
|
+
// call in a screen that is not a harness session. We detect its DOM and
|
|
959
|
+
// step aside; the flag lives on <html> so the CSS above can gate on it.
|
|
960
|
+
function chatModeActive() {
|
|
961
|
+
try {
|
|
962
|
+
if (document.querySelector('[data-dsh-chat-content],[data-dsh-chat-layout]')) return true
|
|
963
|
+
} catch (error) {
|
|
964
|
+
void error
|
|
965
|
+
}
|
|
966
|
+
return false
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function syncChatMode() {
|
|
970
|
+
if (runtime.disposed) return
|
|
971
|
+
var active = tempChatPolicy !== 'show' && chatModeActive()
|
|
972
|
+
setFlag(html, 'data-dsbs-chat-mode', active)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function ensureChatGuard() {
|
|
976
|
+
if (tempChatGuard !== null || runtime.disposed) return
|
|
977
|
+
tempChatGuard = setInterval(syncChatMode, 1500)
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// One row of plain text buttons, the shape the bar had in its first
|
|
981
|
+
// version: the filled button is the chosen layout, and everything after
|
|
982
|
+
// the divider names the action its click performs in that layout.
|
|
983
|
+
function buildBar() {
|
|
984
|
+
var bar = document.createElement('div')
|
|
985
|
+
bar.setAttribute('data-dsbs-bar', '')
|
|
986
|
+
bar.setAttribute('role', 'toolbar')
|
|
987
|
+
|
|
988
|
+
var i
|
|
989
|
+
for (i = 0; i < MODES.length; i++) {
|
|
990
|
+
var mode = MODES[i]
|
|
991
|
+
bar.appendChild(makeButton('mode-' + mode, 'mode.' + mode, 'mode.' + mode + '.hint', (function (value) {
|
|
992
|
+
return function () { setMode(value) }
|
|
993
|
+
})(mode)))
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
var separator = makeSeparator()
|
|
997
|
+
bar.appendChild(separator)
|
|
998
|
+
|
|
999
|
+
var foldExt = makeButton('fold-ext', 'fold.ext.collapse', 'fold.ext.hint', function () { setField('foldExt', !config.values.foldExt) })
|
|
1000
|
+
var foldWs = makeButton('fold-ws', 'fold.ws.collapse', 'fold.ws.hint', function () { setField('foldWs', !config.values.foldWs) })
|
|
1001
|
+
var tabSwap = makeButton('tab-swap', 'tab.to.ws', 'tab.to.ws.hint', function () {
|
|
1002
|
+
setField('tab', config.values.tab === 'ext' ? 'ws' : 'ext')
|
|
1003
|
+
})
|
|
1004
|
+
var pop = makeButton('pop', 'pop.open', 'pop.hint', function () {
|
|
1005
|
+
runtime.popOpen = !runtime.popOpen
|
|
1006
|
+
scheduleSync()
|
|
1007
|
+
})
|
|
1008
|
+
pop.setAttribute('aria-expanded', 'false')
|
|
1009
|
+
var quick = makeButton('quick', 'quick.label', 'quick.hint', function () {
|
|
1010
|
+
var index = MODES.indexOf(config.values.mode)
|
|
1011
|
+
setMode(MODES[(index + 1) % MODES.length])
|
|
1012
|
+
})
|
|
1013
|
+
bar.appendChild(foldExt)
|
|
1014
|
+
bar.appendChild(foldWs)
|
|
1015
|
+
bar.appendChild(tabSwap)
|
|
1016
|
+
bar.appendChild(pop)
|
|
1017
|
+
bar.appendChild(quick)
|
|
1018
|
+
|
|
1019
|
+
runtime.parts = { separator: separator, context: [foldExt, foldWs, tabSwap, pop] }
|
|
1020
|
+
return bar
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function ensureBar(fx) {
|
|
1024
|
+
var bar = runtime.bar
|
|
1025
|
+
if (bar && bar.parentNode !== fx.root) {
|
|
1026
|
+
try {
|
|
1027
|
+
bar.parentNode.removeChild(bar)
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
void error
|
|
1030
|
+
}
|
|
1031
|
+
bar = null
|
|
1032
|
+
}
|
|
1033
|
+
if (!bar) {
|
|
1034
|
+
bar = buildBar()
|
|
1035
|
+
runtime.bar = bar
|
|
1036
|
+
}
|
|
1037
|
+
var anchor = fx.nav || fx.regionArea
|
|
1038
|
+
if (anchor && anchor.parentNode === fx.root) {
|
|
1039
|
+
if (bar.nextSibling !== anchor) fx.root.insertBefore(bar, anchor)
|
|
1040
|
+
} else if (bar.parentNode !== fx.root) {
|
|
1041
|
+
fx.root.appendChild(bar)
|
|
1042
|
+
}
|
|
1043
|
+
return bar
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function barButton(key) {
|
|
1047
|
+
if (key === 'temp') return tempButton
|
|
1048
|
+
if (!runtime.bar) return null
|
|
1049
|
+
return runtime.bar.querySelector('[data-dsbs-btn="' + key + '"]')
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function setVisible(key, visible) {
|
|
1053
|
+
var button = barButton(key)
|
|
1054
|
+
if (!button) return
|
|
1055
|
+
if (visible) button.removeAttribute('hidden')
|
|
1056
|
+
else button.setAttribute('hidden', '')
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function relabel() {
|
|
1060
|
+
if (!runtime.bar) return
|
|
1061
|
+
var nodes = runtime.bar.querySelectorAll('[data-dsbs-label]')
|
|
1062
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
1063
|
+
var node = nodes[i]
|
|
1064
|
+
var labelKey = node.getAttribute('data-dsbs-label')
|
|
1065
|
+
var hintKey = node.getAttribute('data-dsbs-hint')
|
|
1066
|
+
// Rewriting unchanged text on every sync would drop focus from a
|
|
1067
|
+
// button reached with the keyboard, so only touch what changed.
|
|
1068
|
+
if (labelKey) {
|
|
1069
|
+
var label = t(labelKey)
|
|
1070
|
+
if (node.textContent !== label) node.textContent = label
|
|
1071
|
+
}
|
|
1072
|
+
if (hintKey) {
|
|
1073
|
+
var hint = t(hintKey)
|
|
1074
|
+
if (node.title !== hint) node.title = hint
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
var barLabel = t('bar.label')
|
|
1078
|
+
if (runtime.bar.getAttribute('aria-label') !== barLabel) {
|
|
1079
|
+
runtime.bar.setAttribute('aria-label', barLabel)
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// ---- visible state ------------------------------------------------
|
|
1084
|
+
|
|
1085
|
+
function closeOpenOverlays(element) {
|
|
1086
|
+
try {
|
|
1087
|
+
var open = element.querySelector('dialog[open], [role="menu"][data-state="open"], [data-state="open"]')
|
|
1088
|
+
if (!open) return
|
|
1089
|
+
open.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
void error
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function setGroupHidden(element, group, hidden) {
|
|
1096
|
+
if (!element) return
|
|
1097
|
+
var was = runtime.hidden[group] === true
|
|
1098
|
+
if (hidden) {
|
|
1099
|
+
if (!was) closeOpenOverlays(element)
|
|
1100
|
+
element.setAttribute('aria-hidden', 'true')
|
|
1101
|
+
element.setAttribute('inert', '')
|
|
1102
|
+
} else {
|
|
1103
|
+
element.removeAttribute('aria-hidden')
|
|
1104
|
+
element.removeAttribute('inert')
|
|
1105
|
+
}
|
|
1106
|
+
if (was === hidden) return
|
|
1107
|
+
runtime.hidden[group] = hidden
|
|
1108
|
+
if (!hidden) {
|
|
1109
|
+
// Panels and menus position themselves from measured geometry; they
|
|
1110
|
+
// were hidden while that was measured, so ask them to measure again.
|
|
1111
|
+
try {
|
|
1112
|
+
window.dispatchEvent(new Event('resize'))
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
void error
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function positionPop(fx) {
|
|
1120
|
+
if (!fx || !fx.root || !runtime.bar) return
|
|
1121
|
+
// Only the overlay is positioned; skip the two rect reads otherwise so
|
|
1122
|
+
// the heartbeat stays cheap.
|
|
1123
|
+
if (config.values.mode !== 'c' || !runtime.popOpen) return
|
|
1124
|
+
try {
|
|
1125
|
+
var rootRect = fx.root.getBoundingClientRect()
|
|
1126
|
+
var barRect = runtime.bar.getBoundingClientRect()
|
|
1127
|
+
var top = Math.round(barRect.bottom - rootRect.top + 4)
|
|
1128
|
+
if (!isFinite(top) || top < 0) top = 0
|
|
1129
|
+
fx.root.style.setProperty('--dsbs-pop-top', top + 'px')
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
void error
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function paint() {
|
|
1136
|
+
if (runtime.disposed) return
|
|
1137
|
+
var state = config.values
|
|
1138
|
+
html.setAttribute('data-dsbs-mode', state.mode)
|
|
1139
|
+
html.setAttribute('data-dsbs-tab', state.tab)
|
|
1140
|
+
html.setAttribute('data-dsbs-quick', state.showQuickSwitch ? 'on' : 'off')
|
|
1141
|
+
setFlag(html, 'data-dsbs-fold-ext', state.foldExt === true)
|
|
1142
|
+
setFlag(html, 'data-dsbs-fold-ws', state.foldWs === true)
|
|
1143
|
+
setFlag(html, 'data-dsbs-pop', runtime.popOpen && state.mode === 'c')
|
|
1144
|
+
setFlag(html, 'data-dsbs-rail', runtime.rail)
|
|
1145
|
+
|
|
1146
|
+
var fx = resolveSidebar()
|
|
1147
|
+
runtime.root = fx ? fx.root : null
|
|
1148
|
+
runtime.nav = fx ? fx.nav : null
|
|
1149
|
+
runtime.regionArea = fx ? fx.regionArea : null
|
|
1150
|
+
if (!fx) return
|
|
1151
|
+
|
|
1152
|
+
if (fx.nav && !fx.nav.hasAttribute('data-dsbs-ext')) fx.nav.setAttribute('data-dsbs-ext', '')
|
|
1153
|
+
ensureBar(fx)
|
|
1154
|
+
placeTempButton()
|
|
1155
|
+
loadTempStatus()
|
|
1156
|
+
// Bars left behind by an earlier client generation are dead weight and
|
|
1157
|
+
// read as a duplicated control; keep exactly one.
|
|
1158
|
+
var strayBars = document.querySelectorAll('[data-dsbs-bar]')
|
|
1159
|
+
for (var b = 0; b < strayBars.length; b++) {
|
|
1160
|
+
if (strayBars[b] === runtime.bar) continue
|
|
1161
|
+
if (strayBars[b].parentNode) strayBars[b].parentNode.removeChild(strayBars[b])
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// With no panels to show, the buttons that act on that group are
|
|
1165
|
+
// pointless, and hiding the workspaces would leave an empty column.
|
|
1166
|
+
var hasExt = !!fx.nav
|
|
1167
|
+
var showFold = state.mode === 'a'
|
|
1168
|
+
var showTabs = state.mode === 'b'
|
|
1169
|
+
var showPop = state.mode === 'c'
|
|
1170
|
+
setVisible('fold-ext', showFold && hasExt)
|
|
1171
|
+
setVisible('fold-ws', showFold)
|
|
1172
|
+
setVisible('tab-swap', showTabs && hasExt)
|
|
1173
|
+
setVisible('pop', showPop && hasExt)
|
|
1174
|
+
setVisible('quick', state.showQuickSwitch === true)
|
|
1175
|
+
|
|
1176
|
+
// The label states what the click will do, so "收起扩展" and "展开扩展"
|
|
1177
|
+
// never coexist with a stale state.
|
|
1178
|
+
var foldExtButton = barButton('fold-ext')
|
|
1179
|
+
if (foldExtButton) foldExtButton.setAttribute('data-dsbs-label', state.foldExt ? 'fold.ext.expand' : 'fold.ext.collapse')
|
|
1180
|
+
var foldWsButton = barButton('fold-ws')
|
|
1181
|
+
if (foldWsButton) foldWsButton.setAttribute('data-dsbs-label', state.foldWs ? 'fold.ws.expand' : 'fold.ws.collapse')
|
|
1182
|
+
var popButton = barButton('pop')
|
|
1183
|
+
if (popButton) popButton.setAttribute('data-dsbs-label', runtime.popOpen && showPop ? 'pop.close' : 'pop.open')
|
|
1184
|
+
var tabSwapButton = barButton('tab-swap')
|
|
1185
|
+
if (tabSwapButton) {
|
|
1186
|
+
tabSwapButton.setAttribute('data-dsbs-label', state.tab === 'ext' ? 'tab.to.ws' : 'tab.to.ext')
|
|
1187
|
+
tabSwapButton.setAttribute('data-dsbs-hint', state.tab === 'ext' ? 'tab.to.ws.hint' : 'tab.to.ext.hint')
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// The divider only makes sense while at least one action button is
|
|
1191
|
+
// visible; an empty layout (no panels) can leave nothing after it.
|
|
1192
|
+
var parts = runtime.parts
|
|
1193
|
+
if (parts) {
|
|
1194
|
+
var visibleContext = 0
|
|
1195
|
+
for (var c = 0; c < parts.context.length; c++) {
|
|
1196
|
+
if (!parts.context[c].hasAttribute('hidden')) visibleContext++
|
|
1197
|
+
}
|
|
1198
|
+
if (visibleContext > 0) parts.separator.removeAttribute('hidden')
|
|
1199
|
+
else parts.separator.setAttribute('hidden', '')
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
relabel()
|
|
1203
|
+
|
|
1204
|
+
var i
|
|
1205
|
+
for (i = 0; i < MODES.length; i++) {
|
|
1206
|
+
var modeButton = barButton('mode-' + MODES[i])
|
|
1207
|
+
if (modeButton) modeButton.setAttribute('aria-pressed', state.mode === MODES[i] ? 'true' : 'false')
|
|
1208
|
+
}
|
|
1209
|
+
if (foldExtButton) foldExtButton.setAttribute('aria-expanded', state.foldExt ? 'false' : 'true')
|
|
1210
|
+
if (foldWsButton) foldWsButton.setAttribute('aria-expanded', state.foldWs ? 'false' : 'true')
|
|
1211
|
+
if (popButton) {
|
|
1212
|
+
popButton.setAttribute('aria-expanded', runtime.popOpen && showPop ? 'true' : 'false')
|
|
1213
|
+
}
|
|
1214
|
+
if (runtime.bar) {
|
|
1215
|
+
runtime.bar.title = config.local ? t('bar.local') : t('bar.label')
|
|
1216
|
+
runtime.bar.setAttribute('aria-label', t('bar.label'))
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
var extHidden
|
|
1220
|
+
var wsHidden
|
|
1221
|
+
if (state.mode === 'a') {
|
|
1222
|
+
extHidden = state.foldExt === true
|
|
1223
|
+
wsHidden = state.foldWs === true
|
|
1224
|
+
} else if (state.mode === 'b') {
|
|
1225
|
+
extHidden = state.tab !== 'ext'
|
|
1226
|
+
// No panels: keep the workspaces visible rather than the empty half.
|
|
1227
|
+
wsHidden = hasExt && state.tab === 'ext'
|
|
1228
|
+
} else {
|
|
1229
|
+
extHidden = !runtime.popOpen
|
|
1230
|
+
wsHidden = false
|
|
1231
|
+
}
|
|
1232
|
+
setGroupHidden(fx.nav, 'ext', extHidden)
|
|
1233
|
+
setGroupHidden(fx.regionArea, 'ws', wsHidden)
|
|
1234
|
+
|
|
1235
|
+
observeRoot(fx.root)
|
|
1236
|
+
positionPop(fx)
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// ---- state changes ------------------------------------------------
|
|
1240
|
+
|
|
1241
|
+
function setMode(mode) {
|
|
1242
|
+
if (MODES.indexOf(mode) < 0) return
|
|
1243
|
+
if (mode !== 'c' && runtime.popOpen) runtime.popOpen = false
|
|
1244
|
+
runtime.sessionMode = mode
|
|
1245
|
+
setField('mode', mode)
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function setField(field, value) {
|
|
1249
|
+
var patch = {}
|
|
1250
|
+
patch[field] = value
|
|
1251
|
+
config.values = assign({}, config.values, patch)
|
|
1252
|
+
|
|
1253
|
+
// `mode` is the startup default: keep it out of both the config form
|
|
1254
|
+
// and localStorage, so an in-session pick lives only until reload.
|
|
1255
|
+
// The other fields (tab / folds) still persist as before.
|
|
1256
|
+
if (field !== 'mode') {
|
|
1257
|
+
if (config.controller && typeof config.controller.mutate === 'function') {
|
|
1258
|
+
var revision
|
|
1259
|
+
try {
|
|
1260
|
+
var snapshot = config.controller.getSnapshot()
|
|
1261
|
+
revision = snapshot && snapshot.revision
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
revision = undefined
|
|
1264
|
+
}
|
|
1265
|
+
var ops = [{ op: 'set', path: [field], value: value }]
|
|
1266
|
+
try {
|
|
1267
|
+
var result = config.controller.mutate(ops, revision)
|
|
1268
|
+
if (result && typeof result.catch === 'function') result.catch(function () {})
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
void error
|
|
1271
|
+
}
|
|
1272
|
+
} else {
|
|
1273
|
+
var stored = assign({}, DEFAULTS, readLocal())
|
|
1274
|
+
stored[field] = value
|
|
1275
|
+
writeLocal(stored)
|
|
1276
|
+
config.local = true
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
scheduleSync()
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// ---- observation --------------------------------------------------
|
|
1283
|
+
|
|
1284
|
+
function measureRail(fx) {
|
|
1285
|
+
var root = (fx && fx.root) || runtime.root
|
|
1286
|
+
if (!root) return
|
|
1287
|
+
var width = 0
|
|
1288
|
+
try {
|
|
1289
|
+
width = root.getBoundingClientRect().width
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
width = 0
|
|
1292
|
+
}
|
|
1293
|
+
var rail = width > 0 && width < 100
|
|
1294
|
+
// Below this width the bar cannot hold a full row once the cycle icon
|
|
1295
|
+
// is included, and a wrapped icon on its own line reads as debris.
|
|
1296
|
+
var narrow = width > 0 && width < 300
|
|
1297
|
+
if (narrow !== runtime.narrow) {
|
|
1298
|
+
runtime.narrow = narrow
|
|
1299
|
+
setFlag(html, 'data-dsbs-narrow', narrow)
|
|
1300
|
+
}
|
|
1301
|
+
if (rail === runtime.rail) return
|
|
1302
|
+
runtime.rail = rail
|
|
1303
|
+
if (rail) {
|
|
1304
|
+
runtime.railSaved = {
|
|
1305
|
+
mode: config.values.mode,
|
|
1306
|
+
tab: config.values.tab,
|
|
1307
|
+
foldExt: config.values.foldExt,
|
|
1308
|
+
foldWs: config.values.foldWs,
|
|
1309
|
+
}
|
|
1310
|
+
} else if (runtime.railSaved) {
|
|
1311
|
+
// Leaving the rail restores what the user had before the column was
|
|
1312
|
+
// squeezed; the bar is not reachable while the rail is showing.
|
|
1313
|
+
config.values = assign({}, config.values, runtime.railSaved)
|
|
1314
|
+
runtime.railSaved = null
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
var pending = false
|
|
1319
|
+
function scheduleSync() {
|
|
1320
|
+
if (runtime.disposed || pending) return
|
|
1321
|
+
pending = true
|
|
1322
|
+
Promise.resolve().then(function () {
|
|
1323
|
+
pending = false
|
|
1324
|
+
if (!runtime.disposed) sync()
|
|
1325
|
+
})
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function sync() {
|
|
1329
|
+
if (runtime.disposed) return
|
|
1330
|
+
// Both are idempotent: the status call stops after one answer, and the
|
|
1331
|
+
// expand clicker skips groups that are already fully shown.
|
|
1332
|
+
loadTempStatus()
|
|
1333
|
+
expandAllSessions()
|
|
1334
|
+
var fx = resolveSidebar()
|
|
1335
|
+
measureRail(fx)
|
|
1336
|
+
paint()
|
|
1337
|
+
syncChatMode()
|
|
1338
|
+
applyTempRows()
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function observeRoot(root) {
|
|
1342
|
+
if (!root || runtime.observedRoot === root) return
|
|
1343
|
+
if (runtime.resizeObserver) {
|
|
1344
|
+
try {
|
|
1345
|
+
runtime.resizeObserver.disconnect()
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
void error
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
if (runtime.mutationObserver) {
|
|
1351
|
+
try {
|
|
1352
|
+
runtime.mutationObserver.disconnect()
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
void error
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
runtime.observedRoot = root
|
|
1358
|
+
if (typeof ResizeObserver === 'function') {
|
|
1359
|
+
if (!runtime.resizeObserver) {
|
|
1360
|
+
runtime.resizeObserver = new ResizeObserver(function () { scheduleSync() })
|
|
1361
|
+
}
|
|
1362
|
+
try {
|
|
1363
|
+
runtime.resizeObserver.observe(root)
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
void error
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (typeof MutationObserver === 'function') {
|
|
1369
|
+
if (!runtime.mutationObserver) {
|
|
1370
|
+
runtime.mutationObserver = new MutationObserver(function () { scheduleSync() })
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
// Only the root's own child list: React rebuilds the groups there,
|
|
1374
|
+
// and watching anything wider would turn every keystroke into work.
|
|
1375
|
+
runtime.mutationObserver.observe(root, { childList: true })
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
void error
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// ---- configuration -------------------------------------------------
|
|
1383
|
+
|
|
1384
|
+
function syncFromController() {
|
|
1385
|
+
if (!config.controller) return
|
|
1386
|
+
var snapshot = null
|
|
1387
|
+
try {
|
|
1388
|
+
snapshot = config.controller.getSnapshot()
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
snapshot = null
|
|
1391
|
+
}
|
|
1392
|
+
if (!snapshot || typeof snapshot !== 'object') return
|
|
1393
|
+
var next = adoptValues(snapshot.value)
|
|
1394
|
+
// The config form owns the startup default. A push caused by another
|
|
1395
|
+
// field must not undo the mode picked in this session; but a genuine
|
|
1396
|
+
// external change (settings page or cordis.patch.yml) wins and becomes
|
|
1397
|
+
// the new default for the next launch.
|
|
1398
|
+
if (runtime.configMode === null) {
|
|
1399
|
+
runtime.configMode = next.mode
|
|
1400
|
+
} else if (next.mode !== runtime.configMode) {
|
|
1401
|
+
runtime.configMode = next.mode
|
|
1402
|
+
runtime.sessionMode = null
|
|
1403
|
+
} else if (runtime.sessionMode) {
|
|
1404
|
+
next.mode = runtime.sessionMode
|
|
1405
|
+
}
|
|
1406
|
+
config.values = next
|
|
1407
|
+
config.local = false
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function tryAdopt(service) {
|
|
1411
|
+
if (!service || typeof service.get !== 'function') return false
|
|
1412
|
+
if (config.controller) return true
|
|
1413
|
+
var controller = null
|
|
1414
|
+
try {
|
|
1415
|
+
controller = service.get(ENTRY)
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
controller = null
|
|
1418
|
+
}
|
|
1419
|
+
if (!controller) return false
|
|
1420
|
+
config.controller = controller
|
|
1421
|
+
config.local = false
|
|
1422
|
+
syncFromController()
|
|
1423
|
+
var off = null
|
|
1424
|
+
try {
|
|
1425
|
+
if (typeof controller.subscribe === 'function') {
|
|
1426
|
+
off = controller.subscribe(function () {
|
|
1427
|
+
syncFromController()
|
|
1428
|
+
scheduleSync()
|
|
1429
|
+
})
|
|
1430
|
+
}
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
void error
|
|
1433
|
+
}
|
|
1434
|
+
effect(function () {
|
|
1435
|
+
return function () {
|
|
1436
|
+
if (typeof off !== 'function') return
|
|
1437
|
+
try {
|
|
1438
|
+
off()
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
void error
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}, 'sidebar-suite: config subscription')
|
|
1444
|
+
scheduleSync()
|
|
1445
|
+
return true
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function fallbackToLocal() {
|
|
1449
|
+
if (config.controller) return
|
|
1450
|
+
config.local = true
|
|
1451
|
+
config.values = adoptValues(readLocal())
|
|
1452
|
+
scheduleSync()
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// ---- plugin detail page: graphical default-mode picker -------------
|
|
1456
|
+
|
|
1457
|
+
function configController() {
|
|
1458
|
+
if (config.controller) return config.controller
|
|
1459
|
+
try {
|
|
1460
|
+
var service = safeGet(ctx, 'configForms')
|
|
1461
|
+
if (service && typeof service.get === 'function') return service.get(ENTRY)
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
void error
|
|
1464
|
+
}
|
|
1465
|
+
return null
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function readDefaultMode() {
|
|
1469
|
+
var controller = configController()
|
|
1470
|
+
try {
|
|
1471
|
+
var snapshot = controller && controller.getSnapshot && controller.getSnapshot()
|
|
1472
|
+
var value = snapshot && snapshot.value
|
|
1473
|
+
return MODES.indexOf(value && value.mode) >= 0 ? value.mode : DEFAULTS.mode
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
return DEFAULTS.mode
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function writeDefaultMode(mode) {
|
|
1480
|
+
if (MODES.indexOf(mode) < 0) return false
|
|
1481
|
+
var controller = configController()
|
|
1482
|
+
if (!controller || typeof controller.mutate !== 'function') return false
|
|
1483
|
+
try {
|
|
1484
|
+
var snapshot = controller.getSnapshot && controller.getSnapshot()
|
|
1485
|
+
var result = controller.mutate(
|
|
1486
|
+
[{ op: 'set', path: ['mode'], value: mode }],
|
|
1487
|
+
snapshot && snapshot.revision
|
|
1488
|
+
)
|
|
1489
|
+
if (result && typeof result.catch === 'function') result.catch(function () {})
|
|
1490
|
+
// The settings page and the sidebar share this client run, so adopt
|
|
1491
|
+
// the new default immediately instead of waiting for a config push.
|
|
1492
|
+
runtime.sessionMode = null
|
|
1493
|
+
runtime.configMode = mode
|
|
1494
|
+
config.values = assign({}, config.values, { mode: mode })
|
|
1495
|
+
if (runtime.popOpen && mode !== 'c') runtime.popOpen = false
|
|
1496
|
+
scheduleSync()
|
|
1497
|
+
return true
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
return false
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// The Host serves images that sit beside a bundle's detail document.
|
|
1504
|
+
// This is the same URL the Plugin Manager's own markdown renderer uses,
|
|
1505
|
+
// so the small QR in the pinned settings card needs no base64 payload.
|
|
1506
|
+
function supportQrUrl() {
|
|
1507
|
+
try {
|
|
1508
|
+
if (typeof document === 'undefined' || !document.baseURI) return ''
|
|
1509
|
+
var query = 'bundle=' + encodeURIComponent(ENTRY) +
|
|
1510
|
+
'&document=' + encodeURIComponent('DETAIL.md') +
|
|
1511
|
+
'&path=' + encodeURIComponent('assets/wechat-qr.png')
|
|
1512
|
+
return new URL('api/plugin-manager/detail-file?' + query, document.baseURI).href
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
return ''
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// `plugins.bundle.config` is the keyed slot the Plugin Manager declares
|
|
1519
|
+
// for a bundle's own configuration page. The component reads and writes
|
|
1520
|
+
// the same configForms controller as the sidebar, so a click here becomes
|
|
1521
|
+
// the new startup default and the sidebar follows it immediately.
|
|
1522
|
+
function makeModePicker(h, ReactLib) {
|
|
1523
|
+
var box = function (extra) {
|
|
1524
|
+
return h('span', { style: assign({
|
|
1525
|
+
display: 'block', borderRadius: '2px',
|
|
1526
|
+
background: 'var(--dsw-alias-label-tertiary,rgba(127,127,127,.5))'
|
|
1527
|
+
}, extra || {}) })
|
|
1528
|
+
}
|
|
1529
|
+
var frame = function (extra, first, second) {
|
|
1530
|
+
return h('span', { style: assign({
|
|
1531
|
+
display: 'flex', flexDirection: 'column', width: '36px', height: '24px',
|
|
1532
|
+
padding: '2px', gap: '2px', boxSizing: 'border-box', position: 'relative',
|
|
1533
|
+
borderRadius: '4px',
|
|
1534
|
+
border: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3))'
|
|
1535
|
+
}, extra || {}) }, first || null, second || null)
|
|
1536
|
+
}
|
|
1537
|
+
function mini(mode) {
|
|
1538
|
+
if (mode === 'a') {
|
|
1539
|
+
return frame(null, box({ width: '100%', height: '7px' }), box({ width: '100%', height: '7px' }))
|
|
1540
|
+
}
|
|
1541
|
+
if (mode === 'b') {
|
|
1542
|
+
return frame(null, box({ width: '60%', height: '4px' }), box({ width: '100%', height: '11px' }))
|
|
1543
|
+
}
|
|
1544
|
+
return frame(null,
|
|
1545
|
+
box({ width: '100%', height: '100%', opacity: '0.35' }),
|
|
1546
|
+
box({
|
|
1547
|
+
position: 'absolute', left: '9px', top: '5px', width: '18px', height: '13px',
|
|
1548
|
+
background: 'var(--dsw-alias-label-primary,currentColor)', opacity: '0.85',
|
|
1549
|
+
boxShadow: '0 2px 6px rgba(0,0,0,.28)'
|
|
1550
|
+
})
|
|
1551
|
+
)
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
return function ModePicker() {
|
|
1555
|
+
var state = ReactLib.useState(readDefaultMode())
|
|
1556
|
+
var mode = state[0]
|
|
1557
|
+
var setMode = state[1]
|
|
1558
|
+
ReactLib.useEffect(function () {
|
|
1559
|
+
var controller = configController()
|
|
1560
|
+
if (!controller || typeof controller.subscribe !== 'function') return
|
|
1561
|
+
var off = null
|
|
1562
|
+
try {
|
|
1563
|
+
off = controller.subscribe(function () { setMode(readDefaultMode()) })
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
void error
|
|
1566
|
+
}
|
|
1567
|
+
return function () {
|
|
1568
|
+
try { if (typeof off === 'function') off() } catch (error) { void error }
|
|
1569
|
+
}
|
|
1570
|
+
}, [])
|
|
1571
|
+
|
|
1572
|
+
// ---- temporary-session cleanup policy -------------------------
|
|
1573
|
+
// The Host half owns this: it is the only side that can delete files,
|
|
1574
|
+
// and the values live in temp-session-config.txt rather than in the
|
|
1575
|
+
// plugin config the cards above write. So this section talks to
|
|
1576
|
+
// /api/dsh-temp-session.config and never touches configForms — one
|
|
1577
|
+
// source of truth, no "settings page and file disagree" state.
|
|
1578
|
+
var policyState = ReactLib.useState(null)
|
|
1579
|
+
var policy = policyState[0]
|
|
1580
|
+
var setPolicy = policyState[1]
|
|
1581
|
+
var saveState = ReactLib.useState('')
|
|
1582
|
+
var saveStatus = saveState[0]
|
|
1583
|
+
var setSaveStatus = saveState[1]
|
|
1584
|
+
|
|
1585
|
+
// ---- feedback -------------------------------------------------
|
|
1586
|
+
// A plugin bundle has no server to post to, and no mail client it can
|
|
1587
|
+
// trust. So this block builds a complete report out of facts the Host
|
|
1588
|
+
// half already knows (version, OS, layout, button mode, route count,
|
|
1589
|
+
// last error) and then offers three channels that need nothing:
|
|
1590
|
+
// the clipboard, a pre-filled GitHub issue, and the plugin market's
|
|
1591
|
+
// own comment thread. DSH itself has no plugin-feedback field in its
|
|
1592
|
+
// manifest (verified in app.asar: no bugs/feedback handling in the
|
|
1593
|
+
// plugin-manager client); dsh-market is the surface that defines one.
|
|
1594
|
+
// The desktop shell opens http(s) links in the system browser
|
|
1595
|
+
// (app.asar lib/main.js attaches setWindowOpenHandler to the main
|
|
1596
|
+
// window), so an <a>-style jump works; because that handler answers
|
|
1597
|
+
// "deny" after handing the URL to the OS, `window.open` returns null
|
|
1598
|
+
// even on success — so the return value must never be read as "failed".
|
|
1599
|
+
var noteState = ReactLib.useState(readDraft())
|
|
1600
|
+
var note = noteState[0]
|
|
1601
|
+
var setNote = noteState[1]
|
|
1602
|
+
var fbState = ReactLib.useState('')
|
|
1603
|
+
var fbStatus = fbState[0]
|
|
1604
|
+
var setFbStatus = fbState[1]
|
|
1605
|
+
|
|
1606
|
+
function readDraft() {
|
|
1607
|
+
try {
|
|
1608
|
+
return String(window.localStorage.getItem(FEEDBACK_DRAFT_KEY) || '')
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
void error
|
|
1611
|
+
return ''
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function updateNote(value) {
|
|
1616
|
+
setNote(value)
|
|
1617
|
+
try {
|
|
1618
|
+
if (value) window.localStorage.setItem(FEEDBACK_DRAFT_KEY, value)
|
|
1619
|
+
else window.localStorage.removeItem(FEEDBACK_DRAFT_KEY)
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
void error
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function legacyCopy(text) {
|
|
1626
|
+
var area = document.createElement('textarea')
|
|
1627
|
+
area.value = text
|
|
1628
|
+
area.setAttribute('readonly', '')
|
|
1629
|
+
area.style.position = 'fixed'
|
|
1630
|
+
area.style.top = '-1000px'
|
|
1631
|
+
area.style.opacity = '0'
|
|
1632
|
+
document.body.appendChild(area)
|
|
1633
|
+
try {
|
|
1634
|
+
area.select()
|
|
1635
|
+
var done = typeof document.execCommand === 'function' && document.execCommand('copy')
|
|
1636
|
+
if (!done) throw new Error('copy blocked')
|
|
1637
|
+
} finally {
|
|
1638
|
+
try { if (area.parentNode) area.parentNode.removeChild(area) } catch (error) { void error }
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function copyText(text) {
|
|
1643
|
+
var clip = typeof navigator !== 'undefined' ? navigator.clipboard : null
|
|
1644
|
+
if (clip && typeof clip.writeText === 'function') {
|
|
1645
|
+
return clip.writeText(text).catch(function () { legacyCopy(text) })
|
|
1646
|
+
}
|
|
1647
|
+
try {
|
|
1648
|
+
legacyCopy(text)
|
|
1649
|
+
return Promise.resolve()
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
return Promise.reject(error)
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// One status call is enough for a whole session of editing: version
|
|
1656
|
+
// and OS do not change while the page is open.
|
|
1657
|
+
var diagCache = null
|
|
1658
|
+
function loadDiagnostics() {
|
|
1659
|
+
if (diagCache) return Promise.resolve(diagCache)
|
|
1660
|
+
return tempCall('api/dsh-temp-session.status').then(function (data) {
|
|
1661
|
+
if (data && data.ok === true) diagCache = data
|
|
1662
|
+
return diagCache
|
|
1663
|
+
}).catch(function () {
|
|
1664
|
+
return null
|
|
1665
|
+
})
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function reportText(diag) {
|
|
1669
|
+
var env = (diag && diag.env) || {}
|
|
1670
|
+
var purge = diag && diag.lastPurge
|
|
1671
|
+
var lines = []
|
|
1672
|
+
lines.push('### What happened')
|
|
1673
|
+
lines.push((note || '').trim() || '(no description)')
|
|
1674
|
+
lines.push('')
|
|
1675
|
+
lines.push('### Environment')
|
|
1676
|
+
lines.push('- plugin: ' + (env.plugin || '?') + ' · default layout: ' + readDefaultMode())
|
|
1677
|
+
lines.push('- dsh desktop: ' + (env.dshClient || '?')
|
|
1678
|
+
+ ' · node ' + (env.node || '?')
|
|
1679
|
+
+ (env.electron ? ' · electron ' + env.electron : ''))
|
|
1680
|
+
lines.push('- system: ' + ([env.platform, env.os, env.arch].filter(Boolean).join(' ') || '?'))
|
|
1681
|
+
lines.push('- temp button: ' + ((diag && diag.buttonStyle) || '?')
|
|
1682
|
+
+ ' · tracked: ' + (diag ? diag.tempCount : '?')
|
|
1683
|
+
+ ' · routes: ' + ((diag && diag.routes && diag.routes.registered) || 0)
|
|
1684
|
+
+ ' · quit-cleanup: ' + (diag && diag.deleteOnExit === false ? 'off' : 'on')
|
|
1685
|
+
+ (diag && diag.scheduleDelete ? ' · daily ' + diag.scheduleTime : ''))
|
|
1686
|
+
if (purge) {
|
|
1687
|
+
lines.push('- last cleanup: ' + purge.reason + ' @ ' + purge.at
|
|
1688
|
+
+ ' → removed ' + purge.removed + '/' + purge.tried)
|
|
1689
|
+
}
|
|
1690
|
+
if (diag && diag.lastError) lines.push('- last error: ' + diag.lastError)
|
|
1691
|
+
return lines.join('\n')
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function issueUrl(text) {
|
|
1695
|
+
var title = (note || '').trim().split('\n')[0].trim().slice(0, 80)
|
|
1696
|
+
if (!title) title = 'dsh-sidebar-suite feedback'
|
|
1697
|
+
var head = FEEDBACK_REPO + '/issues/new?title=' + encodeURIComponent(title) + '&body='
|
|
1698
|
+
// Long URLs are rejected by the OS handler and by GitHub. Trim the
|
|
1699
|
+
// description first — the environment block is what makes a report
|
|
1700
|
+
// actionable, and it is short.
|
|
1701
|
+
var limit = 6500
|
|
1702
|
+
var body = text
|
|
1703
|
+
var encoded = encodeURIComponent(body)
|
|
1704
|
+
while (body.length > 0 && head.length + encoded.length > limit) {
|
|
1705
|
+
body = text.slice(0, Math.max(0, body.length - 240)) + '\n…(truncated)'
|
|
1706
|
+
encoded = encodeURIComponent(body)
|
|
1707
|
+
}
|
|
1708
|
+
return head + encoded
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
function flashFeedback(kind) {
|
|
1712
|
+
setFbStatus(kind)
|
|
1713
|
+
try {
|
|
1714
|
+
window.setTimeout(function () {
|
|
1715
|
+
setFbStatus(function (previous) { return previous === kind ? '' : previous })
|
|
1716
|
+
}, 3200)
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
void error
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
function copyReport() {
|
|
1723
|
+
setFbStatus('working')
|
|
1724
|
+
loadDiagnostics()
|
|
1725
|
+
.then(function (diag) { return copyText(reportText(diag)) })
|
|
1726
|
+
.then(function () { flashFeedback('copied') })
|
|
1727
|
+
.catch(function () { setFbStatus('failed') })
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
function openIssue() {
|
|
1731
|
+
setFbStatus('working')
|
|
1732
|
+
loadDiagnostics()
|
|
1733
|
+
.then(function (diag) {
|
|
1734
|
+
try {
|
|
1735
|
+
window.open(issueUrl(reportText(diag)), '_blank')
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
void error
|
|
1738
|
+
}
|
|
1739
|
+
flashFeedback('issueOpened')
|
|
1740
|
+
})
|
|
1741
|
+
.catch(function () { setFbStatus('failed') })
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// The plugin market (dsh-market) defines its own feedback entry: one
|
|
1745
|
+
// GitHub Discussion per plugin, keyed on `plugin:<owner/repo>` in the
|
|
1746
|
+
// public catalog repository. No diagnostics are needed — the note is
|
|
1747
|
+
// typed there directly — so this is a plain link to that exact thread.
|
|
1748
|
+
function openMarket() {
|
|
1749
|
+
try {
|
|
1750
|
+
window.open(FEEDBACK_MARKET_URL, '_blank')
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
void error
|
|
1753
|
+
}
|
|
1754
|
+
flashFeedback('issueOpened')
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function adoptPolicy(data, fallbackTime) {
|
|
1758
|
+
setPolicy({
|
|
1759
|
+
deleteOnExit: data.deleteOnExit !== false,
|
|
1760
|
+
scheduleDelete: data.scheduleDelete === true,
|
|
1761
|
+
scheduleTime: typeof data.scheduleTime === 'string' && data.scheduleTime
|
|
1762
|
+
? data.scheduleTime
|
|
1763
|
+
: (fallbackTime || '03:00'),
|
|
1764
|
+
})
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
ReactLib.useEffect(function () {
|
|
1768
|
+
var stopped = false
|
|
1769
|
+
tempCall('api/dsh-temp-session.config')
|
|
1770
|
+
.then(function (data) {
|
|
1771
|
+
if (stopped) return
|
|
1772
|
+
if (!data || data.ok !== true) { setPolicy(false); return }
|
|
1773
|
+
adoptPolicy(data)
|
|
1774
|
+
})
|
|
1775
|
+
.catch(function () {
|
|
1776
|
+
if (!stopped) setPolicy(false)
|
|
1777
|
+
})
|
|
1778
|
+
return function () { stopped = true }
|
|
1779
|
+
}, [])
|
|
1780
|
+
|
|
1781
|
+
function persist(patch) {
|
|
1782
|
+
if (!policy || policy === false) return
|
|
1783
|
+
setPolicy(assign({}, policy, patch))
|
|
1784
|
+
setSaveStatus('saving')
|
|
1785
|
+
tempCall('api/dsh-temp-session.config', { method: 'POST', body: JSON.stringify(patch) })
|
|
1786
|
+
.then(function (data) {
|
|
1787
|
+
if (!data || data.ok !== true) throw new Error((data && data.error) || 'save failed')
|
|
1788
|
+
adoptPolicy(data, patch.scheduleTime)
|
|
1789
|
+
setSaveStatus('saved')
|
|
1790
|
+
// Fade the confirmation out; a later failure must not be wiped.
|
|
1791
|
+
try {
|
|
1792
|
+
window.setTimeout(function () {
|
|
1793
|
+
setSaveStatus(function (previous) { return previous === 'saved' ? '' : previous })
|
|
1794
|
+
}, 2400)
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
void error
|
|
1797
|
+
}
|
|
1798
|
+
})
|
|
1799
|
+
.catch(function (error) {
|
|
1800
|
+
setSaveStatus(t('policy.failed') + String((error && error.message) || error))
|
|
1801
|
+
})
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
var available = !!configController()
|
|
1805
|
+
var policyReady = !!(policy && policy !== false)
|
|
1806
|
+
var dim = { fontSize: '11px', lineHeight: '16px', color: 'var(--dsw-alias-label-secondary,inherit)' }
|
|
1807
|
+
var pillBase = {
|
|
1808
|
+
font: 'inherit', fontSize: '11px', lineHeight: '16px', padding: '2px 10px',
|
|
1809
|
+
minWidth: '46px', borderRadius: '999px', cursor: 'pointer',
|
|
1810
|
+
border: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3))',
|
|
1811
|
+
background: 'transparent',
|
|
1812
|
+
color: 'var(--dsw-alias-label-secondary,inherit)'
|
|
1813
|
+
}
|
|
1814
|
+
var pillOn = {
|
|
1815
|
+
borderColor: 'var(--dsw-alias-label-primary,currentColor)',
|
|
1816
|
+
background: 'var(--dsw-alias-interactive-bg-active,rgba(127,127,127,.22))',
|
|
1817
|
+
color: 'var(--dsw-alias-label-primary,inherit)'
|
|
1818
|
+
}
|
|
1819
|
+
function policyRow(label, active, onToggle, extra) {
|
|
1820
|
+
return h('div', {
|
|
1821
|
+
key: label,
|
|
1822
|
+
style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }
|
|
1823
|
+
},
|
|
1824
|
+
h('span', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-primary,inherit)' } }, label),
|
|
1825
|
+
h('button', {
|
|
1826
|
+
type: 'button',
|
|
1827
|
+
'aria-pressed': active ? 'true' : 'false',
|
|
1828
|
+
disabled: !policyReady,
|
|
1829
|
+
onClick: function () { onToggle(!active) },
|
|
1830
|
+
style: assign({}, pillBase, active ? pillOn : {})
|
|
1831
|
+
}, active ? t('policy.on') : t('policy.off')),
|
|
1832
|
+
extra || null
|
|
1833
|
+
)
|
|
1834
|
+
}
|
|
1835
|
+
var cardBase = {
|
|
1836
|
+
display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '5px',
|
|
1837
|
+
flex: '1 1 140px', minWidth: '140px', maxWidth: '230px', padding: '10px',
|
|
1838
|
+
borderRadius: '10px', font: 'inherit', textAlign: 'left', cursor: 'pointer',
|
|
1839
|
+
color: 'var(--dsw-alias-label-primary,inherit)',
|
|
1840
|
+
border: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3))',
|
|
1841
|
+
background: 'transparent'
|
|
1842
|
+
}
|
|
1843
|
+
return h('div', { 'data-dsbs-config': ENTRY, style: { display: 'flex', flexDirection: 'column', gap: '10px' } },
|
|
1844
|
+
h('div', { 'data-dsbs-intro': ENTRY, style: {
|
|
1845
|
+
fontSize: '11px', lineHeight: '16px',
|
|
1846
|
+
color: 'var(--dsw-alias-label-secondary,inherit)'
|
|
1847
|
+
} }, t('settings.intro')),
|
|
1848
|
+
h('div', { style: { fontSize: '13px', fontWeight: '600', color: 'var(--dsw-alias-label-primary,inherit)' } }, t('settings.title')),
|
|
1849
|
+
h('div', { style: { fontSize: '11px', lineHeight: '16px', color: 'var(--dsw-alias-label-secondary,inherit)' } }, t('settings.help')),
|
|
1850
|
+
h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },
|
|
1851
|
+
MODES.map(function (value) {
|
|
1852
|
+
var active = value === mode
|
|
1853
|
+
return h('button', {
|
|
1854
|
+
key: value, type: 'button',
|
|
1855
|
+
'aria-pressed': active ? 'true' : 'false',
|
|
1856
|
+
title: t('mode.' + value + '.hint'),
|
|
1857
|
+
disabled: !available,
|
|
1858
|
+
style: assign({}, cardBase, active ? {
|
|
1859
|
+
borderColor: 'var(--dsw-alias-label-primary,currentColor)',
|
|
1860
|
+
background: 'var(--dsw-alias-interactive-bg-active,rgba(127,127,127,.22))'
|
|
1861
|
+
} : {}),
|
|
1862
|
+
onClick: function () {
|
|
1863
|
+
if (writeDefaultMode(value)) setMode(value)
|
|
1864
|
+
}
|
|
1865
|
+
},
|
|
1866
|
+
mini(value),
|
|
1867
|
+
h('span', { style: { fontSize: '12px', fontWeight: active ? '600' : '400' } },
|
|
1868
|
+
t('mode.' + value) + (active ? ' · ' + t('settings.current') : '')),
|
|
1869
|
+
h('span', { style: { fontSize: '11px', lineHeight: '15px', color: 'var(--dsw-alias-label-secondary,inherit)' } },
|
|
1870
|
+
t('mode.' + value + '.hint'))
|
|
1871
|
+
)
|
|
1872
|
+
})
|
|
1873
|
+
),
|
|
1874
|
+
available ? null : h('div', { style: { fontSize: '11px', color: 'var(--dsw-alias-label-secondary,inherit)' } }, t('settings.unavailable')),
|
|
1875
|
+
h('div', { 'data-dsbs-temp-policy': ENTRY, style: {
|
|
1876
|
+
display: 'flex', flexDirection: 'column', gap: '8px',
|
|
1877
|
+
paddingTop: '12px', marginTop: '2px',
|
|
1878
|
+
borderTop: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.2))'
|
|
1879
|
+
} },
|
|
1880
|
+
h('div', { style: { fontSize: '13px', fontWeight: '600', color: 'var(--dsw-alias-label-primary,inherit)' } }, t('policy.title')),
|
|
1881
|
+
h('div', { style: dim }, t('policy.help')),
|
|
1882
|
+
policy === null
|
|
1883
|
+
? h('div', { style: dim }, t('policy.loading'))
|
|
1884
|
+
: policy === false
|
|
1885
|
+
? h('div', { style: dim }, t('policy.unavailable'))
|
|
1886
|
+
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
1887
|
+
policyRow(t('policy.onQuit'), policy.deleteOnExit === true, function (next) {
|
|
1888
|
+
persist({ deleteOnExit: next })
|
|
1889
|
+
}),
|
|
1890
|
+
policyRow(t('policy.schedule'), policy.scheduleDelete === true, function (next) {
|
|
1891
|
+
persist({ scheduleDelete: next })
|
|
1892
|
+
}, h('input', {
|
|
1893
|
+
type: 'time',
|
|
1894
|
+
value: policy.scheduleTime,
|
|
1895
|
+
step: 60,
|
|
1896
|
+
'aria-label': t('policy.schedule'),
|
|
1897
|
+
onChange: function (event) {
|
|
1898
|
+
var value = event && event.target ? event.target.value : ''
|
|
1899
|
+
if (!value) return
|
|
1900
|
+
persist({ scheduleTime: value })
|
|
1901
|
+
},
|
|
1902
|
+
style: {
|
|
1903
|
+
font: 'inherit', fontSize: '12px', padding: '1px 6px',
|
|
1904
|
+
borderRadius: '6px', color: 'var(--dsw-alias-label-primary,inherit)',
|
|
1905
|
+
background: 'transparent',
|
|
1906
|
+
opacity: policy.scheduleDelete ? '1' : '0.55',
|
|
1907
|
+
border: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3))'
|
|
1908
|
+
}
|
|
1909
|
+
})),
|
|
1910
|
+
h('div', { style: dim }, t('policy.note')),
|
|
1911
|
+
saveStatus
|
|
1912
|
+
? h('div', {
|
|
1913
|
+
style: {
|
|
1914
|
+
fontSize: '11px', lineHeight: '16px',
|
|
1915
|
+
color: saveStatus === 'saved'
|
|
1916
|
+
? 'var(--dsw-alias-label-secondary,inherit)'
|
|
1917
|
+
: saveStatus === 'saving'
|
|
1918
|
+
? 'var(--dsw-alias-label-tertiary,inherit)'
|
|
1919
|
+
: 'var(--dsw-alias-text-error,#e5484d)'
|
|
1920
|
+
}
|
|
1921
|
+
}, saveStatus === 'saving' ? t('policy.saving') : saveStatus === 'saved' ? t('policy.saved') : saveStatus)
|
|
1922
|
+
: null
|
|
1923
|
+
)
|
|
1924
|
+
),
|
|
1925
|
+
h('div', { 'data-dsbs-feedback': ENTRY, style: {
|
|
1926
|
+
display: 'flex', flexDirection: 'column', gap: '8px',
|
|
1927
|
+
paddingTop: '12px', marginTop: '2px',
|
|
1928
|
+
borderTop: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.2))'
|
|
1929
|
+
} },
|
|
1930
|
+
h('div', { style: { fontSize: '13px', fontWeight: '600', color: 'var(--dsw-alias-label-primary,inherit)' } }, t('feedback.title')),
|
|
1931
|
+
h('div', { style: dim }, t('feedback.help')),
|
|
1932
|
+
h('textarea', {
|
|
1933
|
+
'data-dsbs-feedback-note': '',
|
|
1934
|
+
value: note,
|
|
1935
|
+
rows: 2,
|
|
1936
|
+
maxLength: 4000,
|
|
1937
|
+
placeholder: t('feedback.placeholder'),
|
|
1938
|
+
'aria-label': t('feedback.title'),
|
|
1939
|
+
onChange: function (event) {
|
|
1940
|
+
updateNote(event && event.target ? String(event.target.value || '') : '')
|
|
1941
|
+
},
|
|
1942
|
+
style: {
|
|
1943
|
+
width: '100%', boxSizing: 'border-box', resize: 'vertical',
|
|
1944
|
+
minHeight: '44px', maxHeight: '160px',
|
|
1945
|
+
padding: '6px 8px', borderRadius: '8px',
|
|
1946
|
+
font: 'inherit', fontSize: '12px', lineHeight: '18px',
|
|
1947
|
+
color: 'var(--dsw-alias-label-primary,inherit)',
|
|
1948
|
+
background: 'transparent',
|
|
1949
|
+
border: '1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.3))'
|
|
1950
|
+
}
|
|
1951
|
+
}),
|
|
1952
|
+
h('div', { style: { display: 'flex', gap: '8px', flexWrap: 'wrap', alignItems: 'center' } },
|
|
1953
|
+
h('button', {
|
|
1954
|
+
type: 'button',
|
|
1955
|
+
'data-dsbs-feedback-copy': '',
|
|
1956
|
+
disabled: fbStatus === 'working',
|
|
1957
|
+
onClick: copyReport,
|
|
1958
|
+
style: assign({}, pillBase, { minWidth: '0', padding: '4px 12px' })
|
|
1959
|
+
}, fbStatus === 'copied'
|
|
1960
|
+
? t('feedback.copied')
|
|
1961
|
+
: fbStatus === 'working'
|
|
1962
|
+
? t('feedback.working')
|
|
1963
|
+
: t('feedback.copy')),
|
|
1964
|
+
h('button', {
|
|
1965
|
+
type: 'button',
|
|
1966
|
+
'data-dsbs-feedback-issue': '',
|
|
1967
|
+
disabled: fbStatus === 'working',
|
|
1968
|
+
onClick: openIssue,
|
|
1969
|
+
style: assign({}, pillBase, { minWidth: '0', padding: '4px 12px' })
|
|
1970
|
+
}, fbStatus === 'issueOpened' ? t('feedback.issueOpened') : t('feedback.issue')),
|
|
1971
|
+
h('button', {
|
|
1972
|
+
type: 'button',
|
|
1973
|
+
'data-dsbs-feedback-market': '',
|
|
1974
|
+
onClick: openMarket,
|
|
1975
|
+
style: assign({}, pillBase, { minWidth: '0', padding: '4px 12px' })
|
|
1976
|
+
}, t('feedback.market')),
|
|
1977
|
+
fbStatus === 'failed'
|
|
1978
|
+
? h('span', { style: { fontSize: '11px', lineHeight: '16px', color: 'var(--dsw-alias-text-error,#e5484d)' } }, t('feedback.failed'))
|
|
1979
|
+
: null
|
|
1980
|
+
),
|
|
1981
|
+
h('div', { style: dim }, t('feedback.hint')),
|
|
1982
|
+
h('div', { style: dim }, t('feedback.where')),
|
|
1983
|
+
h('div', { style: dim }, t('feedback.marketHint'))
|
|
1984
|
+
),
|
|
1985
|
+
h('div', { 'data-dsbs-support': ENTRY },
|
|
1986
|
+
supportQrUrl() ? h('img', {
|
|
1987
|
+
src: supportQrUrl(),
|
|
1988
|
+
alt: t('settings.qrAlt'),
|
|
1989
|
+
onError: function (event) {
|
|
1990
|
+
try { event.currentTarget.style.display = 'none' } catch (error) { void error }
|
|
1991
|
+
}
|
|
1992
|
+
}) : null,
|
|
1993
|
+
h('div', { 'data-dsbs-support-text': ENTRY },
|
|
1994
|
+
h('div', { style: { fontSize: '12px', fontWeight: '600', color: 'var(--dsw-alias-label-primary,inherit)' } }, t('settings.support')),
|
|
1995
|
+
h('div', { style: { fontSize: '11px', lineHeight: '16px', color: 'var(--dsw-alias-label-secondary,inherit)' } }, t('settings.supportHint'))
|
|
1996
|
+
)
|
|
1997
|
+
)
|
|
1998
|
+
)
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// Register only when React and the slots service are both available.
|
|
2003
|
+
// Any failure here is contained: the sidebar control bar is unaffected.
|
|
2004
|
+
if (React && React.createElement && React.useState && React.useEffect && typeof ctx.inject === 'function') {
|
|
2005
|
+
try {
|
|
2006
|
+
ctx.inject(['slots'], function (scope) {
|
|
2007
|
+
var slots = scope && scope.slots
|
|
2008
|
+
if (!slots || typeof slots.inject !== 'function' || typeof slots.register !== 'function') return
|
|
2009
|
+
try {
|
|
2010
|
+
slots.inject('plugins.bundle.config', function () {
|
|
2011
|
+
return slots.register({
|
|
2012
|
+
name: 'plugins.bundle.config',
|
|
2013
|
+
key: ENTRY,
|
|
2014
|
+
order: 0,
|
|
2015
|
+
}, makeModePicker(React.createElement, React))
|
|
2016
|
+
})
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
void error
|
|
2019
|
+
}
|
|
2020
|
+
})
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
void error
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
// ---- wiring -------------------------------------------------------
|
|
2027
|
+
|
|
2028
|
+
effect(function () {
|
|
2029
|
+
var existing = document.getElementById(STYLE_ID)
|
|
2030
|
+
if (existing && existing.parentNode) existing.parentNode.removeChild(existing)
|
|
2031
|
+
var style = document.createElement('style')
|
|
2032
|
+
style.id = STYLE_ID
|
|
2033
|
+
style.setAttribute('data-plugin', ENTRY)
|
|
2034
|
+
style.textContent = CSS
|
|
2035
|
+
;(document.head || document.documentElement).appendChild(style)
|
|
2036
|
+
return function () {
|
|
2037
|
+
if (style.parentNode) style.parentNode.removeChild(style)
|
|
2038
|
+
}
|
|
2039
|
+
}, 'sidebar-suite: styles')
|
|
2040
|
+
|
|
2041
|
+
effect(function () {
|
|
2042
|
+
if (!localeService || typeof localeService.subscribe !== 'function') return
|
|
2043
|
+
var off = localeService.subscribe(function () {
|
|
2044
|
+
t.refresh()
|
|
2045
|
+
relabel()
|
|
2046
|
+
scheduleSync()
|
|
2047
|
+
})
|
|
2048
|
+
return function () {
|
|
2049
|
+
try {
|
|
2050
|
+
off()
|
|
2051
|
+
} catch (error) {
|
|
2052
|
+
void error
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}, 'sidebar-suite: locale')
|
|
2056
|
+
|
|
2057
|
+
effect(function () {
|
|
2058
|
+
var stopped = false
|
|
2059
|
+
var timer = null
|
|
2060
|
+
|
|
2061
|
+
function attempt() {
|
|
2062
|
+
if (stopped) return true
|
|
2063
|
+
if (tryAdopt(safeGet(ctx, 'configForms'))) return true
|
|
2064
|
+
return false
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
try {
|
|
2068
|
+
if (typeof ctx.inject === 'function') {
|
|
2069
|
+
ctx.inject(['configForms'], function (scope) {
|
|
2070
|
+
if (stopped) return
|
|
2071
|
+
try {
|
|
2072
|
+
tryAdopt(scope && scope.configForms)
|
|
2073
|
+
} catch (error) {
|
|
2074
|
+
void error
|
|
2075
|
+
}
|
|
2076
|
+
})
|
|
2077
|
+
}
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
void error
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
if (attempt()) return function () { stopped = true }
|
|
2083
|
+
|
|
2084
|
+
// The client's config service may come up after this plugin: retry for
|
|
2085
|
+
// ten seconds, then fall back to device-local storage.
|
|
2086
|
+
var tries = 0
|
|
2087
|
+
timer = setInterval(function () {
|
|
2088
|
+
tries += 1
|
|
2089
|
+
if (attempt() || tries > 40) {
|
|
2090
|
+
clearInterval(timer)
|
|
2091
|
+
timer = null
|
|
2092
|
+
if (!config.controller) fallbackToLocal()
|
|
2093
|
+
}
|
|
2094
|
+
}, 250)
|
|
2095
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
2096
|
+
|
|
2097
|
+
return function () {
|
|
2098
|
+
stopped = true
|
|
2099
|
+
if (timer) {
|
|
2100
|
+
clearInterval(timer)
|
|
2101
|
+
timer = null
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}, 'sidebar-suite: configuration')
|
|
2105
|
+
|
|
2106
|
+
effect(function () {
|
|
2107
|
+
var onPointerDown = function (event) {
|
|
2108
|
+
if (config.values.mode !== 'c' || !runtime.popOpen) return
|
|
2109
|
+
var target = event.target
|
|
2110
|
+
if (!target) return
|
|
2111
|
+
if (runtime.bar && runtime.bar.contains(target)) return
|
|
2112
|
+
if (runtime.nav && runtime.nav.contains(target)) return
|
|
2113
|
+
runtime.popOpen = false
|
|
2114
|
+
scheduleSync()
|
|
2115
|
+
}
|
|
2116
|
+
var onKeyDown = function (event) {
|
|
2117
|
+
if (event.key !== 'Escape' || !runtime.popOpen) return
|
|
2118
|
+
runtime.popOpen = false
|
|
2119
|
+
// Esc must hand focus back to the trigger, or the next Tab starts
|
|
2120
|
+
// from wherever the pointer last landed.
|
|
2121
|
+
var trigger = barButton('pop')
|
|
2122
|
+
if (trigger && typeof trigger.focus === 'function') trigger.focus()
|
|
2123
|
+
scheduleSync()
|
|
2124
|
+
}
|
|
2125
|
+
var onResize = function () {
|
|
2126
|
+
scheduleSync()
|
|
2127
|
+
}
|
|
2128
|
+
document.addEventListener('pointerdown', onPointerDown, true)
|
|
2129
|
+
document.addEventListener('keydown', onKeyDown, true)
|
|
2130
|
+
window.addEventListener('resize', onResize)
|
|
2131
|
+
return function () {
|
|
2132
|
+
document.removeEventListener('pointerdown', onPointerDown, true)
|
|
2133
|
+
document.removeEventListener('keydown', onKeyDown, true)
|
|
2134
|
+
window.removeEventListener('resize', onResize)
|
|
2135
|
+
}
|
|
2136
|
+
}, 'sidebar-suite: dismissal')
|
|
2137
|
+
|
|
2138
|
+
effect(function () {
|
|
2139
|
+
// Safety net: the sidebar root is created and destroyed by the host, so
|
|
2140
|
+
// a root replacement is invisible to an observer that watches the old
|
|
2141
|
+
// root's children. A slow heartbeat re-resolves it (one query and one
|
|
2142
|
+
// rect read) and is skipped while the page is hidden.
|
|
2143
|
+
var timer = setInterval(function () {
|
|
2144
|
+
if (typeof document.hidden === 'boolean' && document.hidden) return
|
|
2145
|
+
sync()
|
|
2146
|
+
}, 1500)
|
|
2147
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
2148
|
+
sync()
|
|
2149
|
+
return function () {
|
|
2150
|
+
clearInterval(timer)
|
|
2151
|
+
}
|
|
2152
|
+
}, 'sidebar-suite: sync')
|
|
2153
|
+
|
|
2154
|
+
var teardown = null
|
|
2155
|
+
effect(function () {
|
|
2156
|
+
teardown = function () {
|
|
2157
|
+
if (runtime.disposed) return
|
|
2158
|
+
runtime.disposed = true
|
|
2159
|
+
try {
|
|
2160
|
+
clearTimeout(tempToastTimer)
|
|
2161
|
+
var toastNode = document.querySelector('[data-dsbs-temp-toast]')
|
|
2162
|
+
if (toastNode && toastNode.parentNode) toastNode.parentNode.removeChild(toastNode)
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
void error
|
|
2165
|
+
}
|
|
2166
|
+
try {
|
|
2167
|
+
var tagged = document.querySelectorAll('[data-dsbs-temp-row]')
|
|
2168
|
+
for (var t = 0; t < tagged.length; t++) {
|
|
2169
|
+
tagged[t].removeAttribute('data-dsbs-temp-row')
|
|
2170
|
+
tagged[t].removeAttribute('data-dsbs-temp-badge')
|
|
2171
|
+
}
|
|
2172
|
+
} catch (error) {
|
|
2173
|
+
void error
|
|
2174
|
+
}
|
|
2175
|
+
try {
|
|
2176
|
+
if (tempSlot && tempSlot.parentNode) tempSlot.parentNode.removeChild(tempSlot)
|
|
2177
|
+
else if (tempButton && tempButton.parentNode) tempButton.parentNode.removeChild(tempButton)
|
|
2178
|
+
tempButton = null
|
|
2179
|
+
tempSlot = null
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
void error
|
|
2182
|
+
}
|
|
2183
|
+
try {
|
|
2184
|
+
if (tempNewChatHost) tempNewChatHost.removeAttribute('data-dsbs-newchat')
|
|
2185
|
+
tempNewChatHost = null
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
void error
|
|
2188
|
+
}
|
|
2189
|
+
try {
|
|
2190
|
+
if (tempChatGuard !== null) clearInterval(tempChatGuard)
|
|
2191
|
+
tempChatGuard = null
|
|
2192
|
+
} catch (error) {
|
|
2193
|
+
void error
|
|
2194
|
+
}
|
|
2195
|
+
try {
|
|
2196
|
+
setFlag(html, 'data-dsbs-chat-mode', false)
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
void error
|
|
2199
|
+
}
|
|
2200
|
+
try {
|
|
2201
|
+
clearTimeout(expandTimer)
|
|
2202
|
+
clearTimeout(expandGateTimer)
|
|
2203
|
+
expandTimer = 0
|
|
2204
|
+
expandGateTimer = 0
|
|
2205
|
+
expandAllKnown = false
|
|
2206
|
+
expandRounds = 0
|
|
2207
|
+
expandCooldown = Object.create(null)
|
|
2208
|
+
html.removeAttribute('data-dsbs-expand-all')
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
void error
|
|
2211
|
+
}
|
|
2212
|
+
try {
|
|
2213
|
+
if (runtime.bar && runtime.bar.parentNode) runtime.bar.parentNode.removeChild(runtime.bar)
|
|
2214
|
+
} catch (error) {
|
|
2215
|
+
void error
|
|
2216
|
+
}
|
|
2217
|
+
try {
|
|
2218
|
+
if (window[OWNER_KEY] === owner) delete window[OWNER_KEY]
|
|
2219
|
+
} catch (error) {
|
|
2220
|
+
void error
|
|
2221
|
+
}
|
|
2222
|
+
try {
|
|
2223
|
+
if (runtime.root) runtime.root.style.removeProperty('--dsbs-pop-top')
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
void error
|
|
2226
|
+
}
|
|
2227
|
+
try {
|
|
2228
|
+
if (runtime.nav) runtime.nav.removeAttribute('data-dsbs-ext')
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
void error
|
|
2231
|
+
}
|
|
2232
|
+
try {
|
|
2233
|
+
if (runtime.nav) {
|
|
2234
|
+
runtime.nav.removeAttribute('aria-hidden')
|
|
2235
|
+
runtime.nav.removeAttribute('inert')
|
|
2236
|
+
}
|
|
2237
|
+
if (runtime.regionArea) {
|
|
2238
|
+
runtime.regionArea.removeAttribute('aria-hidden')
|
|
2239
|
+
runtime.regionArea.removeAttribute('inert')
|
|
2240
|
+
}
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
void error
|
|
2243
|
+
}
|
|
2244
|
+
var attributes = [
|
|
2245
|
+
'data-dsbs-mode',
|
|
2246
|
+
'data-dsbs-tab',
|
|
2247
|
+
'data-dsbs-fold-ext',
|
|
2248
|
+
'data-dsbs-fold-ws',
|
|
2249
|
+
'data-dsbs-pop',
|
|
2250
|
+
'data-dsbs-quick',
|
|
2251
|
+
'data-dsbs-rail',
|
|
2252
|
+
'data-dsbs-narrow',
|
|
2253
|
+
'data-dsbs-temp-solo',
|
|
2254
|
+
'data-dsbs-temp-style',
|
|
2255
|
+
]
|
|
2256
|
+
for (var i = 0; i < attributes.length; i++) {
|
|
2257
|
+
html.removeAttribute(attributes[i])
|
|
2258
|
+
}
|
|
2259
|
+
try {
|
|
2260
|
+
if (runtime.resizeObserver) runtime.resizeObserver.disconnect()
|
|
2261
|
+
if (runtime.mutationObserver) runtime.mutationObserver.disconnect()
|
|
2262
|
+
} catch (error) {
|
|
2263
|
+
void error
|
|
2264
|
+
}
|
|
2265
|
+
runtime.bar = null
|
|
2266
|
+
runtime.root = null
|
|
2267
|
+
runtime.observedRoot = null
|
|
2268
|
+
for (var j = 0; j < manualCleanups.length; j++) {
|
|
2269
|
+
try {
|
|
2270
|
+
manualCleanups[j]()
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
void error
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
manualCleanups.length = 0
|
|
2276
|
+
}
|
|
2277
|
+
return teardown
|
|
2278
|
+
}, 'sidebar-suite: teardown')
|
|
2279
|
+
owner.teardown = teardown
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
return { inject: ['locale'], apply: apply }
|
|
2283
|
+
},
|
|
2284
|
+
})
|
|
2285
|
+
|