fix: make task IDs selectable and copyable with feedback

This commit is contained in:
chenmingxuan committed 2026-09-23 01:53:06 +08:00
1 parent 9d6833f092
commit 60ac066859
8 files changed
+179 -7

No files matched your search

@@ -0,0 +1,66 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { Component, DestroyRef, Input, inject, signal } from '@angular/core';
@Component({
selector: 'app-task-id-copy',
standalone: true,
template: `
<span class="copy-target" role="button" tabindex="0"
[attr.aria-label]="'复制任务 ID:' + taskId" title="点击复制任务 ID,也可选中文字复制"
(click)="copy($event)" (keydown.enter)="copy($event)" (keydown.space)="copy($event)">
<span class="value">ID: {{ taskId }}</span>
<span class="material-symbols-outlined" aria-hidden="true">content_copy</span>
</span>
<span class="feedback" role="status" aria-live="polite">{{ feedback() }}</span>
`,
styles: [`
:host { display: block; min-width: 0; font-size: 11px; color: inherit; user-select: text; }
.copy-target { display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
padding: 3px 0; cursor: pointer; user-select: text; border-radius: 3px; }
.value { min-width: 0; overflow-wrap: anywhere; user-select: text; }
.material-symbols-outlined { flex-shrink: 0; font-size: 14px; user-select: none; }
.copy-target:hover { color: #2563eb; }
.copy-target:focus-visible { outline: 2px solid #60a5fa; outline-offset: 2px; }
.feedback { display: block; line-height: 1.6; user-select: text; }
.feedback:empty { display: none; }
`]
})
export class TaskIdCopyComponent {
@Input({ required: true }) taskId = '';
readonly feedback = signal('');
private readonly clipboard = inject(Clipboard);
private readonly destroyRef = inject(DestroyRef);
private timer: ReturnType<typeof setTimeout> | undefined;
constructor() {
this.destroyRef.onDestroy(() => clearTimeout(this.timer));
}
async copy(event: Event): Promise<void> {
event.stopPropagation();
// A drag selection must remain available for the browser's normal copy action.
const selection = window.getSelection();
if (event.type === 'click' && selection && !selection.isCollapsed
&& (event.currentTarget as HTMLElement).contains(selection.anchorNode)) return;
event.preventDefault();
let copied = false;
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(this.taskId);
copied = true;
} else {
copied = this.clipboard.copy(this.taskId);
}
} catch {
try {
copied = this.clipboard.copy(this.taskId);
} catch {
// Keep manual selection available when the browser blocks both clipboard APIs.
}
}
if (this.destroyRef.destroyed) return;
this.feedback.set(copied ? '任务 ID 已复制' : '复制失败,请选中任务 ID 手动复制');
clearTimeout(this.timer);
this.timer = setTimeout(() => this.feedback.set(''), 3000);
}
}