/** * Shortcodes JavaScript - 实况照片、语音、音乐卡片 * 移植自 Hugo-Theme-Amigo */ // ============================================================ // Live Photo // ============================================================ function initLivePhotoShortcodes() { document.querySelectorAll('.live-photo').forEach(function(livePhoto) { if (livePhoto.__liveBound) return; livePhoto.__liveBound = true; var video = livePhoto.querySelector('video.live-photo-video') || livePhoto.querySelector('video'); var posterImg = livePhoto.querySelector('img.live-photo-poster') || livePhoto.querySelector('img'); var toggleBtn = livePhoto.querySelector('.live-photo-toggle-btn'); var muteBtn = livePhoto.querySelector('.live-photo-mute-btn'); if (!video || !toggleBtn || !muteBtn) return; var HOVER_DELAY = 500; var hoverTimer = null; var isLoaded = false; function syncAspectFromPoster() { if (!posterImg) return; var w = posterImg.naturalWidth || 0; var h = posterImg.naturalHeight || 0; if (!w || !h) return; livePhoto.style.setProperty('--live-photo-aspect', w + ' / ' + h); } if (posterImg && posterImg.complete) { syncAspectFromPoster(); } else if (posterImg) { posterImg.addEventListener('load', syncAspectFromPoster, { once: true }); } function ensureLoaded() { if (isLoaded) return; isLoaded = true; var src = video.dataset.src || ''; if (src && !video.getAttribute('src')) { video.setAttribute('src', src); video.src = src; } try { video.load(); } catch (e) {} } function setMuted(isMuted) { video.muted = !!isMuted; if (isMuted) video.setAttribute('muted', ''); else video.removeAttribute('muted'); muteBtn.setAttribute('data-muted', isMuted ? 'true' : 'false'); } setMuted(true); function playVideo() { ensureLoaded(); livePhoto.classList.add('is-playing'); toggleBtn.setAttribute('data-state', 'live'); try { video.play(); } catch (e) {} } function stopVideo() { livePhoto.classList.remove('is-playing'); toggleBtn.setAttribute('data-state', 'static'); try { video.pause(); video.currentTime = 0; } catch (e) {} } // Toggle button click toggleBtn.addEventListener('click', function(e) { e.stopPropagation(); if (livePhoto.classList.contains('is-playing')) { stopVideo(); } else { playVideo(); } }); // Mute button click muteBtn.addEventListener('click', function(e) { e.stopPropagation(); setMuted(!video.muted); }); // Hover to play livePhoto.addEventListener('mouseenter', function() { hoverTimer = setTimeout(playVideo, HOVER_DELAY); }); livePhoto.addEventListener('mouseleave', function() { clearTimeout(hoverTimer); if (toggleBtn.getAttribute('data-state') !== 'live') { stopVideo(); } }); // Mobile touch livePhoto.addEventListener('touchstart', function() { if (livePhoto.classList.contains('is-playing')) { stopVideo(); } else { playVideo(); } }); // Video ended video.addEventListener('ended', function() { if (toggleBtn.getAttribute('data-state') !== 'live') { stopVideo(); } }); }); } // ============================================================ // Voice Bubble // ============================================================ function initVoiceBubbles() { document.querySelectorAll('.amigo-voice-bubble').forEach(function(bubble) { if (bubble.__voiceBound) return; bubble.__voiceBound = true; var audioSrc = bubble.dataset.src; var audio = null; var isPlaying = false; var durationSpan = bubble.querySelector('.amigo-voice-duration'); // Load audio duration if (audioSrc) { audio = new Audio(audioSrc); audio.addEventListener('loadedmetadata', function() { var duration = Math.round(audio.duration); if (durationSpan) { durationSpan.textContent = duration + '"'; } }); } bubble.addEventListener('click', function() { if (!audio) return; if (isPlaying) { audio.pause(); audio.currentTime = 0; bubble.classList.remove('playing'); isPlaying = false; } else { // Stop other playing voices document.querySelectorAll('.amigo-voice-bubble.playing').forEach(function(other) { if (other !== bubble) { other.classList.remove('playing'); } }); audio.play(); bubble.classList.add('playing'); isPlaying = true; } }); audio.addEventListener('ended', function() { bubble.classList.remove('playing'); isPlaying = false; }); }); } // ============================================================ // Music Card // ============================================================ function initMusicCards() { document.querySelectorAll('.amigo-music-card').forEach(function(card) { if (card.__musicBound) return; card.__musicBound = true; var audioSrc = card.dataset.src; var playBtn = card.querySelector('.amigo-music-card__play'); var seekInput = card.querySelector('.amigo-music-card__seek'); var currentTime = card.querySelector('.amigo-music-card__time--current'); var durationTime = card.querySelector('.amigo-music-card__time--duration'); var audio = null; var isPlaying = false; if (!audioSrc) return; audio = new Audio(audioSrc); // Load metadata audio.addEventListener('loadedmetadata', function() { var duration = formatTime(audio.duration); if (durationTime) durationTime.textContent = duration; }); // Update progress audio.addEventListener('timeupdate', function() { if (currentTime) currentTime.textContent = formatTime(audio.currentTime); if (seekInput && audio.duration) { seekInput.value = (audio.currentTime / audio.duration * 1000).toString(); } }); // Play/Pause function togglePlay() { if (isPlaying) { audio.pause(); card.classList.remove('playing'); isPlaying = false; } else { // Stop other playing music document.querySelectorAll('.amigo-music-card.playing').forEach(function(other) { if (other !== card) { other.classList.remove('playing'); } }); audio.play(); card.classList.add('playing'); isPlaying = true; } } if (playBtn) { playBtn.addEventListener('click', function(e) { e.stopPropagation(); togglePlay(); }); } card.addEventListener('click', togglePlay); // Seek if (seekInput) { seekInput.addEventListener('input', function() { if (audio.duration) { audio.currentTime = (parseFloat(seekInput.value) / 1000) * audio.duration; } }); } // Ended audio.addEventListener('ended', function() { card.classList.remove('playing'); isPlaying = false; }); }); } function formatTime(seconds) { var m = Math.floor(seconds / 60); var s = Math.floor(seconds % 60); return m + ':' + (s < 10 ? '0' : '') + s; } // ============================================================ // Initialize all shortcodes // ============================================================ function initAllShortcodes() { initLivePhotoShortcodes(); initVoiceBubbles(); initMusicCards(); } // DOMContentLoaded document.addEventListener('DOMContentLoaded', initAllShortcodes); // Page load window.addEventListener('load', initAllShortcodes); // PJAX support - re-initialize after page transition document.addEventListener('pjax:complete', initAllShortcodes); document.addEventListener('pjax:success', initAllShortcodes); // Also listen for custom events that some PJAX libraries fire document.addEventListener('page:loaded', initAllShortcodes); document.addEventListener('page:change', initAllShortcodes);