dsh-plugin-lookatstudy 0.12.3 → 0.12.5
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 +94 -19
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +41 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -235,6 +235,9 @@ window.__ModuleLoader__.load({
|
|
|
235
235
|
.lks-zone-h{font-size:14px;color:var(--dsw-alias-label-secondary);margin:0 0 8px;font-weight:600}
|
|
236
236
|
.lks-note{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 14px;margin-bottom:8px}
|
|
237
237
|
.lks-note .lks-note-src{float:right;font-size:11.5px;color:var(--dsw-alias-label-secondary)}
|
|
238
|
+
.lks-note-del{float:right;clear:right;border:none;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;padding:2px;border-radius:5px;line-height:0}
|
|
239
|
+
.lks-note-del:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}
|
|
240
|
+
.lks-note-del.armed{color:#fff;background:var(--dsw-alias-state-error-primary)}
|
|
238
241
|
.lks-note .lks-note-title{font-weight:600;font-size:13px}
|
|
239
242
|
.lks-note .lks-note-text{margin-top:4px;font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.65}
|
|
240
243
|
.lks-note .lks-note-text p{margin:4px 0}
|
|
@@ -298,10 +301,10 @@ window.__ModuleLoader__.load({
|
|
|
298
301
|
* ic_ds_* glyphs vendored from @deepseek-ai/dsh-client-ui-primitives
|
|
299
302
|
* (src/icons/index.tsx, same figma source as the host's icon set) so the
|
|
300
303
|
* plugin's client bundle stays self-contained per the dsh client-bundle
|
|
301
|
-
* contract — only react
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
304
|
+
* contract — only react is runtime-external; importing the host package
|
|
305
|
+
* root would drag its CSS modules into the inlined bundle. Re-sync these
|
|
306
|
+
* three paths if the host icon set redesigns them; scripts/verify.mjs
|
|
307
|
+
* pins the path data.
|
|
305
308
|
*
|
|
306
309
|
* 2026-09-01 critique round: seven more ic_ds glyphs vendored (play/plus/
|
|
307
310
|
* trash/refresh/globe/goal/warning) plus plugin-original learning-domain
|
|
@@ -715,6 +718,18 @@ window.__ModuleLoader__.load({
|
|
|
715
718
|
});
|
|
716
719
|
this.refresh();
|
|
717
720
|
}
|
|
721
|
+
/** Delete one notebook entry from a lesson's Cornell zones. */
|
|
722
|
+
async deleteNote(lessonId, noteId) {
|
|
723
|
+
await fetchJson("/lookatstudy/api/note/delete", {
|
|
724
|
+
method: "POST",
|
|
725
|
+
headers: { "content-type": "application/json" },
|
|
726
|
+
body: JSON.stringify({
|
|
727
|
+
lessonId,
|
|
728
|
+
noteId
|
|
729
|
+
})
|
|
730
|
+
});
|
|
731
|
+
this.refresh();
|
|
732
|
+
}
|
|
718
733
|
};
|
|
719
734
|
/** The one shared store instance backing every study seat component. */
|
|
720
735
|
const studyStore = new StudyStore();
|
|
@@ -727,6 +742,7 @@ window.__ModuleLoader__.load({
|
|
|
727
742
|
setFocus: studyStore.setFocus.bind(studyStore),
|
|
728
743
|
searchLessons: studyStore.searchLessons.bind(studyStore),
|
|
729
744
|
deleteCourse: studyStore.deleteCourse.bind(studyStore),
|
|
745
|
+
deleteNote: studyStore.deleteNote.bind(studyStore),
|
|
730
746
|
bindLessonSession: studyStore.bindLessonSession.bind(studyStore)
|
|
731
747
|
};
|
|
732
748
|
}
|
|
@@ -1567,6 +1583,8 @@ window.__ModuleLoader__.load({
|
|
|
1567
1583
|
"rail.delete": "删除本课程",
|
|
1568
1584
|
"rail.delete.confirm": "确认删除?",
|
|
1569
1585
|
"rail.delete.title.confirm": "再点一次确认删除(含全部进度与笔记)。反悔前可先在设置页备份状态文件",
|
|
1586
|
+
"note.delete": "删除本条笔记",
|
|
1587
|
+
"note.delete.confirm": "确认删除?",
|
|
1570
1588
|
"rail.section.collapse": "折叠本章节",
|
|
1571
1589
|
"rail.section.expand": "展开本章节({count} 课时)",
|
|
1572
1590
|
"rail.section.count": "{count} 课",
|
|
@@ -1687,6 +1705,8 @@ window.__ModuleLoader__.load({
|
|
|
1687
1705
|
"rail.delete": "Delete this course",
|
|
1688
1706
|
"rail.delete.confirm": "Delete?",
|
|
1689
1707
|
"rail.delete.title.confirm": "Click again to confirm (all progress and notes included). Back up via the state file in Settings first",
|
|
1708
|
+
"note.delete": "Delete this note",
|
|
1709
|
+
"note.delete.confirm": "Delete?",
|
|
1690
1710
|
"rail.section.collapse": "Collapse this section",
|
|
1691
1711
|
"rail.section.expand": "Expand this section ({count} lessons)",
|
|
1692
1712
|
"rail.section.count": "{count} lessons",
|
|
@@ -2045,10 +2065,10 @@ window.__ModuleLoader__.load({
|
|
|
2045
2065
|
return runs.at(-1) ?? [];
|
|
2046
2066
|
}
|
|
2047
2067
|
function textOf(blocks) {
|
|
2048
|
-
return blocks.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n\n").trim();
|
|
2068
|
+
return (blocks ?? []).filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n\n").trim();
|
|
2049
2069
|
}
|
|
2050
2070
|
function assistantText(blocks) {
|
|
2051
|
-
return blocks.filter((b) => b.kind === "text").map((b) => b.text).join("\n\n").trim();
|
|
2071
|
+
return (blocks ?? []).filter((b) => b.kind === "text").map((b) => b.text ?? "").join("\n\n").trim();
|
|
2052
2072
|
}
|
|
2053
2073
|
/**
|
|
2054
2074
|
* Fold conversation nodes (plus the streaming partial) into render rows.
|
|
@@ -2088,7 +2108,7 @@ window.__ModuleLoader__.load({
|
|
|
2088
2108
|
case "turn-error": rows.push({
|
|
2089
2109
|
key: `e${node.seq}`,
|
|
2090
2110
|
role: "error",
|
|
2091
|
-
text: node.message
|
|
2111
|
+
text: node.message ?? ""
|
|
2092
2112
|
});
|
|
2093
2113
|
}
|
|
2094
2114
|
if (partial != null) {
|
|
@@ -2111,6 +2131,17 @@ window.__ModuleLoader__.load({
|
|
|
2111
2131
|
if (error === null) return null;
|
|
2112
2132
|
return (0, react.createElement)("div", { className: "lks-propcard-err" }, error);
|
|
2113
2133
|
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Resolve the transcript across host generations. 0.1.3+ hosts expose the
|
|
2136
|
+
* node array as the Chat target's `legacy` slice; pre-0.1.3 hosts hand the
|
|
2137
|
+
* whole conversation snapshot (nodes + partial) through `useSession`, whose
|
|
2138
|
+
* 0.1.3+ successor returns the session MACHINE (no node array). The array
|
|
2139
|
+
* check tells those two `useSession` shapes apart. Pure.
|
|
2140
|
+
*/
|
|
2141
|
+
function pickTranscript(chatLegacy, sessionSnapshot) {
|
|
2142
|
+
if (chatLegacy !== void 0) return chatLegacy;
|
|
2143
|
+
return sessionSnapshot !== void 0 && Array.isArray(sessionSnapshot.nodes) ? sessionSnapshot : void 0;
|
|
2144
|
+
}
|
|
2114
2145
|
/** sessionStorage key keeping the narrow-mode pane across view remounts (tab switches) and reloads. */
|
|
2115
2146
|
const PANE_KEY = "dsh-plugin-lookatstudy:pane";
|
|
2116
2147
|
/** Narrow the stored value back to a pane id; anything else (or nothing stored) falls back to 导师. Pure, unit-testable. */
|
|
@@ -2137,9 +2168,12 @@ window.__ModuleLoader__.load({
|
|
|
2137
2168
|
};
|
|
2138
2169
|
}
|
|
2139
2170
|
/** Tab body: the factory-bound ctx carries workspaces/sessions for the per-lesson session jumps. */
|
|
2140
|
-
function StudyTab({
|
|
2141
|
-
const { data, activate, setMode, setFocus, searchLessons, deleteCourse, bindLessonSession } = useStudy();
|
|
2142
|
-
const
|
|
2171
|
+
function StudyTab({ inputActions, ctx, ...standard }) {
|
|
2172
|
+
const { data, activate, setMode, setFocus, searchLessons, deleteCourse, deleteNote, bindLessonSession } = useStudy();
|
|
2173
|
+
const chatLegacy = standard.useChat?.((s) => s.legacy);
|
|
2174
|
+
const sessionSnapshot = standard.useSession?.((s) => s);
|
|
2175
|
+
const snapshot = pickTranscript(chatLegacy, sessionSnapshot);
|
|
2176
|
+
const currentSessionId = standard.sessionId ?? snapshot?.sessionId ?? "";
|
|
2143
2177
|
const [pane, setPane] = (0, react.useState)(storedPane);
|
|
2144
2178
|
const rootRef = (0, react.useRef)(null);
|
|
2145
2179
|
(0, react.useEffect)(() => {
|
|
@@ -2237,13 +2271,16 @@ window.__ModuleLoader__.load({
|
|
|
2237
2271
|
bindLessonSession,
|
|
2238
2272
|
send,
|
|
2239
2273
|
ctx,
|
|
2240
|
-
currentSessionId
|
|
2274
|
+
currentSessionId
|
|
2241
2275
|
}), (0, react.createElement)(TutorColumn, {
|
|
2242
2276
|
data,
|
|
2243
2277
|
setMode,
|
|
2244
2278
|
send,
|
|
2245
2279
|
snapshot
|
|
2246
|
-
}), (0, react.createElement)(BlackboardColumn, {
|
|
2280
|
+
}), (0, react.createElement)(BlackboardColumn, {
|
|
2281
|
+
data,
|
|
2282
|
+
deleteNote
|
|
2283
|
+
})));
|
|
2247
2284
|
}
|
|
2248
2285
|
/** Left column: course management (pick/delete/search/import), lesson tree, due box. */
|
|
2249
2286
|
function CourseRail({ data, activate, setFocus, searchLessons, deleteCourse, bindLessonSession, send, ctx, currentSessionId }) {
|
|
@@ -2268,7 +2305,7 @@ window.__ModuleLoader__.load({
|
|
|
2268
2305
|
if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`);
|
|
2269
2306
|
const { path } = await area.json();
|
|
2270
2307
|
const workspace = await ctx.workspaces.create({ path });
|
|
2271
|
-
const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId);
|
|
2308
|
+
const sessionId = ctx.workspaces.connectWorkspace !== void 0 ? await ctx.workspaces.connectWorkspace(workspace.workspaceId) : await ctx.sessions.create({ workspaceId: workspace.workspaceId });
|
|
2272
2309
|
const actx = ctx.sessions.scope(sessionId);
|
|
2273
2310
|
const face = actx === void 0 ? void 0 : ctx.sessions.sessionOf(actx);
|
|
2274
2311
|
if (face === void 0) throw new Error("lesson session is not addressable yet");
|
|
@@ -2587,9 +2624,33 @@ window.__ModuleLoader__.load({
|
|
|
2587
2624
|
}, s.label))) : null, (0, react.createElement)(ActionError, { error }));
|
|
2588
2625
|
}
|
|
2589
2626
|
/** Right column: the blackboard — focus-lesson 讲解/脑图/概念图 plus the Cornell 笔记. */
|
|
2590
|
-
function BlackboardColumn({ data }) {
|
|
2627
|
+
function BlackboardColumn({ data, deleteNote }) {
|
|
2591
2628
|
const lesson = data?.lesson ?? null;
|
|
2592
2629
|
const [pane, setPane] = (0, react.useState)("teach");
|
|
2630
|
+
const [error, setError] = (0, react.useState)(null);
|
|
2631
|
+
const [armedNote, setArmedNote] = (0, react.useState)(null);
|
|
2632
|
+
(0, react.useEffect)(() => {
|
|
2633
|
+
if (armedNote === null) return;
|
|
2634
|
+
const disarm = () => {
|
|
2635
|
+
setArmedNote(null);
|
|
2636
|
+
};
|
|
2637
|
+
const onKey = (e) => {
|
|
2638
|
+
if (e.key === "Escape") disarm();
|
|
2639
|
+
};
|
|
2640
|
+
window.addEventListener("click", disarm);
|
|
2641
|
+
window.addEventListener("keydown", onKey);
|
|
2642
|
+
return () => {
|
|
2643
|
+
window.removeEventListener("click", disarm);
|
|
2644
|
+
window.removeEventListener("keydown", onKey);
|
|
2645
|
+
};
|
|
2646
|
+
}, [armedNote]);
|
|
2647
|
+
const fire = (action) => {
|
|
2648
|
+
action.then(() => {
|
|
2649
|
+
setError(null);
|
|
2650
|
+
}, (err) => {
|
|
2651
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2652
|
+
});
|
|
2653
|
+
};
|
|
2593
2654
|
const proseRef = (0, react.useRef)(null);
|
|
2594
2655
|
const diagRef = (0, react.useRef)(null);
|
|
2595
2656
|
(0, react.useEffect)(() => {
|
|
@@ -2645,11 +2706,24 @@ window.__ModuleLoader__.load({
|
|
|
2645
2706
|
}, (0, react.createElement)("div", { className: "lks-zone-h" }, tr(labelKey)), ...lesson.notes.filter((n) => n.zone === zone).map((n) => (0, react.createElement)("div", {
|
|
2646
2707
|
key: n.id,
|
|
2647
2708
|
className: "lks-note"
|
|
2648
|
-
}, (0, react.createElement)("span", { className: "lks-note-src" }, n.source), (0, react.createElement)("
|
|
2709
|
+
}, (0, react.createElement)("span", { className: "lks-note-src" }, n.source), (0, react.createElement)("button", {
|
|
2710
|
+
className: `lks-note-del${armedNote === n.id ? " armed" : ""}`,
|
|
2711
|
+
title: armedNote === n.id ? tr("note.delete.confirm") : tr("note.delete"),
|
|
2712
|
+
"aria-label": armedNote === n.id ? tr("note.delete.confirm") : tr("note.delete"),
|
|
2713
|
+
onClick: (e) => {
|
|
2714
|
+
e.stopPropagation();
|
|
2715
|
+
if (armedNote !== n.id) {
|
|
2716
|
+
setArmedNote(n.id);
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
setArmedNote(null);
|
|
2720
|
+
fire(deleteNote(lesson.lessonId, n.id));
|
|
2721
|
+
}
|
|
2722
|
+
}, (0, react.createElement)(IconTrashOutline16, { size: 12 })), (0, react.createElement)("div", { className: "lks-note-title" }, n.title), (0, react.createElement)("div", {
|
|
2649
2723
|
className: "lks-note-text",
|
|
2650
2724
|
dangerouslySetInnerHTML: { __html: renderMarkdown(n.text) }
|
|
2651
2725
|
}), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `“${n.quote}”`) : null))))));
|
|
2652
|
-
return (0, react.createElement)("div", { className: "lks-col lks-col-bb" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.bb")), body);
|
|
2726
|
+
return (0, react.createElement)("div", { className: "lks-col lks-col-bb" }, (0, react.createElement)("div", { className: "lks-colhead" }, tr("col.bb")), body, (0, react.createElement)(ActionError, { error }));
|
|
2653
2727
|
}
|
|
2654
2728
|
//#endregion
|
|
2655
2729
|
//#region src/client/starter.tsx
|
|
@@ -2669,8 +2743,9 @@ window.__ModuleLoader__.load({
|
|
|
2669
2743
|
* @returns the component for `conversation.input.left`.
|
|
2670
2744
|
*/
|
|
2671
2745
|
function studyStartButton(ctx) {
|
|
2672
|
-
return function StudyStartButton(
|
|
2673
|
-
|
|
2746
|
+
return function StudyStartButton(props) {
|
|
2747
|
+
const machine = props.useSession?.((s) => s);
|
|
2748
|
+
if ((props.session?.blank ?? machine?.blank) !== true) return null;
|
|
2674
2749
|
return (0, react.createElement)(Inner, {
|
|
2675
2750
|
key: "inner",
|
|
2676
2751
|
ctx
|
|
@@ -2693,7 +2768,7 @@ window.__ModuleLoader__.load({
|
|
|
2693
2768
|
if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`);
|
|
2694
2769
|
const { path } = await area.json();
|
|
2695
2770
|
const workspace = await ctx.workspaces.create({ path });
|
|
2696
|
-
const sessionId = await ctx.workspaces.connectWorkspace(workspace.workspaceId);
|
|
2771
|
+
const sessionId = ctx.workspaces.connectWorkspace !== void 0 ? await ctx.workspaces.connectWorkspace(workspace.workspaceId) : await ctx.sessions.create({ workspaceId: workspace.workspaceId });
|
|
2697
2772
|
const actx = ctx.sessions.scope(sessionId);
|
|
2698
2773
|
const face = actx === void 0 ? void 0 : ctx.sessions.sessionOf(actx);
|
|
2699
2774
|
if (face === void 0) throw new Error("study session is not addressable yet");
|