修复codereview复核确认的多选/转发/收藏页/资料页等前端bug
- 多选模式下Drafty链接、名片消息不再意外触发跳转/开新标签页 - 语音/视频消息在多选模式下点击可正常冒泡选中该行 - 转发排除未确认/发送失败消息,识别uncertain转发结果并单独提示 - 群文件"更多操作"菜单竞态、群成员移出状态竞态 - 收藏页链接卡片new URL()崩溃点加防御,并给登录后应用区域加错误边界 - 个人资料页乐观锁版本冲突时清空草稿避免静默覆盖并发修改 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e3eed8bdd4
commit
9abae5030b
@@ -222,8 +222,9 @@ function formatDuration(ms: number | undefined): string {
|
||||
* confirmed by its base64 signature) instead. Tapping swaps the poster for a real `<video>` playing
|
||||
* from `val`/`ref`, mirroring AudioAttachment's inline-vs-authenticated-fetch duality.
|
||||
*
|
||||
* `locked`: see ImageAttachment's own comment -- reuses the existing `disabled` (already there for
|
||||
* a not-yet-loaded video) rather than a second, parallel gating mechanism. */
|
||||
* `locked`: see ImageAttachment's own comment -- gated in `onClick`, not folded into `disabled`,
|
||||
* since a disabled <button> never dispatches `click` at all and would swallow the row-toggle this
|
||||
* click is supposed to bubble up to (unlike FileAttachment's plain anchor, this is a real button). */
|
||||
export function VideoAttachment({ data, locked }: { data: VideoData; locked?: boolean }) {
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const posterInline = typeof data.preview === 'string' ? `data:image/jpeg;base64,${data.preview}` : null;
|
||||
@@ -245,8 +246,11 @@ export function VideoAttachment({ data, locked }: { data: VideoData; locked?: bo
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPlaying(true)}
|
||||
disabled={!videoSrc || locked}
|
||||
onClick={() => {
|
||||
if (locked) return;
|
||||
setPlaying(true);
|
||||
}}
|
||||
disabled={!videoSrc}
|
||||
className="relative block h-[170px] w-[260px] overflow-hidden rounded-lg bg-surface-rail"
|
||||
>
|
||||
{posterUrl ? (
|
||||
@@ -271,9 +275,10 @@ interface AudioData {
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
/** `locked`: see ImageAttachment's own comment -- reuses the existing `disabled` (already there for a
|
||||
* not-yet-loaded voice message) rather than a second, parallel gating mechanism. Ongoing playback
|
||||
* started BEFORE selectMode was entered is left alone -- this only blocks a NEW toggle action. */
|
||||
/** `locked`: see VideoAttachment's own comment -- gated in `toggle()`, not folded into `disabled`,
|
||||
* so the click still bubbles to the row toggle instead of being swallowed by the native button.
|
||||
* Ongoing playback started BEFORE selectMode was entered is left alone -- this only blocks a NEW
|
||||
* toggle action. */
|
||||
export function AudioAttachment({ data, locked }: { data: AudioData; locked?: boolean }) {
|
||||
const inline = typeof data.val === 'string' ? `data:${data.mime || 'audio/webm'};base64,${data.val}` : null;
|
||||
const remote = useAuthedBlobUrl(inline ? undefined : data.ref);
|
||||
@@ -295,6 +300,7 @@ export function AudioAttachment({ data, locked }: { data: AudioData; locked?: bo
|
||||
}, [audioEl]);
|
||||
|
||||
const toggle = () => {
|
||||
if (locked) return;
|
||||
if (!audioEl || !src) return;
|
||||
if (playing) {
|
||||
audioEl.pause();
|
||||
@@ -310,7 +316,7 @@ export function AudioAttachment({ data, locked }: { data: AudioData; locked?: bo
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="button" onClick={toggle} disabled={!src || locked} className="flex w-[200px] items-center gap-2">
|
||||
<button type="button" onClick={toggle} disabled={!src} className="flex w-[200px] items-center gap-2">
|
||||
<Icon
|
||||
name={playError ? 'error_outline' : playing ? 'pause_circle' : 'play_circle'}
|
||||
className={`text-[24px] ${playError ? 'text-danger-text' : 'text-accent'}`}
|
||||
|
||||
Reference in New Issue
Block a user