contactsheet 0.1.7 → 0.1.9
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 +3 -1
- package/dist/canvas/app.js +59 -22
- package/dist/canvas/app.js.map +2 -2
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +2 -2
- package/package.json +1 -1
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
|
-
|
|
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` 换行)、
|
package/dist/canvas/app.js
CHANGED
|
@@ -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
|
-
|
|
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, (
|
|
833
|
-
const next = (ann.refs ?? []).filter((
|
|
834
|
-
void transition(
|
|
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, (
|
|
886
|
-
refs.splice(
|
|
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();
|