@restregular/dsh-notepad 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -34
- package/client.js +781 -145
- package/index.js +1 -1
- package/lib/note-store.js +14 -8
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +5 -3
package/client.js
CHANGED
|
@@ -50,6 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
50
50
|
const SAVE_DELAY = 500;
|
|
51
51
|
const MAX_SEGMENTS = 200;
|
|
52
52
|
const MAX_SEGMENT_CHARS = 160000;
|
|
53
|
+
const MAX_SEGMENT_HEIGHT = 240;
|
|
53
54
|
const MAX_TITLE_CHARS = 200;
|
|
54
55
|
const MAX_IMAGES = 20;
|
|
55
56
|
const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
@@ -88,6 +89,10 @@ window.__ModuleLoader__.load({
|
|
|
88
89
|
insert: '插入到输入框',
|
|
89
90
|
insertSegment: '插入此段',
|
|
90
91
|
insertSelected: '插入选中的 {n} 段',
|
|
92
|
+
insertNewSession: '在新会话中插入',
|
|
93
|
+
insertNewSelected: '在新会话中插入选中的 {n} 段',
|
|
94
|
+
insertNewDone: '已在新会话中填入内容',
|
|
95
|
+
insertNewUnavailable: '无法新建会话,内容已复制',
|
|
91
96
|
inserted: '已插入输入框',
|
|
92
97
|
insertEmpty: '没有可插入的内容',
|
|
93
98
|
insertUnavailable: '输入框暂不可用,内容已复制',
|
|
@@ -99,6 +104,10 @@ window.__ModuleLoader__.load({
|
|
|
99
104
|
send: '发送',
|
|
100
105
|
sendSegment: '发送此段',
|
|
101
106
|
sendSelected: '发送选中的 {n} 段',
|
|
107
|
+
sendNewSession: '在新会话中发送',
|
|
108
|
+
sendNewSelected: '在新会话中发送选中的 {n} 段',
|
|
109
|
+
sendNewDone: '已在新会话中发送',
|
|
110
|
+
sendNewUnavailable: '无法新建会话,内容已复制',
|
|
102
111
|
sending: '发送中…',
|
|
103
112
|
sent: '已发送',
|
|
104
113
|
sendFailed: '发送失败',
|
|
@@ -147,6 +156,12 @@ window.__ModuleLoader__.load({
|
|
|
147
156
|
restoredToast: '已恢复',
|
|
148
157
|
restoredCount: '已恢复 {n} 段',
|
|
149
158
|
deleteSegment: '删除此段',
|
|
159
|
+
deleteActiveSegment: '删除此段(不可恢复)',
|
|
160
|
+
deleteConfirmTitle: '删除这个分段?',
|
|
161
|
+
deleteConfirmBody: '删除后无法恢复。分段里的文字和图片引用会一起移除。',
|
|
162
|
+
deleteConfirmHint: '此操作不可撤销',
|
|
163
|
+
deleteConfirmCancel: '取消',
|
|
164
|
+
deleteConfirmOk: '删除',
|
|
150
165
|
deletedToast: '已删除',
|
|
151
166
|
clearArchive: '清空归档',
|
|
152
167
|
clearedArchive: '已清空 {n} 段归档',
|
|
@@ -160,13 +175,24 @@ window.__ModuleLoader__.load({
|
|
|
160
175
|
show: '显示记事本(Ctrl+Shift+N)',
|
|
161
176
|
chars: '{n} 字符',
|
|
162
177
|
segments: '{n} 段',
|
|
163
|
-
|
|
178
|
+
hintNext: '点击查看下一条快捷键',
|
|
179
|
+
hintEnter: 'Enter 新建分段',
|
|
180
|
+
hintNewline: 'Shift+Enter 分段内换行',
|
|
181
|
+
hintInsert: 'Ctrl+Enter 插入当前分段',
|
|
182
|
+
hintToggle: 'Ctrl+Shift+N 显示 / 隐藏浮窗',
|
|
183
|
+
hintReset: 'Ctrl+Shift+R 复位浮窗',
|
|
184
|
+
hintArrow: '↑ / ↓ 切换分段',
|
|
185
|
+
hintPaste: '粘贴图片自动保存到工作区',
|
|
186
|
+
resetShortcut: '复位记事本位置和尺寸',
|
|
164
187
|
workspace: '工作区',
|
|
165
188
|
resize: '拖动调整大小',
|
|
166
189
|
uiReset: '已复位浮窗位置和尺寸',
|
|
167
190
|
addImage: '添加图片',
|
|
168
191
|
removeImage: '移除图片',
|
|
169
192
|
openImage: '打开图片',
|
|
193
|
+
imagePreview: '图片预览',
|
|
194
|
+
closePreview: '关闭预览',
|
|
195
|
+
openOriginal: '打开原图',
|
|
170
196
|
imageAdded: '已添加 {n} 张图片',
|
|
171
197
|
imageFailed: '图片添加失败',
|
|
172
198
|
imageUnsupported: '只支持 PNG / JPEG / WebP / GIF 图片',
|
|
@@ -196,6 +222,10 @@ window.__ModuleLoader__.load({
|
|
|
196
222
|
insert: 'Insert into composer',
|
|
197
223
|
insertSegment: 'Insert this segment',
|
|
198
224
|
insertSelected: 'Insert {n} selected',
|
|
225
|
+
insertNewSession: 'Insert into a new session',
|
|
226
|
+
insertNewSelected: 'Insert {n} selected into a new session',
|
|
227
|
+
insertNewDone: 'Filled into a new session',
|
|
228
|
+
insertNewUnavailable: 'Could not create a session; copied instead',
|
|
199
229
|
inserted: 'Inserted into composer',
|
|
200
230
|
insertEmpty: 'Nothing to insert',
|
|
201
231
|
insertUnavailable: 'Composer unavailable; copied instead',
|
|
@@ -207,6 +237,10 @@ window.__ModuleLoader__.load({
|
|
|
207
237
|
send: 'Send',
|
|
208
238
|
sendSegment: 'Send this segment',
|
|
209
239
|
sendSelected: 'Send {n} selected',
|
|
240
|
+
sendNewSession: 'Send into a new session',
|
|
241
|
+
sendNewSelected: 'Send {n} selected into a new session',
|
|
242
|
+
sendNewDone: 'Sent into a new session',
|
|
243
|
+
sendNewUnavailable: 'Could not create a session; copied instead',
|
|
210
244
|
sending: 'Sending…',
|
|
211
245
|
sent: 'Sent',
|
|
212
246
|
sendFailed: 'Send failed',
|
|
@@ -255,6 +289,12 @@ window.__ModuleLoader__.load({
|
|
|
255
289
|
restoredToast: 'Restored',
|
|
256
290
|
restoredCount: 'Restored {n}',
|
|
257
291
|
deleteSegment: 'Delete this segment',
|
|
292
|
+
deleteActiveSegment: 'Delete this segment (cannot be undone)',
|
|
293
|
+
deleteConfirmTitle: 'Delete this segment?',
|
|
294
|
+
deleteConfirmBody: 'This cannot be undone. The text and image references in the segment will be removed too.',
|
|
295
|
+
deleteConfirmHint: 'This action cannot be undone',
|
|
296
|
+
deleteConfirmCancel: 'Cancel',
|
|
297
|
+
deleteConfirmOk: 'Delete',
|
|
258
298
|
deletedToast: 'Deleted',
|
|
259
299
|
clearArchive: 'Clear archive',
|
|
260
300
|
clearedArchive: 'Cleared {n} archived',
|
|
@@ -268,13 +308,24 @@ window.__ModuleLoader__.load({
|
|
|
268
308
|
show: 'Show notepad (Ctrl+Shift+N)',
|
|
269
309
|
chars: '{n} chars',
|
|
270
310
|
segments: '{n} segments',
|
|
271
|
-
|
|
311
|
+
hintNext: 'Click for the next shortcut',
|
|
312
|
+
hintEnter: 'Enter creates a segment',
|
|
313
|
+
hintNewline: 'Shift+Enter adds a line break',
|
|
314
|
+
hintInsert: 'Ctrl+Enter inserts the current segment',
|
|
315
|
+
hintToggle: 'Ctrl+Shift+N shows / hides the notepad',
|
|
316
|
+
hintReset: 'Ctrl+Shift+R resets the floating window',
|
|
317
|
+
hintArrow: '↑ / ↓ switch segments',
|
|
318
|
+
hintPaste: 'Pasted images are saved in the workspace',
|
|
319
|
+
resetShortcut: 'Reset the notepad position and size',
|
|
272
320
|
workspace: 'Workspace',
|
|
273
321
|
resize: 'Drag to resize',
|
|
274
322
|
uiReset: 'Window position and size reset',
|
|
275
323
|
addImage: 'Add image',
|
|
276
324
|
removeImage: 'Remove image',
|
|
277
325
|
openImage: 'Open image',
|
|
326
|
+
imagePreview: 'Image preview',
|
|
327
|
+
closePreview: 'Close preview',
|
|
328
|
+
openOriginal: 'Open original',
|
|
278
329
|
imageAdded: 'Added {n} image(s)',
|
|
279
330
|
imageFailed: 'Could not add image',
|
|
280
331
|
imageUnsupported: 'Only PNG / JPEG / WebP / GIF images are supported',
|
|
@@ -287,6 +338,17 @@ window.__ModuleLoader__.load({
|
|
|
287
338
|
},
|
|
288
339
|
};
|
|
289
340
|
|
|
341
|
+
/** Rotating shortcut tips shown one at a time in the footer. */
|
|
342
|
+
const HINT_KEYS = [
|
|
343
|
+
'hintEnter',
|
|
344
|
+
'hintNewline',
|
|
345
|
+
'hintInsert',
|
|
346
|
+
'hintToggle',
|
|
347
|
+
'hintReset',
|
|
348
|
+
'hintArrow',
|
|
349
|
+
'hintPaste',
|
|
350
|
+
];
|
|
351
|
+
|
|
290
352
|
function preferredLanguage() {
|
|
291
353
|
try {
|
|
292
354
|
const tag = String((typeof navigator !== 'undefined' && navigator.language) || 'zh').toLowerCase();
|
|
@@ -576,6 +638,60 @@ window.__ModuleLoader__.load({
|
|
|
576
638
|
);
|
|
577
639
|
}
|
|
578
640
|
|
|
641
|
+
/** Two panels, the second with a plus: start a new session. */
|
|
642
|
+
function NewSessionIcon({ size = 16 }) {
|
|
643
|
+
return h(
|
|
644
|
+
Svg,
|
|
645
|
+
{ size, viewBox: '0 0 16 16' },
|
|
646
|
+
h('rect', {
|
|
647
|
+
x: 1.9, y: 3.2, width: 5.1, height: 9.4, rx: 1.4,
|
|
648
|
+
stroke: 'currentColor', strokeWidth: 1.25,
|
|
649
|
+
}),
|
|
650
|
+
h('rect', {
|
|
651
|
+
x: 8.7, y: 3.2, width: 5.4, height: 9.4, rx: 1.4,
|
|
652
|
+
stroke: 'currentColor', strokeWidth: 1.25,
|
|
653
|
+
}),
|
|
654
|
+
h('path', {
|
|
655
|
+
d: 'M11.4 6.1v3.8M9.5 8h3.8',
|
|
656
|
+
stroke: 'currentColor', strokeWidth: 1.25, strokeLinecap: 'round',
|
|
657
|
+
}),
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/** Circular arrow: reset the floating window geometry. */
|
|
662
|
+
function ResetIcon({ size = 16 }) {
|
|
663
|
+
return h(
|
|
664
|
+
Svg,
|
|
665
|
+
{ size, viewBox: '0 0 16 16' },
|
|
666
|
+
h('path', {
|
|
667
|
+
d: 'M13 8a5 5 0 1 1-1.6-3.7',
|
|
668
|
+
stroke: 'currentColor', strokeWidth: 1.35, strokeLinecap: 'round',
|
|
669
|
+
}),
|
|
670
|
+
h('path', {
|
|
671
|
+
d: 'M13.3 2.7v3.4H9.9',
|
|
672
|
+
stroke: 'currentColor', strokeWidth: 1.35,
|
|
673
|
+
strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
674
|
+
}),
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Arrow leaving a frame: open the original image. */
|
|
679
|
+
function OpenIcon({ size = 16 }) {
|
|
680
|
+
return h(
|
|
681
|
+
Svg,
|
|
682
|
+
{ size, viewBox: '0 0 16 16' },
|
|
683
|
+
h('path', {
|
|
684
|
+
d: 'M9.4 3.2h3.4v3.4M12.8 3.2 7.6 8.4',
|
|
685
|
+
stroke: 'currentColor', strokeWidth: 1.3,
|
|
686
|
+
strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
687
|
+
}),
|
|
688
|
+
h('path', {
|
|
689
|
+
d: 'M12.4 9.4v2.3c0 .7-.6 1.3-1.3 1.3H4.6c-.7 0-1.3-.6-1.3-1.3V5.2c0-.7.6-1.3 1.3-1.3h2.3',
|
|
690
|
+
stroke: 'currentColor', strokeWidth: 1.3, strokeLinecap: 'round',
|
|
691
|
+
}),
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
579
695
|
/** Two arrows converging: merge selected segments into one. */
|
|
580
696
|
function MergeIcon({ size = 16 }) {
|
|
581
697
|
return h(
|
|
@@ -636,7 +752,7 @@ window.__ModuleLoader__.load({
|
|
|
636
752
|
.dsh-np-btn[data-accent=true]{background:var(--np-accent);color:var(--np-accent-fg)}
|
|
637
753
|
.dsh-np-btn[data-accent=true]:hover:not(:disabled){background:color-mix(in srgb,var(--np-accent) 88%,#000);color:var(--np-accent-fg)}
|
|
638
754
|
.dsh-np-btn:disabled{opacity:.38;cursor:default;background:transparent;color:var(--dsw-alias-label-tertiary,#6b7280)}
|
|
639
|
-
.dsh-np-btn:focus-visible,.dsh-np-segbtn:focus-visible,.dsh-np-link:focus-visible,.dsh-np-empty-add:focus-visible,.dsh-np-toast-action:focus-visible,.dsh-np-selectall input:focus-visible,.dsh-np-check input:focus-visible,.dsh-np-title-add:focus-visible,.dsh-np-restore:focus-visible,.dsh-np-img-open:focus-visible,.dsh-np-img-remove:focus-visible{outline:2px solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#4176e6));outline-offset:-2px}
|
|
755
|
+
.dsh-np-btn:focus-visible,.dsh-np-segbtn:focus-visible,.dsh-np-link:focus-visible,.dsh-np-empty-add:focus-visible,.dsh-np-toast-action:focus-visible,.dsh-np-selectall input:focus-visible,.dsh-np-check input:focus-visible,.dsh-np-title-add:focus-visible,.dsh-np-preview-btn:focus-visible,.dsh-np-confirm-btn:focus-visible,button.dsh-np-hint:focus-visible,.dsh-np-restore:focus-visible,.dsh-np-img-open:focus-visible,.dsh-np-img-remove:focus-visible{outline:2px solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#4176e6));outline-offset:-2px}
|
|
640
756
|
.dsh-np-body{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;padding:8px 10px 0;opacity:1;transition:opacity .16s ease,visibility 0s linear 0s}
|
|
641
757
|
.dsh-np-card[data-minimized=true] .dsh-np-body{opacity:0;visibility:hidden;pointer-events:none;transition:opacity .16s ease,visibility 0s linear .16s}
|
|
642
758
|
.dsh-np-tools{display:flex;align-items:center;flex-wrap:wrap;gap:4px;flex:none;padding:0 2px 7px;border-bottom:1px solid var(--dsw-alias-border-l1,rgb(15 23 42/.08))}
|
|
@@ -669,7 +785,14 @@ window.__ModuleLoader__.load({
|
|
|
669
785
|
.dsh-np-segbtn[data-ai=true]{color:var(--np-accent);opacity:1}
|
|
670
786
|
.dsh-np-segbtn[data-danger=true]:hover:not(:disabled){color:var(--dsw-alias-state-error-primary,#e25555);background:var(--dsw-alias-interactive-bg-hover-danger,rgb(236 19 19/.05))}
|
|
671
787
|
.dsh-np-segbtn:disabled{opacity:.3;cursor:default}
|
|
672
|
-
.dsh-np-
|
|
788
|
+
.dsh-np-actiongroup{display:inline-flex;align-items:center;flex:none;padding:1px;border:1px solid transparent;border-radius:8px;transition:border-color .12s ease .12s,background .12s ease .12s}
|
|
789
|
+
.dsh-np-actiongroup:hover{border-color:var(--dsw-alias-border-l2,rgb(15 23 42/.14));background:color-mix(in srgb,var(--np-surface-2) 58%,transparent);transition-delay:.18s}
|
|
790
|
+
.dsh-np-actiongroup:focus-within{border-color:var(--dsw-alias-border-l2,rgb(15 23 42/.14));background:color-mix(in srgb,var(--np-surface-2) 58%,transparent);transition-delay:0s}
|
|
791
|
+
.dsh-np-group-extra{width:0!important;min-width:0;padding:0;border:0;opacity:0;visibility:hidden;pointer-events:none;overflow:hidden;transition:width .14s ease .12s,opacity .12s ease .12s,visibility 0s linear .26s}
|
|
792
|
+
.dsh-np-actiongroup:hover .dsh-np-group-extra{width:24px!important;opacity:1;visibility:visible;pointer-events:auto;transition:width .14s ease .2s,opacity .12s ease .2s,visibility 0s linear .2s}
|
|
793
|
+
.dsh-np-actiongroup:focus-within .dsh-np-group-extra{width:24px!important;opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s}
|
|
794
|
+
.dsh-np-actiongroup[data-size=bar]:hover .dsh-np-group-extra,.dsh-np-actiongroup[data-size=bar]:focus-within .dsh-np-group-extra{width:27px!important}
|
|
795
|
+
.dsh-np-seg-text{display:block;width:100%;box-sizing:border-box;padding:2px 10px 8px;border:0;outline:0;resize:none;background:transparent;color:inherit;font-family:${FONT_STACK};font-size:13px;line-height:1.6;min-height:26px;max-height:240px;overflow-y:hidden;white-space:pre-wrap;overflow-wrap:anywhere;tab-size:2}
|
|
673
796
|
.dsh-np-seg-text::placeholder{color:var(--dsw-alias-label-caption,#9aa3b2)}
|
|
674
797
|
.dsh-np-seg-text:disabled{opacity:.6}
|
|
675
798
|
.dsh-np-file{display:none}
|
|
@@ -706,6 +829,24 @@ window.__ModuleLoader__.load({
|
|
|
706
829
|
.dsh-np-retry{flex:none;padding:0;border:0;background:none;color:var(--dsw-alias-link,#4176e6);font:inherit;font-size:10.5px;cursor:pointer;text-decoration:underline}
|
|
707
830
|
.dsh-np-count{flex:none;font-variant-numeric:tabular-nums}
|
|
708
831
|
.dsh-np-hint{margin-left:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:.85;font-size:10px}
|
|
832
|
+
button.dsh-np-hint{padding:0;border:0;background:none;color:inherit;font:inherit;font-size:10px;cursor:pointer;text-align:right}
|
|
833
|
+
button.dsh-np-hint:hover{opacity:1;color:var(--dsw-alias-label-secondary,#4b5563);text-decoration:underline}
|
|
834
|
+
.dsh-np-preview{position:absolute;inset:0;z-index:20;display:flex;align-items:center;justify-content:center;padding:44px 20px 28px;background:rgb(8 12 20/.74);backdrop-filter:blur(3px);animation:dsh-np-fade .14s ease-out}
|
|
835
|
+
.dsh-np-preview-img{display:block;max-width:100%;max-height:100%;object-fit:contain;border-radius:8px;background:#fff;box-shadow:0 16px 40px rgb(0 0 0/.35)}
|
|
836
|
+
.dsh-np-preview-bar{position:absolute;top:8px;right:8px;display:flex;align-items:center;gap:4px}
|
|
837
|
+
.dsh-np-preview-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid rgb(255 255 255/.18);border-radius:7px;background:rgb(15 23 42/.55);color:#f8fafc;cursor:pointer;backdrop-filter:blur(4px)}
|
|
838
|
+
.dsh-np-preview-btn:hover{background:rgb(15 23 42/.82)}
|
|
839
|
+
.dsh-np-preview-name{position:absolute;left:12px;bottom:8px;max-width:calc(100% - 24px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#e5e7eb;font-size:11px}
|
|
840
|
+
.dsh-np-confirm{position:absolute;inset:0;z-index:21;display:flex;align-items:center;justify-content:center;padding:16px;background:rgb(8 12 20/.55);backdrop-filter:blur(2px);animation:dsh-np-fade .14s ease-out}
|
|
841
|
+
.dsh-np-confirm-card{width:min(100%,300px);padding:14px;border:1px solid var(--dsw-alias-border-l2,rgb(15 23 42/.14));border-radius:12px;background:var(--np-surface);box-shadow:var(--dsw-elevation-prominent,0 20px 50px rgb(15 23 42/.22))}
|
|
842
|
+
.dsh-np-confirm-title{font-size:13px;font-weight:650;color:var(--dsw-alias-label-primary,#1f2430)}
|
|
843
|
+
.dsh-np-confirm-body{margin-top:6px;color:var(--dsw-alias-label-secondary,#4b5563);font-size:12px;line-height:1.55}
|
|
844
|
+
.dsh-np-confirm-hint{margin-top:8px;color:var(--dsw-alias-state-error-primary,#e25555);font-size:11px}
|
|
845
|
+
.dsh-np-confirm-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:12px}
|
|
846
|
+
.dsh-np-confirm-btn{height:28px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2,rgb(15 23 42/.14));border-radius:7px;background:var(--np-surface-2);color:var(--dsw-alias-label-secondary,#4b5563);font:inherit;font-size:12px;cursor:pointer}
|
|
847
|
+
.dsh-np-confirm-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgb(38 49 72/.06));color:var(--dsw-alias-label-primary,#1f2430)}
|
|
848
|
+
.dsh-np-confirm-btn[data-danger=true]{border-color:transparent;background:var(--dsw-alias-state-error-primary,#e25555);color:#fff}
|
|
849
|
+
.dsh-np-confirm-btn[data-danger=true]:hover{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#e25555) 86%,#000);color:#fff}
|
|
709
850
|
.dsh-np-toast{position:absolute;left:50%;bottom:38px;transform:translateX(-50%);z-index:2;display:flex;align-items:center;gap:8px;max-width:calc(100% - 32px);padding:5px 10px;border-radius:8px;background:var(--np-surface-2);border:1px solid var(--dsw-alias-border-l2,rgb(15 23 42/.12));box-shadow:var(--dsw-elevation-lv2,0 8px 24px rgb(15 23 42/.18));color:var(--dsw-alias-label-primary,#1f2430);font-size:11.5px;white-space:nowrap;overflow:hidden;animation:dsh-np-rise .16s ease-out}
|
|
710
851
|
.dsh-np-toast-text{overflow:hidden;text-overflow:ellipsis}
|
|
711
852
|
.dsh-np-toast-action{flex:none;padding:0;border:0;background:none;color:var(--dsw-alias-link,#4176e6);font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;text-decoration:underline}
|
|
@@ -720,6 +861,7 @@ window.__ModuleLoader__.load({
|
|
|
720
861
|
[data-row-key^="workspace:"][data-np-unsent]{position:relative}
|
|
721
862
|
[data-row-key^="workspace:"][data-np-unsent]::after{content:attr(data-np-unsent);position:absolute;top:-4px;left:1px;z-index:2;min-width:15px;height:15px;padding:0 4px;box-sizing:border-box;border-radius:8px;background:var(--dsw-alias-state-business-primary,#4176e6);color:#fff;font-size:9.5px;font-weight:700;line-height:15px;text-align:center;font-variant-numeric:tabular-nums;pointer-events:none;box-shadow:0 0 0 1.5px var(--dsw-specific-sidebar-fill,var(--dsw-alias-bg-layer-1,#fff))}
|
|
722
863
|
@keyframes dsh-np-pulse{0%,100%{opacity:1}50%{opacity:.35}}
|
|
864
|
+
@keyframes dsh-np-fade{from{opacity:0}to{opacity:1}}
|
|
723
865
|
@keyframes dsh-np-rise{from{opacity:0;transform:translate(-50%,4px)}to{opacity:1;transform:translate(-50%,0)}}
|
|
724
866
|
@keyframes dsh-np-item-in{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}}
|
|
725
867
|
@keyframes dsh-np-spin{to{transform:rotate(360deg)}}
|
|
@@ -727,7 +869,7 @@ body[data-ds-dark-theme] .dsh-np-card{--np-accent-fg:#0f1115;box-shadow:0 18px 4
|
|
|
727
869
|
body[data-ds-dark-theme] .dsh-np-seg[data-selected=true]{background:color-mix(in srgb,var(--np-accent) 14%,var(--dsw-alias-bg-layer-2,#2c2c2e))}
|
|
728
870
|
body[data-ds-dark-theme] .dsh-np-btn[data-accent=true]:hover:not(:disabled){background:color-mix(in srgb,var(--np-accent) 84%,#fff)}
|
|
729
871
|
body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
730
|
-
@media (prefers-reduced-motion:reduce){.dsh-np-card,.dsh-np-body,.dsh-np-archive,.dsh-np-restore,.dsh-np-seg,.dsh-np-toast{transition:none!important;animation:none!important}.dsh-np-dot[data-state=saving]{animation:none}.dsh-np-img-spinner,.dsh-np-mini-spinner{animation:none}}`;
|
|
872
|
+
@media (prefers-reduced-motion:reduce){.dsh-np-card,.dsh-np-body,.dsh-np-archive,.dsh-np-restore,.dsh-np-seg,.dsh-np-toast,.dsh-np-actiongroup,.dsh-np-group-extra,.dsh-np-preview,.dsh-np-confirm{transition:none!important;animation:none!important}.dsh-np-dot[data-state=saving]{animation:none}.dsh-np-img-spinner,.dsh-np-mini-spinner{animation:none}}`;
|
|
731
873
|
|
|
732
874
|
// ------------------------------------------------------------------
|
|
733
875
|
// Helpers
|
|
@@ -900,17 +1042,43 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
900
1042
|
return count;
|
|
901
1043
|
}
|
|
902
1044
|
|
|
903
|
-
/**
|
|
904
|
-
function
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1045
|
+
/** A segment with no title, text, or images can be deleted without a prompt. */
|
|
1046
|
+
function isEmptySegment(segment) {
|
|
1047
|
+
if (segment === null || segment === undefined) return false;
|
|
1048
|
+
const title = typeof segment.title === 'string' ? segment.title.trim() : '';
|
|
1049
|
+
const text = typeof segment.text === 'string' ? segment.text.trim() : '';
|
|
1050
|
+
const images = Array.isArray(segment.images) ? segment.images : [];
|
|
1051
|
+
return title === '' && text === '' && images.length === 0;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** Merge image references in order, dropping duplicates and respecting the cap. */
|
|
1055
|
+
function collectImages(segments) {
|
|
1056
|
+
const seen = new Set();
|
|
1057
|
+
const images = [];
|
|
1058
|
+
for (const segment of segments) {
|
|
1059
|
+
for (const image of Array.isArray(segment?.images) ? segment.images : []) {
|
|
1060
|
+
if (seen.has(image.path) || images.length >= MAX_IMAGES) continue;
|
|
1061
|
+
seen.add(image.path);
|
|
1062
|
+
images.push(image);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return images;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Decode a GET /note payload. Version 4 (0.4–0.6) stored newest-first;
|
|
1070
|
+
* version 5 stores oldest-first, so a v4 list is reversed once for display.
|
|
1071
|
+
*/
|
|
1072
|
+
function readServerSegments(data) {
|
|
1073
|
+
let value = normalizeServerSegments(data);
|
|
1074
|
+
if (data?.version === 4) value = value.slice().reverse();
|
|
1075
|
+
return value;
|
|
909
1076
|
}
|
|
910
1077
|
|
|
911
1078
|
/**
|
|
912
1079
|
* Adopt server content without moving cards the user is looking at.
|
|
913
|
-
* Known ids keep the local order; cards created elsewhere land
|
|
1080
|
+
* Known ids keep the local order; cards created elsewhere land at the
|
|
1081
|
+
* bottom, matching the oldest-to-newest display direction.
|
|
914
1082
|
*/
|
|
915
1083
|
function mergeServerOrder(current, incoming) {
|
|
916
1084
|
const byId = new Map();
|
|
@@ -922,7 +1090,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
922
1090
|
}
|
|
923
1091
|
const known = new Set(kept.map((segment) => segment.id));
|
|
924
1092
|
const added = incoming.filter((segment) => !known.has(segment.id));
|
|
925
|
-
return [...
|
|
1093
|
+
return [...kept, ...added];
|
|
926
1094
|
}
|
|
927
1095
|
|
|
928
1096
|
function isAbsolutePath(value) {
|
|
@@ -1049,7 +1217,11 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1049
1217
|
element.style.height = 'auto';
|
|
1050
1218
|
const height = Number(element.scrollHeight);
|
|
1051
1219
|
if (Number.isFinite(height) && height > 0) {
|
|
1052
|
-
|
|
1220
|
+
const capped = height > MAX_SEGMENT_HEIGHT;
|
|
1221
|
+
element.style.height = `${Math.min(MAX_SEGMENT_HEIGHT, Math.max(26, height))}px`;
|
|
1222
|
+
// Keep the native scrollbar hidden until the card really reaches
|
|
1223
|
+
// its limit; otherwise CSS overflow:auto can reserve a gutter early.
|
|
1224
|
+
element.style.overflowY = capped ? 'auto' : 'hidden';
|
|
1053
1225
|
}
|
|
1054
1226
|
} catch {
|
|
1055
1227
|
/* A non-layout shim (tests) may not expose scrollHeight. */
|
|
@@ -1252,6 +1424,38 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1252
1424
|
}
|
|
1253
1425
|
}
|
|
1254
1426
|
|
|
1427
|
+
/** Mounted notepads register here so a host shortcut can reset them. */
|
|
1428
|
+
const resetHandlers = new Set();
|
|
1429
|
+
|
|
1430
|
+
/** Whether the host shortcut service already owns Ctrl/Cmd+Shift+0. */
|
|
1431
|
+
let resetShortcutHandledByHost = false;
|
|
1432
|
+
|
|
1433
|
+
/** Ask every mounted notepad to reset its position and size. */
|
|
1434
|
+
function requestUiReset() {
|
|
1435
|
+
let handled = false;
|
|
1436
|
+
for (const handler of resetHandlers) {
|
|
1437
|
+
try {
|
|
1438
|
+
handler();
|
|
1439
|
+
handled = true;
|
|
1440
|
+
} catch {
|
|
1441
|
+
/* One failing overlay must not block the others. */
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
return handled;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/** Read the General Settings "send behavior while busy" preference. */
|
|
1448
|
+
function busyEnterPreference(ctx) {
|
|
1449
|
+
try {
|
|
1450
|
+
const forms = ctx.get('configForms') ?? ctx.configForms;
|
|
1451
|
+
const form = typeof forms?.get === 'function' ? forms.get('ui-conversation') : undefined;
|
|
1452
|
+
const value = form?.getSnapshot?.()?.value;
|
|
1453
|
+
return value?.busyEnter === 'steer' ? 'steer' : 'queue';
|
|
1454
|
+
} catch {
|
|
1455
|
+
return 'queue';
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1255
1459
|
// ------------------------------------------------------------------
|
|
1256
1460
|
// Overlay
|
|
1257
1461
|
// ------------------------------------------------------------------
|
|
@@ -1280,6 +1484,9 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1280
1484
|
const [segmentUndo, setSegmentUndo] = useState(() => new Map());
|
|
1281
1485
|
const [batchUndo, setBatchUndo] = useState(null);
|
|
1282
1486
|
const [titleEditing, setTitleEditing] = useState('');
|
|
1487
|
+
const [previewImage, setPreviewImage] = useState(null);
|
|
1488
|
+
const [deleteConfirm, setDeleteConfirm] = useState('');
|
|
1489
|
+
const [hintIndex, setHintIndex] = useState(0);
|
|
1283
1490
|
const [wsCounts, setWsCounts] = useState({});
|
|
1284
1491
|
const [currentWsId, setCurrentWsId] = useState('');
|
|
1285
1492
|
|
|
@@ -1420,6 +1627,24 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1420
1627
|
toastTimerRef.current = setTimeout(() => setToast(null), action === undefined || action === null ? 1800 : 5000);
|
|
1421
1628
|
};
|
|
1422
1629
|
|
|
1630
|
+
const resetGeometry = () => {
|
|
1631
|
+
setUi((previous) => clampUi({
|
|
1632
|
+
...previous,
|
|
1633
|
+
x: null,
|
|
1634
|
+
y: null,
|
|
1635
|
+
width: DEFAULT_W,
|
|
1636
|
+
height: DEFAULT_H,
|
|
1637
|
+
}));
|
|
1638
|
+
showToast(t('uiReset'));
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
useEffect(() => {
|
|
1642
|
+
resetHandlers.add(resetGeometry);
|
|
1643
|
+
return () => {
|
|
1644
|
+
resetHandlers.delete(resetGeometry);
|
|
1645
|
+
};
|
|
1646
|
+
}, []);
|
|
1647
|
+
|
|
1423
1648
|
const scheduleSave = () => {
|
|
1424
1649
|
clearTimer();
|
|
1425
1650
|
timerRef.current = setTimeout(() => {
|
|
@@ -1485,12 +1710,17 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1485
1710
|
setSegmentUndo(new Map());
|
|
1486
1711
|
setBatchUndoState(null);
|
|
1487
1712
|
setTitleEditing('');
|
|
1713
|
+
setPreviewImage(null);
|
|
1714
|
+
setDeleteConfirm('');
|
|
1488
1715
|
pendingScrollRef.current = null;
|
|
1489
1716
|
setStatus('idle');
|
|
1490
1717
|
setError('');
|
|
1491
1718
|
return undefined;
|
|
1492
1719
|
}
|
|
1493
|
-
if (dirtyRef.current && rootRef.current !== ''
|
|
1720
|
+
if (dirtyRef.current && rootRef.current !== '') {
|
|
1721
|
+
// Flush the old workspace/session note before this binding replaces
|
|
1722
|
+
// the in-memory segments; a same-workspace session switch must not
|
|
1723
|
+
// drop a still-debounced edit.
|
|
1494
1724
|
saveNowRef.current?.({ force: true });
|
|
1495
1725
|
}
|
|
1496
1726
|
clearTimer();
|
|
@@ -1505,6 +1735,8 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1505
1735
|
setSegmentUndo(new Map());
|
|
1506
1736
|
setBatchUndoState(null);
|
|
1507
1737
|
setTitleEditing('');
|
|
1738
|
+
setPreviewImage(null);
|
|
1739
|
+
setDeleteConfirm('');
|
|
1508
1740
|
pendingScrollRef.current = null;
|
|
1509
1741
|
setStatus('loading');
|
|
1510
1742
|
setError('');
|
|
@@ -1516,10 +1748,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1516
1748
|
fetchNote(root, binding.id)
|
|
1517
1749
|
.then((data) => {
|
|
1518
1750
|
if (cancelled || rootRef.current !== root || mySeq !== loadSeqRef.current) return;
|
|
1519
|
-
let value =
|
|
1520
|
-
// Notes written before 0.4 appended new cards at the bottom;
|
|
1521
|
-
// reverse that legacy order once so the newest card leads.
|
|
1522
|
-
if (isLegacyPayload(data)) value = value.slice().reverse();
|
|
1751
|
+
let value = readServerSegments(data);
|
|
1523
1752
|
if (value.length === 0) value = [newSegment('')];
|
|
1524
1753
|
segmentsRef.current = value;
|
|
1525
1754
|
setSegments(value);
|
|
@@ -1686,16 +1915,10 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1686
1915
|
setUi((previous) => clampUi({ ...previous, hidden: !previous.hidden }));
|
|
1687
1916
|
return;
|
|
1688
1917
|
}
|
|
1689
|
-
if (
|
|
1918
|
+
if (!resetShortcutHandledByHost
|
|
1919
|
+
&& (event.key === 'R' || event.key === 'r' || event.code === 'KeyR')) {
|
|
1690
1920
|
event.preventDefault();
|
|
1691
|
-
|
|
1692
|
-
...previous,
|
|
1693
|
-
x: null,
|
|
1694
|
-
y: null,
|
|
1695
|
-
width: DEFAULT_W,
|
|
1696
|
-
height: DEFAULT_H,
|
|
1697
|
-
}));
|
|
1698
|
-
showToast(t('uiReset'));
|
|
1921
|
+
resetGeometry();
|
|
1699
1922
|
}
|
|
1700
1923
|
};
|
|
1701
1924
|
window.addEventListener('keydown', onKeyDown, true);
|
|
@@ -1708,6 +1931,19 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1708
1931
|
};
|
|
1709
1932
|
}, []);
|
|
1710
1933
|
|
|
1934
|
+
// Close the preview / delete confirmation with Escape ----------------
|
|
1935
|
+
useEffect(() => {
|
|
1936
|
+
if (previewImage === null && deleteConfirm === '') return undefined;
|
|
1937
|
+
const onKeyDown = (event) => {
|
|
1938
|
+
if (event.key !== 'Escape') return;
|
|
1939
|
+
event.preventDefault();
|
|
1940
|
+
setPreviewImage(null);
|
|
1941
|
+
setDeleteConfirm('');
|
|
1942
|
+
};
|
|
1943
|
+
window.addEventListener('keydown', onKeyDown, true);
|
|
1944
|
+
return () => window.removeEventListener('keydown', onKeyDown, true);
|
|
1945
|
+
}, [previewImage, deleteConfirm]);
|
|
1946
|
+
|
|
1711
1947
|
// Flush on page hide / reload -----------------------------------------
|
|
1712
1948
|
useEffect(() => {
|
|
1713
1949
|
const flush = () => {
|
|
@@ -1758,8 +1994,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1758
1994
|
pendingScrollRef.current = null;
|
|
1759
1995
|
return;
|
|
1760
1996
|
}
|
|
1761
|
-
let incoming =
|
|
1762
|
-
if (isLegacyPayload(data)) incoming = incoming.slice().reverse();
|
|
1997
|
+
let incoming = readServerSegments(data);
|
|
1763
1998
|
if (incoming.length === 0) incoming = [newSegment('')];
|
|
1764
1999
|
const value = mergeServerOrder(segmentsRef.current, incoming);
|
|
1765
2000
|
segmentsRef.current = value;
|
|
@@ -1936,6 +2171,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1936
2171
|
const stillReferenced = next.some((segment) =>
|
|
1937
2172
|
(Array.isArray(segment.images) ? segment.images : []).some((image) => image.path === pathValue));
|
|
1938
2173
|
commitSegments(next);
|
|
2174
|
+
if (previewImage !== null && previewImage.path === pathValue) setPreviewImage(null);
|
|
1939
2175
|
if (!stillReferenced) deleteImageFile(pathValue);
|
|
1940
2176
|
};
|
|
1941
2177
|
|
|
@@ -1966,7 +2202,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1966
2202
|
const files = clipboardImageFiles(data);
|
|
1967
2203
|
if (files.length === 0) return;
|
|
1968
2204
|
event.preventDefault?.();
|
|
1969
|
-
event
|
|
2205
|
+
event?.stopPropagation?.();
|
|
1970
2206
|
attachImages(id, files, 'paste');
|
|
1971
2207
|
};
|
|
1972
2208
|
|
|
@@ -1975,7 +2211,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1975
2211
|
setDragSegment('');
|
|
1976
2212
|
if (files.length === 0) return;
|
|
1977
2213
|
event.preventDefault?.();
|
|
1978
|
-
event
|
|
2214
|
+
event?.stopPropagation?.();
|
|
1979
2215
|
attachImages(id, files, 'pick');
|
|
1980
2216
|
};
|
|
1981
2217
|
|
|
@@ -1995,11 +2231,15 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
1995
2231
|
};
|
|
1996
2232
|
|
|
1997
2233
|
const openImage = (image) => {
|
|
2234
|
+
setPreviewImage({ path: image.path, name: image.name });
|
|
2235
|
+
};
|
|
2236
|
+
|
|
2237
|
+
const openOriginalImage = (image) => {
|
|
1998
2238
|
const url = imageUrl(image.path, rootRef.current);
|
|
1999
2239
|
try {
|
|
2000
2240
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
2001
2241
|
} catch {
|
|
2002
|
-
/* Popups may be blocked; the
|
|
2242
|
+
/* Popups may be blocked; the in-panel preview still works. */
|
|
2003
2243
|
}
|
|
2004
2244
|
};
|
|
2005
2245
|
|
|
@@ -2009,7 +2249,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2009
2249
|
return;
|
|
2010
2250
|
}
|
|
2011
2251
|
const segment = newSegment('');
|
|
2012
|
-
commitSegments([
|
|
2252
|
+
commitSegments([...segmentsRef.current, segment], { focus: { id: segment.id, caret: 0 } });
|
|
2013
2253
|
};
|
|
2014
2254
|
|
|
2015
2255
|
const splitSegment = (id, element) => {
|
|
@@ -2021,19 +2261,13 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2021
2261
|
const nextSegment = newSegment(tail);
|
|
2022
2262
|
const updated = segmentsRef.current.map((segment) =>
|
|
2023
2263
|
(segment.id === id ? { ...segment, text: head } : segment));
|
|
2024
|
-
//
|
|
2025
|
-
//
|
|
2026
|
-
|
|
2027
|
-
const
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
for (const segment of updated) {
|
|
2032
|
-
out.push(segment);
|
|
2033
|
-
if (segment.id === id) out.push(nextSegment);
|
|
2034
|
-
}
|
|
2035
|
-
return out;
|
|
2036
|
-
})();
|
|
2264
|
+
// Cards are displayed oldest-first, so the split tail always follows
|
|
2265
|
+
// its source card (also for an empty tail at the end of the note).
|
|
2266
|
+
const next = [];
|
|
2267
|
+
for (const segment of updated) {
|
|
2268
|
+
next.push(segment);
|
|
2269
|
+
if (segment.id === id) next.push(nextSegment);
|
|
2270
|
+
}
|
|
2037
2271
|
commitSegments(next, { focus: { id: nextSegment.id, caret: 0 } });
|
|
2038
2272
|
};
|
|
2039
2273
|
|
|
@@ -2102,6 +2336,28 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2102
2336
|
showToast(t('deletedToast'), { label: t('undo'), onClick: undoLast });
|
|
2103
2337
|
};
|
|
2104
2338
|
|
|
2339
|
+
/** Irreversible delete for an active segment, gated by a confirmation. */
|
|
2340
|
+
const deleteActiveSegment = (id) => {
|
|
2341
|
+
setDeleteConfirm('');
|
|
2342
|
+
setPreviewImage(null);
|
|
2343
|
+
const target = segmentsRef.current.find((segment) => segment.id === id && segment.archived !== true);
|
|
2344
|
+
if (target === undefined) return;
|
|
2345
|
+
// This delete cannot be undone: retire any older snackbar undo so it
|
|
2346
|
+
// cannot resurrect the segment through a stale snapshot.
|
|
2347
|
+
undoRef.current = null;
|
|
2348
|
+
setToast(null);
|
|
2349
|
+
setSegmentUndo((previous) => {
|
|
2350
|
+
if (!previous.has(id)) return previous;
|
|
2351
|
+
const updated = new Map(previous);
|
|
2352
|
+
updated.delete(id);
|
|
2353
|
+
return updated;
|
|
2354
|
+
});
|
|
2355
|
+
const next = segmentsRef.current.filter((segment) => segment.id !== id);
|
|
2356
|
+
commitSegments(next);
|
|
2357
|
+
scheduleImageCleanup(target.images);
|
|
2358
|
+
showToast(t('deletedToast'));
|
|
2359
|
+
};
|
|
2360
|
+
|
|
2105
2361
|
const clearArchive = () => {
|
|
2106
2362
|
const archived = segmentsRef.current.filter((segment) => segment.archived === true);
|
|
2107
2363
|
if (archived.length === 0) return;
|
|
@@ -2129,15 +2385,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2129
2385
|
for (let index = 0; index < firstIndex; index += 1) {
|
|
2130
2386
|
if (!ids.has(segmentsRef.current[index].id)) insertAt += 1;
|
|
2131
2387
|
}
|
|
2132
|
-
const
|
|
2133
|
-
const images = [];
|
|
2134
|
-
for (const segment of chosen) {
|
|
2135
|
-
for (const image of Array.isArray(segment.images) ? segment.images : []) {
|
|
2136
|
-
if (seenPaths.has(image.path) || images.length >= MAX_IMAGES) continue;
|
|
2137
|
-
seenPaths.add(image.path);
|
|
2138
|
-
images.push(image);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2388
|
+
const images = collectImages(chosen);
|
|
2141
2389
|
const merged = {
|
|
2142
2390
|
id: newSegmentId(),
|
|
2143
2391
|
text: chosen.map((segment) => segment.text).filter((text) => text.trim() !== '').join('\n\n'),
|
|
@@ -2199,7 +2447,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2199
2447
|
const types = event?.dataTransfer?.types;
|
|
2200
2448
|
if (types !== undefined && types !== null && !Array.from(types).includes('application/x-dsh-notepad-segment')) return;
|
|
2201
2449
|
event.preventDefault?.();
|
|
2202
|
-
event
|
|
2450
|
+
event?.stopPropagation?.();
|
|
2203
2451
|
const rect = typeof event.currentTarget?.getBoundingClientRect === 'function'
|
|
2204
2452
|
? event.currentTarget.getBoundingClientRect()
|
|
2205
2453
|
: null;
|
|
@@ -2224,7 +2472,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2224
2472
|
setReorderTarget(null);
|
|
2225
2473
|
if (source === '') return;
|
|
2226
2474
|
event.preventDefault?.();
|
|
2227
|
-
event
|
|
2475
|
+
event?.stopPropagation?.();
|
|
2228
2476
|
reorderSegments(source, id, target?.after === true);
|
|
2229
2477
|
};
|
|
2230
2478
|
|
|
@@ -2447,6 +2695,28 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2447
2695
|
showToast(copied ? t('insertUnavailable') : t('copyFailed'));
|
|
2448
2696
|
};
|
|
2449
2697
|
|
|
2698
|
+
/** Start a new session in this workspace with the text already in its composer. */
|
|
2699
|
+
const insertNewSession = async (list) => {
|
|
2700
|
+
const value = mergedText(list);
|
|
2701
|
+
if (value === '') {
|
|
2702
|
+
showToast(t('insertEmpty'));
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const uiWorkspace = ctx.get('uiWorkspace');
|
|
2706
|
+
if (uiWorkspace !== undefined && uiWorkspace !== null && typeof uiWorkspace.startSession === 'function') {
|
|
2707
|
+
try {
|
|
2708
|
+
if (dirtyRef.current) await saveNowRef.current?.({ force: true });
|
|
2709
|
+
uiWorkspace.startSession(undefined, { prompt: value, clearPreviousDraft: true });
|
|
2710
|
+
showToast(t('insertNewDone'));
|
|
2711
|
+
return;
|
|
2712
|
+
} catch {
|
|
2713
|
+
/* Fall through to copy. */
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
const copied = await copyText(value);
|
|
2717
|
+
showToast(copied ? t('insertNewUnavailable') : t('copyFailed'));
|
|
2718
|
+
};
|
|
2719
|
+
|
|
2450
2720
|
const replaceSegments = async (list) => {
|
|
2451
2721
|
const value = mergedText(list);
|
|
2452
2722
|
if (value === '') {
|
|
@@ -2461,55 +2731,93 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2461
2731
|
showToast(copied ? t('replaceUnavailable') : t('copyFailed'));
|
|
2462
2732
|
};
|
|
2463
2733
|
|
|
2734
|
+
/** Resolve queue vs steer exactly like the composer's Send button. */
|
|
2735
|
+
const resolveSendMode = (sessionId) => {
|
|
2736
|
+
try {
|
|
2737
|
+
const sessions = ctx.get('sessions');
|
|
2738
|
+
const row = sessions?.list?.getSnapshot?.()?.byId?.[sessionId];
|
|
2739
|
+
if (row?.running !== true) return 'queue';
|
|
2740
|
+
} catch {
|
|
2741
|
+
return 'queue';
|
|
2742
|
+
}
|
|
2743
|
+
return busyEnterPreference(ctx);
|
|
2744
|
+
};
|
|
2745
|
+
|
|
2746
|
+
/** Send through the session face with an explicit queue/steer mode. */
|
|
2747
|
+
const promptSession = async (sessionId, value, mode) => {
|
|
2748
|
+
const sessions = ctx.get('sessions');
|
|
2749
|
+
if (sessions === undefined || sessions === null || typeof sessions.binding !== 'function') return false;
|
|
2750
|
+
const liveBinding = sessions.binding(sessionId);
|
|
2751
|
+
const session = liveBinding?.session;
|
|
2752
|
+
if (session === undefined || session === null || typeof session.prompt !== 'function') return false;
|
|
2753
|
+
let echo;
|
|
2754
|
+
try {
|
|
2755
|
+
echo = typeof session.beginSubmission === 'function'
|
|
2756
|
+
? session.beginSubmission({ mode, text: value, attachments: [] })
|
|
2757
|
+
: undefined;
|
|
2758
|
+
} catch {
|
|
2759
|
+
echo = undefined;
|
|
2760
|
+
}
|
|
2761
|
+
try {
|
|
2762
|
+
const result = await session.prompt([{ type: 'text', text: value }], mode, undefined, echo?.requestId);
|
|
2763
|
+
if (result !== undefined && result !== null && result.ok === false) {
|
|
2764
|
+
const failure = new Error(result.error?.message ?? 'prompt failed');
|
|
2765
|
+
failure.code = result.error?.code;
|
|
2766
|
+
throw failure;
|
|
2767
|
+
}
|
|
2768
|
+
return true;
|
|
2769
|
+
} catch (reason) {
|
|
2770
|
+
try {
|
|
2771
|
+
echo?.abandon?.();
|
|
2772
|
+
} catch {
|
|
2773
|
+
/* ignore */
|
|
2774
|
+
}
|
|
2775
|
+
throw reason;
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
|
|
2464
2779
|
/**
|
|
2465
|
-
* Send text into
|
|
2466
|
-
*
|
|
2780
|
+
* Send text into a session. Queue prefers the documented scoped conversation
|
|
2781
|
+
* service; steer and its fallback go through the session face. The delivery
|
|
2782
|
+
* mode follows the General Settings "send while busy" preference.
|
|
2467
2783
|
* @returns {Promise<boolean>} whether a prompt was accepted
|
|
2468
2784
|
*/
|
|
2469
|
-
const sendToSession = async (value) => {
|
|
2470
|
-
const sessionId =
|
|
2785
|
+
const sendToSession = async (value, targetSessionId) => {
|
|
2786
|
+
const sessionId = typeof targetSessionId === 'string' && targetSessionId !== ''
|
|
2787
|
+
? targetSessionId
|
|
2788
|
+
: (rootSessionRef.current !== '' ? rootSessionRef.current : bindingRef.current?.id);
|
|
2471
2789
|
if (typeof sessionId !== 'string' || sessionId === '') return false;
|
|
2472
2790
|
const sessions = ctx.get('sessions');
|
|
2473
2791
|
if (sessions === undefined || sessions === null) return false;
|
|
2792
|
+
const mode = resolveSendMode(sessionId);
|
|
2793
|
+
let scoped;
|
|
2474
2794
|
if (typeof sessions.scope === 'function') {
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
if (conversation !== undefined && conversation !== null && typeof conversation.send === 'function') {
|
|
2480
|
-
await conversation.send(value);
|
|
2481
|
-
return true;
|
|
2795
|
+
try {
|
|
2796
|
+
scoped = sessions.scope(sessionId);
|
|
2797
|
+
} catch {
|
|
2798
|
+
scoped = undefined;
|
|
2482
2799
|
}
|
|
2483
2800
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
if (result !== undefined && result !== null && result.ok === false) {
|
|
2499
|
-
throw new Error(result.error?.message ?? 'prompt failed');
|
|
2500
|
-
}
|
|
2801
|
+
const conversation = scoped === undefined || scoped === null
|
|
2802
|
+
? undefined
|
|
2803
|
+
: typeof scoped.get === 'function' ? scoped.get('conversation') : scoped.conversation;
|
|
2804
|
+
if (mode === 'queue' && conversation !== undefined && conversation !== null
|
|
2805
|
+
&& typeof conversation.send === 'function') {
|
|
2806
|
+
await conversation.send(value);
|
|
2807
|
+
return true;
|
|
2808
|
+
}
|
|
2809
|
+
try {
|
|
2810
|
+
return await promptSession(sessionId, value, mode);
|
|
2811
|
+
} catch (reason) {
|
|
2812
|
+
if (mode === 'steer' && reason?.code === 'session/steer-unavailable') {
|
|
2813
|
+
if (conversation !== undefined && conversation !== null && typeof conversation.send === 'function') {
|
|
2814
|
+
await conversation.send(value);
|
|
2501
2815
|
return true;
|
|
2502
|
-
} catch (reason) {
|
|
2503
|
-
try {
|
|
2504
|
-
echo?.abandon?.();
|
|
2505
|
-
} catch {
|
|
2506
|
-
/* ignore */
|
|
2507
|
-
}
|
|
2508
|
-
throw reason;
|
|
2509
2816
|
}
|
|
2817
|
+
return promptSession(sessionId, value, 'queue');
|
|
2510
2818
|
}
|
|
2819
|
+
throw reason;
|
|
2511
2820
|
}
|
|
2512
|
-
return false;
|
|
2513
2821
|
};
|
|
2514
2822
|
|
|
2515
2823
|
const sendSegments = async (list) => {
|
|
@@ -2550,8 +2858,78 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2550
2858
|
}
|
|
2551
2859
|
};
|
|
2552
2860
|
|
|
2861
|
+
/** Create an empty session in a workspace and return its id. */
|
|
2862
|
+
const createWorkspaceSession = async (cwd) => {
|
|
2863
|
+
const sessions = ctx.get('sessions');
|
|
2864
|
+
if (sessions === undefined || sessions === null || typeof sessions.create !== 'function') return null;
|
|
2865
|
+
const result = await sessions.create({ cwd });
|
|
2866
|
+
if (result === undefined || result === null) return null;
|
|
2867
|
+
if (result.ok === false) {
|
|
2868
|
+
const failure = new Error(result.error?.message ?? 'create session failed');
|
|
2869
|
+
failure.code = result.error?.code;
|
|
2870
|
+
throw failure;
|
|
2871
|
+
}
|
|
2872
|
+
const id = result.value?.sessionId ?? result.sessionId
|
|
2873
|
+
?? (typeof result === 'string' ? result : undefined);
|
|
2874
|
+
return typeof id === 'string' && id !== '' ? id : null;
|
|
2875
|
+
};
|
|
2876
|
+
|
|
2877
|
+
/** Start a fresh session, send the text there, then switch to it. */
|
|
2878
|
+
const sendNewSession = async (list) => {
|
|
2879
|
+
if (sendingRef.current) return;
|
|
2880
|
+
const value = mergedText(list);
|
|
2881
|
+
if (value === '') {
|
|
2882
|
+
showToast(t('insertEmpty'));
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const cwd = bindingRef.current?.cwd ?? rootRef.current;
|
|
2886
|
+
if (cwd === '') {
|
|
2887
|
+
showToast(t('sendNewUnavailable'));
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
sendingRef.current = true;
|
|
2891
|
+
setSending(true);
|
|
2892
|
+
try {
|
|
2893
|
+
const newSessionId = await createWorkspaceSession(cwd);
|
|
2894
|
+
if (newSessionId === null) {
|
|
2895
|
+
if (!insertValue(value)) await copyText(value);
|
|
2896
|
+
showToast(t('sendNewUnavailable'));
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
const accepted = await sendToSession(value, newSessionId);
|
|
2900
|
+
if (accepted !== true) {
|
|
2901
|
+
if (!insertValue(value)) await copyText(value);
|
|
2902
|
+
showToast(t('sendUnavailable'));
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
showToast(t('sendNewDone'));
|
|
2906
|
+
const ids = new Set(
|
|
2907
|
+
list.filter((segment) => segment.archived !== true).map((segment) => segment.id),
|
|
2908
|
+
);
|
|
2909
|
+
if (ids.size > 0 && (rootRef.current === '' || rootRef.current === cwd)) {
|
|
2910
|
+
const archivedAt = new Date().toISOString();
|
|
2911
|
+
const next = segmentsRef.current.map((segment) =>
|
|
2912
|
+
(ids.has(segment.id) ? { ...segment, archived: true, archivedAt } : segment));
|
|
2913
|
+
setSelection(new Set([...selectedRef.current].filter((id) => !ids.has(id))));
|
|
2914
|
+
commitSegments(next);
|
|
2915
|
+
await saveNowRef.current?.({ force: true });
|
|
2916
|
+
}
|
|
2917
|
+
const uiWorkspace = ctx.get('uiWorkspace');
|
|
2918
|
+
if (uiWorkspace !== undefined && uiWorkspace !== null && typeof uiWorkspace.openSession === 'function') {
|
|
2919
|
+
uiWorkspace.openSession(newSessionId);
|
|
2920
|
+
}
|
|
2921
|
+
} catch (reason) {
|
|
2922
|
+
showToast(`${t('sendFailed')}: ${String(reason?.message ?? reason)}`);
|
|
2923
|
+
} finally {
|
|
2924
|
+
sendingRef.current = false;
|
|
2925
|
+
setSending(false);
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
|
|
2553
2929
|
const copyAll = async () => {
|
|
2554
|
-
|
|
2930
|
+
// Copy only active work: archived segments were already sent or filed
|
|
2931
|
+
// away and must not leak back into a copy-all.
|
|
2932
|
+
const value = mergedText(segmentsRef.current.filter((segment) => segment.archived !== true));
|
|
2555
2933
|
if (value === '') {
|
|
2556
2934
|
showToast(t('insertEmpty'));
|
|
2557
2935
|
return;
|
|
@@ -2613,6 +2991,43 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2613
2991
|
return true;
|
|
2614
2992
|
};
|
|
2615
2993
|
|
|
2994
|
+
/** Remove a content-empty segment and keep a nearby typing surface. */
|
|
2995
|
+
const deleteEmptySegment = (id, previous, next) => {
|
|
2996
|
+
const remaining = segmentsRef.current.filter((segment) => segment.id !== id);
|
|
2997
|
+
const activeRemaining = remaining.filter((segment) => segment.archived !== true);
|
|
2998
|
+
if (activeRemaining.length === 0) {
|
|
2999
|
+
const fresh = newSegment('');
|
|
3000
|
+
commitSegments([fresh], { focus: { id: fresh.id, caret: 0 } });
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
const focus = previous ?? next;
|
|
3004
|
+
if (focus === undefined) {
|
|
3005
|
+
commitSegments(remaining);
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
const caret = previous !== undefined ? previous.text.length : 0;
|
|
3009
|
+
commitSegments(remaining, { focus: { id: focus.id, caret } });
|
|
3010
|
+
};
|
|
3011
|
+
|
|
3012
|
+
/** Backspace at position 0 joins this segment onto the previous card. */
|
|
3013
|
+
const mergeWithPrevious = (previous, current) => {
|
|
3014
|
+
const title = [previous.title, current.title]
|
|
3015
|
+
.map((value) => (typeof value === 'string' ? value.trim() : ''))
|
|
3016
|
+
.filter((value) => value !== '')
|
|
3017
|
+
.join(' · ')
|
|
3018
|
+
.slice(0, MAX_TITLE_CHARS);
|
|
3019
|
+
const merged = {
|
|
3020
|
+
...previous,
|
|
3021
|
+
text: `${previous.text}${current.text}`,
|
|
3022
|
+
title,
|
|
3023
|
+
images: collectImages([previous, current]),
|
|
3024
|
+
};
|
|
3025
|
+
const next = segmentsRef.current
|
|
3026
|
+
.filter((segment) => segment.id !== current.id)
|
|
3027
|
+
.map((segment) => (segment.id === previous.id ? merged : segment));
|
|
3028
|
+
commitSegments(next, { focus: { id: previous.id, caret: previous.text.length } });
|
|
3029
|
+
};
|
|
3030
|
+
|
|
2616
3031
|
const onSegmentKeyDown = (event, id) => {
|
|
2617
3032
|
if (event.isComposing === true || event.nativeEvent?.isComposing === true) return;
|
|
2618
3033
|
const chord = event.ctrlKey || event.metaKey;
|
|
@@ -2649,6 +3064,27 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2649
3064
|
}
|
|
2650
3065
|
return;
|
|
2651
3066
|
}
|
|
3067
|
+
if (event.key === 'Backspace') {
|
|
3068
|
+
if (event.altKey || event.shiftKey || chord) return;
|
|
3069
|
+
const element = event.currentTarget ?? event.target;
|
|
3070
|
+
const start = typeof element?.selectionStart === 'number' ? element.selectionStart : null;
|
|
3071
|
+
const end = typeof element?.selectionEnd === 'number' ? element.selectionEnd : start;
|
|
3072
|
+
if (start !== 0 || end !== 0) return;
|
|
3073
|
+
const active = segmentsRef.current.filter((segment) => segment.archived !== true);
|
|
3074
|
+
const position = active.findIndex((segment) => segment.id === id);
|
|
3075
|
+
const current = position < 0 ? undefined : active[position];
|
|
3076
|
+
if (current === undefined) return;
|
|
3077
|
+
const previous = position > 0 ? active[position - 1] : undefined;
|
|
3078
|
+
if (isEmptySegment(current)) {
|
|
3079
|
+
event.preventDefault?.();
|
|
3080
|
+
deleteEmptySegment(current.id, previous, active[position + 1]);
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
if (previous === undefined) return;
|
|
3084
|
+
event.preventDefault?.();
|
|
3085
|
+
mergeWithPrevious(previous, current);
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
2652
3088
|
if (event.key !== 'Enter' || event.shiftKey || event.altKey || chord) return;
|
|
2653
3089
|
event.preventDefault();
|
|
2654
3090
|
splitSegment(id, event.currentTarget ?? event.target);
|
|
@@ -2688,7 +3124,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2688
3124
|
if (ui.minimized) return;
|
|
2689
3125
|
if (event.button !== undefined && event.button !== 0) return;
|
|
2690
3126
|
event.preventDefault?.();
|
|
2691
|
-
event
|
|
3127
|
+
event?.stopPropagation?.();
|
|
2692
3128
|
const start = { px: event.clientX, py: event.clientY, width: ui.width, height: ui.height };
|
|
2693
3129
|
resizeRef.current = start;
|
|
2694
3130
|
setResizing(true);
|
|
@@ -2734,6 +3170,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2734
3170
|
const allSelected = activeSegments.length > 0 && selectedCount === activeSegments.length;
|
|
2735
3171
|
const unsentCount = countUnsent(segments);
|
|
2736
3172
|
const totalChars = segments.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
3173
|
+
const activeChars = activeSegments.reduce((sum, segment) => sum + segment.text.length, 0);
|
|
2737
3174
|
const displayWidth = ui.width;
|
|
2738
3175
|
const displayHeight = ui.minimized ? HEAD_H : ui.height;
|
|
2739
3176
|
const fallbackX = Math.max(EDGE_GAP, ((typeof window !== 'undefined' && window.innerWidth) || 1280) - displayWidth - 24);
|
|
@@ -2761,6 +3198,17 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2761
3198
|
h('span', { className: 'dsh-np-title' }, t('title')),
|
|
2762
3199
|
h('span', { className: 'dsh-np-workspace', title: binding.cwd }, workspaceName(binding.cwd)),
|
|
2763
3200
|
h('span', { className: 'dsh-np-spacer' }),
|
|
3201
|
+
h(
|
|
3202
|
+
'button',
|
|
3203
|
+
{
|
|
3204
|
+
type: 'button',
|
|
3205
|
+
className: 'dsh-np-btn',
|
|
3206
|
+
title: `${t('resetShortcut')} · Ctrl+Shift+R`,
|
|
3207
|
+
'aria-label': t('resetShortcut'),
|
|
3208
|
+
onClick: resetGeometry,
|
|
3209
|
+
},
|
|
3210
|
+
h(ResetIcon, { size: 15 }),
|
|
3211
|
+
),
|
|
2764
3212
|
h(
|
|
2765
3213
|
'button',
|
|
2766
3214
|
{
|
|
@@ -2768,7 +3216,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2768
3216
|
className: 'dsh-np-btn',
|
|
2769
3217
|
title: t('copy'),
|
|
2770
3218
|
'aria-label': t('copy'),
|
|
2771
|
-
disabled:
|
|
3219
|
+
disabled: activeChars === 0,
|
|
2772
3220
|
onClick: copyAll,
|
|
2773
3221
|
},
|
|
2774
3222
|
h(CopyIcon, { size: 15 }),
|
|
@@ -2821,6 +3269,35 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2821
3269
|
),
|
|
2822
3270
|
);
|
|
2823
3271
|
|
|
3272
|
+
/** One icon that expands into current-session + new-session actions. */
|
|
3273
|
+
const actionGroup = ({ size, primary, extra, disabled = false }) => {
|
|
3274
|
+
const base = size === 'bar' ? 'dsh-np-btn' : 'dsh-np-segbtn';
|
|
3275
|
+
const primaryProps = {
|
|
3276
|
+
type: 'button',
|
|
3277
|
+
className: base,
|
|
3278
|
+
title: primary.title,
|
|
3279
|
+
'aria-label': primary.title,
|
|
3280
|
+
disabled,
|
|
3281
|
+
onClick: primary.onClick,
|
|
3282
|
+
};
|
|
3283
|
+
if (primary.accent === true) primaryProps['data-accent'] = 'true';
|
|
3284
|
+
const extraProps = {
|
|
3285
|
+
type: 'button',
|
|
3286
|
+
className: `${base} dsh-np-group-extra`,
|
|
3287
|
+
title: extra.title,
|
|
3288
|
+
'aria-label': extra.title,
|
|
3289
|
+
disabled,
|
|
3290
|
+
onClick: extra.onClick,
|
|
3291
|
+
};
|
|
3292
|
+
if (extra.danger === true) extraProps['data-danger'] = 'true';
|
|
3293
|
+
return h(
|
|
3294
|
+
'div',
|
|
3295
|
+
{ className: 'dsh-np-actiongroup', 'data-size': size },
|
|
3296
|
+
h('button', primaryProps, primary.icon),
|
|
3297
|
+
h('button', extraProps, extra.icon ?? h(NewSessionIcon, { size: 14 })),
|
|
3298
|
+
);
|
|
3299
|
+
};
|
|
3300
|
+
|
|
2824
3301
|
const tools = h(
|
|
2825
3302
|
'div',
|
|
2826
3303
|
{ className: 'dsh-np-tools' },
|
|
@@ -2838,18 +3315,19 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2838
3315
|
selectedCount > 0 ? t('selected', { n: selectedCount }) : t('selectHint')),
|
|
2839
3316
|
),
|
|
2840
3317
|
h('span', { className: 'dsh-np-spacer' }),
|
|
2841
|
-
|
|
2842
|
-
'
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
className: 'dsh-np-btn',
|
|
3318
|
+
actionGroup({
|
|
3319
|
+
size: 'bar',
|
|
3320
|
+
disabled: selectedCount === 0,
|
|
3321
|
+
primary: {
|
|
2846
3322
|
title: selectedCount > 0 ? t('insertSelected', { n: selectedCount }) : t('insert'),
|
|
2847
|
-
'aria-label': t('insert'),
|
|
2848
|
-
disabled: selectedCount === 0,
|
|
2849
3323
|
onClick: () => insertSegments(selectedActive),
|
|
3324
|
+
icon: h(InsertIcon, { size: 14 }),
|
|
2850
3325
|
},
|
|
2851
|
-
|
|
2852
|
-
|
|
3326
|
+
extra: {
|
|
3327
|
+
title: selectedCount > 0 ? t('insertNewSelected', { n: selectedCount }) : t('insertNewSession'),
|
|
3328
|
+
onClick: () => insertNewSession(selectedActive),
|
|
3329
|
+
},
|
|
3330
|
+
}),
|
|
2853
3331
|
h(
|
|
2854
3332
|
'button',
|
|
2855
3333
|
{
|
|
@@ -2862,21 +3340,22 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
2862
3340
|
},
|
|
2863
3341
|
h(ReplaceIcon, { size: 14 }),
|
|
2864
3342
|
),
|
|
2865
|
-
|
|
2866
|
-
'
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
className: 'dsh-np-btn',
|
|
2870
|
-
'data-accent': 'true',
|
|
3343
|
+
actionGroup({
|
|
3344
|
+
size: 'bar',
|
|
3345
|
+
disabled: selectedCount === 0 || sending,
|
|
3346
|
+
primary: {
|
|
2871
3347
|
title: selectedCount > 0
|
|
2872
3348
|
? (sending ? t('sending') : t('sendSelected', { n: selectedCount }))
|
|
2873
3349
|
: t('send'),
|
|
2874
|
-
'aria-label': t('send'),
|
|
2875
|
-
disabled: selectedCount === 0 || sending,
|
|
2876
3350
|
onClick: () => sendSegments(selectedActive),
|
|
3351
|
+
icon: h(SendIcon, { size: 14 }),
|
|
3352
|
+
accent: true,
|
|
2877
3353
|
},
|
|
2878
|
-
|
|
2879
|
-
|
|
3354
|
+
extra: {
|
|
3355
|
+
title: selectedCount > 0 ? t('sendNewSelected', { n: selectedCount }) : t('sendNewSession'),
|
|
3356
|
+
onClick: () => sendNewSession(selectedActive),
|
|
3357
|
+
},
|
|
3358
|
+
}),
|
|
2880
3359
|
h(
|
|
2881
3360
|
'button',
|
|
2882
3361
|
{
|
|
@@ -3109,7 +3588,6 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3109
3588
|
'aria-label': t('segmentTitle'),
|
|
3110
3589
|
onClick: () => setTitleEditing(segment.id),
|
|
3111
3590
|
},
|
|
3112
|
-
t('titlePlaceholder'),
|
|
3113
3591
|
),
|
|
3114
3592
|
h(
|
|
3115
3593
|
'button',
|
|
@@ -3122,18 +3600,19 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3122
3600
|
},
|
|
3123
3601
|
h(ImageIcon, { size: 14 }),
|
|
3124
3602
|
),
|
|
3125
|
-
|
|
3126
|
-
'
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
className: 'dsh-np-segbtn',
|
|
3603
|
+
actionGroup({
|
|
3604
|
+
size: 'card',
|
|
3605
|
+
disabled: empty,
|
|
3606
|
+
primary: {
|
|
3130
3607
|
title: t('insertSegment'),
|
|
3131
|
-
'aria-label': t('insertSegment'),
|
|
3132
|
-
disabled: empty,
|
|
3133
3608
|
onClick: () => insertSegments([segment]),
|
|
3609
|
+
icon: h(InsertIcon, { size: 14 }),
|
|
3134
3610
|
},
|
|
3135
|
-
|
|
3136
|
-
|
|
3611
|
+
extra: {
|
|
3612
|
+
title: t('insertNewSession'),
|
|
3613
|
+
onClick: () => insertNewSession([segment]),
|
|
3614
|
+
},
|
|
3615
|
+
}),
|
|
3137
3616
|
h(
|
|
3138
3617
|
'button',
|
|
3139
3618
|
{
|
|
@@ -3146,18 +3625,19 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3146
3625
|
},
|
|
3147
3626
|
h(ReplaceIcon, { size: 14 }),
|
|
3148
3627
|
),
|
|
3149
|
-
|
|
3150
|
-
'
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
className: 'dsh-np-segbtn',
|
|
3628
|
+
actionGroup({
|
|
3629
|
+
size: 'card',
|
|
3630
|
+
disabled: empty || sending,
|
|
3631
|
+
primary: {
|
|
3154
3632
|
title: t('sendSegment'),
|
|
3155
|
-
'aria-label': t('sendSegment'),
|
|
3156
|
-
disabled: empty || sending,
|
|
3157
3633
|
onClick: () => sendSegments([segment]),
|
|
3634
|
+
icon: h(SendIcon, { size: 14 }),
|
|
3158
3635
|
},
|
|
3159
|
-
|
|
3160
|
-
|
|
3636
|
+
extra: {
|
|
3637
|
+
title: t('sendNewSession'),
|
|
3638
|
+
onClick: () => sendNewSession([segment]),
|
|
3639
|
+
},
|
|
3640
|
+
}),
|
|
3161
3641
|
h(
|
|
3162
3642
|
'button',
|
|
3163
3643
|
{
|
|
@@ -3175,17 +3655,23 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3175
3655
|
? h('span', { className: 'dsh-np-mini-spinner' })
|
|
3176
3656
|
: canUndoOptimize ? h(UndoIcon, { size: 14 }) : h(SparkleIcon, { size: 14 }),
|
|
3177
3657
|
),
|
|
3178
|
-
|
|
3179
|
-
'
|
|
3180
|
-
{
|
|
3181
|
-
type: 'button',
|
|
3182
|
-
className: 'dsh-np-segbtn',
|
|
3658
|
+
actionGroup({
|
|
3659
|
+
size: 'card',
|
|
3660
|
+
primary: {
|
|
3183
3661
|
title: t('archiveSegment'),
|
|
3184
|
-
'aria-label': t('archiveSegment'),
|
|
3185
3662
|
onClick: () => archiveSegment(segment.id),
|
|
3663
|
+
icon: h(ArchiveIcon, { size: 14 }),
|
|
3186
3664
|
},
|
|
3187
|
-
|
|
3188
|
-
|
|
3665
|
+
extra: {
|
|
3666
|
+
title: t('deleteActiveSegment'),
|
|
3667
|
+
danger: true,
|
|
3668
|
+
onClick: () => {
|
|
3669
|
+
if (isEmptySegment(segment)) deleteActiveSegment(segment.id);
|
|
3670
|
+
else setDeleteConfirm(segment.id);
|
|
3671
|
+
},
|
|
3672
|
+
icon: h(TrashIcon, { size: 14 }),
|
|
3673
|
+
},
|
|
3674
|
+
}),
|
|
3189
3675
|
),
|
|
3190
3676
|
h(
|
|
3191
3677
|
'div',
|
|
@@ -3210,7 +3696,7 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3210
3696
|
className: 'dsh-np-seg-text',
|
|
3211
3697
|
rows: 1,
|
|
3212
3698
|
value: segment.text,
|
|
3213
|
-
placeholder: index ===
|
|
3699
|
+
placeholder: index === activeSegments.length - 1 ? t('placeholder') : t('segmentPlaceholder'),
|
|
3214
3700
|
spellCheck: false,
|
|
3215
3701
|
maxLength: MAX_SEGMENT_CHARS,
|
|
3216
3702
|
'aria-label': t('editor'),
|
|
@@ -3382,7 +3868,17 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3382
3868
|
h('span', { className: 'dsh-np-count' },
|
|
3383
3869
|
`${t('chars', { n: totalChars })} · ${t('segments', { n: activeSegments.length })}`
|
|
3384
3870
|
+ (totalImages > 0 ? ` · ${t('images', { n: totalImages })}` : '')),
|
|
3385
|
-
h(
|
|
3871
|
+
h(
|
|
3872
|
+
'button',
|
|
3873
|
+
{
|
|
3874
|
+
type: 'button',
|
|
3875
|
+
className: 'dsh-np-hint',
|
|
3876
|
+
title: t('hintNext'),
|
|
3877
|
+
'aria-label': t('hintNext'),
|
|
3878
|
+
onClick: () => setHintIndex((index) => (index + 1) % HINT_KEYS.length),
|
|
3879
|
+
},
|
|
3880
|
+
t(HINT_KEYS[hintIndex % HINT_KEYS.length]),
|
|
3881
|
+
),
|
|
3386
3882
|
);
|
|
3387
3883
|
|
|
3388
3884
|
const toastNode = toast === null
|
|
@@ -3422,6 +3918,107 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3422
3918
|
onPointerDown: onResizePointerDown,
|
|
3423
3919
|
});
|
|
3424
3920
|
|
|
3921
|
+
const previewNode = previewImage === null
|
|
3922
|
+
? null
|
|
3923
|
+
: h(
|
|
3924
|
+
'div',
|
|
3925
|
+
{
|
|
3926
|
+
className: 'dsh-np-preview',
|
|
3927
|
+
role: 'dialog',
|
|
3928
|
+
'aria-modal': 'true',
|
|
3929
|
+
'aria-label': t('imagePreview'),
|
|
3930
|
+
onClick: () => setPreviewImage(null),
|
|
3931
|
+
},
|
|
3932
|
+
h('img', {
|
|
3933
|
+
className: 'dsh-np-preview-img',
|
|
3934
|
+
src: imageUrl(previewImage.path, binding.cwd),
|
|
3935
|
+
alt: previewImage.name,
|
|
3936
|
+
onClick: (event) => event?.stopPropagation?.(),
|
|
3937
|
+
}),
|
|
3938
|
+
h(
|
|
3939
|
+
'div',
|
|
3940
|
+
{ className: 'dsh-np-preview-bar' },
|
|
3941
|
+
h(
|
|
3942
|
+
'button',
|
|
3943
|
+
{
|
|
3944
|
+
type: 'button',
|
|
3945
|
+
className: 'dsh-np-preview-btn',
|
|
3946
|
+
title: t('openOriginal'),
|
|
3947
|
+
'aria-label': t('openOriginal'),
|
|
3948
|
+
onClick: (event) => {
|
|
3949
|
+
event?.stopPropagation?.();
|
|
3950
|
+
openOriginalImage(previewImage);
|
|
3951
|
+
},
|
|
3952
|
+
},
|
|
3953
|
+
h(OpenIcon, { size: 15 }),
|
|
3954
|
+
),
|
|
3955
|
+
h(
|
|
3956
|
+
'button',
|
|
3957
|
+
{
|
|
3958
|
+
type: 'button',
|
|
3959
|
+
className: 'dsh-np-preview-btn',
|
|
3960
|
+
title: t('closePreview'),
|
|
3961
|
+
'aria-label': t('closePreview'),
|
|
3962
|
+
onClick: (event) => {
|
|
3963
|
+
event?.stopPropagation?.();
|
|
3964
|
+
setPreviewImage(null);
|
|
3965
|
+
},
|
|
3966
|
+
},
|
|
3967
|
+
h(CloseIcon, { size: 15 }),
|
|
3968
|
+
),
|
|
3969
|
+
),
|
|
3970
|
+
h('div', { className: 'dsh-np-preview-name' }, previewImage.name),
|
|
3971
|
+
);
|
|
3972
|
+
|
|
3973
|
+
const deleteTarget = deleteConfirm === ''
|
|
3974
|
+
? undefined
|
|
3975
|
+
: segments.find((segment) => segment.id === deleteConfirm && segment.archived !== true);
|
|
3976
|
+
const confirmNode = deleteTarget === undefined
|
|
3977
|
+
? null
|
|
3978
|
+
: h(
|
|
3979
|
+
'div',
|
|
3980
|
+
{
|
|
3981
|
+
className: 'dsh-np-confirm',
|
|
3982
|
+
role: 'alertdialog',
|
|
3983
|
+
'aria-modal': 'true',
|
|
3984
|
+
'aria-label': t('deleteConfirmTitle'),
|
|
3985
|
+
onClick: () => setDeleteConfirm(''),
|
|
3986
|
+
},
|
|
3987
|
+
h(
|
|
3988
|
+
'div',
|
|
3989
|
+
{
|
|
3990
|
+
className: 'dsh-np-confirm-card',
|
|
3991
|
+
onClick: (event) => event?.stopPropagation?.(),
|
|
3992
|
+
},
|
|
3993
|
+
h('div', { className: 'dsh-np-confirm-title' }, t('deleteConfirmTitle')),
|
|
3994
|
+
h('div', { className: 'dsh-np-confirm-body' }, t('deleteConfirmBody')),
|
|
3995
|
+
h('div', { className: 'dsh-np-confirm-hint' }, t('deleteConfirmHint')),
|
|
3996
|
+
h(
|
|
3997
|
+
'div',
|
|
3998
|
+
{ className: 'dsh-np-confirm-actions' },
|
|
3999
|
+
h(
|
|
4000
|
+
'button',
|
|
4001
|
+
{
|
|
4002
|
+
type: 'button',
|
|
4003
|
+
className: 'dsh-np-confirm-btn',
|
|
4004
|
+
onClick: () => setDeleteConfirm(''),
|
|
4005
|
+
},
|
|
4006
|
+
t('deleteConfirmCancel'),
|
|
4007
|
+
),
|
|
4008
|
+
h(
|
|
4009
|
+
'button',
|
|
4010
|
+
{
|
|
4011
|
+
type: 'button',
|
|
4012
|
+
className: 'dsh-np-confirm-btn',
|
|
4013
|
+
'data-danger': 'true',
|
|
4014
|
+
onClick: () => deleteActiveSegment(deleteTarget.id),
|
|
4015
|
+
},
|
|
4016
|
+
t('deleteConfirmOk'),
|
|
4017
|
+
),
|
|
4018
|
+
),
|
|
4019
|
+
),
|
|
4020
|
+
);
|
|
4021
|
+
|
|
3425
4022
|
const card = h(
|
|
3426
4023
|
'section',
|
|
3427
4024
|
{
|
|
@@ -3444,6 +4041,8 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3444
4041
|
},
|
|
3445
4042
|
header,
|
|
3446
4043
|
body,
|
|
4044
|
+
previewNode,
|
|
4045
|
+
confirmNode,
|
|
3447
4046
|
resizeHandle,
|
|
3448
4047
|
);
|
|
3449
4048
|
|
|
@@ -3509,6 +4108,43 @@ body[data-ds-dark-theme] .dsh-np-seg[data-archived=true]{opacity:.7}
|
|
|
3509
4108
|
};
|
|
3510
4109
|
}, 'dsh-notepad: dictionaries');
|
|
3511
4110
|
|
|
4111
|
+
// Register the geometry-reset chord with DSH's shortcut service so it
|
|
4112
|
+
// reaches the page even when the host intercepts native key bindings.
|
|
4113
|
+
const installResetShortcut = (scope) => {
|
|
4114
|
+
const service = typeof scope.get === 'function' ? scope.get('shortcuts') : undefined;
|
|
4115
|
+
if (service === undefined || service === null || typeof service.register !== 'function') return;
|
|
4116
|
+
try {
|
|
4117
|
+
scope.effect(() => service.register({
|
|
4118
|
+
id: 'notepad.resetGeometry',
|
|
4119
|
+
label: () => translate('resetShortcut'),
|
|
4120
|
+
aliases: ['reset notepad', 'reset notepad geometry'],
|
|
4121
|
+
defaults: {
|
|
4122
|
+
'desktop:macos': { code: 'KeyR', modifiers: ['primary', 'shift'] },
|
|
4123
|
+
'desktop:windows': { code: 'KeyR', modifiers: ['primary', 'shift'] },
|
|
4124
|
+
'desktop:linux': { code: 'KeyR', modifiers: ['primary', 'shift'] },
|
|
4125
|
+
'web:macos': { code: 'KeyR', modifiers: ['primary', 'shift'] },
|
|
4126
|
+
'web:windows': { code: 'KeyR', modifiers: ['primary', 'shift'] },
|
|
4127
|
+
'web:linux': { code: 'KeyR', modifiers: ['primary', 'shift'] },
|
|
4128
|
+
},
|
|
4129
|
+
regions: ['page', 'editable'],
|
|
4130
|
+
modals: [],
|
|
4131
|
+
resolve: () => ({ status: 'handled', run: requestUiReset }),
|
|
4132
|
+
}), 'dsh-notepad: reset shortcut');
|
|
4133
|
+
resetShortcutHandledByHost = true;
|
|
4134
|
+
} catch (error) {
|
|
4135
|
+
console.warn('[dsh-notepad] reset shortcut registration failed:', error?.message ?? error);
|
|
4136
|
+
}
|
|
4137
|
+
};
|
|
4138
|
+
if (ctx.get('shortcuts') !== undefined) {
|
|
4139
|
+
installResetShortcut(ctx);
|
|
4140
|
+
} else if (typeof ctx.inject === 'function') {
|
|
4141
|
+
try {
|
|
4142
|
+
ctx.inject(['shortcuts'], (scoped) => installResetShortcut(scoped));
|
|
4143
|
+
} catch {
|
|
4144
|
+
/* The raw key listener and header button remain as fallbacks. */
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
|
|
3512
4148
|
const slots = ctx.get('slots');
|
|
3513
4149
|
if (slots === undefined || typeof slots.register !== 'function' || typeof slots.inject !== 'function') {
|
|
3514
4150
|
console.warn('[dsh-notepad] slots service missing; floating notepad disabled');
|