dsh-tiddlywiki 0.4.0 → 0.6.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 +55 -25
- package/lib/client.bundle.js +903 -386
- package/lib/client.js +903 -386
- package/lib/index.js +796 -32
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/client/index.ts +10 -4
- package/src/client/knowledge-fab.ts +232 -0
- package/src/client/markdown-editor.ts +5 -0
- package/src/client/note-widget.ts +725 -475
- package/src/client/panel.ts +27 -62
- package/src/client/settings-page.ts +3 -3
- package/src/client/styles.ts +110 -0
- package/src/client/sync-button.ts +191 -188
- package/src/host/git.ts +21 -0
- package/src/host/routes.ts +145 -8
- package/src/host/seed-notes.ts +7 -5
- package/src/host/tools.ts +589 -281
- package/src/host/tw-api.ts +80 -6
- package/src/host/wiki.ts +12 -0
- package/src/index.ts +46 -6
- package/src/sdk.ts +8 -2
package/src/client/panel.ts
CHANGED
|
@@ -10,10 +10,13 @@
|
|
|
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
|
-
* The iframe points
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* The iframe points at the SAME-ORIGIN TW proxy (`<origin>/dsh-tiddlywiki/tw/`,
|
|
14
|
+
* remote-access mode R1): the browser only talks to the DSH origin — which it
|
|
15
|
+
* already reaches over loopback, LAN, Tailscale, a domain or HTTPS — and DSH
|
|
16
|
+
* proxies to the loopback TW child. The panel reads /status first and only
|
|
17
|
+
* sets iframe.src when the service is actually running. Because the proxy URL
|
|
18
|
+
* is origin-relative and port-independent, a TW restart never reloads the
|
|
19
|
+
* iframe, so an in-progress edit keeps its unsaved state.
|
|
17
20
|
*
|
|
18
21
|
* @module dsh-tiddlywiki/client/panel
|
|
19
22
|
*/
|
|
@@ -76,6 +79,8 @@ interface StatusPayload {
|
|
|
76
79
|
ok?: boolean
|
|
77
80
|
status: string
|
|
78
81
|
url?: string
|
|
82
|
+
/** Same-origin TW proxy path (e.g. /dsh-tiddlywiki/tw/); the iframe base. */
|
|
83
|
+
twProxy?: string
|
|
79
84
|
wikiPath?: string
|
|
80
85
|
error?: string
|
|
81
86
|
note?: { tag?: string }
|
|
@@ -115,9 +120,6 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
115
120
|
let iframe: HTMLIFrameElement | undefined
|
|
116
121
|
let frameArea: HTMLDivElement | undefined
|
|
117
122
|
let errorArea: HTMLDivElement | undefined
|
|
118
|
-
let chip: HTMLSpanElement | undefined
|
|
119
|
-
let statusDot: HTMLSpanElement | undefined
|
|
120
|
-
let statusFloater: HTMLDivElement | undefined
|
|
121
123
|
let refreshTimer: number | undefined
|
|
122
124
|
let refreshAttempts = 0
|
|
123
125
|
|
|
@@ -143,48 +145,6 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
143
145
|
return view
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
/**
|
|
147
|
-
* Bottom-right floating status/reload buttons (same visual language as the
|
|
148
|
-
* quick-note toggle) — the top panel bar is gone, so TW's menubar reaches
|
|
149
|
-
* the very top of the panel and clears the host's top-right buttons. Mount
|
|
150
|
-
* is gated by `ui.showPanelStatus` (settings page toggle).
|
|
151
|
-
*/
|
|
152
|
-
const buildStatusFloater = (): HTMLDivElement => {
|
|
153
|
-
const wrap = document.createElement('div')
|
|
154
|
-
wrap.className = 'dsh-tw-panel-status'
|
|
155
|
-
|
|
156
|
-
const statusBtn = document.createElement('button')
|
|
157
|
-
statusBtn.type = 'button'
|
|
158
|
-
statusBtn.className = 'dsh-tw-panel-status-btn'
|
|
159
|
-
statusBtn.title = '刷新状态'
|
|
160
|
-
statusDot = document.createElement('span')
|
|
161
|
-
statusDot.className = 'dsh-tw-status-dot'
|
|
162
|
-
statusDot.dataset.state = 'unknown'
|
|
163
|
-
chip = document.createElement('span')
|
|
164
|
-
chip.className = 'dsh-tw-panel-status-text'
|
|
165
|
-
chip.textContent = '—'
|
|
166
|
-
statusBtn.append(statusDot, chip)
|
|
167
|
-
statusBtn.addEventListener('click', () => { void doRefresh() })
|
|
168
|
-
|
|
169
|
-
const reloadBtn = document.createElement('button')
|
|
170
|
-
reloadBtn.type = 'button'
|
|
171
|
-
reloadBtn.className = 'dsh-tw-panel-status-btn'
|
|
172
|
-
reloadBtn.textContent = '重载'
|
|
173
|
-
reloadBtn.title = '重载 TiddlyWiki 面板'
|
|
174
|
-
reloadBtn.addEventListener('click', () => {
|
|
175
|
-
if (iframe !== undefined && !iframe.hidden) iframe.src = iframe.src
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
wrap.append(statusBtn, reloadBtn)
|
|
179
|
-
document.body.append(wrap)
|
|
180
|
-
return wrap
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const setChip = (stateName: string, text: string): void => {
|
|
184
|
-
if (statusDot !== undefined) statusDot.dataset.state = stateName
|
|
185
|
-
if (chip !== undefined) chip.textContent = text
|
|
186
|
-
}
|
|
187
|
-
|
|
188
148
|
/** Pin the overlay to the center column's current viewport rect. */
|
|
189
149
|
const syncRect = (): void => {
|
|
190
150
|
if (container === undefined || columnEl === undefined) return
|
|
@@ -274,18 +234,24 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
274
234
|
}
|
|
275
235
|
const payload = await fetchStatus()
|
|
276
236
|
if (payload === null) {
|
|
277
|
-
setChip('failed', '状态不可达')
|
|
278
237
|
showError('无法访问 /dsh-tiddlywiki/status')
|
|
279
238
|
return
|
|
280
239
|
}
|
|
281
|
-
if (payload.status === 'running'
|
|
282
|
-
setChip('running', '在线')
|
|
240
|
+
if (payload.status === 'running') {
|
|
283
241
|
refreshAttempts = 0
|
|
284
|
-
|
|
242
|
+
// Same-origin proxy URL: build from the page's own origin so it works no
|
|
243
|
+
// matter which host/domain the user reached DSH on. Fall back to the
|
|
244
|
+
// legacy loopback `url` for older servers that do not send twProxy.
|
|
245
|
+
if (typeof payload.twProxy === 'string') {
|
|
246
|
+
showFrame(new URL(payload.twProxy, window.location.origin).href)
|
|
247
|
+
} else if (typeof payload.url === 'string') {
|
|
248
|
+
showFrame(payload.url)
|
|
249
|
+
} else {
|
|
250
|
+
showError('服务未返回编辑器地址')
|
|
251
|
+
}
|
|
285
252
|
return
|
|
286
253
|
}
|
|
287
254
|
if (payload.status === 'starting') {
|
|
288
|
-
setChip('starting', '启动中')
|
|
289
255
|
showStarting()
|
|
290
256
|
// Auto-poll while starting (bounded).
|
|
291
257
|
if (refreshAttempts < 30) {
|
|
@@ -294,7 +260,6 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
294
260
|
}
|
|
295
261
|
return
|
|
296
262
|
}
|
|
297
|
-
setChip('failed', '离线')
|
|
298
263
|
refreshAttempts = 0
|
|
299
264
|
showError(payload.error ?? `服务状态:${payload.status}`)
|
|
300
265
|
}
|
|
@@ -347,12 +312,12 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
347
312
|
ensure()
|
|
348
313
|
applyActive()
|
|
349
314
|
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
315
|
+
// The "知识库" FAB's 重载面板 entry dispatches this event to reload the
|
|
316
|
+
// iframe (the panel itself no longer owns a floating status/reload button).
|
|
317
|
+
const onReloadRequest = (): void => {
|
|
318
|
+
if (iframe !== undefined && !iframe.hidden) iframe.src = iframe.src
|
|
319
|
+
}
|
|
320
|
+
document.addEventListener('dsh-tw-panel-reload', onReloadRequest)
|
|
356
321
|
|
|
357
322
|
return () => {
|
|
358
323
|
if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
|
|
@@ -362,10 +327,10 @@ export function mountPanel(state: PanelState): () => void {
|
|
|
362
327
|
resizeObserver.disconnect()
|
|
363
328
|
document.removeEventListener('click', onClickSidebarRow, true)
|
|
364
329
|
document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
|
|
330
|
+
document.removeEventListener('dsh-tw-panel-reload', onReloadRequest)
|
|
365
331
|
waitObserver.disconnect()
|
|
366
332
|
unsubscribe()
|
|
367
333
|
document.documentElement.removeAttribute(ACTIVE_ATTR)
|
|
368
334
|
container?.remove()
|
|
369
|
-
statusFloater?.remove()
|
|
370
335
|
}
|
|
371
336
|
}
|
|
@@ -210,9 +210,9 @@ function renderConfigSection(body: HTMLElement, config: Record<string, unknown>,
|
|
|
210
210
|
numField('git.debounceMs', '自动 commit 防抖(ms)', typeof git.debounceMs === 'number' ? git.debounceMs : 60_000)
|
|
211
211
|
textField('git.remote', 'git 远端(空=仅本地)', typeof git.remote === 'string' ? git.remote : '')
|
|
212
212
|
textField('git.branch', 'git 分支', typeof git.branch === 'string' ? git.branch : 'main')
|
|
213
|
-
checkField('ui.showQuickNote', '
|
|
214
|
-
checkField('ui.showPanelStatus', '
|
|
215
|
-
checkField('ui.showSyncButton', '
|
|
213
|
+
checkField('ui.showQuickNote', '显示「知识库」按钮里的「快速笔记」入口', ui.showQuickNote !== false)
|
|
214
|
+
checkField('ui.showPanelStatus', '显示「知识库」按钮里的 TW 面板/重载入口与状态行', ui.showPanelStatus !== false)
|
|
215
|
+
checkField('ui.showSyncButton', '显示「知识库」按钮里的「同步」入口与 git 状态点', ui.showSyncButton !== false)
|
|
216
216
|
// 界面语言在下方「语言管理」区块设置(config 的 uiLanguage 仅供启动时自动应用)。
|
|
217
217
|
|
|
218
218
|
const save = make('button', 'dsh-tw-settings-btn dsh-tw-settings-primary', '保存配置')
|
package/src/client/styles.ts
CHANGED
|
@@ -104,6 +104,9 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
104
104
|
display: flex; flex-direction: column; align-items: flex-end; gap: 10px;
|
|
105
105
|
font-family: inherit;
|
|
106
106
|
}
|
|
107
|
+
/* The wrapper's display:flex would beat the UA [hidden]{display:none} rule,
|
|
108
|
+
so the card's show/hide (root.hidden) needs an explicit rule. */
|
|
109
|
+
.dsh-tw-note[hidden] { display: none; }
|
|
107
110
|
.dsh-tw-note-card {
|
|
108
111
|
width: 340px; max-width: calc(100vw - 40px);
|
|
109
112
|
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
@@ -344,6 +347,113 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
|
|
|
344
347
|
.dsh-tw-settings-error { color: var(--dsw-alias-state-error-primary, #d13b3b); font-size: 12px; }
|
|
345
348
|
.dsh-tw-settings-search { flex: 0 0 auto; max-width: 220px; }
|
|
346
349
|
.dsh-tw-settings-check { accent-color: var(--dsw-alias-brand-primary, #3e63dd); }
|
|
350
|
+
|
|
351
|
+
/* ── "知识库" FAB (v0.5: quick-note + sync + panel status merged) ──────────
|
|
352
|
+
One fixed cluster above the shutdown launcher FAB (bottom:24). The FAB
|
|
353
|
+
carries a git status dot; the menu pops upward from it. */
|
|
354
|
+
.dsh-tw-fab-wrap {
|
|
355
|
+
position: fixed; right: 24px; bottom: 88px; z-index: 960;
|
|
356
|
+
display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
|
|
357
|
+
}
|
|
358
|
+
.dsh-tw-fab {
|
|
359
|
+
position: relative;
|
|
360
|
+
width: 46px; height: 46px; border-radius: 50%;
|
|
361
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
362
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
363
|
+
box-shadow: var(--dsw-shadow-lv3, 0 4px 16px rgba(0,0,0,.16)); cursor: pointer;
|
|
364
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
365
|
+
transition: background-color 120ms ease, transform 80ms ease;
|
|
366
|
+
}
|
|
367
|
+
.dsh-tw-fab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.1)); }
|
|
368
|
+
.dsh-tw-fab:active { transform: scale(.94); }
|
|
369
|
+
.dsh-tw-fab-icon { display: inline-flex; }
|
|
370
|
+
.dsh-tw-fab-dot {
|
|
371
|
+
position: absolute; right: 0; bottom: 0; width: 11px; height: 11px;
|
|
372
|
+
border-radius: 50%; border: 2px solid var(--dsw-alias-bg-layer-2, #fff);
|
|
373
|
+
background: #999; box-sizing: border-box;
|
|
374
|
+
}
|
|
375
|
+
.dsh-tw-fab-dot[data-state="clean"] { background: var(--dsw-alias-state-success-primary, #3eaa5f); }
|
|
376
|
+
.dsh-tw-fab-dot[data-state="dirty"] { background: var(--dsw-alias-state-warning-primary, #d9822b); }
|
|
377
|
+
.dsh-tw-fab-dot[data-state="behind"] { background: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
378
|
+
.dsh-tw-fab-dot[data-state="syncing"] { background: var(--dsw-alias-brand-primary, #3e63dd); }
|
|
379
|
+
.dsh-tw-fab-menu {
|
|
380
|
+
min-width: 220px; max-width: calc(100vw - 48px);
|
|
381
|
+
display: flex; flex-direction: column; gap: 2px; padding: 6px;
|
|
382
|
+
border-radius: 12px; border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.15));
|
|
383
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
384
|
+
box-shadow: var(--dsw-shadow-lv3, 0 8px 30px rgba(0,0,0,.22));
|
|
385
|
+
font-size: 13px;
|
|
386
|
+
backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
|
|
387
|
+
animation: dsh-tw-note-in 160ms ease;
|
|
388
|
+
}
|
|
389
|
+
.dsh-tw-fab-menu[hidden] { display: none; }
|
|
390
|
+
.dsh-tw-fab-status {
|
|
391
|
+
display: flex; align-items: center; gap: 7px;
|
|
392
|
+
padding: 6px 9px; font-size: 12px; color: var(--dsw-alias-label-secondary, #666);
|
|
393
|
+
border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.08));
|
|
394
|
+
margin-bottom: 4px;
|
|
395
|
+
}
|
|
396
|
+
.dsh-tw-fab-status + .dsh-tw-fab-status { border-bottom: 0; margin-bottom: 4px; }
|
|
397
|
+
.dsh-tw-fab-status-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
398
|
+
.dsh-tw-fab-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #999; flex: none; }
|
|
399
|
+
.dsh-tw-fab-status-dot[data-state="running"] { background: var(--dsw-alias-state-success-primary, #3eaa5f); }
|
|
400
|
+
.dsh-tw-fab-status-dot[data-state="starting"] { background: var(--dsw-alias-state-warning-primary, #d9822b); }
|
|
401
|
+
.dsh-tw-fab-status-dot[data-state="failed"], .dsh-tw-fab-status-dot[data-state="stopped"] { background: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
402
|
+
.dsh-tw-fab-status-dot[data-state="clean"] { background: var(--dsw-alias-state-success-primary, #3eaa5f); }
|
|
403
|
+
.dsh-tw-fab-status-dot[data-state="dirty"] { background: var(--dsw-alias-state-warning-primary, #d9822b); }
|
|
404
|
+
.dsh-tw-fab-status-dot[data-state="behind"] { background: var(--dsw-alias-state-error-primary, #d13b3b); }
|
|
405
|
+
.dsh-tw-fab-status-dot[data-state="syncing"] { background: var(--dsw-alias-brand-primary, #3e63dd); }
|
|
406
|
+
.dsh-tw-fab-item {
|
|
407
|
+
display: flex; align-items: center; gap: 6px; text-align: left;
|
|
408
|
+
border: none; background: transparent; color: inherit; font: inherit; font-size: 13px;
|
|
409
|
+
padding: 7px 9px; border-radius: 8px; cursor: pointer;
|
|
410
|
+
}
|
|
411
|
+
.dsh-tw-fab-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
412
|
+
.dsh-tw-fab-item:active { background: var(--dsw-alias-interactive-bg-active, rgba(128,128,128,.18)); }
|
|
413
|
+
|
|
414
|
+
/* ── quick-note restored-draft banner ─────────────────────────────────────── */
|
|
415
|
+
.dsh-tw-note-draft {
|
|
416
|
+
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
417
|
+
padding: 5px 9px; border-radius: 8px; font-size: 12px;
|
|
418
|
+
background: color-mix(in srgb, var(--dsw-alias-state-warning-primary, #d9822b) 12%, transparent);
|
|
419
|
+
color: var(--dsw-alias-label-primary, #222);
|
|
420
|
+
}
|
|
421
|
+
.dsh-tw-note-draft[hidden] { display: none; }
|
|
422
|
+
.dsh-tw-note-draft-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
423
|
+
.dsh-tw-note-draft-discard {
|
|
424
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
425
|
+
background: transparent; color: inherit; font: inherit; font-size: 12px;
|
|
426
|
+
padding: 2px 10px; border-radius: 999px; cursor: pointer; flex: none;
|
|
427
|
+
}
|
|
428
|
+
.dsh-tw-note-draft-discard:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
429
|
+
|
|
430
|
+
/* ── quick-note recent-notes picker ───────────────────────────────────────── */
|
|
431
|
+
.dsh-tw-note-recent {
|
|
432
|
+
position: absolute; right: 0; bottom: calc(100% - 8px); z-index: 70;
|
|
433
|
+
width: 340px; max-width: calc(100vw - 40px); max-height: 280px; overflow-y: auto;
|
|
434
|
+
display: flex; flex-direction: column; gap: 2px; padding: 6px;
|
|
435
|
+
border-radius: 12px; border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.15));
|
|
436
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
437
|
+
box-shadow: var(--dsw-shadow-lv3, 0 8px 30px rgba(0,0,0,.22));
|
|
438
|
+
font-size: 13px;
|
|
439
|
+
}
|
|
440
|
+
.dsh-tw-note-recent[hidden] { display: none; }
|
|
441
|
+
.dsh-tw-note-recent-item {
|
|
442
|
+
display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
|
|
443
|
+
padding: 6px 8px; border-radius: 7px; cursor: pointer;
|
|
444
|
+
}
|
|
445
|
+
.dsh-tw-note-recent-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
446
|
+
.dsh-tw-note-recent-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
447
|
+
.dsh-tw-note-recent-meta { flex: none; font-size: 11px; color: var(--dsw-alias-label-dimmed, #999); }
|
|
448
|
+
.dsh-tw-note-recent-muted { padding: 8px 10px; font-size: 12px; color: var(--dsw-alias-label-dimmed, #999); }
|
|
449
|
+
.dsh-tw-note-recent-btn {
|
|
450
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
|
|
451
|
+
background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #222);
|
|
452
|
+
font: inherit; font-size: 12px; padding: 6px 10px; border-radius: 8px; cursor: pointer;
|
|
453
|
+
transition: filter 120ms ease, transform 80ms ease;
|
|
454
|
+
}
|
|
455
|
+
.dsh-tw-note-recent-btn:hover { filter: brightness(.96); }
|
|
456
|
+
.dsh-tw-note-recent-btn:active { transform: scale(.96); }
|
|
347
457
|
`
|
|
348
458
|
|
|
349
459
|
export function injectStyles(): void {
|