From b245c81a421a1950b9a114d2e6b52bc6ab1ac9a7 Mon Sep 17 00:00:00 2001 From: Zain Date: Mon, 29 Dec 2025 15:45:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=96=87=E4=BB=B6=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8):=20=E6=B7=BB=E5=8A=A0=E9=87=8D=E8=AF=95=E5=92=8C?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在文件选择器面板中添加重试失败任务和返回功能 - 为配置面板添加同步重试按钮和返回按钮 - 优化同步状态显示逻辑,仅在必要时显示 - 移除调试日志并添加同步支持状态检查 - 更新多语言翻译添加"返回"和"重新同步"文本 --- src/adapters/quark/quark.ts | 16 ---- src/content/components/config-panel.ts | 53 +++++++++-- src/content/components/file-selector-panel.ts | 94 ++++++++++++++++++- src/core/executor.ts | 7 +- src/locales/en.json | 2 + src/locales/zh_CN.json | 2 + src/locales/zh_TW.json | 2 + src/shared/version.ts | 2 +- src/types/file-selector.ts | 2 + 9 files changed, 153 insertions(+), 27 deletions(-) diff --git a/src/adapters/quark/quark.ts b/src/adapters/quark/quark.ts index cc3f1b3..b6cc2c0 100644 --- a/src/adapters/quark/quark.ts +++ b/src/adapters/quark/quark.ts @@ -268,11 +268,6 @@ export class QuarkAdapter extends BasePlatformAdapter { return { success: true, method: 'none' }; } - logger.info('[DIAG-SYNC-QUARK] syncAfterRename start', { - renameCount: renameInfoById.size, - hasOldName: renameByOldName.size, - }); - // Always patch visible DOM for immediate feedback const patchedById = this.applyRenameMappingToDomById(renameInfoById); const patchedByOldName = this.applyRenameMappingToDomByOldName(renameByOldName); @@ -283,17 +278,6 @@ export class QuarkAdapter extends BasePlatformAdapter { // Best effort: trigger Quark's own re-render so it can rebuild native filename DOM (icon/wrap/etc.) const rerender = await this.tryTriggerNativeFileListRerender(renameInfoById); - logger.info('[DIAG-SYNC-QUARK] syncAfterRename patch result', { - rerender, - patchedById, - patchedByOldName, - patchedCount, - dom: { - hasDataFileId: document.querySelectorAll('[data-file-id]').length, - hasFileNameClass: document.querySelectorAll('.file-name').length, - }, - }); - if (rerender.triggered) { return { success: true, method: 'ui-refresh', message: rerender.message }; } diff --git a/src/content/components/config-panel.ts b/src/content/components/config-panel.ts index c160d61..d926a04 100644 --- a/src/content/components/config-panel.ts +++ b/src/content/components/config-panel.ts @@ -76,6 +76,12 @@ export class ConfigPanel extends LitElement { @property({ type: String }) syncMessage: string | null = null; + /** + * Whether sync is supported by the current adapter (optional) + */ + @property({ type: Boolean }) + syncSupported = false; + /** * Current selected rule type */ @@ -235,6 +241,7 @@ export class ConfigPanel extends LitElement { : this.syncStatus === 'failed' ? I18nService.t('sync_failed') : ''; + const showSyncStatus = Boolean(syncText) || Boolean(this.syncMessage); return html`
@@ -270,10 +277,17 @@ export class ConfigPanel extends LitElement {
- ${this.finished + ${this.finished && showSyncStatus ? html`
${syncText}${this.syncMessage ? `:${this.syncMessage}` : ''} + ${this.syncSupported && this.syncStatus === 'failed' + ? html` + + ` + : ''}
` : ''} @@ -295,13 +309,15 @@ export class ConfigPanel extends LitElement { `; } + const failed = this.progress?.failed ?? 0; + return html`
- -
`; @@ -335,9 +351,18 @@ export class ConfigPanel extends LitElement { ); } - private handleClose(): void { + private handleRetry(): void { this.dispatchEvent( - new CustomEvent('close', { + new CustomEvent('retry', { + bubbles: true, + composed: true, + }) + ); + } + + private handleBack(): void { + this.dispatchEvent( + new CustomEvent('back', { bubbles: true, composed: true, }) @@ -749,6 +774,20 @@ export class ConfigPanel extends LitElement { border-color: #91caff; } + .sync-retry { + margin-left: 8px; + padding: 0; + border: none; + background: transparent; + color: #1677ff; + cursor: pointer; + font-size: 12px; + } + + .sync-retry:hover { + text-decoration: underline; + } + .execution-actions { display: flex; gap: 10px; diff --git a/src/content/components/file-selector-panel.ts b/src/content/components/file-selector-panel.ts index 050aece..7c9cdfa 100644 --- a/src/content/components/file-selector-panel.ts +++ b/src/content/components/file-selector-panel.ts @@ -592,7 +592,6 @@ export class FileSelectorPanel extends LitElement { const result = await this.adapter.syncAfterRename(renames); this.syncStatus = result.success ? 'success' : 'failed'; this.syncMessage = result.success ? null : result.message || null; - logger.info('[DIAG-SYNC] syncAfterRename result', result); } catch (error) { const errorObj = error instanceof Error ? error : new Error(String(error)); this.syncStatus = 'failed'; @@ -641,6 +640,95 @@ export class FileSelectorPanel extends LitElement { void this.syncAfterRename(); } + private async handleRetryFailed(): Promise { + if (this.executing || !this.executionFinished) { + return; + } + + const failedIds = new Set(this.executionItems.filter((item) => item.done === false).map((item) => item.file.id)); + if (failedIds.size === 0) { + return; + } + + try { + this.executing = true; + this.executorState = ExecutorState.RUNNING; + this.syncStatus = 'idle'; + this.syncMessage = null; + + // Reset only failed items back to pending + this.executionItems = this.executionItems.map((item) => { + if (!failedIds.has(item.file.id)) return item; + return { ...item, done: undefined, error: undefined }; + }); + + const tasks = this.executionItems + .map((item, index) => ({ item, index })) + .filter(({ item }) => failedIds.has(item.file.id)) + .map(({ item, index }) => ({ + file: item.file, + newName: item.newName, + index, + })); + + const retryFiles = tasks.map((task) => task.file); + if (retryFiles.length === 0) { + return; + } + + this.progress = { + completed: 0, + total: retryFiles.length, + currentFile: '', + success: 0, + failed: 0, + }; + + const executor = new BatchExecutor(retryFiles, this.ruleConfig, this.adapter, { + requestInterval: this.adapter.getConfig().requestInterval, + tasks, + onProgress: (progress) => { + this.handleProgress(progress); + }, + }); + this.executor = executor; + + const results = await executor.execute(); + this.executionResults = results; + this.executorState = executor.getState(); + + this.applyExecutionResults(results); + this.updateSummaryProgress(); + void this.syncAfterRename(); + } catch (error) { + const errorObj = error instanceof Error ? error : new Error(String(error)); + this.error = errorObj.message; + logger.error('[FileSelectorPanel] Retry failed:', errorObj); + } finally { + this.executing = false; + } + } + + private handleBack(): void { + this.resetExecutionState(); + } + + private updateSummaryProgress(): void { + if (!this.executionItems.length) return; + + const success = this.executionItems.filter((item) => item.done === true).length; + const failed = this.executionItems.filter((item) => item.done === false).length; + const completed = success + failed; + + this.progress = { + completed, + total: this.executionItems.length, + currentFile: '', + success, + failed, + }; + } + private resetExecutionState(): void { this.executor = null; this.executorState = ExecutorState.IDLE; @@ -712,12 +800,14 @@ export class FileSelectorPanel extends LitElement { .paused=${this.executorState === ExecutorState.PAUSED} .syncStatus=${this.syncStatus} .syncMessage=${this.syncMessage} + .syncSupported=${!!this.adapter.syncAfterRename} @config-change=${this.handleConfigChange} @execute=${this.handleExecute} @pause=${this.handlePause} @cancel=${this.handleCancel} @sync=${this.handleSync} - @close=${this.handleClose} + @retry=${this.handleRetryFailed} + @back=${this.handleBack} > void; /** 完成回调 */ @@ -105,7 +107,10 @@ export class BatchExecutor { try { // 准备所有重命名任务 - const tasks = this.prepareTasks(); + const preparedTasks = this.options.tasks ?? this.prepareTasks(); + const tasks = this.options.skipUnchanged + ? preparedTasks.filter((task) => task.newName !== task.file.name) + : preparedTasks; this.tasks = tasks; if (tasks.length === 0) { diff --git a/src/locales/en.json b/src/locales/en.json index 50f0946..775a0e7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -11,6 +11,7 @@ "loading_files": "Loading files...", "error_title": "Error", "retry": "Retry", + "back": "Back", "close": "Close", "search_placeholder": "Search files", "clear_search": "Clear search", @@ -87,6 +88,7 @@ "syncing_list": "Syncing...", "sync_success": "List synced", "sync_failed": "Sync failed, please refresh the page", + "sync_retry": "Resync", "sync_not_supported": "Auto sync is not supported on this platform. Please refresh the page.", "preview_empty": "No files to preview", "preview_loading": "Generating preview...", diff --git a/src/locales/zh_CN.json b/src/locales/zh_CN.json index 427016c..4854f69 100644 --- a/src/locales/zh_CN.json +++ b/src/locales/zh_CN.json @@ -11,6 +11,7 @@ "loading_files": "正在加载文件...", "error_title": "错误", "retry": "重试", + "back": "返回", "close": "关闭", "search_placeholder": "搜索文件名", "clear_search": "清除搜索", @@ -87,6 +88,7 @@ "syncing_list": "同步中...", "sync_success": "已同步列表", "sync_failed": "同步失败,可能需要手动刷新页面", + "sync_retry": "重新同步", "sync_not_supported": "当前平台暂不支持自动同步,请手动刷新页面", "preview_empty": "暂无文件可预览", "preview_loading": "生成预览中...", diff --git a/src/locales/zh_TW.json b/src/locales/zh_TW.json index bde8f60..ca97f57 100644 --- a/src/locales/zh_TW.json +++ b/src/locales/zh_TW.json @@ -11,6 +11,7 @@ "loading_files": "正在載入檔案...", "error_title": "錯誤", "retry": "重試", + "back": "返回", "close": "關閉", "search_placeholder": "搜尋檔案名稱", "clear_search": "清除搜尋", @@ -87,6 +88,7 @@ "syncing_list": "同步中...", "sync_success": "已同步列表", "sync_failed": "同步失敗,可能需要手動重新整理頁面", + "sync_retry": "重新同步", "sync_not_supported": "目前平台暫不支援自動同步,請手動重新整理頁面", "preview_empty": "暫無檔案可預覽", "preview_loading": "產生預覽中...", diff --git a/src/shared/version.ts b/src/shared/version.ts index 15bebc0..70d73dd 100644 --- a/src/shared/version.ts +++ b/src/shared/version.ts @@ -10,7 +10,7 @@ * 1. Edit version in package.json * 2. Run: npm run sync:version (or automatically via prebuild/predev) * - * Generated at: 2025-12-29T06:17:04.003Z + * Generated at: 2025-12-29T07:41:42.086Z */ /** diff --git a/src/types/file-selector.ts b/src/types/file-selector.ts index 08d608a..9b97902 100644 --- a/src/types/file-selector.ts +++ b/src/types/file-selector.ts @@ -55,5 +55,7 @@ export interface FileSelectorEvents { 'type-filter': { type: FileType | 'all' }; 'config-change': RuleConfig; 'execute': void; + 'retry': void; + 'back': void; 'panel-close': void; }