dsh-plugin-deck-studio 0.2.12 → 0.2.13

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/client.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * 数据全部由宿主半聚合好(浏览器半只做展示,不重算业务口径)。
6
6
  *
7
7
  * 形态:主区「Deck 工作台」面板(iframe 复用 /api/deck-studio.app?embed=1)。
8
- * 启动后自动打开(零点击进入),左侧 foote 只保留一个「Deckline」入口用于重新打开;
9
- * 不再注册右侧 dockkit 观测标签页(F-63:入口太多且丑)。
8
+ * 启动后自动打开(零点击进入),侧栏只保留一个带图标的「Deckline」面板入口(与其它插件同形态);
9
+ * 不再注册右侧 dockkit 观测标签页(F-63:入口太多且丑),也不再占用侧栏脚部按钮。
10
10
  * 宿主没有 layout 主区面板契约时降级为 main 面板 + sidebar.panellist。
11
11
  *
12
12
  * Chatbox 说明(ADR-0006 D1):对话入口就是 DSH 原生 Chat——选择
@@ -235,9 +235,34 @@ div[data-sidebar-collapsed]{grid-template-columns:0px minmax(0px,1fr) 0px!import
235
235
  );
236
236
  }
237
237
 
238
- /** 侧栏 glyph(降级 panellist / footer 用)。 */
239
- function DeckGlyph() {
240
- return h("span", { style: { fontSize: 13 } }, "▦");
238
+ /** 侧栏图标(panellist 行用):叠卡片 + 星芒,单色 currentColor,跟随主题深浅色。 */
239
+ function DeckGlyph({ size }) {
240
+ const edge = size || 16;
241
+ return h("svg", {
242
+ width: edge,
243
+ height: edge,
244
+ viewBox: "0 0 16 16",
245
+ fill: "none",
246
+ "aria-hidden": "true",
247
+ style: { display: "block" },
248
+ },
249
+ h("rect", {
250
+ x: 4.8, y: 2.4, width: 8.2, height: 6.2, rx: 1.5,
251
+ stroke: "currentColor", strokeWidth: 1.2, opacity: 0.55,
252
+ }),
253
+ h("rect", {
254
+ x: 2.4, y: 5.2, width: 9.2, height: 6.8, rx: 1.6,
255
+ stroke: "currentColor", strokeWidth: 1.35,
256
+ }),
257
+ h("path", {
258
+ d: "M4.6 7.6h4.6M4.6 9.6h3",
259
+ stroke: "currentColor", strokeWidth: 1.2, strokeLinecap: "round",
260
+ }),
261
+ h("path", {
262
+ d: "M12.5 9.9l.45 1.15 1.15.45-1.15.45-.45 1.15-.45-1.15-1.15-.45 1.15-.45z",
263
+ fill: "currentColor",
264
+ }),
265
+ );
241
266
  }
242
267
 
243
268
  function MainPanel() {
@@ -535,17 +560,16 @@ div[data-sidebar-collapsed]{grid-template-columns:0px minmax(0px,1fr) 0px!import
535
560
  return () => window.clearTimeout(timer);
536
561
  }, "deck-studio: auto-open workbench");
537
562
 
538
- // 唯一入口:侧栏脚部一个「Deckline」按钮(重新打开工作台);→ 对话用工作台里的「← 回到对话」。
563
+ // 唯一入口:侧栏面板行「Deckline」(与 plugins / schedules 等插件同一种形态,带图标);
564
+ // → 对话用工作台里的「← 回到对话」。
539
565
  if (hasRightbar) {
540
- ctx.effect(
541
- () => ctx.slots.inject("sidebar.footer.action", () =>
542
- ctx.slots.register({ name: "sidebar.footer.action", id: `${PLUGIN_ID}-workbench` }, (props) => {
543
- const button = h("button", { onClick: openWorkbench, title: "打开 Deck 工作台(对话在 DSH 原生 Chat)", style: { cursor: "pointer" } },
544
- h("span", { style: { fontSize: 13 } }, "▤"), props?.wide ? h("span", { style: { marginLeft: 4 } }, "Deckline") : null);
545
- return button;
546
- })),
547
- "deck-studio: workbench footer button",
548
- );
566
+ if (layout?.selectPanel) {
567
+ ctx.effect(
568
+ () => ctx.slots.inject("sidebar.panellist", () =>
569
+ ctx.slots.register({ name: "sidebar.panellist", id: WORKBENCH_KEY, order: 46, label: () => "Deckline" }, DeckGlyph)),
570
+ "deck-studio: workbench sidebar entry",
571
+ );
572
+ }
549
573
  ctx.logger?.info?.("deck-studio client: workbench-only mode");
550
574
  } else {
551
575
  ctx.effect(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-deck-studio",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Deckline:DSH 的 image deck / PPT 工作台插件——原生 Chat + 整页图片 deck 工作区 + 设计风格库;用对话做 PPT、按页出图、版本可追踪、导出 PDF/PPTX。",
5
5
  "icon": "assets/icon.svg",
6
6
  "homepage": "https://github.com/yuna78/dsh-deck-studio#readme",
@@ -43,7 +43,7 @@ node -v && pnpm -v && dsh --version
43
43
  dsh --profile deckline --from-default-profile web
44
44
 
45
45
  # 2) install the plugin; dsh plugin add adds the bundle to the profile and enables it
46
- dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.12
46
+ dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.13
47
47
  ```
48
48
 
49
49
  > Do not skip step 1: the `deckline` profile is **never created automatically**. Running
@@ -181,7 +181,7 @@ From zero to 13090, run these three commands in order (options for steps 1–2 a
181
181
  dsh --profile deckline --from-default-profile web
182
182
 
183
183
  # 2) install the plugin (adds it to that profile and enables it)
184
- dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.12
184
+ dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.13
185
185
 
186
186
  # 3) start
187
187
  dsh --profile deckline --port 13090
@@ -45,7 +45,7 @@ node -v && pnpm -v && dsh --version
45
45
  dsh --profile deckline --from-default-profile web
46
46
 
47
47
  # 2) 装插件;dsh plugin add 会自动把组合包加入 profile 并默认启用
48
- dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.12
48
+ dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.13
49
49
  ```
50
50
 
51
51
  > 第 1 步不能省:`deckline` profile **不会**被自动创建。跳过第 1 步直接 `dsh --profile deckline --port 13090`,
@@ -54,7 +54,7 @@ dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.12
54
54
 
55
55
  > 国内网络直连 `registry.npmjs.org` 可能很慢(20 MB 包实测约 19 KB/s,要十几分钟)。换镜像会快很多:
56
56
  > ```sh
57
- > npm_config_registry=https://registry.npmmirror.com dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.12
57
+ > npm_config_registry=https://registry.npmmirror.com dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.13
58
58
  > ```
59
59
  > 或一次性 `npm config set registry https://registry.npmmirror.com`。
60
60
 
@@ -75,7 +75,7 @@ DSH 目前没有官方插件市场,市场是「索引源」模式:添加下
75
75
  https://raw.githubusercontent.com/yuna78/dsh-deck-studio/main/market-index.json
76
76
  ```
77
77
 
78
- 3. 搜索 `Deckline` 或 `PPT` → **安装**,会安装 `dsh-plugin-deck-studio@0.2.12`(索引里声明了国内镜像 registry)。
78
+ 3. 搜索 `Deckline` 或 `PPT` → **安装**,会安装 `dsh-plugin-deck-studio@0.2.13`(索引里声明了国内镜像 registry)。
79
79
  4. 之后同 A 的第 3 步启动。
80
80
 
81
81
  索引文件就是仓库根目录的 [`market-index.json`](../market-index.json)(格式 1),新版本发布后更新版本号即可。
@@ -210,7 +210,7 @@ profile "deckline" does not exist
210
210
  dsh --profile deckline --from-default-profile web
211
211
 
212
212
  # 2) 装插件(自动加进该 profile 并启用)
213
- dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.12
213
+ dsh plugin --profile deckline add dsh-plugin-deck-studio@0.2.13
214
214
 
215
215
  # 3) 启动
216
216
  dsh --profile deckline --port 13090
package/ui/workbench.css CHANGED
@@ -238,6 +238,44 @@ body.resizing{user-select:none;cursor:col-resize}
238
238
  .hn-label{flex:1}
239
239
  .home-nav-item em{font-style:normal;font-size:10px;color:#93a0a3;background:#fffdf8;border:1px solid var(--line);border-radius:9px;padding:0 5px}
240
240
  .home-nav-item.active em{color:var(--teal);border-color:#bfe0dc}
241
+ /* F-193/F-194:左栏「关于」单入口(版本 chip + 有新版小圆点) */
242
+ .home-nav-sep{height:1px;background:var(--line);margin:10px 4px 0}
243
+ .hn-dot{width:6px;height:6px;border-radius:50%;background:var(--gold);box-shadow:0 0 0 3px color-mix(in srgb,var(--gold) 18%,transparent)}
244
+ .hn-dot[hidden]{display:none}
245
+ /* F-194:「关于」单页——从上到下:作者 → 公众号 → 版本 → 开源 → 其他项目 */
246
+ .about-stack{display:flex;flex-direction:column;gap:12px;max-width:880px}
247
+ .about-card{border:1px solid var(--line);background:#fffdf8;border-radius:10px;padding:16px 18px;min-width:0}
248
+ .about-card h3{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;margin:0 0 8px;font-family:"Noto Serif SC",Georgia,serif;font-size:16px;color:var(--ink)}
249
+ .about-role{font-family:Inter,-apple-system,sans-serif;font-size:10.5px;font-weight:400;color:#93a0a3;letter-spacing:.02em}
250
+ .about-hero{display:flex;flex-direction:column;gap:14px;background:linear-gradient(180deg,#fffdf8 0%,#faf5e9 100%)}
251
+ .about-profile{display:flex;gap:14px;align-items:center;min-width:0}
252
+ .about-wechat{display:flex;align-items:center;gap:14px;border-top:1px solid var(--line);padding-top:13px}
253
+ .about-wechat-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
254
+ .about-wechat b{font-size:13px;color:var(--teal);line-height:1.5}
255
+ .about-wechat .eyebrow{color:#93a0a3}
256
+ .about-wechat .about-hint{margin:2px 0 0}
257
+ .about-wechat .about-qr{flex:0 0 auto;width:104px;max-width:104px;border-radius:7px}
258
+ .about-avatar{flex:0 0 auto;width:58px;height:58px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-family:"Noto Serif SC",Georgia,serif;font-size:24px;color:#fffdf8;background:linear-gradient(145deg,#0e5563,#08343d);box-shadow:inset 0 0 0 1px #ffffff22}
259
+ .about-profile-body{min-width:0;max-width:560px}
260
+ .about-desc{margin:0 0 8px;color:#5d7276;font-size:12px;line-height:1.65}
261
+ .about-line{margin:0 0 4px;color:#728086;font-size:11.5px}
262
+ .about-line b{color:var(--teal)}
263
+ .about-line code{font-size:11px;color:#5d7276;background:#f3efe4;border:1px solid var(--line);border-radius:5px;padding:1px 5px}
264
+ .about-hint{margin:8px 0 0;color:#93a0a3;font-size:10.5px;line-height:1.6;word-break:break-word}
265
+ .about-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
266
+ .about-actions .btn{font-size:11px}
267
+ .about-state{margin:8px 0 0;color:var(--gold)}
268
+ .about-qr{display:block;width:140px;max-width:140px;aspect-ratio:1;object-fit:contain;border:1px solid var(--line);border-radius:8px;background:#fff}
269
+ .about-qr-empty{display:flex;align-items:center;justify-content:center;width:140px;aspect-ratio:1;color:#93a0a3;font-size:11px;border:1px dashed var(--line);border-radius:8px;background:#f8f5ed;text-align:center;padding:8px}
270
+ .about-project-list{list-style:none;margin:2px 0 0;padding:0}
271
+ .about-project-row{display:flex;gap:14px;align-items:flex-start;justify-content:space-between;padding:12px 0;border-top:1px solid var(--line)}
272
+ .about-project-row:first-child{border-top:0;padding-top:2px}
273
+ .about-project-main{flex:1;min-width:0}
274
+ .about-project-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
275
+ .about-project-head b{font-size:13px;color:var(--ink)}
276
+ .about-badge{flex:0 0 auto;font-size:10px;color:var(--teal);background:#e2f1ef;border:1px solid #bfe0dc;border-radius:9px;padding:1px 7px}
277
+ .about-project-row .about-desc{margin:4px 0 0}
278
+ .about-project-row .btn{flex:0 0 auto}
241
279
  .home-main{padding:26px 32px;overflow-y:auto;min-width:0}
242
280
  .home-greet{padding-bottom:16px;border-bottom:1px solid var(--line);margin-bottom:18px}
243
281
  .home-greet h1{margin:5px 0 4px;font-family:"Noto Serif SC",Georgia,serif;font-size:30px;color:var(--ink);letter-spacing:.01em}
package/ui/workbench.html CHANGED
@@ -134,6 +134,11 @@
134
134
  <button class="home-nav-item" data-section="resources"><span class="hn-icon">◇</span><span class="hn-label">资源库</span><em id="navCountResources">0</em></button>
135
135
  <button class="home-nav-item" data-section="conversations"><span class="hn-icon">☰</span><span class="hn-label">Conversations</span><em id="navCountConvs">0</em></button>
136
136
  <button class="home-nav-item" data-section="manual"><span class="hn-icon">?</span><span class="hn-label">使用手册</span></button>
137
+ <!-- F-193/F-194:关于只留一页入口;版本 chip + 有新版小圆点仍常驻,升级入口不藏在顶栏图标里 -->
138
+ <div class="home-nav-sep" aria-hidden="true"></div>
139
+ <button class="home-nav-item" data-section="about" id="aboutNavBtn" title="关于 Deckline:版本 / 公众号 / 开源地址 / 其他项目">
140
+ <span class="hn-icon">☺</span><span class="hn-label">关于</span><em id="navUpdateVer">—</em><i class="hn-dot" id="navUpdateDot" hidden></i>
141
+ </button>
137
142
  </nav>
138
143
  </aside>
139
144
  <main class="home-main">
@@ -1449,8 +1454,8 @@ async function copyDataDirDiagnostics() {
1449
1454
 
1450
1455
  /* ---------------- 首页(左侧导航 + 右侧分区 blocks) ---------------- */
1451
1456
 
1452
- const HOME_SECTIONS = ["decks", "designs", "resources", "conversations", "manual"];
1453
- const HOME_TITLES = { decks: "Decks", designs: "Designs", resources: "资源库", conversations: "Conversations" };
1457
+ const HOME_SECTIONS = ["decks", "designs", "resources", "conversations", "manual", "about"];
1458
+ const HOME_TITLES = { decks: "Decks", designs: "Designs", resources: "资源库", conversations: "Conversations", manual: "使用手册", about: "关于" };
1454
1459
 
1455
1460
  function homeDateText() {
1456
1461
  const now = new Date();
@@ -1531,6 +1536,7 @@ function renderHome() {
1531
1536
  resources: `${resources.length} 个资源(logo / 素材);点开预览。`,
1532
1537
  conversations: `${sessions.length} 个历史会话;点一个继续聊。`,
1533
1538
  manual: "从新建到导出:截图 + 步骤;找不到就直接在 Chat 里问。",
1539
+ about: "作者 / 公众号 / 版本更新 / 开源地址 / 其他项目,都在这一页。",
1534
1540
  }[section];
1535
1541
  getEl("homeGreetSub").textContent = greet;
1536
1542
 
@@ -1550,8 +1556,22 @@ function renderHome() {
1550
1556
  if (STATE.manualDoc) openManualDoc(box, STATE.manualDoc);
1551
1557
  else renderManualHome(box);
1552
1558
  }
1559
+ else if (section === "about") {
1560
+ box.innerHTML = renderHomeAbout(resources);
1561
+ paintUpdate(); // 版本 chip / 更新状态在关于页也要跟着顶栏走(F-193)
1562
+ }
1553
1563
  else box.innerHTML = renderHomeConversations(sessions, bindings);
1554
1564
 
1565
+ // F-193:「关于」分区里的按钮(检查更新 / 外链)——外链直接开新标签。
1566
+ box.querySelectorAll("[data-about-check]").forEach((node) => node.addEventListener("click", () => checkPluginUpdate()));
1567
+ box.querySelectorAll("[data-about-external]").forEach((node) => node.addEventListener("click", () => {
1568
+ const links = aboutLinks();
1569
+ const kind = node.dataset.aboutExternal;
1570
+ if (kind === "repo") openExternalUrl(links.repo, "还没拿到仓库地址");
1571
+ else if (kind === "issues") openExternalUrl(links.issues, "还没拿到提建议地址");
1572
+ else if (kind === "profile") openExternalUrl(links.profile, "还没拿到 GitHub 主页地址");
1573
+ }));
1574
+
1555
1575
  box.querySelectorAll("[data-home-deck]").forEach((node) => node.addEventListener("click", async () => {
1556
1576
  const deckId = node.dataset.homeDeck;
1557
1577
  // 先等 deck 切完(iframe 里的 Chat 也要加载完),再选默认会话——否则 postMessage 发早了会落到旧会话。
@@ -1711,6 +1731,90 @@ function renderHomeResources(resources) {
1711
1731
  </button>`).join("")}</div>`;
1712
1732
  }
1713
1733
 
1734
+ /* ---------------- F-193 / F-194:首页「关于」单页(作者 → 公众号 → 版本 → 开源 → 其他项目) ---------------- */
1735
+
1736
+ /** 公众号二维码从资源库现取(个人二维码不进仓库);拿不到就降级成说明,不显示破图。 */
1737
+ function aboutWechatQr(resources = []) {
1738
+ return resources.find((resource) => /二维码|qr|微信|wechat/i.test(`${resource?.name || ""} ${resource?.description || ""} ${resource?.url || ""}`)) || null;
1739
+ }
1740
+
1741
+ function renderHomeAbout(resources = []) {
1742
+ const links = aboutLinks();
1743
+ const qr = aboutWechatQr(resources);
1744
+ const versionLine = UPDATE.current ? `v${UPDATE.current}` : "读取中…";
1745
+ const installNote = UPDATE.installMode === "linked"
1746
+ ? "本机是开发链接安装(link:/file:):更新走仓库,界面不执行 pnpm。"
1747
+ : UPDATE.installMode === "npm"
1748
+ ? "npm 安装:有新版可以在这里直接更新,重启 DSH 生效。"
1749
+ : "没识别出安装方式时,可手动 dsh plugin add。";
1750
+ const repoShort = links.repo.replace(/^https?:\/\//, "");
1751
+ const projectRows = [
1752
+ { name: "Deckline · dsh-deck-studio", badge: "本插件", external: "repo", desc: "DSH 的 image deck / PPT 工作台:故事线、逐页 prompt、AI 出图与版本留存、风格库、导出 PDF/PPTX。" },
1753
+ ];
1754
+ if (links.profile) {
1755
+ projectRows.push({
1756
+ name: `GitHub @${links.profile.split("/").pop()}`,
1757
+ badge: "全部项目",
1758
+ external: "profile",
1759
+ desc: "我的开源主页:插件、实验和工具会陆续挂在这里。",
1760
+ });
1761
+ }
1762
+ return `<div class="section-head"><h2>关于</h2><span class="small">作者 · 公众号 · 版本更新 · 开源</span></div>
1763
+ <div class="about-stack">
1764
+ <section class="about-card about-hero">
1765
+ <div class="about-profile">
1766
+ <div class="about-avatar" aria-hidden="true">Y</div>
1767
+ <div class="about-profile-body">
1768
+ <h3>${esc(ABOUT.author)}<span class="about-role">${esc(ABOUT.role)}</span></h3>
1769
+ <p class="about-desc">${esc(ABOUT.intro)}</p>
1770
+ </div>
1771
+ </div>
1772
+ <div class="about-wechat">
1773
+ <div class="about-wechat-text">
1774
+ <div class="eyebrow">微信公众号</div>
1775
+ <b>${esc(ABOUT.account)}</b>
1776
+ <p class="about-hint">${qr ? "扫码关注 / 加我微信" : `把二维码图片放进数据目录的 <code>resources/</code>(资源库),这里会自动显示。`}</p>
1777
+ </div>
1778
+ ${qr
1779
+ ? `<img class="about-qr" src="${esc(qr.url)}" alt="${esc(qr.name || "公众号二维码")}" loading="lazy">`
1780
+ : `<div class="about-qr about-qr-empty">二维码未收录</div>`}
1781
+ </div>
1782
+ </section>
1783
+
1784
+ <section class="about-card">
1785
+ <h3>版本与更新</h3>
1786
+ <p class="about-line">当前版本 <b>${esc(versionLine)}</b></p>
1787
+ <p class="about-desc">${esc(installNote)}</p>
1788
+ <div class="about-actions">
1789
+ <button class="btn" data-about-check>检查新版本</button>
1790
+ </div>
1791
+ <p class="small about-state" data-about-state></p>
1792
+ </section>
1793
+ <section class="about-card">
1794
+ <h3>开源地址与反馈</h3>
1795
+ <p class="about-line"><code>${esc(repoShort)}</code></p>
1796
+ <p class="about-desc">用着不对、想要什么功能,直接提 issue;仓库里能看到版本记录与路线。</p>
1797
+ <div class="about-actions">
1798
+ <button class="btn ghost" data-about-external="repo">仓库地址</button>
1799
+ <button class="btn alt" data-about-external="issues">提交建议 / 问题</button>
1800
+ </div>
1801
+ </section>
1802
+ <section class="about-card">
1803
+ <h3>其他开源项目</h3>
1804
+ <ul class="about-project-list">${projectRows.map((row) => `
1805
+ <li class="about-project-row">
1806
+ <div class="about-project-main">
1807
+ <div class="about-project-head"><b>${esc(row.name)}</b><span class="about-badge">${esc(row.badge)}</span></div>
1808
+ <p class="about-desc">${esc(row.desc)}</p>
1809
+ </div>
1810
+ <button class="btn ghost" data-about-external="${esc(row.external)}">打开</button>
1811
+ </li>`).join("")}
1812
+ </ul>
1813
+ <p class="about-hint">最新列表以 GitHub 主页为准;想第一时间看到更新,关注公众号「${esc(ABOUT.account)}」。</p>
1814
+ </section>
1815
+ </div>`;
1816
+ }
1817
+
1714
1818
  function renderHomeConversations(sessions, bindings = []) {
1715
1819
  const bound = new Map((bindings || []).map((binding) => [binding.sessionId, binding]));
1716
1820
  const targetLabel = (binding) => {
@@ -1813,19 +1917,55 @@ function dlToast(text, ms = 4200) {
1813
1917
  /** F-188:更新状态(版本 / 安装方式 / 仓库链接)由 workbench payload 带下来。 */
1814
1918
  const UPDATE = { current: "", latest: "", hasUpdate: false, installMode: "", repo: "", suggest: "", note: "" };
1815
1919
 
1920
+ /** F-193:左栏「关于」分组用的作者 / 公众号信息;仓库链接优先用包里的 repository,兜底写死公开地址。 */
1921
+ const ABOUT = {
1922
+ author: "Yuna",
1923
+ role: "Deckline 作者",
1924
+ account: "Yuna 的 AI 修炼手册",
1925
+ intro: "把 AI 落到工作现场:用对话把分享、培训、汇报做成整页图片 deck。",
1926
+ fallbackRepo: "https://github.com/yuna78/dsh-deck-studio",
1927
+ };
1928
+
1929
+ function aboutLinks() {
1930
+ const repo = UPDATE.repo || ABOUT.fallbackRepo;
1931
+ const issues = UPDATE.suggest || UPDATE.issues || `${repo.replace(/\/+$/, "")}/issues/new/choose`;
1932
+ const profile = repo.replace(/^https?:\/\/(?:www\.)?github\.com\/([^/]+).*$/i, "https://github.com/$1");
1933
+ return { repo, issues, profile: profile === repo ? "" : profile };
1934
+ }
1935
+
1936
+ /** 外链统一走这里:拿不到地址时给 toast,不让按钮静默没反应。 */
1937
+ function openExternalUrl(url, missingMessage) {
1938
+ if (!url) {
1939
+ dlToast(missingMessage || "链接还没拿到:插件包或运行环境里没有配置地址", 5000);
1940
+ return;
1941
+ }
1942
+ window.open(url, "_blank", "noopener");
1943
+ }
1944
+
1816
1945
  function paintUpdate() {
1817
1946
  const ver = getEl("updateVer");
1818
1947
  if (ver) ver.textContent = UPDATE.current ? `v${UPDATE.current}` : "—";
1819
1948
  const dot = getEl("updateDot");
1820
1949
  if (dot) dot.hidden = !UPDATE.hasUpdate;
1950
+ const title = UPDATE.hasUpdate
1951
+ ? `有新版 v${UPDATE.latest}(当前 v${UPDATE.current})`
1952
+ : UPDATE.installMode === "linked"
1953
+ ? "本机是开发链接安装:更新走 git pull"
1954
+ : "检查更新";
1821
1955
  const btn = getEl("updateBtn");
1822
- if (btn) {
1823
- btn.title = UPDATE.hasUpdate
1824
- ? `有新版 v${UPDATE.latest}(当前 v${UPDATE.current})`
1825
- : UPDATE.installMode === "linked"
1826
- ? "本机是开发链接安装:更新走 git pull"
1827
- : "检查更新";
1828
- }
1956
+ if (btn) btn.title = title;
1957
+ // F-193:左栏「检查新版本」与顶栏 ↻ 共用同一份状态(版本 chip + 有新版小圆点)
1958
+ const navVer = getEl("navUpdateVer");
1959
+ if (navVer) navVer.textContent = UPDATE.current ? `v${UPDATE.current}` : "—";
1960
+ const navDot = getEl("navUpdateDot");
1961
+ if (navDot) navDot.hidden = !UPDATE.hasUpdate;
1962
+ const aboutNavBtn = getEl("aboutNavBtn");
1963
+ if (aboutNavBtn) aboutNavBtn.title = UPDATE.hasUpdate ? `关于 Deckline · ${title}` : "关于 Deckline:版本 / 公众号 / 开源地址 / 其他项目";
1964
+ const aboutState = document.querySelectorAll("[data-about-state]");
1965
+ aboutState.forEach((node) => {
1966
+ // 只在「有新版」时占用这行;「当前版本」上面已经写过了,别重复。
1967
+ node.textContent = UPDATE.hasUpdate ? `发现新版 v${UPDATE.latest}(当前 v${UPDATE.current})` : "";
1968
+ });
1829
1969
  }
1830
1970
 
1831
1971
  function absorbUpdate(payload) {
@@ -5636,7 +5776,7 @@ function backToManual(box) {
5636
5776
  getEl("workspaceView").style.display = "grid";
5637
5777
  switchChatToBoundSession().catch(() => {});
5638
5778
  } else {
5639
- const match = location.hash.match(/^#\/home\/(decks|designs|resources|conversations|manual)/);
5779
+ const match = location.hash.match(/^#\/home\/(decks|designs|resources|conversations|manual|about)/);
5640
5780
  STATE.homeSection = match ? match[1] : "decks";
5641
5781
  showHome();
5642
5782
  }