From f92cfa183df2f70d5107418c647b0948a63ae00e Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Sun, 26 Apr 2026 17:33:58 +0300 Subject: [PATCH] feat(ui): copy song fields from song info via double-click Double-click the Title, Artist, or Album value to copy plain text to the clipboard with toast feedback. Apply user-select: none on those cells so double-click does not trigger word selection. --- src/components/SongInfoModal.tsx | 29 ++++++++++++++++++++++++++--- src/styles/components.css | 5 +++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/SongInfoModal.tsx b/src/components/SongInfoModal.tsx index 3f77a353..7a73a681 100644 --- a/src/components/SongInfoModal.tsx +++ b/src/components/SongInfoModal.tsx @@ -5,6 +5,8 @@ import { usePlayerStore } from '../store/playerStore'; import { useShallow } from 'zustand/react/shallow'; import { getSong, SubsonicSong } from '../api/subsonic'; import { useTranslation } from 'react-i18next'; +import { copyTextToClipboard } from '../utils/serverMagicString'; +import { showToast } from '../utils/toast'; function formatDuration(s: number): string { const m = Math.floor(s / 60); @@ -28,6 +30,27 @@ function Row({ label, value }: { label: string; value: React.ReactNode }) { ); } +/** Title / Artist / Album: double-click the value cell to copy plain text. */ +function CopyableFieldRow({ label, text }: { label: string; text: string | null | undefined }) { + const { t } = useTranslation(); + if (!text || text === '—') return null; + const onDoubleClick = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const ok = await copyTextToClipboard(text); + if (ok) showToast(t('orbit.tooltipCopied'), 2000, 'info'); + else showToast(t('contextMenu.shareCopyFailed'), 3500, 'error'); + }; + return ( + + {label} + + {text} + + + ); +} + function Divider() { return ; } @@ -96,9 +119,9 @@ export default function SongInfoModal() { {!loading && song && ( - - - + + + {song.albumArtist && song.albumArtist !== song.artist && ( )} diff --git a/src/styles/components.css b/src/styles/components.css index cc66e473..0a7a4db5 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -1394,6 +1394,11 @@ padding: 4px 16px 4px 8px; word-break: break-all; } +/* Double-click copies; avoid browser selecting word on dblclick */ +.song-info-value--no-select { + user-select: none; + -webkit-user-select: none; +} .song-info-divider { padding: 6px 0; border-bottom: 1px solid var(--border-subtle);