mirror of
https://github.com/koel/koel
synced 2024-11-24 13:13:05 +00:00
Add the ability to share song URLs
This commit is contained in:
parent
ae3d2fadf3
commit
56045ef06c
8 changed files with 108 additions and 20 deletions
|
@ -41,6 +41,7 @@
|
|||
"postcss-cssnext": "^2.6.0",
|
||||
"rangeslider.js": "^2.1.1",
|
||||
"rangetouch": "0.0.9",
|
||||
"select": "^1.0.6",
|
||||
"sinon": "^1.17.2",
|
||||
"vue": "^2.0.0-alpha.8",
|
||||
"vue-hot-reload-api": "^1.3.2",
|
||||
|
|
|
@ -33,7 +33,7 @@ import overlay from './components/shared/overlay.vue';
|
|||
import loginForm from './components/auth/login-form.vue';
|
||||
import editSongsForm from './components/modals/edit-songs-form.vue';
|
||||
|
||||
import { event, showOverlay, hideOverlay, loadMainView, forceReloadWindow } from './utils';
|
||||
import { event, showOverlay, hideOverlay, loadMainView, forceReloadWindow, url } from './utils';
|
||||
import { sharedStore, queueStore, songStore, userStore, preferenceStore as preferences } from './stores';
|
||||
import { playback, ls } from './services';
|
||||
import { focusDirective, clickawayDirective } from './directives';
|
||||
|
@ -58,6 +58,9 @@ export default {
|
|||
// Create the element to be the ghost drag image.
|
||||
$('<div id="dragGhost"></div>').appendTo('body');
|
||||
|
||||
// And the textarea to copy stuff
|
||||
$('<textarea id="copyArea"></textarea>').appendTo('body');
|
||||
|
||||
// Add an ugly mac/non-mac class for OS-targeting styles.
|
||||
// I'm crying inside.
|
||||
$('html').addClass(navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : 'non-mac');
|
||||
|
@ -190,6 +193,17 @@ export default {
|
|||
forceReloadWindow();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse song ID from permalink and play.
|
||||
*/
|
||||
'koel:ready': () => {
|
||||
const songId = url.parseSongId();
|
||||
if (!songId) return;
|
||||
const song = songStore.byId(songId);
|
||||
if (!song) return;
|
||||
playback.queueAndPlay(song);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -224,6 +238,17 @@ Vue.directive('koel-clickaway',clickawayDirective);
|
|||
}
|
||||
}
|
||||
|
||||
#copyArea {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
|
||||
html.touchevents & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#main, .login-wrapper {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
|
|
|
@ -3,16 +3,14 @@
|
|||
@blur="close"
|
||||
:style="{ top: top + 'px', left: left + 'px' }"
|
||||
>
|
||||
<li v-if="onlyOneSongSelected" @click="doPlayback">
|
||||
<span v-if="songs[0].playbackState !== 'playing'">Play</span>
|
||||
<span v-else>Pause</span>
|
||||
</li>
|
||||
<li v-if="onlyOneSongSelected" @click="viewAlbumDetails(songs[0].album)">
|
||||
<span>Go to Album</span>
|
||||
</li>
|
||||
<li v-if="onlyOneSongSelected" @click="viewArtistDetails(songs[0].artist)">
|
||||
<span>Go to Artist</span>
|
||||
</li>
|
||||
<template v-show="onlyOneSongSelected">
|
||||
<li @click="doPlayback">
|
||||
<span v-if="!firstSongPlaying">Play</span>
|
||||
<span v-else>Pause</span>
|
||||
</li>
|
||||
<li @click="viewAlbumDetails(songs[0].album)">Go to Album</li>
|
||||
<li @click="viewArtistDetails(songs[0].artist)">Go to Artist</li>
|
||||
</template>
|
||||
<li class="has-sub">Add To
|
||||
<ul class="menu submenu">
|
||||
<li @click="queueSongsAfterCurrent">After Current Song</li>
|
||||
|
@ -20,14 +18,14 @@
|
|||
<li @click="queueSongsToTop">Top of Queue</li>
|
||||
<li class="separator"></li>
|
||||
<li @click="addSongsToFavorite">Favorites</li>
|
||||
<li class="separator" v-show="playlistState.playlists.length"></li>
|
||||
<li v-for="playlist in playlistState.playlists"
|
||||
@click="addSongsToExistingPlaylist(playlist)"
|
||||
>{{ playlist.name }}</li>
|
||||
<li class="separator" v-if="playlistState.playlists.length"></li>
|
||||
<li v-for="p in playlistState.playlists" @click="addSongsToExistingPlaylist(p)">{{ p.name }}</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-if="isAdmin" @click="openEditForm">Edit</li>
|
||||
<li @click="download" v-if="sharedState.allowDownload">Download</li>
|
||||
<li v-if="sharedState.allowDownload" @click="download">Download</li>
|
||||
<!-- somehow v-if doesn't work here -->
|
||||
<li v-show="copyable && onlyOneSongSelected" @click="copyUrl">Copy Shareable URL</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
|
@ -37,8 +35,8 @@ import $ from 'jquery';
|
|||
import songMenuMethods from '../../mixins/song-menu-methods';
|
||||
import artistAlbumDetails from '../../mixins/artist-album-details';
|
||||
|
||||
import { event } from '../../utils';
|
||||
import { sharedStore, queueStore, userStore, playlistStore } from '../../stores';
|
||||
import { event, isClipboardSupported, copyText } from '../../utils';
|
||||
import { sharedStore, songStore, queueStore, userStore, playlistStore } from '../../stores';
|
||||
import { playback, download } from '../../services';
|
||||
|
||||
export default {
|
||||
|
@ -50,6 +48,7 @@ export default {
|
|||
return {
|
||||
playlistState: playlistStore.state,
|
||||
sharedState: sharedStore.state,
|
||||
copyable: isClipboardSupported(),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -58,6 +57,10 @@ export default {
|
|||
return this.songs.length === 1;
|
||||
},
|
||||
|
||||
firstSongPlaying() {
|
||||
return this.songs[0] ? this.songs[0].playbackState === 'playing' : false;
|
||||
},
|
||||
|
||||
isAdmin() {
|
||||
return userStore.current.is_admin;
|
||||
},
|
||||
|
@ -129,6 +132,10 @@ export default {
|
|||
download.fromSongs(this.songs);
|
||||
this.close();
|
||||
},
|
||||
|
||||
copyUrl() {
|
||||
copyText(songStore.getShareableUrl(this.songs[0]));
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -313,6 +313,18 @@ export const songStore = {
|
|||
return `${sharedStore.state.cdnUrl}api/${song.id}/play?jwt-token=${ls.get('jwt-token')}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a song's shareable URL.
|
||||
* Visiting this URL will automatically queue the song and play it.
|
||||
*
|
||||
* @param {Object} song
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getShareableUrl(song) {
|
||||
return `${window.location.origin}/#!/song/${song.id}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the last n recently played songs.
|
||||
*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Other common methods.
|
||||
*/
|
||||
|
||||
import select from 'select';
|
||||
import { event } from '../utils'
|
||||
|
||||
/**
|
||||
|
@ -68,9 +68,22 @@ export function showOverlay(message = 'Just a little patience…', type = 'loadi
|
|||
};
|
||||
|
||||
/**
|
||||
* Hide the overlay
|
||||
* Hide the overlay.
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
export function hideOverlay() {
|
||||
event.emit('overlay:hide');
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a text into clipboard.
|
||||
*
|
||||
* @param {string} txt
|
||||
*/
|
||||
export function copyText(txt) {
|
||||
const copyArea = document.querySelector('#copyArea');
|
||||
copyArea.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
|
||||
copyArea.value = txt;
|
||||
select(copyArea);
|
||||
document.execCommand('copy');
|
||||
};
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from './filters';
|
|||
export * from './formatters';
|
||||
export * from './supports';
|
||||
export * from './common';
|
||||
export * from './url';
|
||||
|
|
|
@ -31,6 +31,14 @@ export function isAudioContextSupported() {
|
|||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if HTML5 clipboard can be used.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isClipboardSupported() {
|
||||
return 'execCommand' in document;
|
||||
};
|
||||
|
||||
const event = {
|
||||
bus: null,
|
||||
|
|
21
resources/assets/js/utils/url.js
Normal file
21
resources/assets/js/utils/url.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* URL-related helpers
|
||||
* @type {Object}
|
||||
*/
|
||||
export const url = {
|
||||
/**
|
||||
* Parse the song ID from a hash.
|
||||
*
|
||||
* @param {string} hash
|
||||
*
|
||||
* @return {string|boolean}
|
||||
*/
|
||||
parseSongId(hash = null) {
|
||||
if (!hash) {
|
||||
hash = window.location.hash;
|
||||
}
|
||||
|
||||
const matches = hash.match(/#!\/song\/([a-f0-9]{32}$)/);
|
||||
return matches ? matches[1] : false;
|
||||
},
|
||||
};
|
Loading…
Reference in a new issue