dsh-tiddlywiki 0.22.3 → 0.22.4
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 +7 -4
- package/lib/client.js +27 -27
- package/package.json +2 -2
- package/src/client/panel.ts +40 -229
- package/src/client/tw-frame.ts +253 -121
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tiddlywiki",
|
|
3
3
|
"description": "TiddlyWiki 5 as the DSH persistent knowledge base: fifteen tiddlywiki_* agent tools (filtered search/recent/tags/batch/rewrite/trash/lint + git sync & conflict resolve), a full TiddlyWiki editor embedded in the GUI center column (same-origin proxy, works over Tailscale/LAN/domain), a right-side wiki tab, a per-session summary tab, a quick-note card with draft auto-save behind a single knowledge FAB, an optional clip bookmarklet that also saves selected images as attachments, and git-based sync with auto-commit. Mounts via the official dsh plugin system — no DSH source changes.",
|
|
4
|
-
"version": "0.22.
|
|
4
|
+
"version": "0.22.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"dsh",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"smoke:client": "node scripts/smoke-client.mjs",
|
|
86
86
|
"selftest": "node scripts/selftest.mjs",
|
|
87
87
|
"verify:static": "node scripts/verify-send-to-agent-bundle.mjs && node scripts/verify-render-bundle.mjs && node scripts/verify-package-contents.mjs && node scripts/verify-version-consistency.mjs && node scripts/verify-constants.mjs",
|
|
88
|
-
"verify:unit": "node scripts/verify-clip-bridge.mjs && node scripts/verify-seed-error-policy.mjs && node scripts/verify-render-sanitizer.mjs && node scripts/verify-secret-masking.mjs && node scripts/verify-write-policy.mjs && node scripts/verify-prompt.mjs && node scripts/verify-frame-guards.mjs && npx tsx scripts/verify-status-cache.mjs",
|
|
88
|
+
"verify:unit": "node scripts/verify-clip-bridge.mjs && node scripts/verify-seed-error-policy.mjs && node scripts/verify-render-sanitizer.mjs && node scripts/verify-secret-masking.mjs && node scripts/verify-write-policy.mjs && node scripts/verify-prompt.mjs && node scripts/verify-frame-guards.mjs && npx tsx scripts/verify-frame-surface.mjs && npx tsx scripts/verify-status-cache.mjs",
|
|
89
89
|
"verify:e2e": "node scripts/verify-auth.mjs && node scripts/verify-tools.mjs && node scripts/verify-audit-fixes.mjs && node scripts/verify-git-resolve.mjs && node scripts/verify-resilience.mjs && node scripts/verify-seed-send-to-agent.mjs && node scripts/verify-seeds-admin.mjs && node scripts/verify-wiki-switch.mjs",
|
|
90
90
|
"verify:large": "node scripts/verify-large-wiki.mjs",
|
|
91
91
|
"verify": "npm run verify:static && npm run verify:unit && npm run verify:e2e && npm run verify:large",
|
package/src/client/panel.ts
CHANGED
|
@@ -10,10 +10,16 @@
|
|
|
10
10
|
* Toggling rides the shared PanelState; cross-plugin exclusivity rides the
|
|
11
11
|
* `dsh-panel-activate` event (same protocol as dsh-taskboard).
|
|
12
12
|
*
|
|
13
|
+
* This module owns the overlay LAYOUT only (rect pinning, lazy build, chrome,
|
|
14
|
+
* exclusion). The iframe's whole lifecycle — `/status` polling, error/starting
|
|
15
|
+
* states, theme sync, FAB reload, tiddler-hash navigation — is the shared
|
|
16
|
+
* kernel in tw-frame.ts (v0.22.4), so the panel and the rightbar tab can no
|
|
17
|
+
* longer drift apart (the drift is what produced the v0.22.3 empty-src bug).
|
|
18
|
+
*
|
|
13
19
|
* The iframe points at the SAME-ORIGIN TW proxy (`<origin>/dsh-tiddlywiki/tw/`,
|
|
14
20
|
* remote-access mode R1): the browser only talks to the DSH origin — which it
|
|
15
21
|
* already reaches over loopback, LAN, Tailscale, a domain or HTTPS — and DSH
|
|
16
|
-
* proxies to the loopback TW child. The
|
|
22
|
+
* proxies to the loopback TW child. The kernel reads /status first and only
|
|
17
23
|
* sets iframe.src when the service is actually running. Because the proxy URL
|
|
18
24
|
* is origin-relative and port-independent, a TW restart never reloads the
|
|
19
25
|
* iframe, so an in-progress edit keeps its unsaved state.
|
|
@@ -22,10 +28,8 @@
|
|
|
22
28
|
*/
|
|
23
29
|
import type { PanelState } from './state.ts'
|
|
24
30
|
import { ENTRY_SELECTOR } from './sidebar-entry.ts'
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// (panel/rightbar 共同依赖)。requestRestart 也只此一份(v0.22.3 去重)。
|
|
28
|
-
import { ACTIVATE_EVENT, loadableFrameUrl, openTiddlerInLiveTab, PANEL_RELOAD_EVENT, requestRestart } from './tw-frame.ts'
|
|
31
|
+
// 事件名与 frame 生命周期助手都只有一份,住在 tw-frame.ts(内核)。
|
|
32
|
+
import { ACTIVATE_EVENT, createTwFrameSurface, openTiddlerInLiveTab, type TwFrameSkin } from './tw-frame.ts'
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
35
|
* Center-column targets, most-specific shell generation first. The official
|
|
@@ -58,7 +62,20 @@ const APP_OVERLAY_Z_INDEX = 20
|
|
|
58
62
|
/** Safety re-measure cadence for shell layout changes CSS can't see. */
|
|
59
63
|
const SYNC_INTERVAL_MS = 2_000
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The panel's skin for the shared frame kernel. The inline flex chain is
|
|
67
|
+
* required because — unlike `.dsh-tw-rightbar-frame-wrap` — the panel's frame
|
|
68
|
+
* wrapper has no stylesheet rule; `data-dsh-tw-view` is what the stylesheet
|
|
69
|
+
* keys on to hide the conversation content the overlay covers.
|
|
70
|
+
*/
|
|
71
|
+
const PANEL_SKIN: TwFrameSkin = {
|
|
72
|
+
view: 'dsh-tw-view',
|
|
73
|
+
viewDataset: { dshTwView: '' },
|
|
74
|
+
frameWrap: 'dsh-tw-panel-frame-wrap',
|
|
75
|
+
frameWrapStyle: 'flex:1;min-height:0;display:flex;flex-direction:column',
|
|
76
|
+
frame: 'dsh-tw-panel-frame',
|
|
77
|
+
error: 'dsh-tw-panel-error',
|
|
78
|
+
}
|
|
62
79
|
|
|
63
80
|
/**
|
|
64
81
|
* Cross-module open request: the reply-stream tool cards and the document
|
|
@@ -102,53 +119,13 @@ function conversationColumn(): HTMLElement | undefined {
|
|
|
102
119
|
export function mountPanel(state: PanelState): () => void {
|
|
103
120
|
let container: HTMLDivElement | undefined
|
|
104
121
|
let columnEl: HTMLElement | undefined
|
|
105
|
-
let iframe: HTMLIFrameElement | undefined
|
|
106
|
-
let frameArea: HTMLDivElement | undefined
|
|
107
|
-
let errorArea: HTMLDivElement | undefined
|
|
108
|
-
let refreshTimer: number | undefined
|
|
109
|
-
/** In-flight hash-readiness retry timers (cancelled by dispose, v0.19.1). */
|
|
110
|
-
const hashWaitTimers = new Set<number>()
|
|
111
|
-
let refreshAttempts = 0
|
|
112
|
-
let themeSyncDispose: (() => void) | undefined
|
|
113
|
-
/** true once the iframe's document finished loading (hash navigation target ready). */
|
|
114
|
-
let frameLoaded = false
|
|
115
|
-
/** A tiddler-hash open request waiting for the frame to become ready. */
|
|
116
|
-
let pendingHash: string | null = null
|
|
117
122
|
/** Set by the disposer: no timers/observers may touch DOM or the iframe after. */
|
|
118
123
|
let disposed = false
|
|
119
124
|
/** Last observed presence of the better-sidebar host layer (applyChrome cache). */
|
|
120
125
|
let lastHasHost: boolean | undefined
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
view.dataset.dshTwView = ''
|
|
125
|
-
view.className = 'dsh-tw-view'
|
|
126
|
-
|
|
127
|
-
frameArea = document.createElement('div')
|
|
128
|
-
frameArea.className = 'dsh-tw-panel-frame-wrap'
|
|
129
|
-
frameArea.style.cssText = 'flex:1;min-height:0;display:flex;flex-direction:column'
|
|
130
|
-
iframe = document.createElement('iframe')
|
|
131
|
-
iframe.className = 'dsh-tw-panel-frame'
|
|
132
|
-
iframe.title = 'TiddlyWiki'
|
|
133
|
-
iframe.hidden = true
|
|
134
|
-
frameArea.append(iframe)
|
|
135
|
-
// Track load so a pending tiddler-hash navigation can target a ready
|
|
136
|
-
// document (setting contentWindow.location.hash before load is a no-op).
|
|
137
|
-
iframe.addEventListener('load', () => {
|
|
138
|
-
frameLoaded = true
|
|
139
|
-
applyPendingHash()
|
|
140
|
-
})
|
|
141
|
-
// Embedded TW follows the DSH light/dark theme (non-persisting palette
|
|
142
|
-
// swap inside the same-origin iframe; re-applied on load + theme change).
|
|
143
|
-
themeSyncDispose = attachThemeSync(iframe)
|
|
144
|
-
|
|
145
|
-
errorArea = document.createElement('div')
|
|
146
|
-
errorArea.className = 'dsh-tw-panel-error'
|
|
147
|
-
errorArea.hidden = true
|
|
148
|
-
|
|
149
|
-
view.append(frameArea, errorArea)
|
|
150
|
-
return view
|
|
151
|
-
}
|
|
127
|
+
/** iframe + /status lifecycle (shared with the rightbar tab, tw-frame.ts). */
|
|
128
|
+
const surface = createTwFrameSurface(PANEL_SKIN)
|
|
152
129
|
|
|
153
130
|
/** Pin the overlay to the center column's current viewport rect. */
|
|
154
131
|
const syncRect = (): void => {
|
|
@@ -193,176 +170,26 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
193
170
|
if (columnEl === undefined || !columnEl.isConnected) columnEl = conversationColumn()
|
|
194
171
|
if (container !== undefined) return
|
|
195
172
|
if (columnEl === undefined) return
|
|
196
|
-
|
|
173
|
+
// 惰性构建:内核自己也记着可见性,晚于 setVisible(true) 建 DOM 时会自动补一次
|
|
174
|
+
// /status(否则面板会停在空壳上)。
|
|
175
|
+
container = surface.build()
|
|
197
176
|
container.style.position = 'fixed'
|
|
198
177
|
applyChrome(true)
|
|
199
178
|
document.body.append(container)
|
|
200
179
|
syncRect()
|
|
201
180
|
}
|
|
202
181
|
|
|
203
|
-
const showError = (message: string): void => {
|
|
204
|
-
if (iframe === undefined || errorArea === undefined || frameArea === undefined) return
|
|
205
|
-
iframe.hidden = true
|
|
206
|
-
errorArea.hidden = false
|
|
207
|
-
errorArea.textContent = ''
|
|
208
|
-
const p = document.createElement('div')
|
|
209
|
-
p.textContent = 'TiddlyWiki 服务不可用'
|
|
210
|
-
const code = document.createElement('code')
|
|
211
|
-
code.textContent = message
|
|
212
|
-
const retry = document.createElement('button')
|
|
213
|
-
retry.type = 'button'
|
|
214
|
-
retry.textContent = '重试'
|
|
215
|
-
retry.addEventListener('click', () => {
|
|
216
|
-
retry.disabled = true
|
|
217
|
-
retry.textContent = '重启中…'
|
|
218
|
-
void requestRestart().finally(() => { void doRefresh() })
|
|
219
|
-
})
|
|
220
|
-
errorArea.append(p, code, retry)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const showStarting = (): void => {
|
|
224
|
-
if (iframe === undefined || errorArea === undefined || frameArea === undefined) return
|
|
225
|
-
iframe.hidden = true
|
|
226
|
-
errorArea.hidden = false
|
|
227
|
-
errorArea.textContent = ''
|
|
228
|
-
const p = document.createElement('div')
|
|
229
|
-
p.textContent = 'TiddlyWiki 服务正在启动…'
|
|
230
|
-
errorArea.append(p)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const showFrame = (url: string): void => {
|
|
234
|
-
if (iframe === undefined || errorArea === undefined) return
|
|
235
|
-
errorArea.hidden = true
|
|
236
|
-
// 只有面板打开时才显示 iframe(对照 tw-frame.ts 的 `frame.hidden = !visible`):
|
|
237
|
-
// doRefresh() 在面板关闭后仍可能在途,无条件 hidden=false 会让已关闭的面板
|
|
238
|
-
// 被一个迟到的 /status 响应重新显示出来。
|
|
239
|
-
iframe.hidden = !state.isOpen()
|
|
240
|
-
// Set the src only when the url actually changed, so an editor in the
|
|
241
|
-
// iframe never loses unsaved state on a status refresh.
|
|
242
|
-
if (iframe.dataset.loaded !== url) {
|
|
243
|
-
iframe.dataset.loaded = url
|
|
244
|
-
frameLoaded = false
|
|
245
|
-
iframe.src = url
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Apply a pending tiddler-hash navigation once the iframe document is ready.
|
|
251
|
-
* Preferred: same-origin `contentWindow.location.hash` — a hashchange INSIDE
|
|
252
|
-
* the frame (no reload), TW's story handler opens the tiddler. Fallback: a
|
|
253
|
-
* full `iframe.src` reload with the hash (TW auto-saves drafts, acceptable).
|
|
254
|
-
*
|
|
255
|
-
* TW registers its hashchange listener in a STARTUP module that runs after
|
|
256
|
-
* the iframe's `load` event, so setting `location.hash` right on load is a
|
|
257
|
-
* no-op (hash lost → TW stays on its home page). We therefore wait for TW to
|
|
258
|
-
* become ready inside the frame (the `$tw` global appears once boot settles)
|
|
259
|
-
* before setting the hash; if it never does (service down / slow boot) we
|
|
260
|
-
* fall back to a full reload with the hash, which TW processes at startup.
|
|
261
|
-
* A newer open request supersedes an in-flight wait.
|
|
262
|
-
*/
|
|
263
|
-
const applyPendingHash = (): void => {
|
|
264
|
-
if (pendingHash === null || iframe === undefined || !frameLoaded) return
|
|
265
|
-
const hash = pendingHash
|
|
266
|
-
const win = iframe.contentWindow
|
|
267
|
-
if (win === null) {
|
|
268
|
-
fallbackLoad(hash)
|
|
269
|
-
return
|
|
270
|
-
}
|
|
271
|
-
const tryOnce = (attempt: number): void => {
|
|
272
|
-
if (disposed) return // unmounted: do not keep waiting or touch the iframe
|
|
273
|
-
if (pendingHash !== hash) return // superseded by a newer request
|
|
274
|
-
const frameTw = win as { $tw?: unknown }
|
|
275
|
-
if (typeof frameTw.$tw !== 'object' || frameTw.$tw === null) {
|
|
276
|
-
if (attempt < 40) {
|
|
277
|
-
// Track the retry timer so dispose() can cancel it (v0.19.1): an
|
|
278
|
-
// untracked 40×150ms chain kept the closure (and the iframe) alive up
|
|
279
|
-
// to ~6s after hot-reload/unmount.
|
|
280
|
-
const timer = window.setTimeout(() => {
|
|
281
|
-
hashWaitTimers.delete(timer)
|
|
282
|
-
tryOnce(attempt + 1)
|
|
283
|
-
}, 150)
|
|
284
|
-
hashWaitTimers.add(timer)
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
fallbackLoad(hash)
|
|
288
|
-
return
|
|
289
|
-
}
|
|
290
|
-
pendingHash = null
|
|
291
|
-
try {
|
|
292
|
-
if (win.location.hash === hash) return
|
|
293
|
-
win.location.hash = hash
|
|
294
|
-
} catch {
|
|
295
|
-
fallbackLoad(hash)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
tryOnce(0)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const fallbackLoad = (hash: string): void => {
|
|
302
|
-
if (disposed || iframe === undefined) return // never drive a detached frame
|
|
303
|
-
if (pendingHash === hash) pendingHash = null
|
|
304
|
-
// dataset.loaded(showFrame 只写这个)而不是 iframe.src:src 为空时读出来
|
|
305
|
-
// 是**DSH 页面自己的 URL**,赋 `'' + '#标题'` 会把 DSH 载进 iframe(v0.22.3)。
|
|
306
|
-
const base = loadableFrameUrl(iframe.dataset)
|
|
307
|
-
if (base === null) return
|
|
308
|
-
const next = `${base.split('#')[0]}${hash}`
|
|
309
|
-
if (iframe.src !== next) iframe.src = next
|
|
310
|
-
}
|
|
311
|
-
|
|
312
182
|
const onOpenTiddler = (event: Event): void => {
|
|
313
183
|
const detail = (event as CustomEvent).detail as { title?: unknown } | undefined
|
|
314
184
|
const title = typeof detail?.title === 'string' && detail.title.length > 0 ? detail.title : ''
|
|
315
185
|
if (title.length === 0) return
|
|
316
|
-
// 侧边栏(rightbar)的 TW tab
|
|
317
|
-
//
|
|
318
|
-
// dsh-panel-activate 协议保证。
|
|
186
|
+
// 侧边栏(rightbar)的 TW tab 可见时,链接直接在那里打开(与聊天并排);
|
|
187
|
+
// 否则退回中央面板。互斥由 tw-frame.ts 共享的 dsh-panel-activate 协议保证。
|
|
319
188
|
if (openTiddlerInLiveTab(title)) return
|
|
320
|
-
|
|
189
|
+
// 顺序有意义:applyActive 由 openPanel() 同步触发,先把内核切到可见,
|
|
190
|
+
// openTiddler() 才会被内核接受(否则内核以 visible=false 拒绝,链接丢失)。
|
|
321
191
|
state.openPanel()
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const doRefresh = async (): Promise<void> => {
|
|
326
|
-
if (refreshTimer !== undefined) {
|
|
327
|
-
window.clearTimeout(refreshTimer)
|
|
328
|
-
refreshTimer = undefined
|
|
329
|
-
}
|
|
330
|
-
const payload = await fetchStatus()
|
|
331
|
-
if (disposed) return // unmounted while fetching: stop, don't touch DOM
|
|
332
|
-
if (payload === null) {
|
|
333
|
-
showError('无法访问 /dsh-tiddlywiki/status')
|
|
334
|
-
return
|
|
335
|
-
}
|
|
336
|
-
if (payload.status === 'running') {
|
|
337
|
-
refreshAttempts = 0
|
|
338
|
-
// Keep the embedded TW's theme adaption in step with the settings page.
|
|
339
|
-
setThemeSyncConfig({
|
|
340
|
-
enabled: payload.ui?.followDshTheme !== false,
|
|
341
|
-
darkPalette: payload.ui?.darkPalette,
|
|
342
|
-
})
|
|
343
|
-
// Same-origin proxy URL: build from the page's own origin so it works no
|
|
344
|
-
// matter which host/domain the user reached DSH on. Fall back to the
|
|
345
|
-
// legacy loopback `url` for older servers that do not send twProxy.
|
|
346
|
-
if (typeof payload.twProxy === 'string') {
|
|
347
|
-
showFrame(new URL(payload.twProxy, window.location.origin).href)
|
|
348
|
-
} else if (typeof payload.url === 'string') {
|
|
349
|
-
showFrame(payload.url)
|
|
350
|
-
} else {
|
|
351
|
-
showError('服务未返回编辑器地址')
|
|
352
|
-
}
|
|
353
|
-
return
|
|
354
|
-
}
|
|
355
|
-
if (payload.status === 'starting') {
|
|
356
|
-
showStarting()
|
|
357
|
-
// Auto-poll while starting (bounded).
|
|
358
|
-
if (refreshAttempts < 30) {
|
|
359
|
-
refreshAttempts++
|
|
360
|
-
refreshTimer = window.setTimeout(() => { void doRefresh() }, 1_500)
|
|
361
|
-
}
|
|
362
|
-
return
|
|
363
|
-
}
|
|
364
|
-
refreshAttempts = 0
|
|
365
|
-
showError(payload.error ?? `服务状态:${payload.status}`)
|
|
192
|
+
surface.openTiddler(title)
|
|
366
193
|
}
|
|
367
194
|
|
|
368
195
|
const applyActive = (): void => {
|
|
@@ -373,15 +200,12 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
373
200
|
// syncRect() 现在只在打开时测量(关闭状态早退),所以打开这一帧必须主动
|
|
374
201
|
// 请求一次布局:否则容器要等到下一个 2s interval 才有 left/top/width/height。
|
|
375
202
|
scheduleLayout()
|
|
376
|
-
void doRefresh()
|
|
377
203
|
} else {
|
|
378
204
|
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
379
|
-
if (refreshTimer !== undefined) {
|
|
380
|
-
window.clearTimeout(refreshTimer)
|
|
381
|
-
refreshTimer = undefined
|
|
382
|
-
}
|
|
383
|
-
refreshAttempts = 0
|
|
384
205
|
}
|
|
206
|
+
// 打开 = 首次加载 + 每次重探 /status;关闭 = 隐藏 frame 并停掉有界轮询
|
|
207
|
+
// (下次打开重新给满预算)。两种语义都在内核里。
|
|
208
|
+
surface.setVisible(state.isOpen())
|
|
385
209
|
}
|
|
386
210
|
const onOtherActivate = (event: Event): void => {
|
|
387
211
|
const detail = (event as CustomEvent).detail
|
|
@@ -431,25 +255,13 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
431
255
|
ensure()
|
|
432
256
|
applyActive()
|
|
433
257
|
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
// 只有真的载入过 TW 代理地址的 iframe 才允许重载:`!iframe.hidden` 已经不足
|
|
438
|
-
// 以证明这一点(showFrame 之外,iframe 也可能是「面板打开但首个 /status 还在
|
|
439
|
-
// 途」),而 `iframe.src = iframe.src` 在 src 为空串时会把 **DSH 页面**载进
|
|
440
|
-
// iframe(v0.22.3)。判据统一用 dataset.loaded(只由 showFrame 写)。
|
|
441
|
-
if (iframe === undefined) return
|
|
442
|
-
const loaded = loadableFrameUrl(iframe.dataset)
|
|
443
|
-
if (loaded !== null && !iframe.hidden) iframe.src = loaded
|
|
444
|
-
}
|
|
445
|
-
document.addEventListener(PANEL_RELOAD_EVENT, onReloadRequest)
|
|
258
|
+
// 链接路由入口(回复流工具卡 / 文档点击拦截器派发,见 `openTiddler()`)。
|
|
259
|
+
// FAB 的「重载面板」事件由内核自己挂监听(含「只重载真的载入过 TW 的 frame」
|
|
260
|
+
// 这条判据),panel 不再参与。
|
|
446
261
|
document.addEventListener(OPEN_TIDDLER_EVENT, onOpenTiddler)
|
|
447
262
|
|
|
448
263
|
return () => {
|
|
449
264
|
disposed = true
|
|
450
|
-
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
|
451
|
-
for (const timer of hashWaitTimers) window.clearTimeout(timer)
|
|
452
|
-
hashWaitTimers.clear()
|
|
453
265
|
if (layoutRaf !== undefined) window.cancelAnimationFrame(layoutRaf)
|
|
454
266
|
layoutRaf = undefined
|
|
455
267
|
window.clearInterval(syncInterval)
|
|
@@ -458,12 +270,11 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
458
270
|
resizeObserver.disconnect()
|
|
459
271
|
document.removeEventListener('click', onClickSidebarRow, true)
|
|
460
272
|
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
461
|
-
document.removeEventListener(PANEL_RELOAD_EVENT, onReloadRequest)
|
|
462
273
|
document.removeEventListener(OPEN_TIDDLER_EVENT, onOpenTiddler)
|
|
463
274
|
waitObserver.disconnect()
|
|
464
275
|
unsubscribe()
|
|
465
|
-
themeSyncDispose?.()
|
|
466
276
|
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
277
|
+
surface.dispose()
|
|
467
278
|
container?.remove()
|
|
468
279
|
}
|
|
469
280
|
}
|