contactsheet 0.1.7 → 0.1.8

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
@@ -153,7 +153,9 @@ open(橙) ──标记完成──▶ resolved(绿·待核验) ──核验通
153
153
  - 「删除」只留给误钉的 pin,会真的从历史里抹掉。
154
154
  - `Cmd/Ctrl + V` → 贴图,存进 `design/.canvas/refs/`。**在批注输入框或气泡编辑态里粘贴 = 挂到这条批注**
155
155
  (缩略图跟着 pin 走,推给 Claude 的上下文里带上它的路径);在别处粘贴才进右下角的全局坞。
156
- 气泡里的图显示为「图片 n」占位符 + 缩略图,点开大图;**悬停缩略图出 ✕ 可从批注移除**(文件保留在 refs/)
156
+ 粘贴时在**光标处插入 `[图片 n]` 占位符**——图在文字里有位置,「把这里改成[图片 1]这样」是完整的话。
157
+ 气泡下方是编号对应的缩略图(点开大图);**悬停缩略图出 ✕ 可移除**,正文占位符同步摘掉并重编号(文件保留在 refs/)。
158
+ 推送给 Claude 的文本里正文自带 `[图片 n]`,下方 `图片 n:路径` 一一对应,Claude 能 Read 到图
157
159
  - `Ctrl + 滚轮` / 捏合 → 以光标为锚点缩放(0.05–2 倍);空白处拖拽或双指滚动 → 平移
158
160
  - `1` 全景(一屏看完整面墙)· `2` 聚焦当前画板 · `0` 回到 100% · `+` / `-` 步进缩放
159
161
  - 点一个 pin 选中它,然后 `Enter` 编辑(编辑中再按 `Enter` 保存,`Shift+Enter` 换行)、
@@ -362,6 +362,34 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
362
362
  return;
363
363
  }
364
364
  }
365
+ var UPLOADING = "__uploading__";
366
+ function removeRefToken(text, idx1) {
367
+ return text.replace(new RegExp(`\\[\u56FE\u7247 ${idx1}\\]`, "g"), "").replace(/\[图片 (\d+)\]/g, (m, d) => {
368
+ const n = Number(d);
369
+ return n > idx1 ? `[\u56FE\u7247 ${n - 1}]` : m;
370
+ });
371
+ }
372
+ function insertAtCursor(ta, token) {
373
+ const start = ta.selectionStart ?? ta.value.length;
374
+ const end = ta.selectionEnd ?? start;
375
+ ta.setRangeText(token, start, end, "end");
376
+ ta.dispatchEvent(new Event("input", { bubbles: true }));
377
+ }
378
+ function attachInline(ta, refs, file, redraw) {
379
+ const n = refs.length + 1;
380
+ refs.push(UPLOADING);
381
+ insertAtCursor(ta, `[\u56FE\u7247 ${n}]`);
382
+ redraw();
383
+ void uploadRef(file).then((path) => {
384
+ refs[n - 1] = path;
385
+ redraw();
386
+ }).catch((err) => {
387
+ refs.splice(n - 1, 1);
388
+ ta.value = removeRefToken(ta.value, n);
389
+ redraw();
390
+ toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error");
391
+ });
392
+ }
365
393
  async function uploadRef(file) {
366
394
  const dataUrl = await readDataUrl(file);
367
395
  const base64 = dataUrl.slice(dataUrl.indexOf(",") + 1);
@@ -818,9 +846,13 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
818
846
  }
819
847
  function refStrip(paths, onRemove) {
820
848
  const strip = h("div", "cs-ref-strip");
821
- paths.forEach(
822
- (p, i) => strip.appendChild(refThumb(p, { index: i + 1, onRemove: onRemove ? () => onRemove(p) : void 0 }))
823
- );
849
+ paths.forEach((p, i) => {
850
+ if (p === UPLOADING) {
851
+ strip.appendChild(h("div", "cs-ref-thumb is-broken", `\u56FE\u7247 ${i + 1} \xB7 \u4E0A\u4F20\u4E2D\u2026`));
852
+ return;
853
+ }
854
+ strip.appendChild(refThumb(p, { index: i + 1, onRemove: onRemove ? () => onRemove(i) : void 0 }));
855
+ });
824
856
  return strip;
825
857
  }
826
858
  function bubble(ann) {
@@ -829,9 +861,13 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
829
861
  box.appendChild(textEl);
830
862
  if (ann.refs?.length) {
831
863
  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)");
864
+ refStrip(ann.refs, (idx) => {
865
+ const next = (ann.refs ?? []).filter((_, i) => i !== idx);
866
+ void transition(
867
+ ann.id,
868
+ { refs: next, text: removeRefToken(ann.text, idx + 1) },
869
+ "\u53C2\u8003\u56FE\u5DF2\u79FB\u9664(\u6587\u4EF6\u4FDD\u7559\u5728 refs/ \u91CC)"
870
+ );
835
871
  })
836
872
  );
837
873
  }
@@ -882,8 +918,9 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
882
918
  const refs = [...ann.refs ?? []];
883
919
  let strip = box.querySelector(".cs-ref-strip");
884
920
  const redrawStrip = () => {
885
- const next = refStrip(refs, (path) => {
886
- refs.splice(refs.indexOf(path), 1);
921
+ const next = refStrip(refs, (idx) => {
922
+ refs.splice(idx, 1);
923
+ ta.value = removeRefToken(ta.value, idx + 1);
887
924
  redrawStrip();
888
925
  });
889
926
  if (strip?.isConnected) strip.replaceWith(next);
@@ -893,13 +930,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
893
930
  redrawStrip();
894
931
  setPasteTarget({
895
932
  el: box,
896
- onFile: (file) => {
897
- void uploadRef(file).then((path) => {
898
- refs.push(path);
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");
901
- }).catch((err) => toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error"));
902
- }
933
+ onFile: (file) => attachInline(ta, refs, file, redrawStrip)
903
934
  });
904
935
  ta.addEventListener("keydown", (e) => {
905
936
  e.stopPropagation();
@@ -916,6 +947,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
916
947
  const save = h("button", "cs-x", "\u4FDD\u5B58");
917
948
  save.addEventListener("click", (e) => {
918
949
  e.stopPropagation();
950
+ if (refs.includes(UPLOADING)) return toast("\u6709\u56FE\u7247\u8FD8\u5728\u4E0A\u4F20,\u7A0D\u7B49\u4E00\u4E0B\u518D\u4FDD\u5B58", "warn");
919
951
  const text = ta.value.trim();
920
952
  const textChanged = !!text && text !== ann.text;
921
953
  const refsChanged = JSON.stringify(refs) !== JSON.stringify(ann.refs ?? []);
@@ -1059,15 +1091,19 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1059
1091
  composer = box;
1060
1092
  ta.focus();
1061
1093
  const refs = [];
1094
+ let stripEl = strip;
1095
+ const redrawStrip = () => {
1096
+ const next = refStrip(refs, (idx) => {
1097
+ refs.splice(idx, 1);
1098
+ ta.value = removeRefToken(ta.value, idx + 1);
1099
+ redrawStrip();
1100
+ });
1101
+ stripEl.replaceWith(next);
1102
+ stripEl = next;
1103
+ };
1062
1104
  setPasteTarget({
1063
1105
  el: box,
1064
- onFile: (file) => {
1065
- void uploadRef(file).then((path) => {
1066
- refs.push(path);
1067
- strip.appendChild(refThumb(path));
1068
- setProbe(`\u53C2\u8003\u56FE\u5DF2\u6302\u5230\u8FD9\u6761\u6279\u6CE8:${path}`);
1069
- }).catch((err) => toast(`\u53C2\u8003\u56FE\u4E0A\u4F20\u5931\u8D25:${String(err)}`, "error"));
1070
- }
1106
+ onFile: (file) => attachInline(ta, refs, file, redrawStrip)
1071
1107
  });
1072
1108
  ta.addEventListener("keydown", (e) => {
1073
1109
  e.stopPropagation();
@@ -1076,6 +1112,7 @@ ${board.entry.kind} \xB7 ${board.entry.id}`);
1076
1112
  exitPinMode();
1077
1113
  } else if (e.key === "Enter" && !e.shiftKey) {
1078
1114
  e.preventDefault();
1115
+ if (refs.includes(UPLOADING)) return toast("\u6709\u56FE\u7247\u8FD8\u5728\u4E0A\u4F20,\u7A0D\u7B49\u4E00\u4E0B\u518D\u63D0\u4EA4", "warn");
1079
1116
  const text = ta.value.trim();
1080
1117
  if (!text) {
1081
1118
  closeComposer();