/** * Shortcodes JavaScript - 实况照片、语音、音乐卡片 * 移植自 Hugo-Theme-Amigo */ // ============================================================ // Live Photo // ============================================================ function initLivePhotoShortcodes() { document.querySelectorAll('.amigo-livephoto').forEach(function(livePhoto) { if (livePhoto.__liveBound) return; livePhoto.__liveBound = true; var video = livePhoto.querySelector('.amigo-livephoto-video'); var posterImg = livePhoto.querySelector('.amigo-livephoto-img'); var soundBtn = livePhoto.querySelector('.amigo-livephoto-sound'); if (!video) return; var HOVER_DELAY = 500; var hoverTimer = null; var isLoaded = false; var isUserToggled = false; 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', ''); livePhoto.classList.add('is-muted'); } else { video.removeAttribute('muted'); livePhoto.classList.remove('is-muted'); } } function playVideo() { ensureLoaded(); livePhoto.classList.add('is-playing'); try { video.play(); } catch (e) {} } function stopVideo() { livePhoto.classList.remove('is-playing'); try { video.pause(); video.currentTime = 0; } catch (e) {} } function togglePlay() { if (livePhoto.classList.contains('is-playing')) { isUserToggled = false; stopVideo(); } else { isUserToggled = true; playVideo(); } } // Click to toggle livePhoto.addEventListener('click', function() { togglePlay(); }); // Sound button click if (soundBtn) { soundBtn.addEventListener('click', function(e) { e.stopPropagation(); setMuted(!video.muted); }); } // Hover to play (desktop) if ('ontouchstart' in window === false) { livePhoto.addEventListener('mouseenter', function() { if (isUserToggled) return; hoverTimer = setTimeout(playVideo, HOVER_DELAY); }); livePhoto.addEventListener('mouseleave', function() { clearTimeout(hoverTimer); if (!isUserToggled) { stopVideo(); } }); } // Initialize muted state setMuted(livePhoto.classList.contains('is-muted')); }); } // ============================================================ // 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('is-playing'); isPlaying = false; } else { // Stop other playing voices document.querySelectorAll('.amigo-voice-bubble.is-playing').forEach(function(other) { if (other !== bubble) { other.classList.remove('is-playing'); } }); audio.play(); bubble.classList.add('is-playing'); isPlaying = true; } }); audio.addEventListener('ended', function() { bubble.classList.remove('is-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); card.__audio = audio; // 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) { var progress = (audio.currentTime / audio.duration) * 100; seekInput.value = (audio.currentTime / audio.duration * 1000).toString(); card.style.setProperty('--music-progress', progress + '%'); } }); // Play/Pause function togglePlay() { if (isPlaying) { audio.pause(); card.setAttribute('data-playing', 'false'); if (playBtn) playBtn.setAttribute('aria-pressed', 'false'); isPlaying = false; } else { // Stop other playing music document.querySelectorAll('.amigo-music-card[data-playing="true"]').forEach(function(other) { if (other !== card) { other.setAttribute('data-playing', 'false'); var otherPlayBtn = other.querySelector('.amigo-music-card__play'); if (otherPlayBtn) otherPlayBtn.setAttribute('aria-pressed', 'false'); var otherAudio = other.__audio; if (otherAudio) { try { otherAudio.pause(); } catch(e) {} } } }); audio.play(); card.setAttribute('data-playing', 'true'); if (playBtn) playBtn.setAttribute('aria-pressed', 'true'); 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) { var progress = (parseFloat(seekInput.value) / 1000) * 100; audio.currentTime = (parseFloat(seekInput.value) / 1000) * audio.duration; card.style.setProperty('--music-progress', progress + '%'); } }); } // Ended audio.addEventListener('ended', function() { card.setAttribute('data-playing', 'false'); if (playBtn) playBtn.setAttribute('aria-pressed', 'false'); isPlaying = false; }); }); } function formatTime(seconds) { var m = Math.floor(seconds / 60); var s = Math.floor(seconds % 60); return m + ':' + (s < 10 ? '0' : '') + s; } // ============================================================ // Cleanup functions for PJAX // ============================================================ function cleanupVoiceBubbles() { document.querySelectorAll('.amigo-voice-bubble').forEach(function(bubble) { if (bubble.__audio) { try { bubble.__audio.pause(); bubble.__audio.src = ''; bubble.__audio = null; } catch (e) {} } bubble.__voiceBound = false; }); } function cleanupMusicCards() { document.querySelectorAll('.amigo-music-card').forEach(function(card) { if (card.__audio) { try { card.__audio.pause(); card.__audio.src = ''; card.__audio = null; } catch (e) {} } card.__musicBound = false; }); } function cleanupLivePhotos() { document.querySelectorAll('.amigo-livephoto').forEach(function(livePhoto) { var video = livePhoto.querySelector('video'); if (video) { try { video.pause(); video.src = ''; } catch (e) {} } livePhoto.__liveBound = false; }); } function cleanupAllShortcodes() { cleanupVoiceBubbles(); cleanupMusicCards(); cleanupLivePhotos(); } // ============================================================ // Initialize all shortcodes // ============================================================ function initAllShortcodes() { initLivePhotoShortcodes(); initVoiceBubbles(); initMusicCards(); } // DOMContentLoaded document.addEventListener('DOMContentLoaded', initAllShortcodes); // Page load window.addEventListener('load', initAllShortcodes); // If DOM is already ready (script loaded late), initialize immediately if (document.readyState === 'interactive' || document.readyState === 'complete') { initAllShortcodes(); } // PJAX support - cleanup before and re-initialize after page transition document.addEventListener('pjax:beforeSend', cleanupAllShortcodes); 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); document.addEventListener('turbo:load', initAllShortcodes); document.addEventListener('turbo:before-render', cleanupAllShortcodes); // History API based navigation document.addEventListener('popstate', function() { cleanupAllShortcodes(); setTimeout(initAllShortcodes, 10); });