dsh-tiddlywiki 0.1.1 → 0.2.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/LICENSE +21 -21
- package/README.md +206 -201
- package/cordis.patch.yml +13 -13
- package/lib/client.bundle.js +254 -19
- package/lib/client.js +254 -19
- package/lib/index.js +133 -22
- package/lib/index.js.map +1 -1
- package/package.json +79 -79
- package/src/client/editor-popup.ts +121 -121
- package/src/client/index.ts +76 -76
- package/src/client/note-widget.ts +183 -20
- package/src/client/panel.ts +345 -304
- package/src/client/settings-page.ts +400 -397
- package/src/client/sidebar-entry.ts +148 -148
- package/src/client/state.ts +39 -39
- package/src/client/styles.ts +285 -235
- package/src/client/toast.ts +22 -22
- package/src/host/admin.ts +408 -408
- package/src/host/config.ts +92 -86
- package/src/host/git.ts +218 -218
- package/src/host/routes.ts +272 -233
- package/src/host/seed-notes.ts +122 -62
- package/src/host/tools.ts +254 -254
- package/src/host/tw-api.ts +157 -157
- package/src/host/wiki.ts +287 -287
- package/src/index.ts +346 -331
- package/src/sdk.ts +198 -198
package/src/client/panel.ts
CHANGED
|
@@ -1,304 +1,345 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Center-column TiddlyWiki editor panel (design doc §12).
|
|
3
|
-
*
|
|
4
|
-
* A fixed-position overlay sized to the CENTER column's bounding rect and
|
|
5
|
-
* appended to document.body, so it never depends on the shell's flex/grid
|
|
6
|
-
* height model or on the column being a positioning ancestor (both caused the
|
|
7
|
-
* earlier "half-height, scrollable" symptom). The rect is re-measured on
|
|
8
|
-
* resize, on layout mutations, and on a light interval. While active it hides
|
|
9
|
-
* the conversation content via a stylesheet rule keyed on `data-dsh-tw-active`.
|
|
10
|
-
* Toggling rides the shared PanelState; cross-plugin exclusivity rides the
|
|
11
|
-
* `dsh-panel-activate` event (same protocol as dsh-taskboard).
|
|
12
|
-
*
|
|
13
|
-
* The iframe points DIRECTLY at the TW service (http://127.0.0.1:<port>),
|
|
14
|
-
* bypassing reverse-proxy sensitivity (design doc R1 — proxy is only a
|
|
15
|
-
* fallback). The panel reads /status first and only sets iframe.src when the
|
|
16
|
-
* service is actually running.
|
|
17
|
-
*
|
|
18
|
-
* @module dsh-tiddlywiki/client/panel
|
|
19
|
-
*/
|
|
20
|
-
import type { PanelState } from './state.ts'
|
|
21
|
-
import { ENTRY_SELECTOR } from './sidebar-entry.ts'
|
|
22
|
-
|
|
23
|
-
export const PANEL_VIEW_SELECTOR = '[data-dsh-tw-view]'
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Center-column targets, most-specific shell generation first. The official
|
|
27
|
-
* layout shell (dsh-client-ui-layout) drops data-pane and uses a CSS-Module
|
|
28
|
-
* hashed `centerCol`; older shells put `data-pane="conversation"` on the same
|
|
29
|
-
* full-height grid item; DSH Desktop exposes the non-compat
|
|
30
|
-
* `.dshDesktopConversationSurface`.
|
|
31
|
-
*/
|
|
32
|
-
const COLUMN_SELECTORS = ['[class*="centerCol"]', '[data-pane="conversation"]', '.dshDesktopConversationSurface']
|
|
33
|
-
|
|
34
|
-
const ACTIVE_ATTR = 'data-dsh-tw-active'
|
|
35
|
-
/** Sibling panels' activation attributes, evicted when this panel opens. */
|
|
36
|
-
const OTHER_ACTIVE_ATTRS = ['data-dsh-atb-active', 'data-dsh-taskboard-active', 'data-dsh-ssh-active']
|
|
37
|
-
/** Cross-plugin activation event; detail is the activating panel name. */
|
|
38
|
-
const ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
39
|
-
const PANEL_NAME = 'dsh-tiddlywiki'
|
|
40
|
-
|
|
41
|
-
/** Overlay z-index: above the shell content, below the note widget (950). */
|
|
42
|
-
const PANEL_Z_INDEX = 40
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Center-column TiddlyWiki editor panel (design doc §12).
|
|
3
|
+
*
|
|
4
|
+
* A fixed-position overlay sized to the CENTER column's bounding rect and
|
|
5
|
+
* appended to document.body, so it never depends on the shell's flex/grid
|
|
6
|
+
* height model or on the column being a positioning ancestor (both caused the
|
|
7
|
+
* earlier "half-height, scrollable" symptom). The rect is re-measured on
|
|
8
|
+
* resize, on layout mutations, and on a light interval. While active it hides
|
|
9
|
+
* the conversation content via a stylesheet rule keyed on `data-dsh-tw-active`.
|
|
10
|
+
* Toggling rides the shared PanelState; cross-plugin exclusivity rides the
|
|
11
|
+
* `dsh-panel-activate` event (same protocol as dsh-taskboard).
|
|
12
|
+
*
|
|
13
|
+
* The iframe points DIRECTLY at the TW service (http://127.0.0.1:<port>),
|
|
14
|
+
* bypassing reverse-proxy sensitivity (design doc R1 — proxy is only a
|
|
15
|
+
* fallback). The panel reads /status first and only sets iframe.src when the
|
|
16
|
+
* service is actually running.
|
|
17
|
+
*
|
|
18
|
+
* @module dsh-tiddlywiki/client/panel
|
|
19
|
+
*/
|
|
20
|
+
import type { PanelState } from './state.ts'
|
|
21
|
+
import { ENTRY_SELECTOR } from './sidebar-entry.ts'
|
|
22
|
+
|
|
23
|
+
export const PANEL_VIEW_SELECTOR = '[data-dsh-tw-view]'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Center-column targets, most-specific shell generation first. The official
|
|
27
|
+
* layout shell (dsh-client-ui-layout) drops data-pane and uses a CSS-Module
|
|
28
|
+
* hashed `centerCol`; older shells put `data-pane="conversation"` on the same
|
|
29
|
+
* full-height grid item; DSH Desktop exposes the non-compat
|
|
30
|
+
* `.dshDesktopConversationSurface`.
|
|
31
|
+
*/
|
|
32
|
+
const COLUMN_SELECTORS = ['[class*="centerCol"]', '[data-pane="conversation"]', '.dshDesktopConversationSurface']
|
|
33
|
+
|
|
34
|
+
const ACTIVE_ATTR = 'data-dsh-tw-active'
|
|
35
|
+
/** Sibling panels' activation attributes, evicted when this panel opens. */
|
|
36
|
+
const OTHER_ACTIVE_ATTRS = ['data-dsh-atb-active', 'data-dsh-taskboard-active', 'data-dsh-ssh-active']
|
|
37
|
+
/** Cross-plugin activation event; detail is the activating panel name. */
|
|
38
|
+
const ACTIVATE_EVENT = 'dsh-panel-activate'
|
|
39
|
+
const PANEL_NAME = 'dsh-tiddlywiki'
|
|
40
|
+
|
|
41
|
+
/** Overlay z-index: above the shell content, below the note widget (950). */
|
|
42
|
+
const PANEL_Z_INDEX = 40
|
|
43
|
+
/**
|
|
44
|
+
* dsh-better-sidebar's unified fixed host layer (its persistent
|
|
45
|
+
* expand/collapse toggle cluster lives inside it, at a global z-index of 25
|
|
46
|
+
* — its internal 45 is trapped by the host's stacking context). When this
|
|
47
|
+
* host is present the panel must stay BELOW it, so the sidebar toggle
|
|
48
|
+
* buttons stay visible and clickable above the full-screen TW panel.
|
|
49
|
+
*/
|
|
50
|
+
const PANEL_HOST_SELECTOR = '[data-dsh-panel-host]'
|
|
51
|
+
/** The app's own shell overlay layer (dsh-client-ui-layout pins it at 20). */
|
|
52
|
+
const APP_OVERLAY_Z_INDEX = 20
|
|
53
|
+
/** Safety re-measure cadence for shell layout changes CSS can't see. */
|
|
54
|
+
const SYNC_INTERVAL_MS = 2_000
|
|
55
|
+
|
|
56
|
+
const STATUS_ENDPOINT = '/dsh-tiddlywiki/status'
|
|
57
|
+
const RESTART_ENDPOINT = '/dsh-tiddlywiki/restart'
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The panel's z-index: below any dsh-better-sidebar host layer so its
|
|
61
|
+
* persistent toggle cluster (top-right corner) stays visible and clickable
|
|
62
|
+
* above the full-screen TW panel, otherwise the default 40. The host's live
|
|
63
|
+
* computed z-index is read instead of hardcoding 25, so the rule tracks
|
|
64
|
+
* plugin updates; the panel is still clamped above the app's own shell
|
|
65
|
+
* overlay layer (dsh-client-ui-layout pins it at 20).
|
|
66
|
+
*/
|
|
67
|
+
function resolvePanelZIndex(): number {
|
|
68
|
+
const host = document.querySelector<HTMLElement>(PANEL_HOST_SELECTOR)
|
|
69
|
+
if (host === null) return PANEL_Z_INDEX
|
|
70
|
+
const parsed = parseInt(getComputedStyle(host).zIndex, 10)
|
|
71
|
+
if (!Number.isFinite(parsed)) return PANEL_Z_INDEX
|
|
72
|
+
return Math.max(APP_OVERLAY_Z_INDEX, Math.min(PANEL_Z_INDEX, parsed - 1))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface StatusPayload {
|
|
76
|
+
ok?: boolean
|
|
77
|
+
status: string
|
|
78
|
+
url?: string
|
|
79
|
+
wikiPath?: string
|
|
80
|
+
error?: string
|
|
81
|
+
note?: { tag?: string }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function conversationColumn(): HTMLElement | undefined {
|
|
85
|
+
for (const selector of COLUMN_SELECTORS) {
|
|
86
|
+
const el = document.querySelector<HTMLElement>(selector)
|
|
87
|
+
if (el !== null) return el
|
|
88
|
+
}
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function fetchStatus(): Promise<StatusPayload | null> {
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(STATUS_ENDPOINT, { signal: AbortSignal.timeout(8_000) })
|
|
95
|
+
if (!res.ok) return null
|
|
96
|
+
return (await res.json()) as StatusPayload
|
|
97
|
+
} catch {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function requestRestart(): Promise<boolean> {
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(RESTART_ENDPOINT, { method: 'POST', signal: AbortSignal.timeout(8_000) })
|
|
105
|
+
return res.ok
|
|
106
|
+
} catch {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function mountPanel(state: PanelState): () => void {
|
|
112
|
+
let container: HTMLDivElement | undefined
|
|
113
|
+
let columnEl: HTMLElement | undefined
|
|
114
|
+
let iframe: HTMLIFrameElement | undefined
|
|
115
|
+
let frameArea: HTMLDivElement | undefined
|
|
116
|
+
let errorArea: HTMLDivElement | undefined
|
|
117
|
+
let chip: HTMLSpanElement | undefined
|
|
118
|
+
let refreshTimer: number | undefined
|
|
119
|
+
let refreshAttempts = 0
|
|
120
|
+
|
|
121
|
+
const build = (): HTMLDivElement => {
|
|
122
|
+
const view = document.createElement('div')
|
|
123
|
+
view.dataset.dshTwView = ''
|
|
124
|
+
view.className = 'dsh-tw-view'
|
|
125
|
+
|
|
126
|
+
const bar = document.createElement('div')
|
|
127
|
+
bar.className = 'dsh-tw-panel-bar'
|
|
128
|
+
const title = document.createElement('span')
|
|
129
|
+
title.className = 'dsh-tw-panel-title'
|
|
130
|
+
title.textContent = 'TiddlyWiki 知识库'
|
|
131
|
+
chip = document.createElement('span')
|
|
132
|
+
chip.className = 'dsh-tw-status-chip'
|
|
133
|
+
chip.dataset.state = 'unknown'
|
|
134
|
+
chip.textContent = '—'
|
|
135
|
+
const reload = document.createElement('button')
|
|
136
|
+
reload.type = 'button'
|
|
137
|
+
reload.textContent = '重载'
|
|
138
|
+
reload.addEventListener('click', () => {
|
|
139
|
+
if (iframe !== undefined && !iframe.hidden) iframe.src = iframe.src
|
|
140
|
+
})
|
|
141
|
+
const refresh = document.createElement('button')
|
|
142
|
+
refresh.type = 'button'
|
|
143
|
+
refresh.textContent = '状态'
|
|
144
|
+
refresh.addEventListener('click', () => { void doRefresh() })
|
|
145
|
+
bar.append(title, chip, refresh, reload)
|
|
146
|
+
|
|
147
|
+
frameArea = document.createElement('div')
|
|
148
|
+
frameArea.className = 'dsh-tw-panel-frame-wrap'
|
|
149
|
+
frameArea.style.cssText = 'flex:1;min-height:0;display:flex;flex-direction:column'
|
|
150
|
+
iframe = document.createElement('iframe')
|
|
151
|
+
iframe.className = 'dsh-tw-panel-frame'
|
|
152
|
+
iframe.title = 'TiddlyWiki'
|
|
153
|
+
iframe.hidden = true
|
|
154
|
+
frameArea.append(iframe)
|
|
155
|
+
|
|
156
|
+
errorArea = document.createElement('div')
|
|
157
|
+
errorArea.className = 'dsh-tw-panel-error'
|
|
158
|
+
errorArea.hidden = true
|
|
159
|
+
|
|
160
|
+
view.append(bar, frameArea, errorArea)
|
|
161
|
+
return view
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const setChip = (stateName: string, text: string): void => {
|
|
165
|
+
if (chip === undefined) return
|
|
166
|
+
chip.dataset.state = stateName
|
|
167
|
+
chip.textContent = text
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Pin the overlay to the center column's current viewport rect. */
|
|
171
|
+
const syncRect = (): void => {
|
|
172
|
+
if (container === undefined || columnEl === undefined) return
|
|
173
|
+
const rect = columnEl.getBoundingClientRect()
|
|
174
|
+
if (rect.width === 0 && rect.height === 0) return
|
|
175
|
+
const left = `${rect.left}px`
|
|
176
|
+
const top = `${rect.top}px`
|
|
177
|
+
const width = `${rect.width}px`
|
|
178
|
+
const height = `${rect.height}px`
|
|
179
|
+
if (container.style.left !== left) container.style.left = left
|
|
180
|
+
if (container.style.top !== top) container.style.top = top
|
|
181
|
+
if (container.style.width !== width) container.style.width = width
|
|
182
|
+
if (container.style.height !== height) container.style.height = height
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Coexist with dsh-better-sidebar: keep the panel's z-index below the
|
|
187
|
+
* host layer (so its toggle cluster stays clickable above the panel) and
|
|
188
|
+
* flag the host's presence so CSS can reserve the cluster's width at the
|
|
189
|
+
* panel bar's right end. Re-run whenever the host mounts/unmounts.
|
|
190
|
+
*/
|
|
191
|
+
const applyChrome = (): void => {
|
|
192
|
+
if (container === undefined) return
|
|
193
|
+
container.style.zIndex = String(resolvePanelZIndex())
|
|
194
|
+
const hasHost = document.querySelector(PANEL_HOST_SELECTOR) !== null
|
|
195
|
+
if (hasHost) container.dataset.sidebarHost = '1'
|
|
196
|
+
else delete container.dataset.sidebarHost
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const ensure = (): void => {
|
|
200
|
+
if (container !== undefined) return
|
|
201
|
+
columnEl = conversationColumn()
|
|
202
|
+
if (columnEl === undefined) return
|
|
203
|
+
container = build()
|
|
204
|
+
container.style.position = 'fixed'
|
|
205
|
+
applyChrome()
|
|
206
|
+
document.body.append(container)
|
|
207
|
+
syncRect()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const showError = (message: string): void => {
|
|
211
|
+
if (iframe === undefined || errorArea === undefined || frameArea === undefined) return
|
|
212
|
+
iframe.hidden = true
|
|
213
|
+
errorArea.hidden = false
|
|
214
|
+
errorArea.textContent = ''
|
|
215
|
+
const p = document.createElement('div')
|
|
216
|
+
p.textContent = 'TiddlyWiki 服务不可用'
|
|
217
|
+
const code = document.createElement('code')
|
|
218
|
+
code.textContent = message
|
|
219
|
+
const retry = document.createElement('button')
|
|
220
|
+
retry.type = 'button'
|
|
221
|
+
retry.textContent = '重试'
|
|
222
|
+
retry.addEventListener('click', () => {
|
|
223
|
+
retry.disabled = true
|
|
224
|
+
retry.textContent = '重启中…'
|
|
225
|
+
void requestRestart().finally(() => { void doRefresh() })
|
|
226
|
+
})
|
|
227
|
+
errorArea.append(p, code, retry)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const showStarting = (): void => {
|
|
231
|
+
if (iframe === undefined || errorArea === undefined || frameArea === undefined) return
|
|
232
|
+
iframe.hidden = true
|
|
233
|
+
errorArea.hidden = false
|
|
234
|
+
errorArea.textContent = ''
|
|
235
|
+
const p = document.createElement('div')
|
|
236
|
+
p.textContent = 'TiddlyWiki 服务正在启动…'
|
|
237
|
+
errorArea.append(p)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const showFrame = (url: string): void => {
|
|
241
|
+
if (iframe === undefined || errorArea === undefined) return
|
|
242
|
+
errorArea.hidden = true
|
|
243
|
+
iframe.hidden = false
|
|
244
|
+
// Set the src only when the url actually changed, so an editor in the
|
|
245
|
+
// iframe never loses unsaved state on a status refresh.
|
|
246
|
+
if (iframe.dataset.loaded !== url) {
|
|
247
|
+
iframe.dataset.loaded = url
|
|
248
|
+
iframe.src = url
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const doRefresh = async (): Promise<void> => {
|
|
253
|
+
if (refreshTimer !== undefined) {
|
|
254
|
+
window.clearTimeout(refreshTimer)
|
|
255
|
+
refreshTimer = undefined
|
|
256
|
+
}
|
|
257
|
+
const payload = await fetchStatus()
|
|
258
|
+
if (payload === null) {
|
|
259
|
+
setChip('failed', '状态不可达')
|
|
260
|
+
showError('无法访问 /dsh-tiddlywiki/status')
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
if (payload.status === 'running' && typeof payload.url === 'string') {
|
|
264
|
+
setChip('running', '在线')
|
|
265
|
+
refreshAttempts = 0
|
|
266
|
+
showFrame(payload.url)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
if (payload.status === 'starting') {
|
|
270
|
+
setChip('starting', '启动中')
|
|
271
|
+
showStarting()
|
|
272
|
+
// Auto-poll while starting (bounded).
|
|
273
|
+
if (refreshAttempts < 30) {
|
|
274
|
+
refreshAttempts++
|
|
275
|
+
refreshTimer = window.setTimeout(() => { void doRefresh() }, 1_500)
|
|
276
|
+
}
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
setChip('failed', '离线')
|
|
280
|
+
refreshAttempts = 0
|
|
281
|
+
showError(payload.error ?? `服务状态:${payload.status}`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const applyActive = (): void => {
|
|
285
|
+
if (state.isOpen()) {
|
|
286
|
+
for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
|
|
287
|
+
document.documentElement.setAttribute(ACTIVE_ATTR, '')
|
|
288
|
+
document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
|
|
289
|
+
void doRefresh()
|
|
290
|
+
} else {
|
|
291
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
292
|
+
if (refreshTimer !== undefined) {
|
|
293
|
+
window.clearTimeout(refreshTimer)
|
|
294
|
+
refreshTimer = undefined
|
|
295
|
+
}
|
|
296
|
+
refreshAttempts = 0
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const onOtherActivate = (event: Event): void => {
|
|
300
|
+
const detail = (event as CustomEvent).detail
|
|
301
|
+
if (detail !== PANEL_NAME && state.isOpen()) state.closePanel()
|
|
302
|
+
}
|
|
303
|
+
const onClickSidebarRow = (event: MouseEvent): void => {
|
|
304
|
+
if (!state.isOpen()) return
|
|
305
|
+
const target = event.target as HTMLElement | null
|
|
306
|
+
if (target === null) return
|
|
307
|
+
if (target.closest(ENTRY_SELECTOR) !== null) return
|
|
308
|
+
const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
|
|
309
|
+
if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) state.closePanel()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Mount the container once the column exists; self-heal on re-renders and
|
|
313
|
+
// re-resolve the coexistence chrome when better-sidebar mounts/unmounts.
|
|
314
|
+
const waitObserver = new MutationObserver(() => { ensure(); applyChrome() })
|
|
315
|
+
waitObserver.observe(document.body, { childList: true, subtree: true })
|
|
316
|
+
|
|
317
|
+
// Keep the overlay pinned to the column: resize, layout mutations, scroll.
|
|
318
|
+
const resizeObserver = new ResizeObserver(() => syncRect())
|
|
319
|
+
resizeObserver.observe(document.body)
|
|
320
|
+
const syncInterval = window.setInterval(syncRect, SYNC_INTERVAL_MS)
|
|
321
|
+
const onWindowResize = (): void => syncRect()
|
|
322
|
+
window.addEventListener('resize', onWindowResize)
|
|
323
|
+
const onAnyScroll = (): void => syncRect()
|
|
324
|
+
window.addEventListener('scroll', onAnyScroll, true)
|
|
325
|
+
|
|
326
|
+
document.addEventListener('click', onClickSidebarRow, true)
|
|
327
|
+
document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
328
|
+
const unsubscribe = state.subscribe(applyActive)
|
|
329
|
+
ensure()
|
|
330
|
+
applyActive()
|
|
331
|
+
|
|
332
|
+
return () => {
|
|
333
|
+
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
|
334
|
+
window.clearInterval(syncInterval)
|
|
335
|
+
window.removeEventListener('resize', onWindowResize)
|
|
336
|
+
window.removeEventListener('scroll', onAnyScroll, true)
|
|
337
|
+
resizeObserver.disconnect()
|
|
338
|
+
document.removeEventListener('click', onClickSidebarRow, true)
|
|
339
|
+
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
340
|
+
waitObserver.disconnect()
|
|
341
|
+
unsubscribe()
|
|
342
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
343
|
+
container?.remove()
|
|
344
|
+
}
|
|
345
|
+
}
|