feat(web): unify attachment open behavior in context menus
Some checks failed
CI / test (push) Failing after 2m19s

This commit is contained in:
2026-03-08 22:24:28 +03:00
parent 4697193243
commit 751f8c9067
3 changed files with 23 additions and 5 deletions

View File

@@ -22,7 +22,7 @@ Legend:
13. Reactions - `DONE` 13. Reactions - `DONE`
14. Delivery Status - `DONE` (sent/delivered/read + reconnect reconciliation after backend restarts) 14. Delivery Status - `DONE` (sent/delivered/read + reconnect reconciliation after backend restarts)
15. Typing Realtime - `DONE` (web: typing start/stop + recording voice start/stop; `recording_video_*` remains for mobile circle-video clients) 15. Typing Realtime - `DONE` (web: typing start/stop + recording voice start/stop; `recording_video_*` remains for mobile circle-video clients)
16. Media & Attachments - `DONE` (upload/preview/download/gallery; sticker/GIF inline media no longer opens photo viewer on click; Chat Info and message context menus use blob-download flow with success/error toasts) 16. Media & Attachments - `DONE` (upload/preview/download/gallery; sticker/GIF inline media no longer opens photo viewer on click; Chat Info and message context menus now have consistent `Open/Download/Copy` behavior with menu auto-close and download toasts)
17. Voice Messages - `PARTIAL` (record/send/play/seek + global speed 1x/1.5x/2x; recorder uses improved mime priority for better duration metadata; backend media allowlist includes `audio/mp4`/`audio/x-m4a`; audio store tracks duration via `durationchange/seekable` fallback; websocket send/recorder stop race on fast chat switch is guarded; UX still being polished) 17. Voice Messages - `PARTIAL` (record/send/play/seek + global speed 1x/1.5x/2x; recorder uses improved mime priority for better duration metadata; backend media allowlist includes `audio/mp4`/`audio/x-m4a`; audio store tracks duration via `durationchange/seekable` fallback; websocket send/recorder stop race on fast chat switch is guarded; UX still being polished)
18. Circle Video Messages - `PARTIAL` (mobile-priority only: backend type/realtime supported; web composer intentionally does not send circles) 18. Circle Video Messages - `PARTIAL` (mobile-priority only: backend type/realtime supported; web composer intentionally does not send circles)
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites) 19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)

View File

@@ -987,9 +987,16 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
style={{ left: attachmentCtx.x, top: attachmentCtx.y }} style={{ left: attachmentCtx.x, top: attachmentCtx.y }}
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
> >
<a className="block rounded px-2 py-1.5 text-sm hover:bg-slate-800" href={attachmentCtx.url} rel="noreferrer" target="_blank"> <button
className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
onClick={() => {
window.open(attachmentCtx.url, "_blank", "noopener,noreferrer");
setAttachmentCtx(null);
}}
type="button"
>
Open Open
</a> </button>
<button <button
className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800" className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
onClick={async () => { onClick={async () => {

View File

@@ -807,9 +807,20 @@ export function MessageList() {
{ctx.attachmentUrl ? ( {ctx.attachmentUrl ? (
<> <>
<div className="my-1 h-px bg-slate-700/80" /> <div className="my-1 h-px bg-slate-700/80" />
<a className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800" href={ctx.attachmentUrl} rel="noreferrer" target="_blank"> <button
className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
onClick={() => {
const url = ctx.attachmentUrl;
if (!url) {
return;
}
window.open(url, "_blank", "noopener,noreferrer");
setCtx(null);
}}
type="button"
>
Open media Open media
</a> </button>
<button <button
className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800" className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
onClick={async () => { onClick={async () => {