dsh-plugin-lookatstudy 0.12.1 → 0.12.2
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 +129 -78
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +6 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -106,7 +106,7 @@ window.__ModuleLoader__.load({
|
|
|
106
106
|
.lks-sec{font-size:12px;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em;margin:14px 0 4px}
|
|
107
107
|
/* Collapsible section head: a real button (keyboard can toggle it), sticky so
|
|
108
108
|
the current chapter stays identified while 4000px of nodes scroll under it. */
|
|
109
|
-
.lks-sechead{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);text-
|
|
109
|
+
.lks-sechead{display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);text-align:left;padding:6px 4px;margin:14px 0 4px;border-radius:6px;position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-base)}
|
|
110
110
|
.lks-sechead:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
111
111
|
.lks-sechead:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
112
112
|
.lks-sechead-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -261,6 +261,10 @@ window.__ModuleLoader__.load({
|
|
|
261
261
|
.lks-dock-streak{color:var(--dsw-alias-state-business-primary)}
|
|
262
262
|
.lks-dockseg.lks-muted{color:var(--dsw-alias-label-secondary)}
|
|
263
263
|
|
|
264
|
+
/* concept map legend (amber = weak) */
|
|
265
|
+
.lks-cmap-legend{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--dsw-alias-label-secondary);margin-top:4px}
|
|
266
|
+
.lks-cmap-legend i{width:10px;height:10px;border-radius:3px;background:var(--dsw-alias-state-warn-tertiary,#fef5e7);border:1px solid var(--dsw-alias-state-warn-primary,#dd8629)}
|
|
267
|
+
|
|
264
268
|
/* keyed tool.call.toolview cards (conversation tab tool rows) */
|
|
265
269
|
.lks-tv{display:flex;flex-direction:column;gap:4px;padding:6px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-size:13px}
|
|
266
270
|
.lks-tv.err{border-color:var(--dsw-alias-state-error-primary)}
|
|
@@ -269,6 +273,11 @@ window.__ModuleLoader__.load({
|
|
|
269
273
|
.lks-tv-chip.ok{background:var(--dsw-alias-state-success-tertiary);color:var(--lks-ok-ink)}
|
|
270
274
|
.lks-tv-chip.bad{background:var(--dsw-alias-state-error-tertiary);color:var(--lks-err-ink)}
|
|
271
275
|
.lks-tv-line{color:var(--dsw-alias-label-secondary);line-height:1.6;white-space:pre-wrap}
|
|
276
|
+
/* exam star card + the failure moment */
|
|
277
|
+
.lks-tv-stars{display:inline-flex;align-items:center;gap:5px;font-size:13px;font-weight:600;color:var(--lks-warn-ink)}
|
|
278
|
+
.lks-tv-stars .dim{opacity:.22}
|
|
279
|
+
.lks-tv-stars-label{font-weight:400;color:var(--dsw-alias-label-secondary)}
|
|
280
|
+
.lks-tv-failnote{margin-top:4px;font-size:12.5px;color:var(--dsw-alias-label-secondary);line-height:1.6}
|
|
272
281
|
`;
|
|
273
282
|
/**
|
|
274
283
|
* Inject the study stylesheet into `document.head` (idempotent).
|
|
@@ -1512,77 +1521,6 @@ window.__ModuleLoader__.load({
|
|
|
1512
1521
|
return hubId;
|
|
1513
1522
|
}
|
|
1514
1523
|
//#endregion
|
|
1515
|
-
//#region src/client/diagrams.ts
|
|
1516
|
-
/**
|
|
1517
|
-
* Blackboard diagram views (SPEC Phase 2): the lesson concept map (ELK layered
|
|
1518
|
-
* layout via the vendored cmap-elk-layout + CDN elkjs, draw.io-flavored SVG
|
|
1519
|
-
* skin rendered plugin-side). Degrades honestly: any CDN/ELK failure leaves a
|
|
1520
|
-
* one-line notice instead of a broken pane.
|
|
1521
|
-
*
|
|
1522
|
-
* The lesson mind map view was RETIRED with upstream v0.26.0 (2026-09-01
|
|
1523
|
-
* port): body text that has headings already shows its structure, text without
|
|
1524
|
-
* headings yields a first-line-truncated node graph nobody can read — both
|
|
1525
|
-
* lose — and this concept map already covers the semantic-structure need at
|
|
1526
|
-
* higher quality. Its three CDN imports went with it.
|
|
1527
|
-
*/
|
|
1528
|
-
/** Lesson concept map: the lesson node + its concepts (weak ones accented),
|
|
1529
|
-
* laid out by the vendored ELK pipeline, skinned draw.io-style (plugin-original,
|
|
1530
|
-
* upstream's skin is coupled to its token pipeline). Mastery shades the nodes. */
|
|
1531
|
-
async function renderLessonConceptMap(container, lessonTitle, concepts) {
|
|
1532
|
-
const layout = await layoutConceptMap([{
|
|
1533
|
-
id: "lesson",
|
|
1534
|
-
label: lessonTitle
|
|
1535
|
-
}, ...concepts.map((c) => ({
|
|
1536
|
-
id: `c-${c.title}`,
|
|
1537
|
-
label: c.title
|
|
1538
|
-
}))], concepts.map((c) => ({
|
|
1539
|
-
from: "lesson",
|
|
1540
|
-
to: `c-${c.title}`
|
|
1541
|
-
})));
|
|
1542
|
-
const masteryBy = new Map(concepts.map((c) => [`c-${c.title}`, c.masteryPct]));
|
|
1543
|
-
const ns = "http://www.w3.org/2000/svg";
|
|
1544
|
-
const svg = document.createElementNS(ns, "svg");
|
|
1545
|
-
svg.setAttribute("width", String(Math.max(320, layout.width)));
|
|
1546
|
-
svg.setAttribute("height", String(Math.max(200, layout.height)));
|
|
1547
|
-
svg.setAttribute("viewBox", `0 0 ${layout.width} ${layout.height}`);
|
|
1548
|
-
svg.setAttribute("style", "max-width:100%;background:var(--dsw-surface-1, #fff);border:1px solid var(--dsw-border-1, #d0d7de);border-radius:8px");
|
|
1549
|
-
for (const e of layout.edges) {
|
|
1550
|
-
const path = document.createElementNS(ns, "path");
|
|
1551
|
-
path.setAttribute("d", e.pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" "));
|
|
1552
|
-
path.setAttribute("fill", "none");
|
|
1553
|
-
path.setAttribute("stroke", "var(--dsw-border-2, #8b949e)");
|
|
1554
|
-
path.setAttribute("stroke-width", "1.5");
|
|
1555
|
-
svg.append(path);
|
|
1556
|
-
}
|
|
1557
|
-
for (const n of layout.nodes) {
|
|
1558
|
-
const g = document.createElementNS(ns, "g");
|
|
1559
|
-
g.setAttribute("transform", `translate(${n.x},${n.y})`);
|
|
1560
|
-
const rect = document.createElementNS(ns, "rect");
|
|
1561
|
-
const isLesson = n.id === "lesson";
|
|
1562
|
-
const mastery = masteryBy.get(n.id);
|
|
1563
|
-
rect.setAttribute("width", String(n.w));
|
|
1564
|
-
rect.setAttribute("height", String(n.h));
|
|
1565
|
-
rect.setAttribute("rx", "6");
|
|
1566
|
-
rect.setAttribute("fill", mastery !== void 0 && mastery < 70 ? "#fff3d9" : "var(--dsw-surface-2, #f6f8fa)");
|
|
1567
|
-
rect.setAttribute("stroke", isLesson ? "var(--dsw-accent-1, #0969da)" : "var(--dsw-border-2, #8b949e)");
|
|
1568
|
-
rect.setAttribute("stroke-width", isLesson ? "2" : "1");
|
|
1569
|
-
g.append(rect);
|
|
1570
|
-
n.lines.forEach((line, li) => {
|
|
1571
|
-
const label = document.createElementNS(ns, "text");
|
|
1572
|
-
label.setAttribute("x", String(n.w / 2));
|
|
1573
|
-
label.setAttribute("y", String(n.h / 2 - (n.lines.length - 1) * 7 + li * 14 + 4));
|
|
1574
|
-
label.setAttribute("text-anchor", "middle");
|
|
1575
|
-
label.setAttribute("font-size", isLesson ? "13" : "12");
|
|
1576
|
-
label.setAttribute("font-weight", isLesson ? "600" : "400");
|
|
1577
|
-
label.setAttribute("fill", "var(--dsw-ink-1, #1f2328)");
|
|
1578
|
-
label.textContent = line;
|
|
1579
|
-
g.append(label);
|
|
1580
|
-
});
|
|
1581
|
-
svg.append(g);
|
|
1582
|
-
}
|
|
1583
|
-
container.append(svg);
|
|
1584
|
-
}
|
|
1585
|
-
//#endregion
|
|
1586
1524
|
//#region src/client/locale.ts
|
|
1587
1525
|
/** Simplified Chinese dictionary (also the fallback locale). */
|
|
1588
1526
|
const ZH = {
|
|
@@ -1661,11 +1599,15 @@ window.__ModuleLoader__.load({
|
|
|
1661
1599
|
"bb.empty": "黑板还空着",
|
|
1662
1600
|
"bb.empty.hint": "在左侧课程树选择一课",
|
|
1663
1601
|
"bb.mastery": "掌握度 {pct}%",
|
|
1602
|
+
"cmap.legend": "琥珀色 = 薄弱知识点(掌握度 <70%)",
|
|
1603
|
+
"cmap.node.pct": "掌握度 {pct}%",
|
|
1664
1604
|
"bb.strategy": "本课学法",
|
|
1665
1605
|
"bb.fallback.cmap.empty": "概念图需要先由导师定义本课概念",
|
|
1666
1606
|
"bb.fallback.cmap": "概念图渲染不可用(布局引擎加载失败) — 已回退讲解视图",
|
|
1667
1607
|
"bb.notes": "笔记",
|
|
1668
1608
|
"bb.notes.empty": "这一课还没有笔记",
|
|
1609
|
+
"tv.exam.stars": "本次 {n}★(历史最佳 {best}★)",
|
|
1610
|
+
"tv.exam.fail": "这轮没拿到星——错题就是下一课的地图,从每题的解析看起。",
|
|
1669
1611
|
"active.off.title": "注册 study 工具并载入导师人格,之后普通对话和现在一样",
|
|
1670
1612
|
"active.on.title": "注销 study 工具与导师人格;学习进度保留,可随时重新开启",
|
|
1671
1613
|
"active.off": "▶ 开始学习",
|
|
@@ -1676,8 +1618,8 @@ window.__ModuleLoader__.load({
|
|
|
1676
1618
|
"settings.mode.hint": "导师的讲解风格;也可在学习页随时切换",
|
|
1677
1619
|
"settings.studyMode": "学习模式",
|
|
1678
1620
|
"settings.studyMode.hint": "开启后注册 study_* 工具并载入导师人格;进度与笔记始终保留",
|
|
1679
|
-
"settings.on": "
|
|
1680
|
-
"settings.off": "
|
|
1621
|
+
"settings.on": "状态:已开启",
|
|
1622
|
+
"settings.off": "状态:已关闭",
|
|
1681
1623
|
"settings.turnOn": "开启",
|
|
1682
1624
|
"settings.turnOff": "关闭",
|
|
1683
1625
|
"settings.stats": "学习统计",
|
|
@@ -1777,11 +1719,15 @@ window.__ModuleLoader__.load({
|
|
|
1777
1719
|
"bb.empty": "The blackboard is empty",
|
|
1778
1720
|
"bb.empty.hint": "Pick a lesson in the course tree on the left",
|
|
1779
1721
|
"bb.mastery": "mastery {pct}%",
|
|
1722
|
+
"cmap.legend": "Amber = weak concept (mastery < 70%)",
|
|
1723
|
+
"cmap.node.pct": "mastery {pct}%",
|
|
1780
1724
|
"bb.strategy": "Approach",
|
|
1781
1725
|
"bb.fallback.cmap.empty": "The concept map needs the tutor to define this lesson's concepts first",
|
|
1782
1726
|
"bb.fallback.cmap": "Concept map unavailable (layout engine failed to load) — fell back to the teach view",
|
|
1783
1727
|
"bb.notes": "Notes",
|
|
1784
1728
|
"bb.notes.empty": "No notes for this lesson yet",
|
|
1729
|
+
"tv.exam.stars": "This attempt {n}★ (best {best}★)",
|
|
1730
|
+
"tv.exam.fail": "No stars this round — the wrong answers are the map for the next one; start with their explanations.",
|
|
1785
1731
|
"active.off.title": "Register the study tools and load the tutor persona; plain chats stay as they are",
|
|
1786
1732
|
"active.on.title": "Retire the study tools and tutor persona; progress is kept and you can re-enable anytime",
|
|
1787
1733
|
"active.off": "▶ Start learning",
|
|
@@ -1792,8 +1738,8 @@ window.__ModuleLoader__.load({
|
|
|
1792
1738
|
"settings.mode.hint": "The tutor's explanation style; switchable anytime in the study tab too",
|
|
1793
1739
|
"settings.studyMode": "Study mode",
|
|
1794
1740
|
"settings.studyMode.hint": "On registers the study_* tools and the tutor persona; progress and notes are always kept",
|
|
1795
|
-
"settings.on": "
|
|
1796
|
-
"settings.off": "
|
|
1741
|
+
"settings.on": "Status: on",
|
|
1742
|
+
"settings.off": "Status: off",
|
|
1797
1743
|
"settings.turnOn": "Turn on",
|
|
1798
1744
|
"settings.turnOff": "Turn off",
|
|
1799
1745
|
"settings.stats": "Study stats",
|
|
@@ -1879,6 +1825,86 @@ window.__ModuleLoader__.load({
|
|
|
1879
1825
|
return activeT(key, params);
|
|
1880
1826
|
}
|
|
1881
1827
|
//#endregion
|
|
1828
|
+
//#region src/client/diagrams.ts
|
|
1829
|
+
/**
|
|
1830
|
+
* Blackboard diagram views (SPEC Phase 2): the lesson concept map (ELK layered
|
|
1831
|
+
* layout via the vendored cmap-elk-layout + CDN elkjs, draw.io-flavored SVG
|
|
1832
|
+
* skin rendered plugin-side). Degrades honestly: any CDN/ELK failure leaves a
|
|
1833
|
+
* one-line notice instead of a broken pane.
|
|
1834
|
+
*
|
|
1835
|
+
* The lesson mind map view was RETIRED with upstream v0.26.0 (2026-09-01
|
|
1836
|
+
* port): body text that has headings already shows its structure, text without
|
|
1837
|
+
* headings yields a first-line-truncated node graph nobody can read — both
|
|
1838
|
+
* lose — and this concept map already covers the semantic-structure need at
|
|
1839
|
+
* higher quality. Its three CDN imports went with it.
|
|
1840
|
+
*/
|
|
1841
|
+
/** Lesson concept map: the lesson node + its concepts (weak ones accented),
|
|
1842
|
+
* laid out by the vendored ELK pipeline, skinned draw.io-style (plugin-original,
|
|
1843
|
+
* upstream's skin is coupled to its token pipeline). Mastery shades the nodes. */
|
|
1844
|
+
async function renderLessonConceptMap(container, lessonTitle, concepts) {
|
|
1845
|
+
const layout = await layoutConceptMap([{
|
|
1846
|
+
id: "lesson",
|
|
1847
|
+
label: lessonTitle
|
|
1848
|
+
}, ...concepts.map((c) => ({
|
|
1849
|
+
id: `c-${c.title}`,
|
|
1850
|
+
label: c.title
|
|
1851
|
+
}))], concepts.map((c) => ({
|
|
1852
|
+
from: "lesson",
|
|
1853
|
+
to: `c-${c.title}`
|
|
1854
|
+
})));
|
|
1855
|
+
const masteryBy = new Map(concepts.map((c) => [`c-${c.title}`, c.masteryPct]));
|
|
1856
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
1857
|
+
const svg = document.createElementNS(ns, "svg");
|
|
1858
|
+
svg.setAttribute("width", String(Math.max(320, layout.width)));
|
|
1859
|
+
svg.setAttribute("height", String(Math.max(200, layout.height)));
|
|
1860
|
+
svg.setAttribute("viewBox", `0 0 ${layout.width} ${layout.height}`);
|
|
1861
|
+
svg.setAttribute("style", "max-width:100%;background:var(--dsw-alias-bg-layer-1, #fff);border:1px solid var(--dsw-alias-border-l2, #d0d7de);border-radius:8px");
|
|
1862
|
+
for (const e of layout.edges) {
|
|
1863
|
+
const path = document.createElementNS(ns, "path");
|
|
1864
|
+
path.setAttribute("d", e.pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" "));
|
|
1865
|
+
path.setAttribute("fill", "none");
|
|
1866
|
+
path.setAttribute("stroke", "var(--dsw-alias-border-l2, #8b949e)");
|
|
1867
|
+
path.setAttribute("stroke-width", "1.5");
|
|
1868
|
+
svg.append(path);
|
|
1869
|
+
}
|
|
1870
|
+
for (const n of layout.nodes) {
|
|
1871
|
+
const g = document.createElementNS(ns, "g");
|
|
1872
|
+
g.setAttribute("transform", `translate(${n.x},${n.y})`);
|
|
1873
|
+
const rect = document.createElementNS(ns, "rect");
|
|
1874
|
+
const isLesson = n.id === "lesson";
|
|
1875
|
+
const mastery = masteryBy.get(n.id);
|
|
1876
|
+
rect.setAttribute("width", String(n.w));
|
|
1877
|
+
rect.setAttribute("height", String(n.h));
|
|
1878
|
+
rect.setAttribute("rx", "6");
|
|
1879
|
+
rect.setAttribute("fill", mastery !== void 0 && mastery < 70 ? "var(--dsw-alias-state-warn-tertiary, #fef5e7)" : "var(--dsw-alias-bg-layer-2, #f6f8fa)");
|
|
1880
|
+
rect.setAttribute("stroke", isLesson ? "var(--dsw-alias-state-business-primary, #0969da)" : "var(--dsw-alias-border-l2, #8b949e)");
|
|
1881
|
+
rect.setAttribute("stroke-width", isLesson ? "2" : "1");
|
|
1882
|
+
g.append(rect);
|
|
1883
|
+
n.lines.forEach((line, li) => {
|
|
1884
|
+
const label = document.createElementNS(ns, "text");
|
|
1885
|
+
label.setAttribute("x", String(n.w / 2));
|
|
1886
|
+
label.setAttribute("y", String(n.h / 2 - (n.lines.length - 1) * 7 + li * 14 + 4));
|
|
1887
|
+
label.setAttribute("text-anchor", "middle");
|
|
1888
|
+
label.setAttribute("font-size", isLesson ? "13" : "12");
|
|
1889
|
+
label.setAttribute("font-weight", isLesson ? "600" : "400");
|
|
1890
|
+
label.setAttribute("fill", "var(--dsw-alias-label-primary, #1f2328)");
|
|
1891
|
+
label.textContent = line;
|
|
1892
|
+
g.append(label);
|
|
1893
|
+
});
|
|
1894
|
+
const title = document.createElementNS(ns, "title");
|
|
1895
|
+
const label = n.lines.join(" ");
|
|
1896
|
+
title.textContent = isLesson || mastery === void 0 ? label : `${label} · ${tr("cmap.node.pct", { pct: mastery })}`;
|
|
1897
|
+
g.append(title);
|
|
1898
|
+
svg.append(g);
|
|
1899
|
+
}
|
|
1900
|
+
container.append(svg);
|
|
1901
|
+
const legend = document.createElement("span");
|
|
1902
|
+
legend.className = "lks-cmap-legend";
|
|
1903
|
+
const swatch = document.createElement("i");
|
|
1904
|
+
legend.append(swatch, document.createTextNode(tr("cmap.legend")));
|
|
1905
|
+
container.append(legend);
|
|
1906
|
+
}
|
|
1907
|
+
//#endregion
|
|
1882
1908
|
//#region src/client/views.tsx
|
|
1883
1909
|
/**
|
|
1884
1910
|
* The study tab: one `conversation.view` entry rendering the whole plugin —
|
|
@@ -2884,9 +2910,34 @@ window.__ModuleLoader__.load({
|
|
|
2884
2910
|
function StudyDueView({ toolName, block }) {
|
|
2885
2911
|
return studyToolCard(toolName, block);
|
|
2886
2912
|
}
|
|
2887
|
-
/**
|
|
2913
|
+
/** Stars from the exam card's presentationMeta line (`N★`); null when absent. Pure. */
|
|
2914
|
+
function examStars(block) {
|
|
2915
|
+
const lines = metaLines(block);
|
|
2916
|
+
for (const line of lines) {
|
|
2917
|
+
const m = /^([0-3])★\s*\(?(?:best\s*([0-3])★)?/.exec(line.trim()) ?? /^(?:本次\s*)?([0-3])★/.exec(line.trim());
|
|
2918
|
+
if (m !== null) return {
|
|
2919
|
+
stars: Number(m[1]),
|
|
2920
|
+
best: Number(m[2] ?? m[1])
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
return null;
|
|
2924
|
+
}
|
|
2925
|
+
/** study_exam_result star card — and the failure moment finally gets its line. */
|
|
2888
2926
|
function StudyExamView({ toolName, block }) {
|
|
2889
|
-
|
|
2927
|
+
const r = examStars(block);
|
|
2928
|
+
const stars = r === null ? null : (0, react.createElement)("span", { className: "lks-tv-stars" }, [
|
|
2929
|
+
0,
|
|
2930
|
+
1,
|
|
2931
|
+
2
|
|
2932
|
+
].map((i) => (0, react.createElement)("span", {
|
|
2933
|
+
key: i,
|
|
2934
|
+
className: i < r.stars ? "full" : "dim"
|
|
2935
|
+
}, "★")), (0, react.createElement)("span", { className: "lks-tv-stars-label" }, tr("tv.exam.stars", {
|
|
2936
|
+
n: r.stars,
|
|
2937
|
+
best: r.best
|
|
2938
|
+
})));
|
|
2939
|
+
const fail = r !== null && r.stars === 0 ? (0, react.createElement)("div", { className: "lks-tv-failnote" }, tr("tv.exam.fail")) : null;
|
|
2940
|
+
return studyToolCard(toolName, block, (0, react.createElement)("span", null, stars, fail));
|
|
2890
2941
|
}
|
|
2891
2942
|
//#endregion
|
|
2892
2943
|
//#region src/client/index.ts
|