dsh-tiddlywiki 0.28.12 → 0.28.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/docs/CHANGELOG.md +2 -0
- package/docs/features.md +1 -1
- package/docs/usage.md +1 -0
- package/lib/client.js +27 -27
- package/package.json +1 -1
- package/src/client/sidebar-entry.ts +87 -16
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tiddlywiki",
|
|
3
3
|
"description": "把 TiddlyWiki 5 变成你和 Agent 共用的本地文档中心(纯文本 + git):你在 DSH 界面里阅读、写作、记日记,Agent 用 15 个 tiddlywiki_* 工具读写同一批笔记;每条笔记都是你能读、能改、能审计、能随时带走的文件——不是自动记忆插件,不做后台抓取。可选一键发布到微信公众号草稿箱。 Turn TiddlyWiki 5 into a document center you and your agent share, in plain text under git — a knowledge base you can read, edit, audit and take with you, not an auto-memory plugin. Mounts via the official dsh plugin system; no DSH source changes.",
|
|
4
|
-
"version": "0.28.
|
|
4
|
+
"version": "0.28.13",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"dsh",
|
|
@@ -12,12 +12,41 @@
|
|
|
12
12
|
import type { PanelState } from './state.ts'
|
|
13
13
|
import { fetchUiConfig, subscribeUiConfig } from './ui-config.ts'
|
|
14
14
|
import { fetchStatus } from './status-cache.ts'
|
|
15
|
-
import { resolveFocusWiki, setFocusWiki, subscribeFocusWiki } from './wiki-focus.ts'
|
|
15
|
+
import { resolveFocusWiki, setFocusWiki, subscribeFocusWiki, type FocusableWiki } from './wiki-focus.ts'
|
|
16
16
|
import { applyWikiIcon } from './wiki-icon.ts'
|
|
17
17
|
|
|
18
18
|
/** Stable data attribute identifying this entry row. */
|
|
19
19
|
export const ENTRY_SELECTOR = '[data-dsh-tw-entry]'
|
|
20
20
|
|
|
21
|
+
/** What a click on an entry row does — see {@link resolveEntryClick}. */
|
|
22
|
+
export type EntryClickAction = 'open' | 'close' | 'switch'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decide what clicking an entry row should do (v0.28.13).
|
|
26
|
+
*
|
|
27
|
+
* 症状(作者 2026-09-29 报障):「左侧多 wiki 入口,有时候要点击两次才能切换到对应的库」。
|
|
28
|
+
* 根因:点行**一律** `state.toggle()` —— 面板开着 A 时点 B,`setFocusWiki(B)` 确实把焦点
|
|
29
|
+
* 换过去了(内核也开始换库),但同一击里的 toggle 又把面板关掉;用户看到的只是"面板没了",
|
|
30
|
+
* 得再点一次才看到 B。开着面板时点另一个库,用户要的是**切换**,不是"关面板"。
|
|
31
|
+
*
|
|
32
|
+
* 判定表:
|
|
33
|
+
* - 面板关着 → `open`(点哪个库就开哪个库);
|
|
34
|
+
* - 面板开着 && 就是这个库 → `close`(入口同时也是开关,再点一次收起);
|
|
35
|
+
* - 面板开着 && 是别的库 → `switch`(只切焦点库,面板保持打开);
|
|
36
|
+
* - 单库那一行(`wikiId === undefined`)逐字保留原来的 toggle 语义。
|
|
37
|
+
*/
|
|
38
|
+
export function resolveEntryClick(input: {
|
|
39
|
+
open: boolean
|
|
40
|
+
/** 这一行绑定的库;`undefined` = 单库模式那唯一一行。 */
|
|
41
|
+
wikiId: string | undefined
|
|
42
|
+
/** 面板此刻**实际显示**的库(见 `mountSidebarEntry` 的 `shownWiki`)。 */
|
|
43
|
+
shown: string | undefined
|
|
44
|
+
}): EntryClickAction {
|
|
45
|
+
if (!input.open) return 'open'
|
|
46
|
+
if (input.wikiId === undefined) return 'close'
|
|
47
|
+
return input.wikiId === input.shown ? 'close' : 'switch'
|
|
48
|
+
}
|
|
49
|
+
|
|
21
50
|
|
|
22
51
|
/** Family entries from sibling plugins, kept in a stable relative order. */
|
|
23
52
|
const FAMILY_SELECTOR = '[data-dsh-tw-entry], [data-dsh-atb-entry], [data-dsh-taskboard-entry], [data-dsh-ssh-entry]'
|
|
@@ -47,15 +76,43 @@ function newSessionButton(root: HTMLElement): HTMLButtonElement | undefined {
|
|
|
47
76
|
return buttons.find(button => !button.matches(ENTRY_SELECTOR) && /新会话|新建会话|new session/i.test(button.textContent ?? ''))
|
|
48
77
|
}
|
|
49
78
|
|
|
79
|
+
/** What a row needs from its owner: the shared panel state and "which wiki is on screen". */
|
|
80
|
+
export interface EntryHooks {
|
|
81
|
+
state: PanelState
|
|
82
|
+
/** The wiki the panel is displaying right now (`resolveEntryClick` needs it). */
|
|
83
|
+
shownWiki: () => string | undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Run one entry-row click: decide, move the focus wiki, then open/close the panel.
|
|
88
|
+
*
|
|
89
|
+
* 顺序是本函数唯一容易写错的地方:`shownWiki()` 必须在 `setFocusWiki()` **之前**求值。
|
|
90
|
+
* 反过来的话,刚切过去的那个库会被 `resolveEntryClick` 当成"面板此刻显示的就是它",
|
|
91
|
+
* 于是判成 `close` —— 又变回"点两次才能切库"。
|
|
92
|
+
*
|
|
93
|
+
* @returns the action that was taken (the guards assert on it).
|
|
94
|
+
*/
|
|
95
|
+
export function applyEntryClick(hooks: EntryHooks, wikiId: string | undefined): EntryClickAction {
|
|
96
|
+
const action = resolveEntryClick({ open: hooks.state.isOpen(), wikiId, shown: hooks.shownWiki() })
|
|
97
|
+
// 多库:先把这个库设为「焦点库」,面板随后加载它自己的 /tw/<id>/。
|
|
98
|
+
if (wikiId !== undefined) setFocusWiki(wikiId)
|
|
99
|
+
// 'open' 与 'switch' 都保持/变成打开:'switch' 时面板本来就开着,openPanel() 是空操作,
|
|
100
|
+
// 换库由焦点订阅驱动内核重载(tw-frame.ts),所以**绝不能**在这里 toggle —— 那正是
|
|
101
|
+
// 「点两次才能切库」的成因(v0.28.13)。
|
|
102
|
+
if (action === 'close') hooks.state.closePanel()
|
|
103
|
+
else hooks.state.openPanel()
|
|
104
|
+
return action
|
|
105
|
+
}
|
|
106
|
+
|
|
50
107
|
/**
|
|
51
108
|
* Build one entry row (a detached button; inserted once the shell is up).
|
|
52
109
|
*
|
|
53
110
|
* `wikiId` is optional and only used in multi-wiki mode: clicking the row focuses
|
|
54
|
-
* that knowledge base before
|
|
111
|
+
* that knowledge base before showing the panel, so the row a user clicks is the
|
|
55
112
|
* wiki they get. Single-wiki installs pass nothing and behave exactly as before.
|
|
56
113
|
*/
|
|
57
114
|
function createEntry(
|
|
58
|
-
|
|
115
|
+
hooks: EntryHooks,
|
|
59
116
|
label: string,
|
|
60
117
|
wikiId?: string,
|
|
61
118
|
icon?: string,
|
|
@@ -73,11 +130,7 @@ function createEntry(
|
|
|
73
130
|
labelEl.className = 'dsh-tw-entry-label'
|
|
74
131
|
labelEl.textContent = label
|
|
75
132
|
entry.append(iconEl, labelEl)
|
|
76
|
-
entry.addEventListener('click', () => {
|
|
77
|
-
// 多库:先把这个库设为「焦点库」,面板随后加载它自己的 /tw/<id>/。
|
|
78
|
-
if (wikiId !== undefined) setFocusWiki(wikiId)
|
|
79
|
-
state.toggle()
|
|
80
|
-
})
|
|
133
|
+
entry.addEventListener('click', () => { applyEntryClick(hooks, wikiId) })
|
|
81
134
|
return { entry, labelEl, iconEl }
|
|
82
135
|
}
|
|
83
136
|
|
|
@@ -117,7 +170,19 @@ function placeEntry(root: HTMLElement, entry: HTMLButtonElement): boolean {
|
|
|
117
170
|
export function mountSidebarEntry(state: PanelState): () => void {
|
|
118
171
|
/** 多库:每个**在运行**的库一个入口行,用自己的显示名(作者 2026-09-28 要求)。 */
|
|
119
172
|
const rows = new Map<string, { entry: HTMLButtonElement; labelEl: HTMLSpanElement; iconEl: HTMLSpanElement }>()
|
|
120
|
-
|
|
173
|
+
/**
|
|
174
|
+
* `/status` 最近一次给出的「在运行库」与默认库(v0.28.13)。
|
|
175
|
+
*
|
|
176
|
+
* 点击一个入口行时要判断「面板此刻显示的是哪个库」,才能区分"切换"与"关面板"
|
|
177
|
+
* (见 `resolveEntryClick`)——判定必须与面板/内核用**同一套解析**:
|
|
178
|
+
* 记忆值 → 默认库 → 第一个可用库(`resolveFocusWiki`)。
|
|
179
|
+
*/
|
|
180
|
+
let runningWikis: FocusableWiki[] = []
|
|
181
|
+
let defaultWikiId: string | undefined
|
|
182
|
+
/** 面板此刻显示的那个库。 */
|
|
183
|
+
const shownWiki = (): string | undefined => resolveFocusWiki(runningWikis, defaultWikiId)
|
|
184
|
+
const entryHooks: EntryHooks = { state, shownWiki }
|
|
185
|
+
const { entry, labelEl, iconEl } = createEntry(entryHooks, 'TiddlyWiki')
|
|
121
186
|
rows.set('', { entry, labelEl, iconEl })
|
|
122
187
|
let disposed = false
|
|
123
188
|
/**
|
|
@@ -149,6 +214,9 @@ export function mountSidebarEntry(state: PanelState): () => void {
|
|
|
149
214
|
if (disposed) return
|
|
150
215
|
const list = Array.isArray(payload?.wikis) ? payload.wikis : []
|
|
151
216
|
const running = list.filter((w) => w.running)
|
|
217
|
+
// 点击判定与高亮都要用同一份名册(v0.28.13):存下来,`shownWiki()` 据此解析。
|
|
218
|
+
runningWikis = running
|
|
219
|
+
defaultWikiId = payload?.defaultId
|
|
152
220
|
// 单库:不建额外行,标签仍归 ui.sidebarLabel 管。
|
|
153
221
|
if (running.length <= 1) {
|
|
154
222
|
for (const [key, row] of rows) {
|
|
@@ -172,15 +240,15 @@ export function mountSidebarEntry(state: PanelState): () => void {
|
|
|
172
240
|
for (const w of running) {
|
|
173
241
|
let row = rows.get(w.id)
|
|
174
242
|
if (row === undefined) {
|
|
175
|
-
row = createEntry(
|
|
243
|
+
row = createEntry(entryHooks, w.label, w.id, w.icon)
|
|
176
244
|
rows.set(w.id, row)
|
|
177
245
|
}
|
|
178
246
|
row.labelEl.textContent = w.label
|
|
179
247
|
// 图标每次同步(v0.28.4):在设置页改完图标,10s 内的轮询就会把它换过来。
|
|
180
248
|
applyWikiIcon(row.iconEl, w.icon)
|
|
181
249
|
row.entry.setAttribute('aria-label', `TiddlyWiki 知识库:${w.label}`)
|
|
182
|
-
//
|
|
183
|
-
const focused =
|
|
250
|
+
// 当前焦点库高亮(与点击判定同源:`shownWiki()`,不会出现"高亮的行点一下反而关掉面板")
|
|
251
|
+
const focused = shownWiki() === w.id
|
|
184
252
|
if (focused) row.entry.dataset.focus = 'true'
|
|
185
253
|
else delete row.entry.dataset.focus
|
|
186
254
|
}
|
|
@@ -273,12 +341,15 @@ export function mountSidebarEntry(state: PanelState): () => void {
|
|
|
273
341
|
* wiki — the focused one. Highlighting every row made it look like all three
|
|
274
342
|
* were open (作者报障). The row the user clicked becomes the focus, and that is
|
|
275
343
|
* the row that lights up.
|
|
344
|
+
*
|
|
345
|
+
* v0.28.13: the resolver is the SAME one the click handler uses (`shownWiki`),
|
|
346
|
+
* so "which row is lit" and "which row closes the panel when clicked" can never
|
|
347
|
+
* disagree. The old call passed `undefined` as the default id, which made the
|
|
348
|
+
* highlight fall back to the FIRST running row instead of the actual default
|
|
349
|
+
* wiki whenever no focus had been stored yet.
|
|
276
350
|
*/
|
|
277
351
|
const syncActive = (): void => {
|
|
278
|
-
const focus =
|
|
279
|
-
activeRows().map((el) => ({ id: el.dataset.wiki ?? '' })).filter((w) => w.id.length > 0),
|
|
280
|
-
undefined,
|
|
281
|
-
)
|
|
352
|
+
const focus = shownWiki()
|
|
282
353
|
for (const el of activeRows()) {
|
|
283
354
|
const id = el.dataset.wiki
|
|
284
355
|
// 单库默认行没有 data-wiki:它就是唯一那个库,面板开着时它就该亮。
|