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;
}