dsh-tiddlywiki 0.7.4 → 0.8.0
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 +24 -1
- package/lib/client.bundle.js +264 -2
- package/lib/client.js +264 -2
- package/lib/index.js +18 -5
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/editor-popup.ts +127 -121
- package/src/client/panel.ts +12 -1
- package/src/client/settings-page.ts +2 -0
- package/src/client/styles.ts +4 -2
- package/src/client/theme-sync.ts +312 -0
- package/src/host/config.ts +14 -0
- package/src/host/routes.ts +5 -1
- package/src/index.ts +15 -9
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|---|---|
|
|
18
18
|
| 🤖 **Agent 工具** | `tiddlywiki_search` / `get` / `put` / `batch_put` / `rename` / `delete` / `recent` / `list_tags` / `git_sync` / `git_resolve` 十个工具:检索、读写、批量、重命名、删除、git 同步与冲突解决 |
|
|
19
19
|
| 🧭 **内嵌编辑器** | 侧边栏「TiddlyWiki」入口 → 中央列内嵌完整 TW 5 编辑器(**同源代理**,经 DSH origin 访问,Tailscale/内网/域名/HTTPS 均可用) |
|
|
20
|
+
| 🌗 **跟随 DSH 主题** | 嵌入式 TW(中央面板 +「在 TW 中编辑」弹窗)**自适应 DSH 深浅主题**:暗色自动切深色 palette、浅色恢复原 palette;**纯内存切换,不写回 wiki、不进 git**;设置页可关可换深色 palette |
|
|
20
21
|
| 📝 **快速笔记** | 右下角「知识库」悬浮按钮 → 快速笔记卡片:**CodeMirror 6** Markdown 编辑器(语法高亮 + 撤销/重做)、文件上传、多选/自动补全 tag、**草稿自动保存(刷新不丢)**、**「🕘 最近」一键载入旧笔记**,Ctrl+Enter 保存;可整体隐藏 |
|
|
21
22
|
| 🔄 **一键同步** | 「知识库」按钮 →「🔁 同步」:pull → commit → push;FAB 上的状态点实时反映 git 状态(已同步/待提交/可更新/离线) |
|
|
22
23
|
| ⚙️ **设置页** | DSH 设置 →「TiddlyWiki 知识库」:插件/主题/语言管理与运行配置,应用后自动重启 TW |
|
|
@@ -25,6 +26,18 @@
|
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
29
|
+
## 🕘 版本记录
|
|
30
|
+
|
|
31
|
+
> 最近几个主要版本的一句话更新记录(完整变更见 git log / Releases)。
|
|
32
|
+
|
|
33
|
+
- **v0.8.0**(2026-09-04):嵌入式 TW **跟随 DSH 深浅主题**——暗色自动切深色 palette(默认 CupertinoDark)、浅色恢复原 palette,纯内存切换**不写回 wiki、不进 git**,设置页可开关/换深色 palette。
|
|
34
|
+
- **v0.7.2**(2026-09-03):「一键发送到 dsh」改为**工作区优先**——新增 POST `/agent/create` 按 cwd 落入真实 Workspace,新会话不再丢到「未分组」;自动给新笔记补打 `agent-written` 标签并在首页分区展示。
|
|
35
|
+
- **v0.7.0**(2026-09-03):TW 里**一键把当前笔记发送到 dsh 会话**(`/agent/sessions` + `/agent/send` + TW 按钮,可配置开关)。
|
|
36
|
+
- **v0.6.0**(2026-09-03):**同源代理**——嵌入式 TW 编辑器改为经 DSH origin(`/dsh-tiddlywiki/tw/`)访问,Tailscale / 内网 / 域名 / HTTPS 下都能正常加载与编辑。
|
|
37
|
+
- **v0.5.0**(2026-09-03):Agent 工具扩充(搜索 tag/type/since 过滤,`recent` / `list_tags` / `batch_put` / `rename` / `git_resolve`);快速笔记**草稿自动保存**与「🕘 最近」一键载入;右下角统一「知识库」悬浮按钮(快速笔记 + 同步 + TW 面板)。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
28
41
|
## 📦 安装
|
|
29
42
|
|
|
30
43
|
插件随 dsh 插件系统安装,三种方式任选(装完**重启 dsh web** 生效;把 `--profile web` 换成你自己的 profile 名):
|
|
@@ -89,6 +102,12 @@ dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
|
|
|
89
102
|
|
|
90
103
|
**🧭 中央列编辑器** — 侧边栏「TiddlyWiki」按钮开关中央编辑器面板(**同源代理**:iframe 指向 `<DSH origin>/dsh-tiddlywiki/tw/`,由 DSH 转发到回环上的 TW 服务),完整 TW 5 编辑器。
|
|
91
104
|
|
|
105
|
+
**🌗 跟随 DSH 主题** — 中央面板与「✏️ 在 TW 中编辑」弹窗里的 TW **自动跟随 DSH 的深浅主题**(v0.8.0 起,`ui.followDshTheme`,默认开):
|
|
106
|
+
- DSH 处于**暗色**时,TW 的活动 palette 临时切到深色 palette(默认 `$:/palettes/CupertinoDark`);切回**浅色**时自动恢复你原来的 palette;
|
|
107
|
+
- 如果你本来就用着深色 palette(如 SolarizedDark),暗色下不打扰;浅色下也不会把你的选择改掉;
|
|
108
|
+
- 切换是**纯内存**的(写入 `$:/palette` 后同步校准 syncer 的 changeCount,阻止它 PUT 回服务端)——**不写回 wiki、不进 git 历史**,你的 palette 选择与知识库 git 状态始终干净;
|
|
109
|
+
- 开关与深色 palette 可在设置页「常规配置」调整。
|
|
110
|
+
|
|
92
111
|
**📝 快速笔记** — 右下角「**知识库**」悬浮按钮 →「📝 快速笔记」(可折叠):
|
|
93
112
|
- **CodeMirror 6 编辑器**:真正的 Markdown 语法树高亮(标题/列表/代码/链接/表格/任务清单/删除线等,GFM),支持撤销/重做与行内编辑体验;
|
|
94
113
|
- **草稿自动保存**:正文/标题/标签 500ms 防抖写入本地,关掉卡片或刷新页面都不丢;重开自动恢复,可一键「丢弃」;
|
|
@@ -111,7 +130,7 @@ dsh plugin --profile web add link:/path/to/your/dsh-tiddlywiki
|
|
|
111
130
|
| 区块 | 内容 |
|
|
112
131
|
|---|---|
|
|
113
132
|
| 状态/重启 | TW 运行状态 + git 概览 + 「同步」按钮 + 「重启 TW」按钮 |
|
|
114
|
-
| 常规配置 | 快速笔记默认 tag、git 自动 commit/防抖/远端/分支、ui
|
|
133
|
+
| 常规配置 | 快速笔记默认 tag、git 自动 commit/防抖/远端/分支、ui 开关(快速笔记/面板状态/同步按钮——分别控制「知识库」按钮里的对应入口)、**跟随 DSH 主题开关 + 深色 palette**——改了什么保存什么 |
|
|
115
134
|
| 插件管理 | 自带官方插件勾选(可搜索)→ 应用并自动重启 TW |
|
|
116
135
|
| 主题管理 | 自带主题**多选加载 + 单选活动** → 应用并自动重启 TW |
|
|
117
136
|
| 语言管理 | 自带官方语言包勾选(含 zh-Hans 简体)→ 应用并自动重启 TW |
|
|
@@ -182,6 +201,8 @@ TW 子进程只监听 **127.0.0.1 回环**(更安全),**agent 工具、快
|
|
|
182
201
|
showQuickNote: true # 是否显示「知识库」按钮里的「快速笔记」入口
|
|
183
202
|
showPanelStatus: true # 是否显示「知识库」按钮里的 TW 面板/重载入口与状态行
|
|
184
203
|
showSyncButton: true # 是否显示「知识库」按钮里的「同步」入口与 git 状态点
|
|
204
|
+
followDshTheme: true # 嵌入式 TW 是否跟随 DSH 深浅主题(纯内存切换)
|
|
205
|
+
darkPalette: "$:/palettes/CupertinoDark" # DSH 暗色时 TW 使用的深色 palette
|
|
185
206
|
auth:
|
|
186
207
|
username: "" # 默认 loopback 匿名;暴露到非 loopback 时才需要
|
|
187
208
|
password: ""
|
|
@@ -203,6 +224,7 @@ npm install
|
|
|
203
224
|
npm run typecheck # tsc --noEmit
|
|
204
225
|
npm run build # clean + host tsdown + client tsdown + wrap
|
|
205
226
|
npm run selftest # headless:spawn TW → REST 读写 → git → 退出回收
|
|
227
|
+
node scripts/verify-theme-browser.mjs # 可选:真实浏览器验证「跟随 DSH 主题」的 palette 切换与不持久化(需 puppeteer-core + Chrome,缺则 SKIP)
|
|
206
228
|
```
|
|
207
229
|
|
|
208
230
|
**产物约定**(发布必守):`lib/` 内**零** `@deepseek-ai` 运行时 import(`src/sdk.ts` 自实现 `defineTool` / `dshHomePath`,类型用结构接口)。发布前用 `grep -r "@deepseek-ai" lib/` 验证。
|
|
@@ -260,6 +282,7 @@ src/
|
|
|
260
282
|
├── styles.ts / state.ts / toast.ts
|
|
261
283
|
├── sidebar-entry.ts # 侧边栏入口
|
|
262
284
|
├── panel.ts # 中央列 iframe 面板(fixed 覆盖层,钉住整列)
|
|
285
|
+
├── theme-sync.ts # 跟随 DSH 主题:检测深浅 + 纯内存切 TW palette(不写回 wiki)
|
|
263
286
|
├── note-widget.ts # 悬浮快速笔记
|
|
264
287
|
├── markdown-editor.ts # 快速笔记编辑器(CodeMirror 6 + Lezer Markdown 高亮)
|
|
265
288
|
├── sync-button.ts # 一键同步悬浮按钮
|
package/lib/client.bundle.js
CHANGED
|
@@ -101,7 +101,9 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
101
101
|
.dsh-tw-status-dot[data-state="starting"] { background: var(--dsw-alias-state-warning-primary, #d9822b); }
|
|
102
102
|
.dsh-tw-status-dot[data-state="failed"], .dsh-tw-status-dot[data-state="stopped"] { background: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
/* The iframe background follows the DSH theme so a blank/preload frame never
|
|
105
|
+
flashes pure white in dark mode (TW paints its own palette once loaded). */
|
|
106
|
+
.dsh-tw-panel-frame { flex: 1; min-height: 0; border: 0; width: 100%; display: block; background: var(--dsw-alias-bg-layer-1, #fff); }
|
|
105
107
|
/* [hidden] must beat the author display rules above (UA hidden is overridden). */
|
|
106
108
|
.dsh-tw-panel-frame[hidden],
|
|
107
109
|
.dsh-tw-panel-error[hidden] { display: none !important; }
|
|
@@ -281,7 +283,7 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
281
283
|
color: var(--dsw-alias-label-dimmed, #888); padding: 5px 8px; border-radius: 6px;
|
|
282
284
|
}
|
|
283
285
|
.dsh-tw-editor-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, #222); }
|
|
284
|
-
.dsh-tw-editor-frame { flex: 1; min-height: 0; width: 100%; border: 0; background: #fff; }
|
|
286
|
+
.dsh-tw-editor-frame { flex: 1; min-height: 0; width: 100%; border: 0; background: var(--dsw-alias-bg-layer-1, #fff); }
|
|
285
287
|
.dsh-tw-editor-resize { position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize; }
|
|
286
288
|
.dsh-tw-note-toggle {
|
|
287
289
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
@@ -637,6 +639,253 @@ function mountSidebarEntry(state) {
|
|
|
637
639
|
};
|
|
638
640
|
}
|
|
639
641
|
//#endregion
|
|
642
|
+
//#region src/client/theme-sync.ts
|
|
643
|
+
/**
|
|
644
|
+
* Adaptive DSH theme for the embedded TiddlyWiki iframes (design doc §12).
|
|
645
|
+
*
|
|
646
|
+
* Both the center-column panel and the quick-note editor popup embed the
|
|
647
|
+
* SAME-ORIGIN TW proxy, so this module can reach the TW runtime directly
|
|
648
|
+
* (`iframe.contentWindow.$tw`) and drive its native theming.
|
|
649
|
+
*
|
|
650
|
+
* How TW theming works
|
|
651
|
+
* --------------------
|
|
652
|
+
* TW 5 renders its colours through the ACTIVE palette: `$:/palette` holds the
|
|
653
|
+
* title of a palette tiddler (`$:/palettes/…`), and every `<<colour x>>` in
|
|
654
|
+
* the theme stylesheet resolves against that tiddler at render time. TW
|
|
655
|
+
* re-renders all stylesheets live on any tiddler change, so flipping
|
|
656
|
+
* `$:/palette` re-themes the whole embedded UI instantly — the exact mechanism
|
|
657
|
+
* TW's own palette switcher uses. Overriding the rendered CSS variables is a
|
|
658
|
+
* dead end for the default Vanilla theme because its base stylesheet bakes the
|
|
659
|
+
* palette values in as literals; switching the palette is the supported path.
|
|
660
|
+
*
|
|
661
|
+
* DSH theme detection
|
|
662
|
+
* -------------------
|
|
663
|
+
* The DSH shell's ThemePresenter projects the resolved theme onto the page:
|
|
664
|
+
* `data-ds-dark-theme` on <body> (dark) plus an inline `color-scheme` on
|
|
665
|
+
* <html> that is always "light"|"dark" (the "system" preference is resolved
|
|
666
|
+
* upstream). Those two signals are authoritative; `prefers-color-scheme` is
|
|
667
|
+
* only a fallback when neither is present (no DSH theming projected).
|
|
668
|
+
*
|
|
669
|
+
* Non-persistence
|
|
670
|
+
* ---------------
|
|
671
|
+
* `$:/palette` IS included in TW's default sync filter, so a naive write would
|
|
672
|
+
* be PUT back to the TiddlyWeb server and land in the wiki's git history on
|
|
673
|
+
* every theme toggle. We suppress that: the syncer's dirty check is
|
|
674
|
+
* `getChangeCount(title) > tiddlerInfo[title].changeCount`, and the change
|
|
675
|
+
* event that would drive the save is dispatched on a microtask — so after
|
|
676
|
+
* writing we synchronously re-align the recorded changeCount and the syncer
|
|
677
|
+
* sees the tiddler as already clean. The flip stays in-memory only; the wiki's
|
|
678
|
+
* stored palette and the user's choice are never modified.
|
|
679
|
+
*
|
|
680
|
+
* @module dsh-tiddlywiki/client/theme-sync
|
|
681
|
+
*/
|
|
682
|
+
/** Default dark palette used when DSH is dark (overridable in settings). */
|
|
683
|
+
const DARK_PALETTE_DEFAULT = "$:/palettes/CupertinoDark";
|
|
684
|
+
/** Fallback light palette when no user palette was ever captured. */
|
|
685
|
+
const LIGHT_PALETTE_FALLBACK = "$:/palettes/Vanilla";
|
|
686
|
+
/** DSH's dark-mode marker (set/removed on <body> by dsh-client-ui-layout). */
|
|
687
|
+
const DARK_ATTR = "data-ds-dark-theme";
|
|
688
|
+
/** The TW tiddler holding the active palette tiddler title. */
|
|
689
|
+
const PALETTE_TIDDLER = "$:/palette";
|
|
690
|
+
/** Appliers registered for live frames; config changes re-apply to all. */
|
|
691
|
+
const frameAppliers = /* @__PURE__ */ new Set();
|
|
692
|
+
/** Syncers already wrapped with the palette save guard (per iframe window). */
|
|
693
|
+
const guardedSyncers = /* @__PURE__ */ new WeakSet();
|
|
694
|
+
/** Remembered user palette (light) that we restore when leaving dark mode. */
|
|
695
|
+
let userLightPalette = "";
|
|
696
|
+
/** Whether the adaptive feature is on (settings `ui.followDshTheme`). */
|
|
697
|
+
let syncEnabled = true;
|
|
698
|
+
/** Configured dark palette (settings `ui.darkPalette`). */
|
|
699
|
+
let darkPalette = DARK_PALETTE_DEFAULT;
|
|
700
|
+
/**
|
|
701
|
+
* Resolve the DSH shell's current effective dark state. The body attribute
|
|
702
|
+
* and the inline root `color-scheme` projected by the ThemePresenter are
|
|
703
|
+
* authoritative; the OS media query only answers when DSH isn't projecting a
|
|
704
|
+
* theme snapshot (plain embed / non-DSH page).
|
|
705
|
+
*/
|
|
706
|
+
function readDshDark() {
|
|
707
|
+
try {
|
|
708
|
+
const root = document.documentElement;
|
|
709
|
+
const body = document.body;
|
|
710
|
+
if (body !== null && body.hasAttribute(DARK_ATTR)) return true;
|
|
711
|
+
if (root !== null) {
|
|
712
|
+
const scheme = root.style.getPropertyValue("color-scheme").trim().toLowerCase();
|
|
713
|
+
if (scheme === "dark") return true;
|
|
714
|
+
if (scheme === "light") return false;
|
|
715
|
+
}
|
|
716
|
+
} catch {}
|
|
717
|
+
try {
|
|
718
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
719
|
+
} catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/** Active palette tiddler title (falls back to the light default). */
|
|
724
|
+
function activePaletteTitle($tw) {
|
|
725
|
+
try {
|
|
726
|
+
const title = $tw.wiki.getTiddlerText(PALETTE_TIDDLER);
|
|
727
|
+
return typeof title === "string" && title.trim().length > 0 ? title.trim() : LIGHT_PALETTE_FALLBACK;
|
|
728
|
+
} catch {
|
|
729
|
+
return LIGHT_PALETTE_FALLBACK;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
/** True when the active palette tiddler declares `color-scheme: dark`. */
|
|
733
|
+
function activePaletteIsDark($tw) {
|
|
734
|
+
try {
|
|
735
|
+
const fields = $tw.wiki.getTiddler(activePaletteTitle($tw))?.fields;
|
|
736
|
+
return fields !== void 0 && fields["color-scheme"] === "dark";
|
|
737
|
+
} catch {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Mark `$:/palette` as clean in the syncer's records so a FORCED flip is never
|
|
743
|
+
* PUT back to the TiddlyWeb server. Runs synchronously right after the write,
|
|
744
|
+
* before the microtask-dispatched change event lets the syncer evaluate
|
|
745
|
+
* dirtiness; user-initiated palette changes (bumped afterwards) still sync.
|
|
746
|
+
*/
|
|
747
|
+
function suppressPaletteSync($tw) {
|
|
748
|
+
try {
|
|
749
|
+
const syncer = $tw.syncer;
|
|
750
|
+
if (syncer?.tiddlerInfo === void 0) return;
|
|
751
|
+
const changeCount = $tw.wiki.getChangeCount(PALETTE_TIDDLER);
|
|
752
|
+
const info = syncer.tiddlerInfo[PALETTE_TIDDLER];
|
|
753
|
+
if (info !== void 0) info.changeCount = changeCount;
|
|
754
|
+
else syncer.tiddlerInfo[PALETTE_TIDDLER] = { changeCount };
|
|
755
|
+
} catch {}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Install a one-shot guard on this frame's syncadaptor: while a palette WE
|
|
759
|
+
* forced is active (`userLightPalette` captured), a PUT of `$:/palette` whose
|
|
760
|
+
* text is still our forced dark palette is swallowed instead of reaching the
|
|
761
|
+
* TiddlyWeb server. This makes the non-persistence guarantee deterministic —
|
|
762
|
+
* TW's boot-time sync (which may load `$:/palette` from the server and bump its
|
|
763
|
+
* changeCount AFTER our re-align) can otherwise queue a save of the forced
|
|
764
|
+
* palette during the first ~1s. Idempotent per syncer instance.
|
|
765
|
+
*/
|
|
766
|
+
function installPaletteSaveGuard($tw) {
|
|
767
|
+
try {
|
|
768
|
+
const syncer = $tw.syncer;
|
|
769
|
+
const adaptor = syncer?.syncadaptor;
|
|
770
|
+
if (syncer === void 0 || adaptor === void 0 || guardedSyncers.has(syncer)) return;
|
|
771
|
+
const origSave = adaptor.saveTiddler.bind(adaptor);
|
|
772
|
+
adaptor.saveTiddler = (tiddler, callback) => {
|
|
773
|
+
if (syncEnabled && userLightPalette.length > 0 && tiddler?.fields?.title === PALETTE_TIDDLER && tiddler?.fields?.text === darkPalette) {
|
|
774
|
+
callback(null, {}, 0);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
origSave(tiddler, callback);
|
|
778
|
+
};
|
|
779
|
+
guardedSyncers.add(syncer);
|
|
780
|
+
} catch {}
|
|
781
|
+
}
|
|
782
|
+
/** Write the active palette (no-op when unchanged) without persisting. */
|
|
783
|
+
function setPalette($tw, target) {
|
|
784
|
+
try {
|
|
785
|
+
if (activePaletteTitle($tw) === target) return;
|
|
786
|
+
$tw.wiki.setText(PALETTE_TIDDLER, "text", void 0, target);
|
|
787
|
+
suppressPaletteSync($tw);
|
|
788
|
+
} catch {}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Drive `$:/palette` toward the DSH theme. In dark mode a light palette is
|
|
792
|
+
* swapped for `darkPalette` (remembering the user's palette first); in light
|
|
793
|
+
* mode a palette WE forced is restored, while a user-chosen dark palette is
|
|
794
|
+
* left alone.
|
|
795
|
+
*/
|
|
796
|
+
function applyPalette($tw, dark) {
|
|
797
|
+
const current = activePaletteTitle($tw);
|
|
798
|
+
if (dark) {
|
|
799
|
+
if (activePaletteIsDark($tw)) return;
|
|
800
|
+
if (current !== darkPalette) userLightPalette = current;
|
|
801
|
+
setPalette($tw, darkPalette);
|
|
802
|
+
} else if (activePaletteIsDark($tw) && current === darkPalette && userLightPalette.length > 0) setPalette($tw, userLightPalette);
|
|
803
|
+
}
|
|
804
|
+
/** Keep the iframe's native `color-scheme` in step with the ACTIVE palette. */
|
|
805
|
+
function syncColorScheme($tw, frame) {
|
|
806
|
+
try {
|
|
807
|
+
const html = frame.contentDocument?.documentElement;
|
|
808
|
+
if (html === void 0 || html === null) return;
|
|
809
|
+
const target = activePaletteIsDark($tw) ? "dark" : "light";
|
|
810
|
+
if (html.style.getPropertyValue("color-scheme") !== target) html.style.setProperty("color-scheme", target);
|
|
811
|
+
} catch {}
|
|
812
|
+
}
|
|
813
|
+
/** Apply the current DSH theme to one TW iframe (no-op until TW is ready). */
|
|
814
|
+
function applyToFrame(frame) {
|
|
815
|
+
const $tw = frame.contentWindow?.$tw;
|
|
816
|
+
if ($tw === void 0 || $tw.wiki === void 0) return;
|
|
817
|
+
installPaletteSaveGuard($tw);
|
|
818
|
+
if (syncEnabled) applyPalette($tw, readDshDark());
|
|
819
|
+
else applyPalette($tw, false);
|
|
820
|
+
syncColorScheme($tw, frame);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Configure the feature and re-apply to every live frame. `enabled` is the
|
|
824
|
+
* settings `ui.followDshTheme`; `darkPalette` the settings `ui.darkPalette`.
|
|
825
|
+
*/
|
|
826
|
+
function setThemeSyncConfig(opts) {
|
|
827
|
+
const enabled = opts.enabled !== false;
|
|
828
|
+
const palette = typeof opts.darkPalette === "string" && opts.darkPalette.trim().length > 0 ? opts.darkPalette.trim() : DARK_PALETTE_DEFAULT;
|
|
829
|
+
const changed = enabled !== syncEnabled || palette !== darkPalette;
|
|
830
|
+
syncEnabled = enabled;
|
|
831
|
+
darkPalette = palette;
|
|
832
|
+
if (changed) for (const apply of [...frameAppliers]) apply();
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Make one TW iframe follow the DSH theme for as long as the returned
|
|
836
|
+
* disposer is not called. Re-applies on the iframe's `load` (covers TW
|
|
837
|
+
* restarts and proxy reloads), on DSH theme changes (body-attribute mutations
|
|
838
|
+
* and `prefers-color-scheme`), and once immediately for already-loaded frames.
|
|
839
|
+
*/
|
|
840
|
+
function attachThemeSync(frame) {
|
|
841
|
+
if (frame === null) return () => {};
|
|
842
|
+
const apply = () => applyToFrame(frame);
|
|
843
|
+
frameAppliers.add(apply);
|
|
844
|
+
const onLoad = () => apply();
|
|
845
|
+
frame.addEventListener("load", onLoad);
|
|
846
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
847
|
+
const onMedia = () => apply();
|
|
848
|
+
try {
|
|
849
|
+
media.addEventListener("change", onMedia);
|
|
850
|
+
} catch {
|
|
851
|
+
media.addListener?.(onMedia);
|
|
852
|
+
}
|
|
853
|
+
let observer;
|
|
854
|
+
try {
|
|
855
|
+
observer = new MutationObserver(() => apply());
|
|
856
|
+
if (document.body !== null) observer.observe(document.body, {
|
|
857
|
+
attributes: true,
|
|
858
|
+
attributeFilter: [DARK_ATTR]
|
|
859
|
+
});
|
|
860
|
+
if (document.documentElement !== null) observer.observe(document.documentElement, {
|
|
861
|
+
attributes: true,
|
|
862
|
+
attributeFilter: ["style"]
|
|
863
|
+
});
|
|
864
|
+
} catch {
|
|
865
|
+
observer = void 0;
|
|
866
|
+
}
|
|
867
|
+
const timers = [
|
|
868
|
+
150,
|
|
869
|
+
600,
|
|
870
|
+
1500,
|
|
871
|
+
3e3
|
|
872
|
+
].map((ms) => window.setTimeout(apply, ms));
|
|
873
|
+
apply();
|
|
874
|
+
return () => {
|
|
875
|
+
frameAppliers.delete(apply);
|
|
876
|
+
frame.removeEventListener("load", onLoad);
|
|
877
|
+
for (const id of timers) window.clearTimeout(id);
|
|
878
|
+
try {
|
|
879
|
+
media.removeEventListener("change", onMedia);
|
|
880
|
+
} catch {
|
|
881
|
+
media.removeListener?.(onMedia);
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
observer?.disconnect();
|
|
885
|
+
} catch {}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
//#endregion
|
|
640
889
|
//#region src/client/panel.ts
|
|
641
890
|
/**
|
|
642
891
|
* Center-column targets, most-specific shell generation first. The official
|
|
@@ -724,6 +973,7 @@ function mountPanel(state) {
|
|
|
724
973
|
let errorArea;
|
|
725
974
|
let refreshTimer;
|
|
726
975
|
let refreshAttempts = 0;
|
|
976
|
+
let themeSyncDispose;
|
|
727
977
|
const build = () => {
|
|
728
978
|
const view = document.createElement("div");
|
|
729
979
|
view.dataset.dshTwView = "";
|
|
@@ -736,6 +986,7 @@ function mountPanel(state) {
|
|
|
736
986
|
iframe.title = "TiddlyWiki";
|
|
737
987
|
iframe.hidden = true;
|
|
738
988
|
frameArea.append(iframe);
|
|
989
|
+
themeSyncDispose = attachThemeSync(iframe);
|
|
739
990
|
errorArea = document.createElement("div");
|
|
740
991
|
errorArea.className = "dsh-tw-panel-error";
|
|
741
992
|
errorArea.hidden = true;
|
|
@@ -829,6 +1080,10 @@ function mountPanel(state) {
|
|
|
829
1080
|
}
|
|
830
1081
|
if (payload.status === "running") {
|
|
831
1082
|
refreshAttempts = 0;
|
|
1083
|
+
setThemeSyncConfig({
|
|
1084
|
+
enabled: payload.ui?.followDshTheme !== false,
|
|
1085
|
+
darkPalette: payload.ui?.darkPalette
|
|
1086
|
+
});
|
|
832
1087
|
if (typeof payload.twProxy === "string") showFrame(new URL(payload.twProxy, window.location.origin).href);
|
|
833
1088
|
else if (typeof payload.url === "string") showFrame(payload.url);
|
|
834
1089
|
else showError("服务未返回编辑器地址");
|
|
@@ -907,6 +1162,7 @@ function mountPanel(state) {
|
|
|
907
1162
|
document.removeEventListener("dsh-tw-panel-reload", onReloadRequest);
|
|
908
1163
|
waitObserver.disconnect();
|
|
909
1164
|
unsubscribe();
|
|
1165
|
+
themeSyncDispose?.();
|
|
910
1166
|
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
911
1167
|
container?.remove();
|
|
912
1168
|
};
|
|
@@ -951,6 +1207,7 @@ function toast(message) {
|
|
|
951
1207
|
let root;
|
|
952
1208
|
let frame;
|
|
953
1209
|
let titleEl;
|
|
1210
|
+
let themeSyncDispose;
|
|
954
1211
|
/** Open (create on first use) the popup and load `url` (twUrl#draftTitle). */
|
|
955
1212
|
function openEditorPopup(url, label) {
|
|
956
1213
|
ensurePopup();
|
|
@@ -961,6 +1218,8 @@ function openEditorPopup(url, label) {
|
|
|
961
1218
|
}
|
|
962
1219
|
/** Remove the popup DOM entirely (plugin dispose). */
|
|
963
1220
|
function disposeEditorPopup() {
|
|
1221
|
+
themeSyncDispose?.();
|
|
1222
|
+
themeSyncDispose = void 0;
|
|
964
1223
|
root?.remove();
|
|
965
1224
|
root = void 0;
|
|
966
1225
|
frame = void 0;
|
|
@@ -986,6 +1245,7 @@ function ensurePopup() {
|
|
|
986
1245
|
frame.className = "dsh-tw-editor-frame";
|
|
987
1246
|
frame.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups");
|
|
988
1247
|
frame.title = "TiddlyWiki 编辑器";
|
|
1248
|
+
themeSyncDispose = attachThemeSync(frame);
|
|
989
1249
|
const resize = document.createElement("div");
|
|
990
1250
|
resize.className = "dsh-tw-editor-resize";
|
|
991
1251
|
resize.title = "拖拽调整大小";
|
|
@@ -27739,6 +27999,8 @@ function renderConfigSection(body, config, refresh) {
|
|
|
27739
27999
|
checkField("ui.showQuickNote", "显示「知识库」按钮里的「快速笔记」入口", ui.showQuickNote !== false);
|
|
27740
28000
|
checkField("ui.showPanelStatus", "显示「知识库」按钮里的 TW 面板/重载入口与状态行", ui.showPanelStatus !== false);
|
|
27741
28001
|
checkField("ui.showSyncButton", "显示「知识库」按钮里的「同步」入口与 git 状态点", ui.showSyncButton !== false);
|
|
28002
|
+
checkField("ui.followDshTheme", "嵌入式 TW 跟随 DSH 深浅主题(暗色时自动切深色 palette,不写回 wiki)", ui.followDshTheme !== false);
|
|
28003
|
+
textField("ui.darkPalette", "暗色时 TW palette(tiddler 标题)", typeof ui.darkPalette === "string" && ui.darkPalette.length > 0 ? ui.darkPalette : "$:/palettes/CupertinoDark");
|
|
27742
28004
|
const save = make("button", "dsh-tw-settings-btn dsh-tw-settings-primary", "保存配置");
|
|
27743
28005
|
save.type = "button";
|
|
27744
28006
|
save.addEventListener("click", () => {
|