343 lines
11 KiB
JavaScript
343 lines
11 KiB
JavaScript
/**
|
|
* 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);
|
|
});
|