contactsheet 0.1.6 → 0.1.7

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 CHANGED
@@ -152,7 +152,8 @@ open(橙) ──标记完成──▶ resolved(绿·待核验) ──核验通
152
152
  这份文件就是历史记录,日后沉淀 skill / 复盘"当初都提过什么、怎么改的"的原料。
153
153
  - 「删除」只留给误钉的 pin,会真的从历史里抹掉。
154
154
  - `Cmd/Ctrl + V` → 贴图,存进 `design/.canvas/refs/`。**在批注输入框或气泡编辑态里粘贴 = 挂到这条批注**
155
- (缩略图跟着 pin 走,推给 Claude 的上下文里带上它的路径);在别处粘贴才进右下角的全局坞
155
+ (缩略图跟着 pin 走,推给 Claude 的上下文里带上它的路径);在别处粘贴才进右下角的全局坞。
156
+ 气泡里的图显示为「图片 n」占位符 + 缩略图,点开大图;**悬停缩略图出 ✕ 可从批注移除**(文件保留在 refs/)
156
157
  - `Ctrl + 滚轮` / 捏合 → 以光标为锚点缩放(0.05–2 倍);空白处拖拽或双指滚动 → 平移
157
158
  - `1` 全景(一屏看完整面墙)· `2` 聚焦当前画板 · `0` 回到 100% · `+` / `-` 步进缩放
158
159
  - 点一个 pin 选中它,然后 `Enter` 编辑(编辑中再按 `Enter` 保存,`Shift+Enter` 换行)、
@@ -383,7 +383,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
383
383
  function refSrc(path) {
384
384
  return localSrc.get(path) ?? refUrl(path);
385
385
  }
386
- function refThumb(path) {
386
+ function refThumb(path, opts) {
387
387
  const card = h("div", "cs-ref-thumb");
388
388
  card.title = path;
389
389
  const img = h("img");
@@ -395,11 +395,21 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
395
395
  img.alt = path;
396
396
  img.src = refSrc(path);
397
397
  if (img.complete && img.naturalWidth === 0) broken();
398
- card.append(img, h("span", "cs-ref-name", fileOf(path)));
398
+ card.append(img, h("span", "cs-ref-name", opts?.index ? `\u56FE\u7247 ${opts.index}` : fileOf(path)));
399
399
  card.addEventListener("click", (e) => {
400
400
  e.stopPropagation();
401
401
  openLightbox(refSrc(path), path);
402
402
  });
403
+ if (opts?.onRemove) {
404
+ const rm = h("button", "cs-ref-rm", "\u2715");
405
+ rm.title = "\u4ECE\u8FD9\u6761\u6279\u6CE8\u79FB\u9664(\u6587\u4EF6\u4FDD\u7559\u5728 refs/)";
406
+ rm.setAttribute("aria-label", rm.title);
407
+ rm.addEventListener("click", (e) => {
408
+ e.stopPropagation();
409
+ opts.onRemove?.();
410
+ });
411
+ card.appendChild(rm);
412
+ }
403
413
  return card;
404
414
  }
405
415
  function openLightbox(src, caption) {
@@ -806,16 +816,25 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
806
816
  }, 250);
807
817
  });
808
818
  }
809
- function refStrip(paths) {
819
+ function refStrip(paths, onRemove) {
810
820
  const strip = h("div", "cs-ref-strip");
811
- for (const p of paths) strip.appendChild(refThumb(p));
821
+ paths.forEach(
822
+ (p, i) => strip.appendChild(refThumb(p, { index: i + 1, onRemove: onRemove ? () => onRemove(p) : void 0 }))
823
+ );
812
824
  return strip;
813
825
  }
814
826
  function bubble(ann) {
815
827
  const box = h("div", "cs-pin-bubble");
816
828
  const textEl = h("div", "cs-pin-text", ann.text);
817
829
  box.appendChild(textEl);
818
- if (ann.refs?.length) box.appendChild(refStrip(ann.refs));
830
+ if (ann.refs?.length) {
831
+ box.appendChild(
832
+ refStrip(ann.refs, (path) => {
833
+ const next = (ann.refs ?? []).filter((r) => r !== path);
834
+ void transition(ann.id, { refs: next }, "\u53C2\u8003\u56FE\u5DF2\u79FB\u9664(\u6587\u4EF6\u4FDD\u7559\u5728 refs/ \u91CC)");
835
+ })
836
+ );
837
+ }
819
838
  const meta = h("div", "cs-pin-meta");
820
839
  meta.appendChild(h("span", void 0, ann.status === "open" ? "open" : "\u5F85\u6838\u9A8C"));
821
840
  const act = (label, cls, fn) => {
@@ -861,15 +880,24 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
861
880
  const ta = h("textarea", "cs-pin-edit");
862
881
  ta.value = ann.text;
863
882
  const refs = [...ann.refs ?? []];
864
- const strip = box.querySelector(".cs-ref-strip") ?? refStrip([]);
865
- if (!strip.isConnected) box.insertBefore(strip, box.querySelector(".cs-pin-meta"));
883
+ let strip = box.querySelector(".cs-ref-strip");
884
+ const redrawStrip = () => {
885
+ const next = refStrip(refs, (path) => {
886
+ refs.splice(refs.indexOf(path), 1);
887
+ redrawStrip();
888
+ });
889
+ if (strip?.isConnected) strip.replaceWith(next);
890
+ else box.insertBefore(next, box.querySelector(".cs-pin-meta"));
891
+ strip = next;
892
+ };
893
+ redrawStrip();
866
894
  setPasteTarget({
867
895
  el: box,
868
896
  onFile: (file) => {
869
897
  void uploadRef(file).then((path) => {
870
898
  refs.push(path);
871
- strip.appendChild(refThumb(path));
872
- toast(`\u53C2\u8003\u56FE\u5DF2\u4E0A\u4F20:${path} \u2014\u2014 \u70B9\u300C\u4FDD\u5B58\u300D\u540E\u624D\u6302\u5230\u8FD9\u6761\u6279\u6CE8`, "ok");
899
+ redrawStrip();
900
+ toast(`\u53C2\u8003\u56FE\u5DF2\u4E0A\u4F20 \u2014\u2014 \u70B9\u300C\u4FDD\u5B58\u300D\u540E\u624D\u6302\u5230\u8FD9\u6761\u6279\u6CE8`, "ok");
873
901
  }).catch((err) => toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error"));
874
902
  }
875
903
  });
@@ -890,7 +918,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
890
918
  e.stopPropagation();
891
919
  const text = ta.value.trim();
892
920
  const textChanged = !!text && text !== ann.text;
893
- const refsChanged = refs.length !== (ann.refs?.length ?? 0);
921
+ const refsChanged = JSON.stringify(refs) !== JSON.stringify(ann.refs ?? []);
894
922
  if (!textChanged && !refsChanged) return renderPins();
895
923
  const patch = {};
896
924
  if (textChanged) patch.text = text;
@@ -1255,6 +1283,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1255
1283
  viewport.addEventListener(
1256
1284
  "wheel",
1257
1285
  (e) => {
1286
+ if (e.target?.closest?.(".cs-pin-bubble, .cs-pin-input")) return;
1258
1287
  e.preventDefault();
1259
1288
  if (e.ctrlKey || e.metaKey) {
1260
1289
  zoomAt(e.clientX, e.clientY, Math.exp(-e.deltaY / 260));