diff --git a/src/locale/lang/en.js b/src/locale/lang/en.js index 6728b8a..d880e32 100644 --- a/src/locale/lang/en.js +++ b/src/locale/lang/en.js @@ -244,6 +244,8 @@ export default { minePlaylists: 'My Playlists', likedPlaylists: 'Liked Playlists', cardiacMode: 'Cardiac Mode', + copyLyric: 'Copy Lyric', + copyLyricWithTranslation: 'Copy Lyric With Translation', }, toast: { savedToPlaylist: 'Saved to playlist', diff --git a/src/locale/lang/tr.js b/src/locale/lang/tr.js index 296aac3..fda2939 100644 --- a/src/locale/lang/tr.js +++ b/src/locale/lang/tr.js @@ -230,6 +230,8 @@ export default { minePlaylists: 'My Playlists', likedPlaylists: 'Liked Playlists', cardiacMode: 'Cardiac Mode', + copyLyric: 'Copy Lyric', + copyLyricWithTranslation: 'Copy Lyric With Translation', }, toast: { savedToMyLikedSongs: 'Beğendiğim Müziklere Kaydet', diff --git a/src/locale/lang/zh-CN.js b/src/locale/lang/zh-CN.js index 9fbcafc..543132a 100644 --- a/src/locale/lang/zh-CN.js +++ b/src/locale/lang/zh-CN.js @@ -243,6 +243,8 @@ export default { minePlaylists: '创建的歌单', likedPlaylists: '收藏的歌单', cardiacMode: '心动模式', + copyLyric: '复制歌词', + copyLyricWithTranslation: '复制歌词(含翻译)', }, toast: { savedToPlaylist: '已添加到歌单', diff --git a/src/locale/lang/zh-TW.js b/src/locale/lang/zh-TW.js index 0882921..384a0cf 100644 --- a/src/locale/lang/zh-TW.js +++ b/src/locale/lang/zh-TW.js @@ -240,6 +240,8 @@ export default { minePlaylists: '我建立的歌單', likedPlaylists: '收藏的歌單', cardiacMode: '心動模式', + copyLyric: '複製歌詞', + copyLyricWithTranslation: '複製歌詞(含翻譯)', }, toast: { savedToPlaylist: '已新增至歌單', diff --git a/src/utils/lyrics.js b/src/utils/lyrics.js index b9a7e59..b883f46 100644 --- a/src/utils/lyrics.js +++ b/src/utils/lyrics.js @@ -84,3 +84,30 @@ function trimContent(content) { let t = content.trim(); return t.length < 1 ? content : t; } + +/** + * @param {string} lyric + */ +export async function copyLyric(lyric) { + const textToCopy = lyric; + if (navigator.clipboard && navigator.clipboard.writeText) { + try { + await navigator.clipboard.writeText(textToCopy); + } catch (err) { + alert('复制失败,请手动复制!'); + } + } else { + const tempInput = document.createElement('textarea'); + tempInput.value = textToCopy; + tempInput.style.position = 'absolute'; + tempInput.style.left = '-9999px'; + document.body.appendChild(tempInput); + tempInput.select(); + try { + document.execCommand('copy'); + } catch (err) { + alert('复制失败,请手动复制!'); + } + document.body.removeChild(tempInput); + } +} diff --git a/src/views/lyrics.vue b/src/views/lyrics.vue index a593fdd..7d30cba 100644 --- a/src/views/lyrics.vue +++ b/src/views/lyrics.vue @@ -248,7 +248,11 @@ @dblclick="clickLyricLine(line.time, true)" >
- {{ line.contents[0] }} + {{ line.contents[0] }}
{{ line.contents[1] }}
+ +
{{ + $t('contextMenu.copyLyric') + }}
+
{{ $t('contextMenu.copyLyricWithTranslation') }}
+
@@ -284,9 +304,10 @@ import { mapState, mapMutations, mapActions } from 'vuex'; import VueSlider from 'vue-slider-component'; +import ContextMenu from '@/components/ContextMenu.vue'; import { formatTrackTime } from '@/utils/common'; import { getLyric } from '@/api/track'; -import { lyricParser } from '@/utils/lyrics'; +import { lyricParser, copyLyric } from '@/utils/lyrics'; import ButtonIcon from '@/components/ButtonIcon.vue'; import * as Vibrant from 'node-vibrant/dist/vibrant.worker.min.js'; import Color from 'color'; @@ -299,6 +320,7 @@ export default { components: { VueSlider, ButtonIcon, + ContextMenu, }, data() { return { @@ -312,6 +334,7 @@ export default { background: '', date: this.formatTime(new Date()), isFullscreen: !!document.fullscreenElement, + rightClickLyric: null, }; }, computed: { @@ -587,6 +610,21 @@ export default { this.player.play(); } }, + openLyricMenu(e, lyric, idx) { + this.rightClickLyric = { ...lyric, idx }; + this.$refs.lyricMenu.openMenu(e); + e.preventDefault(); + }, + copyLyric(withTranslation) { + if (this.rightClickLyric) { + const idx = this.rightClickLyric.idx; + if (!withTranslation) { + copyLyric(this.rightClickLyric.contents[idx]); + } else { + copyLyric(this.rightClickLyric.contents.join(' ')); + } + } + }, setLyricsInterval() { this.lyricsInterval = setInterval(() => { const progress = this.player.seek(null, false) ?? 0; @@ -926,6 +964,7 @@ export default { transform-origin: center left; transform: scale(0.95); transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94); + user-select: none; span { opacity: 0.28;