shadow-claw 1.23.3 → 1.23.4
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/bin/assert-version-bump.mjs +60 -38
- package/bin/assert-version-bump.test.mjs +74 -11
- package/dist/public/agent.worker.js +6 -6
- package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
- package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
- package/dist/public/config-BYAuxx3j.js +1 -0
- package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-B4Ym9VMj.js} +1 -1
- package/dist/public/{connections-xd_66YVU.js → connections-BEn6JYdu.js} +1 -1
- package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-DdYlUozq.js} +1 -1
- package/dist/public/docs/architecture/storage.md +5 -2
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +5 -2
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/publishing/index.html +19 -76
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/{downloadGroupFile-BABv_1mJ.js → downloadGroupFile-Drdvg1Ev.js} +1 -1
- package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-BQ4KzUt5.js} +1 -1
- package/dist/public/{file-viewer-CCSABMsE.js → file-viewer-CcZYw-PS.js} +1 -1
- package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{git-BkjrsRXX.js → git-DEUvQTUA.js} +2 -2
- package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-0NS4XLOd.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-llGkSJXg.js} +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-6MBrIo-S.js → markdown-CyYR4App.js} +1 -1
- package/dist/public/mcp-reconnect-CSx4aMwy.js +1 -0
- package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-Dwg8NhAZ.js} +1 -1
- package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-DRfEudXk.js} +18 -18
- package/dist/public/orchestrator-o9Fj67BN.js +15 -0
- package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-CurBuKvk.js} +1 -1
- package/dist/public/{push-client-Dqq06wbB.js → push-client-Dii-vPEh.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-5IHaDDMJ.js} +2 -2
- package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-CZvz4iQz.js} +3 -3
- package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BKXFyRS3.js} +1 -1
- package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-CCLuhWdI.js} +3 -3
- package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-DV-87UUh.js} +1 -1
- package/dist/public/shadow-claw-file-viewer-Dk2dbAUd.js +577 -0
- package/dist/public/shadow-claw-files-BpfpJIuP.js +840 -0
- package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-DRA7_qck.js} +3 -3
- package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-1Xu5snuV.js} +1 -1
- package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-6Aci1BDP.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-BUjAeVXC.js} +1 -1
- package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-FQe4nCp3.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-DV2wibL3.js} +1 -1
- package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-B63S4HTU.js} +1 -1
- package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DE3jWj5K.js} +3 -3
- package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-S6DAKfYx.js} +3 -3
- package/dist/public/shadow-claw-storage-DaacA3_K.js +219 -0
- package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-IjKX042p.js} +1 -1
- package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Cy0JYmGU.js} +4 -4
- package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-znAt7HyP.js} +1 -1
- package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-Cutv9Si5.js} +2 -2
- package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BH8mPDEu.js} +1 -1
- package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/static-main-manifest.json +1 -1
- package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-BqJxaMGR.js} +1 -1
- package/package.json +1 -1
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +44 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +105 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
- package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
- package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +219 -0
- package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
- package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
- package/src/config/config.test.ts +9 -0
- package/src/config/config.ts +5 -0
- package/src/core/app-routes.test.ts +56 -0
- package/src/core/app-routes.ts +8 -1
- package/src/db/openDatabase.test.ts +10 -1
- package/src/db/openDatabase.ts +3 -1
- package/src/storage/migrateLegacyOpfs.test.ts +327 -0
- package/src/storage/migrateLegacyOpfs.ts +117 -0
- package/dist/public/config-AL77QAx4.js +0 -1
- package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
- package/dist/public/orchestrator-4su7iyWj.js +0 -15
- package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
- package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
- package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
|
@@ -251,6 +251,11 @@ export class ShadowClawFileViewer extends ShadowClawElement {
|
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
const content = root.querySelector(".file-content");
|
|
255
|
+
content?.addEventListener("click", (event: Event) => {
|
|
256
|
+
void this.handlePreviewLinkClick(event as MouseEvent);
|
|
257
|
+
});
|
|
258
|
+
|
|
254
259
|
const modal = root.querySelector(".file-modal");
|
|
255
260
|
modal?.addEventListener("cancel", (event: Event) => {
|
|
256
261
|
event.preventDefault();
|
|
@@ -706,9 +711,8 @@ export class ShadowClawFileViewer extends ShadowClawElement {
|
|
|
706
711
|
|
|
707
712
|
const current = fileViewerStore.file;
|
|
708
713
|
const filePath = current?.path || current?.name || "";
|
|
709
|
-
const routeDir =
|
|
710
|
-
orchestratorStore.activeGroupId,
|
|
711
|
-
filePath,
|
|
714
|
+
const routeDir = applyBasePath(
|
|
715
|
+
getFileRouteDirPath(orchestratorStore.activeGroupId, filePath),
|
|
712
716
|
);
|
|
713
717
|
const resolved = resolveHrefAgainstRoute(
|
|
714
718
|
payload.href,
|
|
@@ -1110,9 +1114,8 @@ export class ShadowClawFileViewer extends ShadowClawElement {
|
|
|
1110
1114
|
return html;
|
|
1111
1115
|
}
|
|
1112
1116
|
|
|
1113
|
-
const routeDir =
|
|
1114
|
-
orchestratorStore.activeGroupId,
|
|
1115
|
-
filePath,
|
|
1117
|
+
const routeDir = applyBasePath(
|
|
1118
|
+
getFileRouteDirPath(orchestratorStore.activeGroupId, filePath),
|
|
1116
1119
|
);
|
|
1117
1120
|
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
1118
1121
|
|
|
@@ -1509,9 +1512,8 @@ export class ShadowClawFileViewer extends ShadowClawElement {
|
|
|
1509
1512
|
const current = fileViewerStore.file;
|
|
1510
1513
|
const basePath = current?.path || current?.name || "";
|
|
1511
1514
|
const href = link.getAttribute("href") || "";
|
|
1512
|
-
const routeDir =
|
|
1513
|
-
orchestratorStore.activeGroupId,
|
|
1514
|
-
basePath,
|
|
1515
|
+
const routeDir = applyBasePath(
|
|
1516
|
+
getFileRouteDirPath(orchestratorStore.activeGroupId, basePath),
|
|
1515
1517
|
);
|
|
1516
1518
|
const resolved = resolveHrefAgainstRoute(
|
|
1517
1519
|
href,
|
|
@@ -361,7 +361,8 @@
|
|
|
361
361
|
|
|
362
362
|
.files__new-label,
|
|
363
363
|
.files__rename-label,
|
|
364
|
-
.files__paste-rename-label
|
|
364
|
+
.files__paste-rename-label,
|
|
365
|
+
.files__upload-conflict-rename-label {
|
|
365
366
|
color: var(--shadow-claw-text-secondary);
|
|
366
367
|
font-size: var(--shadow-claw-font-size-sm);
|
|
367
368
|
font-weight: 600;
|
|
@@ -369,7 +370,8 @@
|
|
|
369
370
|
|
|
370
371
|
.files__new-input,
|
|
371
372
|
.files__rename-input,
|
|
372
|
-
.files__paste-rename-input
|
|
373
|
+
.files__paste-rename-input,
|
|
374
|
+
.files__upload-conflict-rename-input {
|
|
373
375
|
background-color: var(--shadow-claw-bg-primary);
|
|
374
376
|
border: 0.0625rem solid var(--shadow-claw-border-color);
|
|
375
377
|
border-radius: var(--shadow-claw-radius-s);
|
|
@@ -379,7 +381,8 @@
|
|
|
379
381
|
padding: 0.375rem 0.5rem;
|
|
380
382
|
}
|
|
381
383
|
|
|
382
|
-
.files__paste-rename-container
|
|
384
|
+
.files__paste-rename-container,
|
|
385
|
+
.files__upload-conflict-rename-container {
|
|
383
386
|
display: flex;
|
|
384
387
|
flex-direction: column;
|
|
385
388
|
gap: 0.5rem;
|
|
@@ -401,7 +404,8 @@
|
|
|
401
404
|
|
|
402
405
|
.files__new-input:focus,
|
|
403
406
|
.files__rename-input:focus,
|
|
404
|
-
.files__paste-rename-input:focus
|
|
407
|
+
.files__paste-rename-input:focus,
|
|
408
|
+
.files__upload-conflict-rename-input:focus {
|
|
405
409
|
border-color: var(--shadow-claw-accent-primary);
|
|
406
410
|
box-shadow: 0 0 0 0.125rem var(--shadow-claw-bg-tertiary);
|
|
407
411
|
outline: none;
|
|
@@ -469,7 +473,8 @@
|
|
|
469
473
|
opacity: 0.5;
|
|
470
474
|
}
|
|
471
475
|
|
|
472
|
-
.files__paste-dialog
|
|
476
|
+
.files__paste-dialog,
|
|
477
|
+
.files__upload-conflict-dialog {
|
|
473
478
|
background-color: var(--shadow-claw-bg-primary);
|
|
474
479
|
border: 0.0625rem solid var(--shadow-claw-border-color);
|
|
475
480
|
border-radius: var(--shadow-claw-radius-l, 1.5rem);
|
|
@@ -479,31 +484,36 @@
|
|
|
479
484
|
width: calc(100vw - 2rem);
|
|
480
485
|
}
|
|
481
486
|
|
|
482
|
-
.files__paste-dialog::backdrop
|
|
487
|
+
.files__paste-dialog::backdrop,
|
|
488
|
+
.files__upload-conflict-dialog::backdrop {
|
|
483
489
|
background-color: rgba(0, 0, 0, 0.35);
|
|
484
490
|
}
|
|
485
491
|
|
|
486
|
-
.files__paste-form
|
|
492
|
+
.files__paste-form,
|
|
493
|
+
.files__upload-conflict-form {
|
|
487
494
|
display: flex;
|
|
488
495
|
flex-direction: column;
|
|
489
496
|
gap: 1rem;
|
|
490
497
|
padding: 1.5rem;
|
|
491
498
|
}
|
|
492
499
|
|
|
493
|
-
.files__paste-title
|
|
500
|
+
.files__paste-title,
|
|
501
|
+
.files__upload-conflict-title {
|
|
494
502
|
font-size: 1.1rem;
|
|
495
503
|
font-weight: 700;
|
|
496
504
|
margin: 0;
|
|
497
505
|
}
|
|
498
506
|
|
|
499
|
-
.files__paste-message
|
|
507
|
+
.files__paste-message,
|
|
508
|
+
.files__upload-conflict-message {
|
|
500
509
|
color: var(--shadow-claw-text-secondary);
|
|
501
510
|
font-size: var(--shadow-claw-font-size-sm);
|
|
502
511
|
line-height: 1.4;
|
|
503
512
|
margin: 0;
|
|
504
513
|
}
|
|
505
514
|
|
|
506
|
-
.files__paste-actions
|
|
515
|
+
.files__paste-actions,
|
|
516
|
+
.files__upload-conflict-actions {
|
|
507
517
|
display: flex;
|
|
508
518
|
gap: 0.5rem;
|
|
509
519
|
justify-content: flex-end;
|
|
@@ -514,7 +524,10 @@
|
|
|
514
524
|
.files__paste-continue,
|
|
515
525
|
.files__paste-apply,
|
|
516
526
|
.files__paste-ok,
|
|
517
|
-
.files__paste-overwrite
|
|
527
|
+
.files__paste-overwrite,
|
|
528
|
+
.files__upload-conflict-cancel,
|
|
529
|
+
.files__upload-conflict-ok,
|
|
530
|
+
.files__upload-conflict-overwrite {
|
|
518
531
|
background-color: transparent;
|
|
519
532
|
border: 0.0625rem solid var(--shadow-claw-border-color);
|
|
520
533
|
border-radius: var(--shadow-claw-radius-pill);
|
|
@@ -528,13 +541,16 @@
|
|
|
528
541
|
transition: all 0.15s ease;
|
|
529
542
|
}
|
|
530
543
|
|
|
531
|
-
.files__paste-overwrite
|
|
544
|
+
.files__paste-overwrite,
|
|
545
|
+
.files__upload-conflict-overwrite {
|
|
532
546
|
border-color: var(--shadow-claw-error-color);
|
|
533
547
|
color: var(--shadow-claw-error-color);
|
|
534
548
|
}
|
|
535
549
|
|
|
536
550
|
.files__paste-overwrite:hover,
|
|
537
|
-
.files__paste-overwrite:focus-visible
|
|
551
|
+
.files__paste-overwrite:focus-visible,
|
|
552
|
+
.files__upload-conflict-overwrite:hover,
|
|
553
|
+
.files__upload-conflict-overwrite:focus-visible {
|
|
538
554
|
background-color: var(--shadow-claw-error-color);
|
|
539
555
|
color: var(--shadow-claw-on-error, white);
|
|
540
556
|
outline: none;
|
|
@@ -542,7 +558,8 @@
|
|
|
542
558
|
|
|
543
559
|
.files__paste-continue,
|
|
544
560
|
.files__paste-apply,
|
|
545
|
-
.files__paste-ok
|
|
561
|
+
.files__paste-ok,
|
|
562
|
+
.files__upload-conflict-ok {
|
|
546
563
|
background-color: var(--shadow-claw-text-primary);
|
|
547
564
|
border-color: var(--shadow-claw-text-primary);
|
|
548
565
|
color: var(--shadow-claw-bg-primary);
|
|
@@ -553,7 +570,9 @@
|
|
|
553
570
|
.files__paste-apply:hover,
|
|
554
571
|
.files__paste-apply:focus-visible,
|
|
555
572
|
.files__paste-ok:hover,
|
|
556
|
-
.files__paste-ok:focus-visible
|
|
573
|
+
.files__paste-ok:focus-visible,
|
|
574
|
+
.files__upload-conflict-ok:hover,
|
|
575
|
+
.files__upload-conflict-ok:focus-visible {
|
|
557
576
|
background-color: var(--shadow-claw-accent-primary);
|
|
558
577
|
border-color: var(--shadow-claw-accent-primary);
|
|
559
578
|
color: var(--shadow-claw-on-primary);
|
|
@@ -561,7 +580,9 @@
|
|
|
561
580
|
}
|
|
562
581
|
|
|
563
582
|
.files__paste-cancel:hover,
|
|
564
|
-
.files__paste-cancel:focus-visible
|
|
583
|
+
.files__paste-cancel:focus-visible,
|
|
584
|
+
.files__upload-conflict-cancel:hover,
|
|
585
|
+
.files__upload-conflict-cancel:focus-visible {
|
|
565
586
|
border-color: var(--shadow-claw-text-primary);
|
|
566
587
|
box-shadow: var(--shadow-claw-shadow-md);
|
|
567
588
|
outline: none;
|
|
@@ -570,7 +591,10 @@
|
|
|
570
591
|
.files__paste-cancel:disabled,
|
|
571
592
|
.files__paste-continue:disabled,
|
|
572
593
|
.files__paste-apply:disabled,
|
|
573
|
-
.files__paste-ok:disabled
|
|
594
|
+
.files__paste-ok:disabled,
|
|
595
|
+
.files__upload-conflict-cancel:disabled,
|
|
596
|
+
.files__upload-conflict-ok:disabled,
|
|
597
|
+
.files__upload-conflict-overwrite:disabled {
|
|
574
598
|
cursor: not-allowed;
|
|
575
599
|
opacity: 0.6;
|
|
576
600
|
}
|
|
@@ -172,6 +172,41 @@
|
|
|
172
172
|
</div>
|
|
173
173
|
</form>
|
|
174
174
|
</shadow-claw-dialog>
|
|
175
|
+
<shadow-claw-dialog
|
|
176
|
+
dialog-class="files__upload-conflict-dialog"
|
|
177
|
+
dialog-aria-label="File upload conflict"
|
|
178
|
+
>
|
|
179
|
+
<form class="files__upload-conflict-form" method="dialog">
|
|
180
|
+
<h3 class="files__upload-conflict-title">File Already Exists</h3>
|
|
181
|
+
<p class="files__upload-conflict-message"></p>
|
|
182
|
+
<div class="files__upload-conflict-rename-container">
|
|
183
|
+
<label
|
|
184
|
+
class="files__upload-conflict-rename-label"
|
|
185
|
+
for="files-upload-conflict-name"
|
|
186
|
+
>New Name</label
|
|
187
|
+
>
|
|
188
|
+
<input
|
|
189
|
+
class="files__upload-conflict-rename-input"
|
|
190
|
+
id="files-upload-conflict-name"
|
|
191
|
+
name="files-upload-conflict-name"
|
|
192
|
+
type="text"
|
|
193
|
+
autocomplete="off"
|
|
194
|
+
required
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
<div class="files__upload-conflict-actions">
|
|
198
|
+
<button class="files__upload-conflict-cancel" type="button">
|
|
199
|
+
Cancel
|
|
200
|
+
</button>
|
|
201
|
+
<button class="files__upload-conflict-overwrite" type="button">
|
|
202
|
+
Overwrite
|
|
203
|
+
</button>
|
|
204
|
+
<button class="files__upload-conflict-ok" type="submit">
|
|
205
|
+
Rename
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
</form>
|
|
209
|
+
</shadow-claw-dialog>
|
|
175
210
|
<div class="files__content">
|
|
176
211
|
<div class="files__terminal-slot" data-terminal-slot hidden></div>
|
|
177
212
|
<p class="files__drop-hint">Drop files here to upload</p>
|
|
@@ -41,6 +41,14 @@ jest.unstable_mockModule("../../storage/uploadGroupFile.js", () => ({
|
|
|
41
41
|
uploadGroupFile: jest.fn(),
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
|
+
jest.unstable_mockModule("../../db/getConfig.js", () => ({
|
|
45
|
+
getConfig: jest.fn<any>().mockResolvedValue(undefined),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
jest.unstable_mockModule("../../utils/ulid.js", () => ({
|
|
49
|
+
ulid: jest.fn(() => "mock-ulid"),
|
|
50
|
+
}));
|
|
51
|
+
|
|
44
52
|
jest.unstable_mockModule("../../storage/writeGroupFile.js", () => ({
|
|
45
53
|
writeGroupFile: jest.fn(),
|
|
46
54
|
}));
|
|
@@ -104,6 +112,9 @@ jest.unstable_mockModule("../../db/db.js", () => ({
|
|
|
104
112
|
getDb: jest.fn<any>().mockResolvedValue({} as any),
|
|
105
113
|
}));
|
|
106
114
|
|
|
115
|
+
const { CONFIG_KEYS } = await import("../../config/config.js");
|
|
116
|
+
const { getConfig } = await import("../../db/getConfig.js");
|
|
117
|
+
const { uploadGroupFile } = await import("../../storage/uploadGroupFile.js");
|
|
107
118
|
const { ShadowClawFiles } = await import("./shadow-claw-files.js");
|
|
108
119
|
const { deleteGroupFile } = await import("../../storage/deleteGroupFile.js");
|
|
109
120
|
const { renameGroupEntry } = await import("../../storage/renameGroupEntry.js");
|
|
@@ -145,6 +156,8 @@ describe("shadow-claw-files", () => {
|
|
|
145
156
|
|
|
146
157
|
expect(template).toContain("files__rename-dialog");
|
|
147
158
|
|
|
159
|
+
expect(template).toContain("files__upload-conflict-dialog");
|
|
160
|
+
|
|
148
161
|
expect(template).toContain("files__drop-hint");
|
|
149
162
|
|
|
150
163
|
expect(template).toContain("files__upload-progress");
|
|
@@ -910,4 +923,210 @@ describe("shadow-claw-files", () => {
|
|
|
910
923
|
|
|
911
924
|
document.body.removeChild(component);
|
|
912
925
|
});
|
|
926
|
+
|
|
927
|
+
it("uploads files without ULID prefix by default", async () => {
|
|
928
|
+
const component = new ShadowClawFiles();
|
|
929
|
+
document.body.appendChild(component);
|
|
930
|
+
await component.render();
|
|
931
|
+
|
|
932
|
+
(getConfig as any).mockResolvedValue(undefined);
|
|
933
|
+
(orchestratorStore as any).currentPath = ".";
|
|
934
|
+
(orchestratorStore as any).files = [];
|
|
935
|
+
|
|
936
|
+
const file = new File(["content"], "document.pdf", {
|
|
937
|
+
type: "application/pdf",
|
|
938
|
+
});
|
|
939
|
+
await component.uploadFileList({} as any, [file]);
|
|
940
|
+
|
|
941
|
+
expect(uploadGroupFile).toHaveBeenCalledWith(
|
|
942
|
+
{} as any,
|
|
943
|
+
"default",
|
|
944
|
+
"document.pdf",
|
|
945
|
+
file,
|
|
946
|
+
);
|
|
947
|
+
expect(showSuccess).toHaveBeenCalledWith("Uploaded 1 file", 3000);
|
|
948
|
+
|
|
949
|
+
document.body.removeChild(component);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it("uploads files with ULID prefix when setting is enabled", async () => {
|
|
953
|
+
const component = new ShadowClawFiles();
|
|
954
|
+
document.body.appendChild(component);
|
|
955
|
+
await component.render();
|
|
956
|
+
|
|
957
|
+
(getConfig as any).mockImplementation((_db: any, key: string) => {
|
|
958
|
+
if (key === CONFIG_KEYS.FILES_UPLOAD_APPEND_ULID) {
|
|
959
|
+
return Promise.resolve("true");
|
|
960
|
+
}
|
|
961
|
+
return Promise.resolve(undefined);
|
|
962
|
+
});
|
|
963
|
+
(orchestratorStore as any).currentPath = "subfolder";
|
|
964
|
+
(orchestratorStore as any).files = [];
|
|
965
|
+
|
|
966
|
+
const file = new File(["content"], "image.png", {
|
|
967
|
+
type: "image/png",
|
|
968
|
+
});
|
|
969
|
+
await component.uploadFileList({} as any, [file]);
|
|
970
|
+
|
|
971
|
+
expect(uploadGroupFile).toHaveBeenCalledWith(
|
|
972
|
+
{} as any,
|
|
973
|
+
"default",
|
|
974
|
+
"subfolder/mock-ulid-image.png",
|
|
975
|
+
file,
|
|
976
|
+
);
|
|
977
|
+
expect(showSuccess).toHaveBeenCalledWith("Uploaded 1 file", 3000);
|
|
978
|
+
|
|
979
|
+
document.body.removeChild(component);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it("prompts conflict dialog and overwrites existing file when selected", async () => {
|
|
983
|
+
const component = new ShadowClawFiles();
|
|
984
|
+
document.body.appendChild(component);
|
|
985
|
+
await component.render();
|
|
986
|
+
|
|
987
|
+
(getConfig as any).mockResolvedValue(undefined);
|
|
988
|
+
(orchestratorStore as any).currentPath = ".";
|
|
989
|
+
(orchestratorStore as any).files = ["existing.txt"];
|
|
990
|
+
(orchestratorStore as any).pages = [
|
|
991
|
+
{ groupId: "default", path: "existing.txt" },
|
|
992
|
+
];
|
|
993
|
+
|
|
994
|
+
jest.spyOn(component, "promptUploadConflict").mockResolvedValue({
|
|
995
|
+
action: "overwrite",
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
const file = new File(["new content"], "existing.txt", {
|
|
999
|
+
type: "text/plain",
|
|
1000
|
+
});
|
|
1001
|
+
await component.uploadFileList({} as any, [file]);
|
|
1002
|
+
|
|
1003
|
+
expect(component.promptUploadConflict).toHaveBeenCalledWith(
|
|
1004
|
+
"existing.txt",
|
|
1005
|
+
expect.any(Set),
|
|
1006
|
+
);
|
|
1007
|
+
expect(mockRemovePage).toHaveBeenCalledWith(
|
|
1008
|
+
{} as any,
|
|
1009
|
+
"existing.txt",
|
|
1010
|
+
"default",
|
|
1011
|
+
);
|
|
1012
|
+
expect(uploadGroupFile).toHaveBeenCalledWith(
|
|
1013
|
+
{} as any,
|
|
1014
|
+
"default",
|
|
1015
|
+
"existing.txt",
|
|
1016
|
+
file,
|
|
1017
|
+
);
|
|
1018
|
+
expect(showSuccess).toHaveBeenCalledWith("Uploaded 1 file", 3000);
|
|
1019
|
+
|
|
1020
|
+
document.body.removeChild(component);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it("prompts conflict dialog and renames file when new name is chosen", async () => {
|
|
1024
|
+
const component = new ShadowClawFiles();
|
|
1025
|
+
document.body.appendChild(component);
|
|
1026
|
+
await component.render();
|
|
1027
|
+
|
|
1028
|
+
(getConfig as any).mockResolvedValue(undefined);
|
|
1029
|
+
(orchestratorStore as any).currentPath = "docs";
|
|
1030
|
+
(orchestratorStore as any).files = ["existing.txt"];
|
|
1031
|
+
|
|
1032
|
+
jest.spyOn(component, "promptUploadConflict").mockResolvedValue({
|
|
1033
|
+
action: "rename",
|
|
1034
|
+
name: "existing-renamed.txt",
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
const file = new File(["content"], "existing.txt", {
|
|
1038
|
+
type: "text/plain",
|
|
1039
|
+
});
|
|
1040
|
+
await component.uploadFileList({} as any, [file]);
|
|
1041
|
+
|
|
1042
|
+
expect(component.promptUploadConflict).toHaveBeenCalledWith(
|
|
1043
|
+
"existing.txt",
|
|
1044
|
+
expect.any(Set),
|
|
1045
|
+
);
|
|
1046
|
+
expect(uploadGroupFile).toHaveBeenCalledWith(
|
|
1047
|
+
{} as any,
|
|
1048
|
+
"default",
|
|
1049
|
+
"docs/existing-renamed.txt",
|
|
1050
|
+
file,
|
|
1051
|
+
);
|
|
1052
|
+
expect(showSuccess).toHaveBeenCalledWith("Uploaded 1 file", 3000);
|
|
1053
|
+
|
|
1054
|
+
document.body.removeChild(component);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
it("prompts conflict dialog and skips upload when user cancels", async () => {
|
|
1058
|
+
const component = new ShadowClawFiles();
|
|
1059
|
+
document.body.appendChild(component);
|
|
1060
|
+
await component.render();
|
|
1061
|
+
|
|
1062
|
+
(getConfig as any).mockResolvedValue(undefined);
|
|
1063
|
+
(orchestratorStore as any).currentPath = ".";
|
|
1064
|
+
(orchestratorStore as any).files = ["existing.txt"];
|
|
1065
|
+
|
|
1066
|
+
jest.spyOn(component, "promptUploadConflict").mockResolvedValue(null);
|
|
1067
|
+
|
|
1068
|
+
const file = new File(["content"], "existing.txt", {
|
|
1069
|
+
type: "text/plain",
|
|
1070
|
+
});
|
|
1071
|
+
await component.uploadFileList({} as any, [file]);
|
|
1072
|
+
|
|
1073
|
+
expect(component.promptUploadConflict).toHaveBeenCalledWith(
|
|
1074
|
+
"existing.txt",
|
|
1075
|
+
expect.any(Set),
|
|
1076
|
+
);
|
|
1077
|
+
expect(uploadGroupFile).not.toHaveBeenCalled();
|
|
1078
|
+
|
|
1079
|
+
document.body.removeChild(component);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it("handles conflict dialog UI submission and validation", async () => {
|
|
1083
|
+
const component = new ShadowClawFiles();
|
|
1084
|
+
document.body.appendChild(component);
|
|
1085
|
+
await component.render();
|
|
1086
|
+
|
|
1087
|
+
const existingNames = new Set(["existing.txt", "taken.txt"]);
|
|
1088
|
+
const conflictPromise = component.promptUploadConflict(
|
|
1089
|
+
"existing.txt",
|
|
1090
|
+
existingNames,
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
const input = component.shadowRoot?.querySelector(
|
|
1094
|
+
".files__upload-conflict-rename-input",
|
|
1095
|
+
) as HTMLInputElement;
|
|
1096
|
+
const form = component.shadowRoot?.querySelector(
|
|
1097
|
+
".files__upload-conflict-form",
|
|
1098
|
+
) as HTMLFormElement;
|
|
1099
|
+
|
|
1100
|
+
expect(input.value).toBe("existing.txt");
|
|
1101
|
+
|
|
1102
|
+
// 1. Try empty name
|
|
1103
|
+
input.value = " ";
|
|
1104
|
+
form.dispatchEvent(new Event("submit"));
|
|
1105
|
+
expect(showWarning).toHaveBeenCalledWith("Please enter a name", 3000);
|
|
1106
|
+
|
|
1107
|
+
// 2. Try path name
|
|
1108
|
+
input.value = "folder/name.txt";
|
|
1109
|
+
form.dispatchEvent(new Event("submit"));
|
|
1110
|
+
expect(showWarning).toHaveBeenCalledWith(
|
|
1111
|
+
"Use only a name, not a path",
|
|
1112
|
+
3500,
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
// 3. Try already taken name
|
|
1116
|
+
input.value = "taken.txt";
|
|
1117
|
+
form.dispatchEvent(new Event("submit"));
|
|
1118
|
+
expect(showWarning).toHaveBeenCalledWith(
|
|
1119
|
+
'"taken.txt" already exists. Please choose a different name.',
|
|
1120
|
+
4000,
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
// 4. Valid new name
|
|
1124
|
+
input.value = "brand-new.txt";
|
|
1125
|
+
form.dispatchEvent(new Event("submit"));
|
|
1126
|
+
|
|
1127
|
+
const result = await conflictPromise;
|
|
1128
|
+
expect(result).toEqual({ action: "rename", name: "brand-new.txt" });
|
|
1129
|
+
|
|
1130
|
+
document.body.removeChild(component);
|
|
1131
|
+
});
|
|
913
1132
|
});
|