248
248
@dblclick =" clickLyricLine(line.time, true)"
249
249
>
250
250
<div class =" content" >
251
- <span v-if =" line.contents[0]" >{{ line.contents[0] }}</span >
251
+ <span
252
+ v-if =" line.contents[0]"
253
+ @click.right =" openLyricMenu($event, line, 0)"
254
+ >{{ line.contents[0] }}</span
255
+ >
252
256
<br />
253
257
<span
254
258
v-if ="
255
259
line.contents[1] &&
256
260
$store.state.settings.showLyricsTranslation
257
261
"
258
262
class =" translation"
263
+ @click.right =" openLyricMenu($event, line, 1)"
259
264
>{{ line.contents[1] }}</span
260
265
>
261
266
</div >
262
267
</div >
268
+ <ContextMenu v-if =" !noLyric" ref =" lyricMenu" >
269
+ <div class =" item" @click =" copyLyric(false)" >{{
270
+ $t('contextMenu.copyLyric')
271
+ }}</div >
272
+ <div
273
+ v-if ="
274
+ rightClickLyric &&
275
+ rightClickLyric.contents[1] &&
276
+ $store.state.settings.showLyricsTranslation
277
+ "
278
+ class =" item"
279
+ @click =" copyLyric(true)"
280
+ >{{ $t('contextMenu.copyLyricWithTranslation') }}</div
281
+ >
282
+ </ContextMenu >
263
283
</div >
264
284
</transition >
265
285
</div >
284
304
285
305
import { mapState , mapMutations , mapActions } from ' vuex' ;
286
306
import VueSlider from ' vue-slider-component' ;
307
+ import ContextMenu from ' @/components/ContextMenu.vue' ;
287
308
import { formatTrackTime } from ' @/utils/common' ;
288
309
import { getLyric } from ' @/api/track' ;
289
- import { lyricParser } from ' @/utils/lyrics' ;
310
+ import { lyricParser , copyLyric } from ' @/utils/lyrics' ;
290
311
import ButtonIcon from ' @/components/ButtonIcon.vue' ;
291
312
import * as Vibrant from ' node-vibrant/dist/vibrant.worker.min.js' ;
292
313
import Color from ' color' ;
@@ -299,6 +320,7 @@ export default {
299
320
components: {
300
321
VueSlider,
301
322
ButtonIcon,
323
+ ContextMenu,
302
324
},
303
325
data () {
304
326
return {
@@ -312,6 +334,7 @@ export default {
312
334
background: ' ' ,
313
335
date: this .formatTime (new Date ()),
314
336
isFullscreen: !! document .fullscreenElement ,
337
+ rightClickLyric: null ,
315
338
};
316
339
},
317
340
computed: {
@@ -587,6 +610,21 @@ export default {
587
610
this .player .play ();
588
611
}
589
612
},
613
+ openLyricMenu (e , lyric , idx ) {
614
+ this .rightClickLyric = { ... lyric, idx };
615
+ this .$refs .lyricMenu .openMenu (e);
616
+ e .preventDefault ();
617
+ },
618
+ copyLyric (withTranslation ) {
619
+ if (this .rightClickLyric ) {
620
+ const idx = this .rightClickLyric .idx ;
621
+ if (! withTranslation) {
622
+ copyLyric (this .rightClickLyric .contents [idx]);
623
+ } else {
624
+ copyLyric (this .rightClickLyric .contents .join (' ' ));
625
+ }
626
+ }
627
+ },
590
628
setLyricsInterval () {
591
629
this .lyricsInterval = setInterval (() => {
592
630
const progress = this .player .seek (null , false ) ?? 0 ;
@@ -926,6 +964,7 @@ export default {
926
964
transform- origin: center left;
927
965
transform: scale (0.95 );
928
966
transition: all 0 .35s cubic- bezier (0.25 , 0.46 , 0.45 , 0.94 );
967
+ user- select: none;
929
968
930
969
span {
931
970
opacity: 0.28 ;
0 commit comments