dsh-files-native 0.1.1 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/lib/client.js +67 -36
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -81,5 +81,7 @@ dsh plugin --profile web add .
81
81
 
82
82
  ## 兼容性
83
83
 
84
- - DeepSeek Harness `0.1.1-rc.2` 与 `0.1.2-alpha.4`(web profile)
84
+ - DeepSeek Harness `0.1.2-rc.1`(仍兼容 `0.1.1-rc.2` 与 `0.1.2-alpha.4`,web profile)
85
85
  - 不要和 `dsh-file-fix` / `dsh-file-upload` / `dsh-paste-to-path` 叠装
86
+
87
+ 改仓库前先读 [CONTRIBUTING.md](./CONTRIBUTING.md)(Issue → 分支 → Draft PR)。思考原则见 [AI-ISSUE-WORKFLOW.md](./AI-ISSUE-WORKFLOW.md)。插件硬约束见 [AGENTS.md](./AGENTS.md)。
package/lib/client.js CHANGED
@@ -125,8 +125,8 @@ function subscribe(fn) {
125
125
  listeners.delete(fn);
126
126
  };
127
127
  }
128
- function pending() {
129
- return items.slice();
128
+ function pendingFor(sessionId) {
129
+ return items.filter((item) => item.sessionId === sessionId);
130
130
  }
131
131
  function add(sessionId, item) {
132
132
  items.push({ ...item, sessionId: item.sessionId || sessionId });
@@ -225,9 +225,9 @@ var FR_CSS = `
225
225
  .fr-title{font:var(--dsw-font-l-20, 600 20px/28px system-ui);margin-top:16px}
226
226
  .fr-desc{font:var(--dsw-font-s-14, 14px/20px system-ui);color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;margin-top:16px}
227
227
  .fr-illust{width:115px;height:84px}
228
- .fr-pick{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;
229
- background:transparent;color:var(--dsw-alias-label-secondary);border-radius:8px;cursor:pointer;padding:0}
230
- .fr-pick:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
228
+ .fr-pick{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:transparent;
229
+ border:none;border-radius:999px;flex:none;place-items:center;display:grid;padding:0}
230
+ .fr-pick:hover{background:var(--dsw-alias-interactive-bg-hover)}
231
231
  .fr-hidden{display:none}
232
232
  .fr-lightbox{z-index:1100;position:fixed;inset:0;background:rgba(0,0,0,.72);display:grid;place-items:center;cursor:zoom-out}
233
233
  .fr-lightbox img{max-width:min(92vw,1200px);max-height:88vh;border-radius:12px;box-shadow:var(--dsw-shadow-lv2)}
@@ -344,7 +344,7 @@ function uid() {
344
344
  }
345
345
  async function uploadFile(sessionId, file) {
346
346
  const id = uid();
347
- const pending2 = {
347
+ const pending = {
348
348
  id,
349
349
  sessionId,
350
350
  name: file.name,
@@ -353,14 +353,14 @@ async function uploadFile(sessionId, file) {
353
353
  mediaType: file.type || "application/octet-stream",
354
354
  status: "uploading"
355
355
  };
356
- add(sessionId, pending2);
356
+ add(sessionId, pending);
357
357
  const url = `/plugins/file-native/upload?sessionId=${encodeURIComponent(sessionId)}&name=${encodeURIComponent(file.name)}&type=${encodeURIComponent(file.type)}`;
358
358
  try {
359
359
  const res = await fetch(url, { method: "POST", body: file });
360
360
  const body = await res.json();
361
361
  if (!res.ok || !body.ok || body.file === void 0) {
362
362
  patch(sessionId, id, { status: "error", error: body.error ?? "\u4E0A\u4F20\u5931\u8D25" });
363
- return { ...pending2, status: "error", error: body.error ?? "\u4E0A\u4F20\u5931\u8D25" };
363
+ return { ...pending, status: "error", error: body.error ?? "\u4E0A\u4F20\u5931\u8D25" };
364
364
  }
365
365
  patch(sessionId, id, {
366
366
  status: "done",
@@ -369,10 +369,10 @@ async function uploadFile(sessionId, file) {
369
369
  size: body.file.size,
370
370
  mediaType: body.file.mediaType
371
371
  });
372
- return { ...pending2, ...body.file, status: "done" };
372
+ return { ...pending, ...body.file, status: "done" };
373
373
  } catch {
374
374
  patch(sessionId, id, { status: "error", error: "\u7F51\u7EDC\u9519\u8BEF" });
375
- return { ...pending2, status: "error", error: "\u7F51\u7EDC\u9519\u8BEF" };
375
+ return { ...pending, status: "error", error: "\u7F51\u7EDC\u9519\u8BEF" };
376
376
  }
377
377
  }
378
378
  function intake(sessionId, files, onAddImages) {
@@ -436,9 +436,10 @@ function ImageTile({ item, onRemove, onOpen }) {
436
436
  }
437
437
  function FileTile({ item, sessionId }) {
438
438
  const remove2 = () => {
439
- remove(sessionId, item.id);
439
+ const sid = item.sessionId || sessionId;
440
+ remove(sid, item.id);
440
441
  if (item.relPath) {
441
- void fetch(`/plugins/file-native/remove?sessionId=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(item.relPath)}`, { method: "POST" });
442
+ void fetch(`/plugins/file-native/remove?sessionId=${encodeURIComponent(sid)}&path=${encodeURIComponent(item.relPath)}`, { method: "POST" });
442
443
  }
443
444
  };
444
445
  const meta = item.status === "uploading" ? "\u4E0A\u4F20\u4E2D\u2026" : item.status === "error" ? item.error ?? "\u5931\u8D25" : formatSize(item.size);
@@ -462,7 +463,7 @@ function FileTile({ item, sessionId }) {
462
463
  function FileRail(props) {
463
464
  ensureStyles();
464
465
  const sessionId = String(props.sessionId ?? "");
465
- const [files, setFiles] = (0, import_react2.useState)(() => pending());
466
+ const [files, setFiles] = (0, import_react2.useState)(() => pendingFor(sessionId));
466
467
  const [, setVersion] = (0, import_react2.useState)(() => version());
467
468
  const [preview, setPreview] = (0, import_react2.useState)(null);
468
469
  const [edges, setEdges] = (0, import_react2.useState)({ left: false, right: false });
@@ -470,12 +471,12 @@ function FileRail(props) {
470
471
  const countRef = (0, import_react2.useRef)(0);
471
472
  (0, import_react2.useEffect)(() => {
472
473
  const sync = () => {
473
- setFiles(pending());
474
+ setFiles(pendingFor(sessionId));
474
475
  setVersion(version());
475
476
  };
476
477
  sync();
477
478
  return subscribe(sync);
478
- }, []);
479
+ }, [sessionId]);
479
480
  const updateEdges = () => {
480
481
  const el = rowRef.current;
481
482
  if (el === null) return;
@@ -483,7 +484,8 @@ function FileRail(props) {
483
484
  const right = el.scrollLeft < el.scrollWidth - el.clientWidth - 1;
484
485
  setEdges((prev) => prev.left === left && prev.right === right ? prev : { left, right });
485
486
  };
486
- const itemCount = props.attachments.length + files.length;
487
+ const visibleFiles = files.filter((item) => item.sessionId === sessionId);
488
+ const itemCount = props.attachments.length + visibleFiles.length;
487
489
  (0, import_react2.useLayoutEffect)(() => {
488
490
  const grew = countRef.current !== 0 && itemCount > countRef.current;
489
491
  countRef.current = itemCount;
@@ -519,7 +521,7 @@ function FileRail(props) {
519
521
  el.scrollBy({ left: direction * Math.max(el.clientWidth - 64, 200), behavior: "smooth" });
520
522
  };
521
523
  const hasImages = props.attachments.length > 0;
522
- const hasFiles = files.length > 0;
524
+ const hasFiles = visibleFiles.length > 0;
523
525
  if (!hasImages && !hasFiles) return null;
524
526
  return (0, import_react2.createElement)(
525
527
  "div",
@@ -537,7 +539,7 @@ function FileRail(props) {
537
539
  onRemove: () => props.onRemoveImage(item.id),
538
540
  onOpen: () => setPreview(item)
539
541
  })),
540
- ...files.map((item) => (0, import_react2.createElement)(FileTile, { key: item.id, item, sessionId }))
542
+ ...visibleFiles.map((item) => (0, import_react2.createElement)(FileTile, { key: item.id, item, sessionId }))
541
543
  ),
542
544
  edges.right ? (0, import_react2.createElement)("button", { type: "button", className: "fr-arrow fr-arrow-right", "aria-label": "\u5411\u53F3", onClick: () => page(1) }, (0, import_react2.createElement)(IconChevronRight)) : null
543
545
  ),
@@ -581,7 +583,9 @@ var import_react_dom2 = require("react-dom");
581
583
  var owner = null;
582
584
  var lastSessionId = "";
583
585
  var dragDepth = 0;
586
+ var generation2 = 0;
584
587
  var listeners2 = /* @__PURE__ */ new Set();
588
+ var queuedImages = /* @__PURE__ */ new Map();
585
589
  function emit2() {
586
590
  for (const fn of [...listeners2]) fn();
587
591
  }
@@ -591,14 +595,42 @@ function subscribeLive(fn) {
591
595
  listeners2.delete(fn);
592
596
  };
593
597
  }
598
+ function flushQueuedImages() {
599
+ if (!owner?.onAddImages) return;
600
+ const sid = owner.sessionId;
601
+ const files = (queuedImages.get(sid) ?? []).concat(sid ? queuedImages.get("") ?? [] : []);
602
+ queuedImages.delete(sid);
603
+ if (sid) queuedImages.delete("");
604
+ if (files.length === 0) return;
605
+ owner.onAddImages(files);
606
+ }
594
607
  function setLiveOwner(next) {
608
+ generation2 += 1;
595
609
  owner = next;
596
610
  if (next?.sessionId) lastSessionId = next.sessionId;
597
611
  emit2();
612
+ if (next) flushQueuedImages();
613
+ return generation2;
614
+ }
615
+ function clearLiveOwner(token) {
616
+ if (token !== generation2) return;
617
+ owner = null;
618
+ emit2();
598
619
  }
599
620
  function getLiveOwner() {
600
621
  return owner;
601
622
  }
623
+ function addImages(sessionId, files) {
624
+ if (files.length === 0) return;
625
+ const sid = sessionId || currentSessionId();
626
+ if (owner?.onAddImages && (!owner.sessionId || owner.sessionId === sid)) {
627
+ owner.onAddImages(files);
628
+ return;
629
+ }
630
+ const key = sid || owner?.sessionId || lastSessionId;
631
+ const prev = queuedImages.get(key) ?? [];
632
+ queuedImages.set(key, prev.concat([...files]));
633
+ }
602
634
  function setDragDepth(depth) {
603
635
  const next = Math.max(0, depth);
604
636
  if (next === dragDepth) return;
@@ -696,11 +728,9 @@ function PaperclipButton(props) {
696
728
  const onChange = (event) => {
697
729
  const files = Array.from(event.currentTarget.files ?? []);
698
730
  event.currentTarget.value = "";
699
- const live = getLiveOwner();
700
- const sid = String(props.sessionId || live?.sessionId || currentSessionId());
731
+ const sid = String(props.sessionId || currentSessionId());
701
732
  if (files.length === 0) return;
702
- intakeFiles(sid, files, live?.onAddImages ?? (() => {
703
- }));
733
+ intakeFiles(sid, files, (images) => addImages(sid, images));
704
734
  };
705
735
  return (0, import_react5.createElement)(
706
736
  "span",
@@ -1035,15 +1065,19 @@ var inject = ["slots"];
1035
1065
  function RailSlot(props) {
1036
1066
  ensureStyles();
1037
1067
  const sessionId = resolveSessionId(props);
1038
- setLiveOwner({
1039
- attachments: props.attachments,
1040
- canAcceptDrop: props.canAcceptDrop,
1041
- onAddImages: props.onAddImages,
1042
- onRemoveImage: props.onRemoveImage,
1043
- sessionId,
1044
- dropLimits: props.dropLimits
1045
- });
1046
- (0, import_react8.useEffect)(() => () => setLiveOwner(null), []);
1068
+ rememberSessionId(sessionId);
1069
+ const tokenRef = (0, import_react8.useRef)(0);
1070
+ (0, import_react8.useLayoutEffect)(() => {
1071
+ tokenRef.current = setLiveOwner({
1072
+ attachments: props.attachments,
1073
+ canAcceptDrop: props.canAcceptDrop,
1074
+ onAddImages: props.onAddImages,
1075
+ onRemoveImage: props.onRemoveImage,
1076
+ sessionId,
1077
+ dropLimits: props.dropLimits
1078
+ });
1079
+ }, [sessionId, props.attachments, props.canAcceptDrop, props.onAddImages, props.onRemoveImage, props.dropLimits]);
1080
+ (0, import_react8.useLayoutEffect)(() => () => clearLiveOwner(tokenRef.current), []);
1047
1081
  return null;
1048
1082
  }
1049
1083
  function PickerSlot(props) {
@@ -1082,11 +1116,8 @@ function installLiveIntercept() {
1082
1116
  canAccept: () => true,
1083
1117
  onDepth: (depth) => setDragDepth(depth),
1084
1118
  onFiles: (files) => {
1085
- const live = getLiveOwner();
1086
- const sessionId = live?.sessionId || currentSessionId();
1087
- const addImages = live?.onAddImages ?? (() => {
1088
- });
1089
- intakeFiles(sessionId, files, addImages);
1119
+ const sessionId = currentSessionId();
1120
+ intakeFiles(sessionId, files, (images) => addImages(sessionId, images));
1090
1121
  }
1091
1122
  });
1092
1123
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-files-native",
3
3
  "description": "DSH 插件:接近原生质感的附件上传——拖入/粘贴/回形针,图片缩略图与文件卡片混排在官方附件栏",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {