dsh-tiddlywiki 0.3.1 → 0.5.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.
@@ -115,9 +115,6 @@ export function mountPanel(state: PanelState): () => void {
115
115
  let iframe: HTMLIFrameElement | undefined
116
116
  let frameArea: HTMLDivElement | undefined
117
117
  let errorArea: HTMLDivElement | undefined
118
- let chip: HTMLSpanElement | undefined
119
- let statusDot: HTMLSpanElement | undefined
120
- let statusFloater: HTMLDivElement | undefined
121
118
  let refreshTimer: number | undefined
122
119
  let refreshAttempts = 0
123
120
 
@@ -143,48 +140,6 @@ export function mountPanel(state: PanelState): () => void {
143
140
  return view
144
141
  }
145
142
 
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
143
  /** Pin the overlay to the center column's current viewport rect. */
189
144
  const syncRect = (): void => {
190
145
  if (container === undefined || columnEl === undefined) return
@@ -274,18 +229,15 @@ export function mountPanel(state: PanelState): () => void {
274
229
  }
275
230
  const payload = await fetchStatus()
276
231
  if (payload === null) {
277
- setChip('failed', '状态不可达')
278
232
  showError('无法访问 /dsh-tiddlywiki/status')
279
233
  return
280
234
  }
281
235
  if (payload.status === 'running' && typeof payload.url === 'string') {
282
- setChip('running', '在线')
283
236
  refreshAttempts = 0
284
237
  showFrame(payload.url)
285
238
  return
286
239
  }
287
240
  if (payload.status === 'starting') {
288
- setChip('starting', '启动中')
289
241
  showStarting()
290
242
  // Auto-poll while starting (bounded).
291
243
  if (refreshAttempts < 30) {
@@ -294,7 +246,6 @@ export function mountPanel(state: PanelState): () => void {
294
246
  }
295
247
  return
296
248
  }
297
- setChip('failed', '离线')
298
249
  refreshAttempts = 0
299
250
  showError(payload.error ?? `服务状态:${payload.status}`)
300
251
  }
@@ -347,12 +298,12 @@ export function mountPanel(state: PanelState): () => void {
347
298
  ensure()
348
299
  applyActive()
349
300
 
350
- // Bottom-right status/reload buttons, gated by ui.showPanelStatus.
351
- void (async () => {
352
- const payload = await fetchStatus()
353
- if (payload?.ui?.showPanelStatus === false) return
354
- statusFloater = buildStatusFloater()
355
- })()
301
+ // The "知识库" FAB's 重载面板 entry dispatches this event to reload the
302
+ // iframe (the panel itself no longer owns a floating status/reload button).
303
+ const onReloadRequest = (): void => {
304
+ if (iframe !== undefined && !iframe.hidden) iframe.src = iframe.src
305
+ }
306
+ document.addEventListener('dsh-tw-panel-reload', onReloadRequest)
356
307
 
357
308
  return () => {
358
309
  if (refreshTimer !== undefined) window.clearTimeout(refreshTimer)
@@ -362,10 +313,10 @@ export function mountPanel(state: PanelState): () => void {
362
313
  resizeObserver.disconnect()
363
314
  document.removeEventListener('click', onClickSidebarRow, true)
364
315
  document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
316
+ document.removeEventListener('dsh-tw-panel-reload', onReloadRequest)
365
317
  waitObserver.disconnect()
366
318
  unsubscribe()
367
319
  document.documentElement.removeAttribute(ACTIVE_ATTR)
368
320
  container?.remove()
369
- statusFloater?.remove()
370
321
  }
371
322
  }
@@ -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', '显示「快速笔记」悬浮按钮', ui.showQuickNote !== false)
214
- checkField('ui.showPanelStatus', '显示 TW 面板右下角「状态/重载」悬浮按钮', ui.showPanelStatus !== false)
215
- checkField('ui.showSyncButton', '显示右下角「同步」悬浮按钮', ui.showSyncButton !== false)
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', '保存配置')
@@ -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);
@@ -130,75 +133,52 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
130
133
  font: inherit; font-size: 15px; line-height: 1; padding: 2px 6px; border-radius: 6px; cursor: pointer;
131
134
  }
132
135
  .dsh-tw-note-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, #222); }
133
- .dsh-tw-note-fields { display: flex; gap: 8px; }
136
+ /* 标题与标签上下分行:标题一行,标签 chips+输入框独占一行,
137
+ 避免标签增多时把并排的标题栏一起撑高。 */
138
+ .dsh-tw-note-fields { display: flex; flex-direction: column; gap: 8px; }
134
139
  .dsh-tw-note-fields input {
135
- flex: 1; min-width: 0; border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
140
+ width: 100%; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18));
136
141
  border-radius: 8px; padding: 6px 9px; font: inherit; font-size: 12px;
137
142
  background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, inherit);
138
143
  transition: border-color 120ms ease, box-shadow 120ms ease;
139
144
  }
140
- .dsh-tw-note-fields input:focus, .dsh-tw-note-text:focus {
145
+ .dsh-tw-note-fields input:focus, .dsh-tw-note-editor .cm-editor.cm-focused {
141
146
  outline: none; border-color: var(--dsw-alias-brand-primary, #3e63dd);
142
147
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 25%, transparent);
143
148
  }
144
- .dsh-tw-note-text::placeholder { color: var(--dsw-alias-label-dimmed, #999); }
145
- /* ── Markdown editor (highlighted-textarea overlay) ────────────────
146
- The <pre> highlight layer and the transparent <textarea> share identical
147
- metrics (border/padding/font/line-height/white-space) so lines align 1:1. */
149
+ /* ── Markdown editor (CodeMirror 6, see markdown-editor.ts) ──────────
150
+ The .cm-editor box carries the border/radius/min-height the old textarea
151
+ had; font metrics live on the scroller so lines/selection stay aligned. */
148
152
  .dsh-tw-note-editor { position: relative; min-width: 0; }
149
- .dsh-tw-note-editor pre,
150
- .dsh-tw-note-editor textarea {
151
- margin: 0; box-sizing: border-box; width: 100%;
153
+ .dsh-tw-note-editor .cm-editor {
152
154
  border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.18)); border-radius: 8px;
153
- padding: 8px 9px; font: inherit; font-size: 13px; line-height: 1.5; min-height: 120px;
154
- white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; tab-size: 4;
155
+ background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #222);
156
+ font-size: 13px; min-height: 120px;
155
157
  transition: border-color 120ms ease, box-shadow 120ms ease;
156
158
  }
157
- .dsh-tw-note-editor .dsh-tw-note-hl {
158
- position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 0;
159
- background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #222);
160
- pointer-events: none; overflow: hidden;
159
+ .dsh-tw-note-editor .cm-scroller {
160
+ font-family: inherit; font-size: 13px; line-height: 1.5;
161
+ min-height: 120px; overflow: auto;
162
+ }
163
+ .dsh-tw-note-editor .cm-content {
164
+ caret-color: var(--dsw-alias-label-primary, #222);
165
+ padding: 8px 9px;
166
+ }
167
+ .dsh-tw-note-editor .cm-placeholder { color: var(--dsw-alias-label-dimmed, #999); }
168
+ .dsh-tw-note-editor .cm-line { padding: 0; }
169
+ /* Selection + active line follow the theme. */
170
+ .dsh-tw-note-editor .cm-editor .cm-selectionBackground,
171
+ .dsh-tw-note-editor .cm-editor.cm-focused .cm-selectionBackground {
172
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 22%, transparent) !important;
161
173
  }
162
- .dsh-tw-note-editor .dsh-tw-note-text {
163
- position: relative; z-index: 1; resize: vertical;
164
- background: transparent; color: transparent; caret-color: var(--dsw-alias-label-primary, #222);
165
- overflow: auto;
174
+ .dsh-tw-note-editor .cm-editor .cm-activeLine {
175
+ background: color-mix(in srgb, var(--dsw-alias-label-secondary, #888) 8%, transparent);
166
176
  }
167
- .dsh-tw-note-editor .dsh-tw-note-text:focus { outline: none; }
168
- .dsh-tw-note-drop .dsh-tw-note-text,
169
- .dsh-tw-note-drop .dsh-tw-note-hl {
177
+ /* Drag-over highlight (file upload) targets the CodeMirror box now. */
178
+ .dsh-tw-note-drop .cm-editor {
170
179
  border-color: var(--dsw-alias-brand-primary, #3e63dd);
171
180
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 25%, transparent);
172
181
  }
173
- /* ── markdown highlight tokens (colors only — never size/height, so the
174
- overlay keeps line-for-line alignment with the invisible textarea) ── */
175
- .dsh-tw-note-hl .md-heading { font-weight: 700; color: var(--dsw-alias-brand-primary, #3e63dd); }
176
- .dsh-tw-note-hl .md-hash { font-weight: 700; color: color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 55%, transparent); }
177
- .dsh-tw-note-hl .md-code,
178
- .dsh-tw-note-hl .md-code-block,
179
- .dsh-tw-note-hl .md-fence {
180
- font-family: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
181
- }
182
- .dsh-tw-note-hl .md-code {
183
- background: color-mix(in srgb, var(--dsw-alias-label-secondary, #888) 14%, transparent);
184
- border-radius: 4px; padding: 0 3px;
185
- }
186
- .dsh-tw-note-hl .md-code-block,
187
- .dsh-tw-note-hl .md-fence {
188
- display: block; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #888) 8%, transparent);
189
- }
190
- .dsh-tw-note-hl .md-bold { font-weight: 700; }
191
- .dsh-tw-note-hl .md-italic { font-style: italic; }
192
- .dsh-tw-note-hl .md-strike { text-decoration: line-through; opacity: .75; }
193
- .dsh-tw-note-hl .md-link { color: var(--dsw-alias-brand-primary, #3e63dd); text-decoration: underline; }
194
- .dsh-tw-note-hl .md-url { color: color-mix(in srgb, var(--dsw-alias-brand-primary, #3e63dd) 70%, #999); text-decoration: underline dotted; }
195
- .dsh-tw-note-hl .md-image { color: var(--dsw-alias-state-success-primary, #3eaa5f); font-weight: 600; }
196
- .dsh-tw-note-hl .md-bullet { font-weight: 700; color: var(--dsw-alias-brand-primary, #3e63dd); }
197
- .dsh-tw-note-hl .md-number { font-weight: 700; color: var(--dsw-alias-state-warning-primary, #d9822b); }
198
- .dsh-tw-note-hl .md-quote { font-style: italic; color: var(--dsw-alias-label-secondary, #888); }
199
- .dsh-tw-note-hl .md-hr { color: var(--dsw-alias-label-dimmed, #999); text-decoration: line-through; }
200
- .dsh-tw-note-hl .md-task { color: var(--dsw-alias-label-secondary, #888); }
201
- .dsh-tw-note-hl .md-task-checked { color: var(--dsw-alias-state-success-primary, #3eaa5f); font-weight: 600; }
202
182
  .dsh-tw-note-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
203
183
  .dsh-tw-note-foot-left, .dsh-tw-note-foot-right { display: flex; align-items: center; gap: 8px; }
204
184
  .dsh-tw-note-hint { font-size: 11px; color: var(--dsw-alias-label-dimmed, #999); }
@@ -228,7 +208,7 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
228
208
  .dsh-tw-note-edit:active:not(:disabled) { transform: scale(.96); }
229
209
  .dsh-tw-note-edit:disabled { opacity: .55; cursor: default; }
230
210
  /* ── Multi-tag chip editor (quick-note tags) ─────────────────────────────── */
231
- .dsh-tw-note-tags { position: relative; flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
211
+ .dsh-tw-note-tags { position: relative; width: 100%; display: flex; flex-direction: column; gap: 4px; }
232
212
  .dsh-tw-note-chips { display: flex; flex-wrap: wrap; gap: 4px; }
233
213
  .dsh-tw-note-tagchip {
234
214
  display: inline-flex; align-items: center; gap: 4px;
@@ -367,6 +347,113 @@ html[data-dsh-tw-active] .dshDesktopConversationSurface > :not([data-dsh-tw-view
367
347
  .dsh-tw-settings-error { color: var(--dsw-alias-state-error-primary, #d13b3b); font-size: 12px; }
368
348
  .dsh-tw-settings-search { flex: 0 0 auto; max-width: 220px; }
369
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); }
370
457
  `
371
458
 
372
459
  export function injectStyles(): void {