dsh-plugin 1.3.2 → 1.3.3

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.
@@ -141,6 +141,12 @@ export const zh = {
141
141
  logOpenFile: '打开文件',
142
142
  logOpenFileTip: '在系统文件管理器中定位日志文件',
143
143
  logOpenFileFail: '打开失败,请在文件管理器中手动定位',
144
+ logClearAll: '清空所有日志',
145
+ logClearConfirmTitle: '清空所有日志?',
146
+ logClearConfirmDesc: '将删除全部日志记录,且不可恢复。',
147
+ logClearing: '正在清空…',
148
+ logClearAllTip: '清空全部日志记录',
149
+ logClearFailed: '清空失败,请重试',
144
150
  logPathSettingTitle: '日志存放位置',
145
151
  logPathSettingDesc: '默认存于宿主配置目录,可填目录或 .log 文件路径改存别处',
146
152
  logPathPlaceholder: '留空恢复默认',
@@ -241,7 +247,7 @@ export const zh = {
241
247
  restartNow: '立即重启',
242
248
  restartLater: '稍后重启',
243
249
  restarting: '正在重启…',
244
- restartHint: '部分插件(如插件市场、顶层 bundle)需重启后才会生效。',
250
+ restartHint: '重启会中断正在进行的安装/卸载任务,其进度将丢失。部分插件(如插件市场、顶层 bundle)需重启后才会生效。',
245
251
  done: '完成',
246
252
  // 待重启:装完没重启的插件常驻提醒,直到宿主真正重启
247
253
  restartPendingHint: '安装成功,重启后生效',
@@ -254,7 +260,11 @@ export const zh = {
254
260
  notificationsDesc: '安装、卸载任务与更新提醒都会记录在这里,即使错过提示也能随时回来查看。',
255
261
  notificationsEmpty: '暂无通知记录',
256
262
  notificationsClear: '清空通知',
263
+ notificationsClearConfirmTitle: '清空所有通知?',
264
+ notificationsClearConfirmDesc: '将删除全部通知记录,且不可恢复。',
257
265
  removeNotification: '删除这条通知',
266
+ removeNotificationConfirmTitle: '删除这条通知?',
267
+ removeNotificationConfirmDesc: '该通知将被移除,且不可恢复。',
258
268
  // 串行队列:安装/卸载弹窗内可后台关闭,排队任务自动串行执行
259
269
  queuedTitle: '已加入安装排队',
260
270
  queuedUninstallTitle: '已加入卸载排队',
@@ -458,6 +468,12 @@ export const en = {
458
468
  logOpenFile: 'Reveal',
459
469
  logOpenFileTip: 'Reveal the log file in your system file manager',
460
470
  logOpenFileFail: 'Could not open; locate the file manually',
471
+ logClearAll: 'Clear all logs',
472
+ logClearConfirmTitle: 'Clear all logs?',
473
+ logClearConfirmDesc: 'All log entries will be deleted. This cannot be undone.',
474
+ logClearing: 'Clearing…',
475
+ logClearAllTip: 'Delete all log entries',
476
+ logClearFailed: 'Failed to clear; try again',
461
477
  logPathSettingTitle: 'Log Location',
462
478
  logPathSettingDesc: 'Stored in the host config directory by default — set a directory or a .log file path to store elsewhere',
463
479
  logPathPlaceholder: 'Leave empty for default',
@@ -558,7 +574,7 @@ export const en = {
558
574
  restartNow: 'Restart now',
559
575
  restartLater: 'Later',
560
576
  restarting: 'Restarting…',
561
- restartHint: 'Some plugins (such as plugin markets / top-level bundles) only take effect after a restart.',
577
+ restartHint: 'Restarting interrupts any in-progress install / uninstall task — its progress will be lost. Some plugins (such as plugin markets / top-level bundles) only take effect after a restart.',
562
578
  done: 'Done',
563
579
  restartPendingHint: 'Installed — takes effect after restart',
564
580
  restartPendingHintUninstall: 'Removed — cleaned up after restart',
@@ -570,7 +586,11 @@ export const en = {
570
586
  notificationsDesc: 'Every install, remove and update notice is logged here — even if you missed the prompt, you can always check back.',
571
587
  notificationsEmpty: 'No notifications yet',
572
588
  notificationsClear: 'Clear notifications',
589
+ notificationsClearConfirmTitle: 'Clear all notifications?',
590
+ notificationsClearConfirmDesc: 'All notification records will be deleted. This cannot be undone.',
573
591
  removeNotification: 'Remove this notification',
592
+ removeNotificationConfirmTitle: 'Remove this notification?',
593
+ removeNotificationConfirmDesc: 'This notification will be removed. This cannot be undone.',
574
594
  queuedTitle: 'Queued…',
575
595
  queuedUninstallTitle: 'Queued for removal…',
576
596
  queuedHint: 'Queued — starts automatically after earlier tasks finish. You may close this window.',
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Self-heal missing plugin CSS tags.
3
+ *
4
+ * CSS injection is a one-shot side effect of the bundle factory (guarded by
5
+ * "tag already present"). When the host restarts the page survives, but the
6
+ * host HMR chain may remove the <style data-plugin> tags while the in-memory
7
+ * module cache still holds the old factory — so the factory never re-runs and
8
+ * the styles never come back until some interaction triggers a reload.
9
+ *
10
+ * The tsdown `dsh-css-modules-inline` build plugin registers every module's
11
+ * { tagId, css } into the global list `__DSH_PLUGIN_CSS__` at factory time.
12
+ * This helper compares that list against the live DOM and re-injects any
13
+ * missing tags, turning the "click around to fix it" self-heal into an
14
+ * automatic one on mount / view switch.
15
+ */
16
+
17
+ interface PluginCssEntry {
18
+ tagId: string
19
+ css: string
20
+ }
21
+
22
+ declare global {
23
+ interface Window {
24
+ __DSH_PLUGIN_CSS__?: PluginCssEntry[]
25
+ }
26
+ }
27
+
28
+ /** Re-inject any registered stylesheet that is missing from the DOM. Idempotent. */
29
+ export function ensurePluginCss(): void {
30
+ if (typeof document === 'undefined') return
31
+ const registry = window.__DSH_PLUGIN_CSS__
32
+ if (!registry || registry.length === 0) return
33
+ for (const { tagId, css } of registry) {
34
+ const selector = 'style[data-plugin-css=' + JSON.stringify(tagId) + ']'
35
+ if (document.querySelector(selector) !== null) continue
36
+ const tag = document.createElement('style')
37
+ tag.dataset.plugin = 'dsh-plugin'
38
+ tag.dataset.pluginCss = tagId
39
+ tag.textContent = css
40
+ document.head.appendChild(tag)
41
+ }
42
+ }
@@ -62,16 +62,37 @@
62
62
  cursor: default;
63
63
  }
64
64
 
65
- /* 主操作:导出日志,品牌色实底 */
66
- .btnPrimary {
67
- color: #ffffff;
68
- background: var(--hub-brand);
69
- border-color: var(--hub-brand);
65
+ /* 弹窗头部动作:清空所有日志(危险操作,二次确认时转红) */
66
+ .headActions {
67
+ flex: 1;
68
+ display: flex;
69
+ justify-content: flex-end;
70
+ margin-right: 4px;
71
+ }
72
+
73
+ .clearBtn {
74
+ flex: none;
75
+ height: 22px;
76
+ padding: 0 10px;
77
+ font-size: 11px;
78
+ font-weight: 500;
79
+ color: var(--hub-text-secondary);
80
+ background: transparent;
81
+ border: 1px solid var(--hub-border-2);
82
+ border-radius: 6px;
83
+ cursor: pointer;
84
+ user-select: none;
85
+ transition: color 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
70
86
  }
71
87
 
72
- .btnPrimary:hover:not(:disabled) {
73
- background: var(--hub-brand-hover);
74
- border-color: var(--hub-brand-hover);
88
+ .clearBtn:hover:not(:disabled) {
89
+ color: var(--hub-text-primary);
90
+ background: var(--hub-bg-2);
91
+ }
92
+
93
+ .clearBtn:disabled {
94
+ opacity: 0.55;
95
+ cursor: default;
75
96
  }
76
97
 
77
98
  /* 日志列表:一行一条,悬浮高亮;超出限高内部滚动 */
@@ -122,6 +122,83 @@
122
122
  flex-shrink: 0;
123
123
  }
124
124
 
125
+ /* 确认弹窗图标区:图标居中占位,位于标题下方、描述上方 —— 所有询问框都要有对应图标 */
126
+ .confirmIconWrap {
127
+ display: flex;
128
+ justify-content: center;
129
+ padding: 2px 0 6px;
130
+ }
131
+
132
+ /* 确认弹窗图标:圆形徽标(48px),普通确认 = 品牌蓝底白图形 */
133
+ .confirmIcon {
134
+ width: 48px;
135
+ height: 48px;
136
+ flex: none;
137
+ display: inline-flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ border-radius: 50%;
141
+ background: var(--hub-brand);
142
+ box-shadow: 0 4px 12px rgba(79, 110, 247, 0.28);
143
+ }
144
+
145
+ /* 危险操作确认图标:红色底白图形 —— 清空/删除/卸载/重置,醒目区分于普通确认 */
146
+ .confirmIconDanger {
147
+ background: var(--hub-danger);
148
+ box-shadow: 0 4px 12px rgba(209, 36, 47, 0.3);
149
+ }
150
+
151
+ /* 危险操作确认按钮:红色实底白字 —— 清空/删除/卸载/重置的最终确认,显著醒目 */
152
+ .dangerConfirm {
153
+ flex: none;
154
+ height: 28px;
155
+ padding: 0 14px;
156
+ font-size: 12px;
157
+ font-weight: 600;
158
+ color: #ffffff;
159
+ background: var(--hub-danger);
160
+ border: 1px solid var(--hub-danger);
161
+ border-radius: 6px;
162
+ cursor: pointer;
163
+ user-select: none;
164
+ transition: background-color 0.12s ease, border-color 0.12s ease;
165
+ }
166
+
167
+ .dangerConfirm:hover:not(:disabled) {
168
+ background: var(--hub-danger-hover);
169
+ border-color: var(--hub-danger-hover);
170
+ }
171
+
172
+ .dangerConfirm:disabled {
173
+ opacity: 0.55;
174
+ cursor: default;
175
+ }
176
+
177
+ /* 普通确认按钮:品牌蓝实底白字 —— 安装/更新/重启/忽略等常规确认 */
178
+ .confirmPrimary {
179
+ flex: none;
180
+ height: 28px;
181
+ padding: 0 14px;
182
+ font-size: 12px;
183
+ font-weight: 600;
184
+ color: #ffffff;
185
+ background: var(--hub-brand);
186
+ border: 1px solid var(--hub-brand);
187
+ border-radius: 6px;
188
+ cursor: pointer;
189
+ user-select: none;
190
+ transition: filter 0.12s ease;
191
+ }
192
+
193
+ .confirmPrimary:hover:not(:disabled) {
194
+ filter: brightness(1.08);
195
+ }
196
+
197
+ .confirmPrimary:disabled {
198
+ opacity: 0.55;
199
+ cursor: default;
200
+ }
201
+
125
202
  .modalDesc {
126
203
  font-size: 12px;
127
204
  line-height: 18px;
@@ -20,7 +20,7 @@ import { resolveNpmPackage } from '../services/install/npm-resolve.ts'
20
20
  import { preflightTarget } from '../services/install/preflight.ts'
21
21
  import { isDshPlugin, isEntryLoaded } from '../services/loader.ts'
22
22
  import { loadSettings, saveSettings, resetSettings, type HubSettings } from '../services/settings.ts'
23
- import { appendLog, readLog, logFilePath, defaultLogFilePath, customLogFile } from '../services/log.ts'
23
+ import { appendLog, clearLog, readLog, logFilePath, defaultLogFilePath, customLogFile } from '../services/log.ts'
24
24
 
25
25
  export interface WebRoute {
26
26
  kind: 'exact'
@@ -325,6 +325,25 @@ export function mountPluginHubRoutes(webServer: WebServerService, profile: strin
325
325
  }
326
326
  },
327
327
  }),
328
+ webServer.register({
329
+ kind: 'exact',
330
+ path: '/dsh-plugin-hub/clear-log',
331
+ handler: (request, response) => {
332
+ if (!requireTrustedPost(request, response)) return
333
+ // 清空所有日志:截断当前日志文件为空
334
+ try {
335
+ const ok = clearLog(profile)
336
+ if (!ok) {
337
+ sendJson(response, 500, { error: 'failed to clear log' })
338
+ return
339
+ }
340
+ appendLog(profile, { at: Date.now(), level: 'info', category: 'system', event: 'system.clear', message: '日志已由用户清空' })
341
+ sendJson(response, 200, { ok: true })
342
+ } catch {
343
+ sendJson(response, 500, { error: 'failed to clear log' })
344
+ }
345
+ },
346
+ }),
328
347
  webServer.register({
329
348
  kind: 'exact',
330
349
  path: '/dsh-plugin-hub/open-log',
@@ -105,6 +105,18 @@ export function appendLog(profile: string, entry: LogEntry): void {
105
105
  }
106
106
  }
107
107
 
108
+ /** 清空系统日志:把当前日志文件截断为空。失败返回 false(不阻断调用方)。 */
109
+ export function clearLog(profile: string): boolean {
110
+ try {
111
+ const file = logFilePath(profile)
112
+ mkdirSync(dirname(file), { recursive: true })
113
+ writeFileSync(file, '')
114
+ return true
115
+ } catch {
116
+ return false
117
+ }
118
+ }
119
+
108
120
  /** 兼容旧格式:早期无 category(level 用 info/ok/fail/warn),归一化到新版。 */
109
121
  function normalize(raw: unknown): LogEntry | null {
110
122
  if (typeof raw !== 'object' || raw === null) return null