镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-20 05:37:47 +08:00
feat: surface conflict and error info
这个提交包含在:
父节点
733978c786
当前提交
add84da7b4
@ -74,6 +74,15 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
* Render a single preview item
|
* Render a single preview item
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
private resolveErrorText(error?: string): string | undefined {
|
||||||
|
if (!error) return undefined;
|
||||||
|
|
||||||
|
const translationKey = `error_${error}`;
|
||||||
|
const translated = I18nService.t(translationKey);
|
||||||
|
|
||||||
|
return translated === translationKey ? error : translated;
|
||||||
|
}
|
||||||
|
|
||||||
private renderPreviewItem(item: PreviewItem): any {
|
private renderPreviewItem(item: PreviewItem): any {
|
||||||
const statusClass = item.conflict
|
const statusClass = item.conflict
|
||||||
? 'conflict'
|
? 'conflict'
|
||||||
@ -95,19 +104,28 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
? html`<span class="status-badge pending">${I18nService.t('status_pending')}</span>`
|
? html`<span class="status-badge pending">${I18nService.t('status_pending')}</span>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
const errorText = this.resolveErrorText(item.error);
|
||||||
|
const errorTitle = item.error ?? errorText ?? '';
|
||||||
|
const conflictHint = item.conflict
|
||||||
|
? html`<span class="conflict-hint">${I18nService.t('error_api_conflict')}</span>`
|
||||||
|
: '';
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="preview-item ${statusClass}">
|
<div class="preview-item ${statusClass}">
|
||||||
<div class="preview-content">
|
<div class="preview-content">
|
||||||
<div class="old-name" title=${item.file.name}>
|
<div class="old-name-row">
|
||||||
${item.file.name}
|
<div class="old-name" title=${item.file.name}>
|
||||||
</div>
|
${item.file.name}
|
||||||
<div class="new-name ${statusClass}" title=${item.newName}>
|
</div>
|
||||||
${item.newName}
|
|
||||||
${statusBadge}
|
${statusBadge}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="new-name ${statusClass}" title=${item.newName}>
|
||||||
|
<span class="new-name-text">${item.newName}</span>
|
||||||
|
${conflictHint}
|
||||||
|
</div>
|
||||||
${
|
${
|
||||||
item.error
|
errorText
|
||||||
? html`<div class="error-message" title=${item.error}>${item.error}</div>`
|
? html`<div class="error-message" title=${errorTitle}>${errorText}</div>`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -232,10 +250,16 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.old-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.old-name {
|
.old-name {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--cdr-text-secondary, #595959);
|
color: var(--cdr-text-secondary, #595959);
|
||||||
margin-bottom: 4px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -253,6 +277,14 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-name-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.new-name.conflict {
|
.new-name.conflict {
|
||||||
color: var(--cdr-warning-text, #fa8c16);
|
color: var(--cdr-warning-text, #fa8c16);
|
||||||
}
|
}
|
||||||
@ -269,6 +301,12 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
color: var(--cdr-text-secondary, #595959);
|
color: var(--cdr-text-secondary, #595959);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conflict-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--cdr-warning-text, #fa8c16);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { VirtualPreviewList } from '../../src/content/components/virtual-preview-list';
|
import { VirtualPreviewList } from '../../src/content/components/virtual-preview-list';
|
||||||
|
import { I18nService } from '../../src/utils/i18n';
|
||||||
|
|
||||||
describe('VirtualPreviewList pending preview mapping', () => {
|
describe('VirtualPreviewList pending preview mapping', () => {
|
||||||
it('renders original name and new name for a pending preview item', async () => {
|
it('renders original name and new name for a pending preview item', async () => {
|
||||||
@ -29,4 +30,92 @@ describe('VirtualPreviewList pending preview mapping', () => {
|
|||||||
|
|
||||||
element.remove();
|
element.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders conflict items with status badge and helper text', async () => {
|
||||||
|
const element = new VirtualPreviewList();
|
||||||
|
element.items = [
|
||||||
|
{
|
||||||
|
file: {
|
||||||
|
id: 'file-conflict',
|
||||||
|
name: 'old-conflict.mkv',
|
||||||
|
ext: 'mkv',
|
||||||
|
size: 1,
|
||||||
|
mtime: Date.now(),
|
||||||
|
isDir: false,
|
||||||
|
},
|
||||||
|
newName: 'new-conflict.mkv',
|
||||||
|
conflict: true,
|
||||||
|
done: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
document.body.appendChild(element);
|
||||||
|
await element.updateComplete;
|
||||||
|
|
||||||
|
const textContent = element.shadowRoot?.textContent ?? '';
|
||||||
|
expect(textContent).toContain('old-conflict.mkv');
|
||||||
|
expect(textContent).toContain('new-conflict.mkv');
|
||||||
|
expect(textContent).toContain(I18nService.t('preview_badge_conflict'));
|
||||||
|
expect(textContent).toContain(I18nService.t('error_api_conflict'));
|
||||||
|
|
||||||
|
const badge = element.shadowRoot?.querySelector('.old-name-row .status-badge.conflict');
|
||||||
|
expect(badge).toBeTruthy();
|
||||||
|
|
||||||
|
element.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows localized extract_episode_not_found message instead of raw error key', async () => {
|
||||||
|
const element = new VirtualPreviewList();
|
||||||
|
element.items = [
|
||||||
|
{
|
||||||
|
file: {
|
||||||
|
id: 'file-error',
|
||||||
|
name: 'old-error.mkv',
|
||||||
|
ext: 'mkv',
|
||||||
|
size: 1,
|
||||||
|
mtime: Date.now(),
|
||||||
|
isDir: false,
|
||||||
|
},
|
||||||
|
newName: 'new-error.mkv',
|
||||||
|
conflict: false,
|
||||||
|
error: 'extract_episode_not_found',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
document.body.appendChild(element);
|
||||||
|
await element.updateComplete;
|
||||||
|
|
||||||
|
const textContent = element.shadowRoot?.textContent ?? '';
|
||||||
|
expect(textContent).toContain(I18nService.t('error_extract_episode_not_found'));
|
||||||
|
|
||||||
|
element.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps pending badge next to old name row when showStatus is true', async () => {
|
||||||
|
const element = new VirtualPreviewList();
|
||||||
|
element.showStatus = true;
|
||||||
|
element.items = [
|
||||||
|
{
|
||||||
|
file: {
|
||||||
|
id: 'file-pending',
|
||||||
|
name: 'old-pending.mkv',
|
||||||
|
ext: 'mkv',
|
||||||
|
size: 1,
|
||||||
|
mtime: Date.now(),
|
||||||
|
isDir: false,
|
||||||
|
},
|
||||||
|
newName: 'new-pending.mkv',
|
||||||
|
conflict: false,
|
||||||
|
done: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
document.body.appendChild(element);
|
||||||
|
await element.updateComplete;
|
||||||
|
|
||||||
|
const badge = element.shadowRoot?.querySelector('.old-name-row .status-badge.pending');
|
||||||
|
expect(badge).toBeTruthy();
|
||||||
|
|
||||||
|
element.remove();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户