dsh-plugin-lookatstudy 0.21.0 → 0.21.1
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/lib/client.js +53 -12
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +56 -8
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -937,6 +937,12 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
937
937
|
.lks14-composer{flex:none;display:flex;gap:6px;align-items:flex-end;padding:8px 12px 10px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
938
938
|
.lks14-composertext{flex:1;font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:7px 10px;resize:none;box-sizing:border-box}
|
|
939
939
|
.lks14-composertext:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
940
|
+
/* issue #7: the composer's pending-attachment chip (thumbnail + name + remove) */
|
|
941
|
+
.lks14-attachchip{flex:none;display:flex;align-items:center;gap:8px;margin:0 0 6px;padding:5px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-2);max-width:100%}
|
|
942
|
+
.lks14-attachchip-thumb{flex:none;width:44px;height:44px;object-fit:cover;border-radius:7px;border:1px solid var(--dsw-alias-border-l1)}
|
|
943
|
+
.lks14-attachchip-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;color:var(--dsw-alias-label-primary)}
|
|
944
|
+
.lks14-attachchip-x{flex:none;border:none;background:none;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;cursor:pointer;border-radius:6px;padding:2px 6px}
|
|
945
|
+
.lks14-attachchip-x:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
940
946
|
|
|
941
947
|
/* ============================================================
|
|
942
948
|
* v0.29 pane-resize 拖拽手柄(PaneResizeHandle)——
|
|
@@ -2179,6 +2185,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2179
2185
|
"settings.budget.on": "开启历史预算",
|
|
2180
2186
|
"settings.budget.off": "关闭历史预算",
|
|
2181
2187
|
"attach.sent": "我发了一个文件:{name}(工作区路径 {path})——请读取它再继续。",
|
|
2188
|
+
"attach.remove": "移除待发附件",
|
|
2182
2189
|
"attach.fail": "附件上传失败",
|
|
2183
2190
|
"attach.label": "附件(≤20MiB,存入学习工作区)",
|
|
2184
2191
|
"rail.mastered": "毕业 {mastered}/{total} 课",
|
|
@@ -2525,6 +2532,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2525
2532
|
"settings.budget.on": "Enable history budget",
|
|
2526
2533
|
"settings.budget.off": "Disable history budget",
|
|
2527
2534
|
"attach.sent": "I attached a file: {name} (workspace path {path}) — read it before continuing.",
|
|
2535
|
+
"attach.remove": "Remove pending attachment",
|
|
2528
2536
|
"attach.fail": "Attachment upload failed",
|
|
2529
2537
|
"attach.label": "Attach (≤20MiB, lands in the study workspace)",
|
|
2530
2538
|
"rail.mastered": "{mastered}/{total} lessons graduated",
|
|
@@ -2765,10 +2773,13 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2765
2773
|
}
|
|
2766
2774
|
/** Render inline markup (code, bold, italic, links, images) over escaped text.
|
|
2767
2775
|
* Image src allowlist: https?:// or data:image/ — folder imports inline local
|
|
2768
|
-
* images as data URLs (capped), GitHub imports reference jsDelivr;
|
|
2769
|
-
*
|
|
2776
|
+
* images as data URLs (capped), GitHub imports reference jsDelivr; the
|
|
2777
|
+
* plugin's own attachment route (/lookatstudy/api/attachment/<name>, issue #7
|
|
2778
|
+
* — same-origin, server-side validated) renders workspace attachments;
|
|
2779
|
+
* anything else stays literal text (no broken relative images, no exotic
|
|
2780
|
+
* schemes). */
|
|
2770
2781
|
function inline(escaped) {
|
|
2771
|
-
return escaped.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/!\[([^\]]*)\]\((https?:\/\/[^)\s]+|data:image\/[^)\s]+)\)/g, (_m, alt, src) => `<img src="${src}" alt="${alt}" loading="lazy">`).replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noreferrer\">$1</a>");
|
|
2782
|
+
return escaped.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/!\[([^\]]*)\]\((https?:\/\/[^)\s]+|data:image\/[^)\s]+|\/lookatstudy\/api\/attachment\/[^)\s]+)\)/g, (_m, alt, src) => `<img src="${src}" alt="${alt}" loading="lazy">`).replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noreferrer\">$1</a>");
|
|
2772
2783
|
}
|
|
2773
2784
|
/** True when the line opens a GFM table row (pipes with a delimiter row next). */
|
|
2774
2785
|
function isTableRow(line) {
|
|
@@ -8504,6 +8515,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8504
8515
|
const stuck = (0, react.useRef)(true);
|
|
8505
8516
|
const [showFab, setShowFab] = (0, react.useState)(false);
|
|
8506
8517
|
const [threadsOpen, setThreadsOpen] = (0, react.useState)(false);
|
|
8518
|
+
const [pendingAttach, setPendingAttach] = (0, react.useState)(null);
|
|
8507
8519
|
const onStreamScroll = () => {
|
|
8508
8520
|
const el = streamEl.current;
|
|
8509
8521
|
if (el === null) return;
|
|
@@ -8755,18 +8767,31 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8755
8767
|
for (let i = 0; i < bytes.length; i += CHUNK) bin += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
8756
8768
|
return uploadAttachment(file.name, btoa(bin));
|
|
8757
8769
|
}).then((path) => {
|
|
8758
|
-
|
|
8770
|
+
setPendingAttach({
|
|
8759
8771
|
name: file.name,
|
|
8760
|
-
path
|
|
8761
|
-
|
|
8772
|
+
path,
|
|
8773
|
+
image: /\.(?:png|jpe?g|gif|webp|bmp)$/i.test(file.name)
|
|
8774
|
+
});
|
|
8762
8775
|
}, () => {
|
|
8763
8776
|
showStudyToast(tr("attach.fail"), { severity: "error" });
|
|
8764
8777
|
});
|
|
8765
8778
|
};
|
|
8779
|
+
/** One send = draft + pending attachment merged into a single message (issue #7). */
|
|
8780
|
+
const submitComposer = () => {
|
|
8781
|
+
if (busy) return;
|
|
8782
|
+
const text = draft.trim();
|
|
8783
|
+
if (text === "" && pendingAttach === null) return;
|
|
8784
|
+
const attachLine = pendingAttach === null ? "" : tr("attach.sent", {
|
|
8785
|
+
name: pendingAttach.name,
|
|
8786
|
+
path: pendingAttach.path
|
|
8787
|
+
});
|
|
8788
|
+
setPendingAttach(null);
|
|
8789
|
+
send(attachLine === "" ? text : text === "" ? attachLine : `${text}\n\n${attachLine}`);
|
|
8790
|
+
};
|
|
8766
8791
|
const onComposerKey = (e) => {
|
|
8767
8792
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
8768
8793
|
e.preventDefault();
|
|
8769
|
-
if (draft.trim() !== "" && !busy)
|
|
8794
|
+
if ((draft.trim() !== "" || pendingAttach !== null) && !busy) submitComposer();
|
|
8770
8795
|
}
|
|
8771
8796
|
};
|
|
8772
8797
|
const proposal = data?.pendingProposals[0] ?? null;
|
|
@@ -8893,7 +8918,18 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8893
8918
|
onClick: () => {
|
|
8894
8919
|
if (!dormant) fire(setMode(mode.id));
|
|
8895
8920
|
}
|
|
8896
|
-
}, mode.id === "direct" ? (0, react.createElement)(IconBoltFill16, { size: 12 }) : mode.id === "guide" ? (0, react.createElement)(IconGoalOutline16, { size: 12 }) : (0, react.createElement)(IconBookFill16, { size: 12 }), tr(mode.labelKey))))), (0, react.createElement)("
|
|
8921
|
+
}, mode.id === "direct" ? (0, react.createElement)(IconBoltFill16, { size: 12 }) : mode.id === "guide" ? (0, react.createElement)(IconGoalOutline16, { size: 12 }) : (0, react.createElement)(IconBookFill16, { size: 12 }), tr(mode.labelKey))))), pendingAttach !== null ? (0, react.createElement)("div", { className: "lks14-attachchip" }, pendingAttach.image ? (0, react.createElement)("img", {
|
|
8922
|
+
className: "lks14-attachchip-thumb",
|
|
8923
|
+
src: `/lookatstudy/api/attachment/${encodeURIComponent(pendingAttach.path.split("/").pop() ?? "")}`,
|
|
8924
|
+
alt: pendingAttach.name
|
|
8925
|
+
}) : null, (0, react.createElement)("span", { className: "lks14-attachchip-name" }, pendingAttach.name), (0, react.createElement)("button", {
|
|
8926
|
+
className: "lks14-attachchip-x",
|
|
8927
|
+
"aria-label": tr("attach.remove"),
|
|
8928
|
+
"data-tooltip": tr("attach.remove"),
|
|
8929
|
+
onClick: () => {
|
|
8930
|
+
setPendingAttach(null);
|
|
8931
|
+
}
|
|
8932
|
+
}, "✕")) : null, (0, react.createElement)("textarea", {
|
|
8897
8933
|
className: "lks14-composertext",
|
|
8898
8934
|
placeholder: busy ? tr("composer.busy") : tr("tutor.empty.hint"),
|
|
8899
8935
|
value: draft,
|
|
@@ -8983,9 +9019,9 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8983
9019
|
}, (0, react.createElement)(IconCloseFill16, { size: 14 })) : (0, react.createElement)("button", {
|
|
8984
9020
|
className: "lks-btn-send",
|
|
8985
9021
|
"aria-label": tr("composer.send"),
|
|
8986
|
-
disabled: dormant || draft.trim() === "",
|
|
9022
|
+
disabled: dormant || draft.trim() === "" && pendingAttach === null,
|
|
8987
9023
|
onClick: () => {
|
|
8988
|
-
|
|
9024
|
+
submitComposer();
|
|
8989
9025
|
}
|
|
8990
9026
|
}, (0, react.createElement)(IconArrowUpFill16, { size: 18 }))), showFab ? (0, react.createElement)("button", {
|
|
8991
9027
|
className: `lks14-scrollfab${busy ? " streaming" : ""}`,
|
|
@@ -9008,6 +9044,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
9008
9044
|
const [zoneOverrides, setZoneOverrides] = (0, react.useState)({});
|
|
9009
9045
|
const zoneCounts = (0, react.useRef)({});
|
|
9010
9046
|
const [editingNote, setEditingNote] = (0, react.useState)(null);
|
|
9047
|
+
const [noteRev, setNoteRev] = (0, react.useState)(0);
|
|
9011
9048
|
const [editDraft, setEditDraft] = (0, react.useState)("");
|
|
9012
9049
|
const [pendingLocate, setPendingLocate] = (0, react.useState)(null);
|
|
9013
9050
|
const [cmapExpanded, setCmapExpanded] = (0, react.useState)(false);
|
|
@@ -9431,7 +9468,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
9431
9468
|
}));
|
|
9432
9469
|
}
|
|
9433
9470
|
}, tr(labelKey), (0, react.createElement)("span", { className: "lks14-zonecount" }, String(zoneNotes.length)), (0, react.createElement)("span", { className: "lks14-zonecaret" }, open ? "▾" : "▸")), ...open ? zoneNotes.map((n) => (0, react.createElement)("div", {
|
|
9434
|
-
key: n.id
|
|
9471
|
+
key: `${n.id}:${String(noteRev)}`,
|
|
9435
9472
|
className: `lks-note${n.pinned ? " pinned" : ""}`
|
|
9436
9473
|
}, (0, react.createElement)("span", { className: "lks-note-src" }, tr("note.src." + n.source)), (0, react.createElement)("button", {
|
|
9437
9474
|
className: "lks-note-act",
|
|
@@ -9492,6 +9529,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
9492
9529
|
disabled: editDraft.trim() === "",
|
|
9493
9530
|
onClick: () => {
|
|
9494
9531
|
setEditingNote(null);
|
|
9532
|
+
setNoteRev((r) => r + 1);
|
|
9495
9533
|
fire(editNote(lesson.lessonId, n.id, editDraft));
|
|
9496
9534
|
}
|
|
9497
9535
|
}, tr("note.save")))) : (0, react.createElement)(ContentBoundary, {
|
|
@@ -9499,7 +9537,10 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
9499
9537
|
boundaryKey: `note-${n.id}`
|
|
9500
9538
|
}, (0, react.createElement)("div", {
|
|
9501
9539
|
className: "lks-note-text",
|
|
9502
|
-
|
|
9540
|
+
ref: (el) => {
|
|
9541
|
+
if (el !== null) enhanceRendered(el).catch(() => {});
|
|
9542
|
+
},
|
|
9543
|
+
dangerouslySetInnerHTML: { __html: renderMarkdown(normalizeMathNotation(n.text)) }
|
|
9503
9544
|
})), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `'${n.quote}'`) : null)) : []);
|
|
9504
9545
|
})));
|
|
9505
9546
|
return (0, react.createElement)("div", { className: "lks14-col lks14-note" }, body, confirmNoteDel !== null ? (0, react.createElement)(ConfirmCard, {
|