Bri-Tunes/src/views/public/playlist.ejs

174 lines
12 KiB
Plaintext

<div class="song-detail">
<% if (playlist.coverPath) { %>
<img src="/media/<%= playlist.coverPath %>" alt="" class="song-detail-cover lb-trigger" data-lightbox />
<% } else { %>
<div class="song-detail-cover cover-fallback">🎧</div>
<% } %>
<div class="song-detail-info">
<div class="song-detail-meta">
<h1><%= playlist.title %></h1>
<% if (playlist.description) { %><p class="muted"><%= playlist.description %></p><% } %>
<p class="muted small"><%= tracks.length %> track<%= tracks.length === 1 ? '' : 's' %></p>
<div class="social-row">
<button type="button"
class="btn-social<%= playlist.userLiked ? ' active' : '' %>"
data-social-action="like" data-social-type="playlist" data-social-id="<%= playlist.id %>"
<% if (!user) { %>data-require-login<% } %>
aria-pressed="<%= playlist.userLiked ? 'true' : 'false' %>"
title="Like">👍 <span class="social-count"><%= playlist.likeCount || 0 %></span></button>
<button type="button"
class="btn-social<%= playlist.userFavorited ? ' active' : '' %>"
data-social-action="favorite" data-social-type="playlist" data-social-id="<%= playlist.id %>"
<% if (!user) { %>data-require-login<% } %>
aria-pressed="<%= playlist.userFavorited ? 'true' : 'false' %>"
title="Favorite">⭐ <span class="social-count"><%= playlist.favoriteCount || 0 %></span></button>
</div>
<% const npMeta = JSON.stringify({ title: playlist.title, description: playlist.description || null, cover: playlist.coverPath ? '/media/' + playlist.coverPath : null, trackCount: tracks.length, creator: creator ? { displayName: creator.displayName, avatarPath: creator.avatarPath || null, slug: creator.slug || null } : null }); %>
<div class="playlist-btn-row">
<div class="playlist-btn-left">
<button type="button" class="btn-primary"
id="play-playlist-btn"
data-playlist-id="<%= playlist.id %>"
data-nowplaying-meta="<%= npMeta %>"
data-play-playlist="<%= JSON.stringify(tracks.map((t) => ({ id: t.id, title: t.title, artist: t.artist, cover: t.coverPath ? '/media/' + t.coverPath : null, userLiked: !!t.userLiked, userFavorited: !!t.userFavorited, likeCount: t.likeCount || 0, favoriteCount: t.favoriteCount || 0 }))) %>">
▶ Play all
</button>
<button type="button" class="btn-primary"
data-playlist-id="<%= playlist.id %>"
data-nowplaying-meta="<%= npMeta %>"
data-shuffle-playlist="<%= JSON.stringify(tracks.map((t) => ({ id: t.id, title: t.title, artist: t.artist, cover: t.coverPath ? '/media/' + t.coverPath : null, userLiked: !!t.userLiked, userFavorited: !!t.userFavorited, likeCount: t.likeCount || 0, favoriteCount: t.favoriteCount || 0 }))) %>">
⇄ Shuffle all
</button>
<% if (user && playlist.createdBy === user.id) { %>
<a href="/mymusic/playlists/<%= playlist.id %>/edit" class="btn-secondary">✎ Edit</a>
<% } %>
<a href="/playlists" class="btn-link">← Back</a>
</div>
<button type="button" id="np-reopen-btn" class="btn-nowplaying" style="display:none"
data-match-playlist-id="<%= playlist.id %>">
▶ Now Playing
</button>
</div>
<% const shareUrl = appBaseUrl + '/playlists/' + playlist.slug; %>
<% const shareText = siteName + ' Playlist: ' + playlist.title; %>
<div class="share-row">
<span class="share-label">Share</span>
<button type="button" class="share-btn" id="share-copy" title="Copy link"
data-share-url="<%= shareUrl %>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg>
<span class="share-copy-label">Copy link</span>
</button>
<a class="share-btn share-btn--x" target="_blank" rel="noopener noreferrer" title="Share on X"
href="https://twitter.com/intent/tweet?url=<%= encodeURIComponent(shareUrl) %>&text=<%= encodeURIComponent(shareText) %>">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.737-8.835L2.25 2.25h6.988l4.26 5.632 4.746-5.632zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
<a class="share-btn share-btn--facebook" target="_blank" rel="noopener noreferrer" title="Share on Facebook"
href="https://www.facebook.com/sharer/sharer.php?u=<%= encodeURIComponent(shareUrl) %>">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047V9.41c0-3.025 1.792-4.697 4.533-4.697 1.312 0 2.686.236 2.686.236v2.97h-1.513c-1.491 0-1.956.93-1.956 1.886v2.267h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z"/>
</svg>
</a>
<a class="share-btn share-btn--reddit" target="_blank" rel="noopener noreferrer" title="Share on Reddit"
href="https://www.reddit.com/submit?url=<%= encodeURIComponent(shareUrl) %>&title=<%= encodeURIComponent(shareText) %>">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/>
</svg>
</a>
<a class="share-btn share-btn--whatsapp" target="_blank" rel="noopener noreferrer" title="Share on WhatsApp"
href="https://wa.me/?text=<%= encodeURIComponent(shareText + ' ' + shareUrl) %>">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413z"/>
</svg>
</a>
<button type="button" class="share-btn share-btn--mastodon" title="Share on Mastodon"
data-share-url="<%= shareUrl %>" data-share-text="<%= shareText %>">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.273.45 2.52.88 3.731.736 1.97 2.773 3.595 4.773 4.265 2.245.74 4.65.896 6.98.565.424-.06.84-.14 1.25-.238l-.007-.054c-.108-.82-.124-1.647-.015-2.47.049-.357.144-.706.273-1.044l.036-.078c-.296.04-.598.074-.907.1-1.83.174-3.674.098-5.48-.23-.546-.1-1.085-.242-1.603-.44-1.16-.44-2.03-1.347-2.323-2.567-.102-.42-.157-.851-.164-1.284l-.002-.073.003-.004c.697.172 1.406.296 2.12.37 2.265.238 4.554.21 6.81-.082 1.55-.2 3.07-.594 4.47-1.17 1.224-.508 2.305-1.272 3.124-2.27.702-.87 1.158-1.912 1.313-3.013.078-.556.117-1.117.117-1.678 0-.19-.003-.38-.01-.569zm-3.927 7.049c-.337 1.564-1.732 2.736-3.356 2.912-.29.032-.583.055-.877.066-1.065.038-2.133.015-3.194-.07-.367-.029-.73-.076-1.09-.14.07.577.095 1.156.073 1.736-.022.577-.097 1.15-.223 1.712-.076.337-.18.666-.31.984-.06.147-.125.292-.196.434-.025.05-.05.1-.078.149v.001c.07.006.14.013.21.018.637.047 1.278.07 1.919.07 1.024 0 2.046-.077 3.055-.232 1.265-.196 2.505-.558 3.666-1.08.713-.317 1.378-.726 1.962-1.218.483-.41.897-.896 1.226-1.438.295-.49.5-1.03.6-1.591.067-.374.1-.753.1-1.134v-.084c-.002-.197-.02-.393-.055-.587-.034-.19-.083-.377-.147-.558z"/>
</svg>
</button>
</div>
<script>
(function () {
const copyBtn = document.getElementById('share-copy');
if (copyBtn) {
copyBtn.addEventListener('click', function () {
const url = copyBtn.dataset.shareUrl;
function onCopied() {
const label = copyBtn.querySelector('.share-copy-label');
const prev = label.textContent;
label.textContent = 'Copied!';
copyBtn.classList.add('share-btn--copied');
setTimeout(function () { label.textContent = prev; copyBtn.classList.remove('share-btn--copied'); }, 2000);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(onCopied);
} else {
const ta = document.createElement('textarea');
ta.value = url;
ta.style.cssText = 'position:fixed;opacity:0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
onCopied();
}
});
}
const mastoBtn = document.querySelector('.share-btn--mastodon');
if (mastoBtn) {
mastoBtn.addEventListener('click', function () {
const LS_KEY = 'mastodon_instance';
const saved = localStorage.getItem(LS_KEY) || '';
const raw = window.prompt('Your Mastodon instance (e.g. mastodon.social):', saved);
if (!raw) return;
const instance = raw.trim().replace(/^https?:\/\//i, '').replace(/\/$/, '');
if (!instance) return;
localStorage.setItem(LS_KEY, instance);
const text = mastoBtn.dataset.shareText + ' ' + mastoBtn.dataset.shareUrl;
window.open('https://' + instance + '/share?text=' + encodeURIComponent(text), '_blank', 'noopener,noreferrer');
});
}
})();
</script>
</div>
<% if (creator) { %>
<% if (creator.slug) { %>
<a href="/profiles/<%= creator.slug %>" class="detail-creator-card">
<% } else { %>
<div class="detail-creator-card">
<% } %>
<% if (creator.avatarPath) { %>
<img src="/media/<%= creator.avatarPath %>" alt="" class="detail-creator-avatar">
<% } else { %>
<div class="detail-creator-avatar detail-creator-placeholder">
<%= (creator.displayName || creator.email).charAt(0).toUpperCase() %>
</div>
<% } %>
<p class="detail-creator-label">Created by</p>
<p class="detail-creator-name"><%= creator.displayName %></p>
<% if (creator.slug) { %>
</a>
<% } else { %>
</div>
<% } %>
<% } %>
</div>
</div>
<% if (tracks.length > 0) { %>
<%- include('../partials/song_list', { songs: tracks }) %>
<% } %>