Difference between revisions of "MediaWiki:Common.js"
From Conceptual Reconstructionism Project
Thaumasnot (talk | contribs) |
Thaumasnot (talk | contribs) |
||
(45 intermediate revisions by the same user not shown) | |||
Line 36: | Line 36: | ||
function PlaybackData() { | function PlaybackData() { | ||
this.autostopTime = null; | this.autostopTime = null; | ||
this.noAutoscroll = false; | |||
this.segments = []; | this.segments = []; | ||
this.htmlAudios = []; | this.htmlAudios = []; | ||
Line 44: | Line 45: | ||
var playbackData = new PlaybackData(); | var playbackData = new PlaybackData(); | ||
function buildTimeSegmentFromSpec(spec, progressElement) { | |||
var m = /(?<track>\d+\/)?((?<minuteStart>\d+):)?(?<secondStart>\d+(.\d*)?)-((?<minuteEnd>\d+):)?(?<secondEnd>\d+(.\d*)?)/.exec(spec); | |||
if (m) { | |||
return new TimeSegment( | |||
m.groups.track? parseInt(m.groups.track) : 0, | |||
(m.groups.minuteStart? parseInt(m.groups.minuteStart) : 0) * 60 + parseFloat(m.groups.secondStart), | |||
(m.groups.minuteEnd? parseInt(m.groups.minuteEnd) : 0) * 60 + parseFloat(m.groups.secondEnd), | |||
progressElement | |||
); | |||
} | |||
return null; | |||
} | |||
// Helper functions for music reconstruction writers. | |||
// They can be called from the developer console in Chrome. | |||
function playSample(trackIndex, segmentSpec) { | |||
var segment = buildTimeSegmentFromSpec(segmentSpec, null); | |||
playSample2(trackIndex, segment.startTime, segment.endTime); | |||
if (!playbackData.noAutoscroll) { | |||
playbackData.noAutoscroll = true; | |||
console.log('Autoscroll during playback is now disabled'); | |||
} | |||
copyToClipboard(segmentSpec); | |||
} | |||
function copyToClipboard(text) { | |||
var tempInput = $('<input></input>').prop('value', text).appendTo($('body')); | |||
tempInput.get(0).select(); | |||
document.execCommand('copy'); | |||
tempInput.detach(); | |||
} | |||
function playSample2(trackIndex, startTime, endTime) { | |||
playbackData.autostopTime = endTime; | |||
playbackData.htmlAudios[trackIndex].currentTime = startTime; | |||
playbackData.htmlAudios[trackIndex].play(); | |||
} | |||
Line 51: | Line 97: | ||
// Create one audio player per declared track | // Create one audio player per declared track | ||
var audioPlayer = $('<div class="audio-player"></div>').insertAfter($('*[data-track]').last()); | |||
function selectAudioPlayer(trackNumber) { | |||
audioPlayer.find('figure.active').removeClass('active'); | |||
audioPlayer.find('figure').eq(trackNumber).addClass('active'); | |||
audioPlayer.find('.selected').removeClass('selected'); | |||
audioPlayer.find('.track-menu div').eq(trackNumber).addClass('selected'); | |||
} | |||
$('*[data-track]').each(function(trackNumber) { | $('*[data-track]').each(function(trackNumber) { | ||
var audio = $('<audio></audio>') | var audio = $('<audio></audio>') | ||
.prop('controls', true) | .prop('controls', true) | ||
.attr('controlsList', 'nodownload') | |||
.attr('preload', 'auto') | .attr('preload', 'auto') | ||
.attr('src', $(this).attr('data-track')); | .attr('src', $(this).attr('data-track')); | ||
$('<figure></figure>') | $('<figure></figure>') | ||
.addClass(trackNumber === 0? 'active': '') | |||
.append($('<figcaption></figcaption>') | .append($('<figcaption></figcaption>') | ||
.text($(this).attr('data-track-title'))) | .text($(this).attr('data-track-title'))) | ||
.appendTo( | .appendTo(audioPlayer) | ||
.append(audio); | .append(audio); | ||
function onPlay(event) { | function onPlay(event) { | ||
if (playbackData.playingAudioIndex | if (playbackData.playingAudioIndex == null) { | ||
selectAudioPlayer(trackNumber); | |||
} | |||
else if (playbackData.playingAudioIndex !== trackNumber) { | |||
playbackData.htmlAudios[playbackData.playingAudioIndex].pause(); | playbackData.htmlAudios[playbackData.playingAudioIndex].pause(); | ||
selectAudioPlayer(trackNumber); | |||
} | } | ||
playbackData.playingAudioIndex = trackNumber; | playbackData.playingAudioIndex = trackNumber; | ||
Line 76: | Line 136: | ||
function onPause(event) { | function onPause(event) { | ||
playbackData. | if (playbackData.playingAudioIndex === trackNumber) { | ||
playbackData.playingAudioIndex = null; | |||
playbackData.autostopTime = null; | |||
} | |||
playbackData.closestPlayableSegIndex = 0; | playbackData.closestPlayableSegIndex = 0; | ||
} | } | ||
Line 91: | Line 153: | ||
onSeeked(event); | onSeeked(event); | ||
if (nextPlayingAudioIndex < playbackData.htmlAudios.length) { | if (nextPlayingAudioIndex < playbackData.htmlAudios.length) { | ||
playbackData.htmlAudios[nextPlayingAudioIndex].play(); | var nextAudio = playbackData.htmlAudios[nextPlayingAudioIndex]; | ||
nextAudio.currentTime = 0; | |||
nextAudio.play(); | |||
} | } | ||
} | } | ||
Line 102: | Line 166: | ||
playbackData.htmlAudios.push(audio.get(0)); | playbackData.htmlAudios.push(audio.get(0)); | ||
}); | }); | ||
if ($('*[data-track]').length > 1) { | |||
var trackMenu = $('<div class="track-menu"></div>').appendTo(audioPlayer); | |||
$('*[data-track]').each(function(trackNumber) { | |||
$('<div></div>') | |||
.text(trackNumber + 1) | |||
.click(function() { selectAudioPlayer(trackNumber); } ) | |||
.addClass(trackNumber === 0? 'selected': '') | |||
.prop('title', audioPlayer.find('figcaption').eq(trackNumber).text()) | |||
.tooltip() | |||
.appendTo(trackMenu); | |||
}); | |||
} | |||
Line 107: | Line 185: | ||
$('*[data-def]'). | $('*[data-def]').each(function() { | ||
var url = location.href; | var url = location.href; | ||
var anchor = "#" + encodeURIComponent($(this).attr('data-def').toLowerCase()) | |||
if ($(anchor).length === 0) { | |||
$(this).addClass('broken-ref-link'); | |||
} | |||
else { | |||
$(this).click(function(evt) { | |||
location.href = anchor; | |||
history.replaceState(null, null, url); | |||
evt.stopPropagation(); // Inner markup has priority over outer markup | |||
}); | |||
} | |||
}); | }); | ||
// Create context menu for music references | |||
function playSampleFromMenu(menuItem, segment) { | |||
var m = /\[(\-?\d+)s \+(\d+)s\]/.exec(menuItem.text()); | |||
if (m) { | |||
var lowerBound = parseInt(m[1]); | |||
var upperBound = parseInt(m[2]); | |||
var trackIndex = segment.trackIndex; | |||
playSample2(segment.trackIndex, segment.startTime + lowerBound, segment.endTime + upperBound); | |||
} | |||
} | |||
var soundContextMenu = $('<div class="sound-meta-menu meta-menu">') | |||
.append($('<div>\u25B6 [-0s +0s]</div>')) | |||
.append($('<div>\u25B6 [-0s +5s]</div>')) | |||
.append($('<div>\u25B6 [-5s +0s]</div>')) | |||
.append($('<div>\u25B6 [-2s +2s]</div>')) | |||
.append($('<div>\u25B6 [-5s +5s]</div>')) | |||
.append($('<div>\u25B6 [-10s +10s]</div>')); | |||
// | // Store timing data and attach context menu | ||
$('*[data-segment]').each(function() { | $('*[data-segment]').each(function() { | ||
var | var segment = buildTimeSegmentFromSpec($(this).attr('data-segment'), $(this).find('.meta-progress').eq(0)); | ||
if ( | if (segment) { | ||
if ( | if (playbackData.htmlAudios.length <= segment.trackIndex) { | ||
$(this).append('Track number does not refer to existing track (note that the index starts with 0)'); | $(this).append('Track number does not refer to existing track (note that the index starts with 0)'); | ||
} | } | ||
else { | else { | ||
function attachSoundContextMenu() { | |||
soundContextMenu.detach().appendTo($(this)); | |||
soundContextMenu.children('div').off('click').click(function () { playSampleFromMenu($(this), segment); }); | |||
} | |||
var contextMenuAnchor = $('<span class="context-menu-anchor">\u23F5</span>').insertBefore($(this)); | |||
if (!isMobile) { | |||
contextMenuAnchor.hover(attachSoundContextMenu); | |||
} else { | |||
contextMenuAnchor.click(attachSoundContextMenu); | |||
} | |||
playbackData.segments.push(segment); | |||
} | } | ||
} | } | ||
Line 140: | Line 256: | ||
}); | }); | ||
playbackData.segments.sort(function(a, b) { return a.startTime - b.startTime; }); | playbackData.segments.sort(function(a, b) { return a.trackIndex - b.trackIndex || a.startTime - b.startTime; }); | ||
// Playing tick | // Playing tick | ||
Line 200: | Line 282: | ||
if (segment.isDisabled()) { | if (segment.isDisabled()) { | ||
if (segment.startTime > currentTime) { | if (segment.startTime > currentTime && segment.trackIndex === playbackData.playingAudioIndex) { | ||
break; | break; | ||
} | } | ||
Line 228: | Line 310: | ||
} | } | ||
if (newActiveSegment != null) { | if (newActiveSegment != null && !playbackData.noAutoscroll) { | ||
ensureVisible(newActiveSegment.element); | ensureVisible(newActiveSegment.element); | ||
} | } |
Latest revision as of 18:04, 16 January 2022
/* Any JavaScript here will be loaded for all users on every page load. */ var isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent); function TimeSegment(trackIndex, startTime, endTime, element) { this.trackIndex = trackIndex; this.startTime = startTime; this.endTime = endTime; this.element = element; } TimeSegment.prototype.isDisabled = function() { return !this.element.hasClass('active'); }; TimeSegment.prototype.disable = function() { this.element.removeClass('active'); }; TimeSegment.prototype.update = function(trackIndex, currentTime) { var percent = 100.0 * Math.max(currentTime - this.startTime, 0) / (this.endTime - this.startTime) if (trackIndex === this.trackIndex && percent > 0.0 && percent < 100.0) { this.element.get(0).style.setProperty('--gauge-fill', percent + "%"); this.element.addClass('active'); } else { this.disable(); } }; function PlaybackData() { this.autostopTime = null; this.noAutoscroll = false; this.segments = []; this.htmlAudios = []; this.closestPlayableSegIndex = 0; this.playingAudioIndex = null; }; var playbackData = new PlaybackData(); function buildTimeSegmentFromSpec(spec, progressElement) { var m = /(?<track>\d+\/)?((?<minuteStart>\d+):)?(?<secondStart>\d+(.\d*)?)-((?<minuteEnd>\d+):)?(?<secondEnd>\d+(.\d*)?)/.exec(spec); if (m) { return new TimeSegment( m.groups.track? parseInt(m.groups.track) : 0, (m.groups.minuteStart? parseInt(m.groups.minuteStart) : 0) * 60 + parseFloat(m.groups.secondStart), (m.groups.minuteEnd? parseInt(m.groups.minuteEnd) : 0) * 60 + parseFloat(m.groups.secondEnd), progressElement ); } return null; } // Helper functions for music reconstruction writers. // They can be called from the developer console in Chrome. function playSample(trackIndex, segmentSpec) { var segment = buildTimeSegmentFromSpec(segmentSpec, null); playSample2(trackIndex, segment.startTime, segment.endTime); if (!playbackData.noAutoscroll) { playbackData.noAutoscroll = true; console.log('Autoscroll during playback is now disabled'); } copyToClipboard(segmentSpec); } function copyToClipboard(text) { var tempInput = $('<input></input>').prop('value', text).appendTo($('body')); tempInput.get(0).select(); document.execCommand('copy'); tempInput.detach(); } function playSample2(trackIndex, startTime, endTime) { playbackData.autostopTime = endTime; playbackData.htmlAudios[trackIndex].currentTime = startTime; playbackData.htmlAudios[trackIndex].play(); } $(document).ready(function() { // Create one audio player per declared track var audioPlayer = $('<div class="audio-player"></div>').insertAfter($('*[data-track]').last()); function selectAudioPlayer(trackNumber) { audioPlayer.find('figure.active').removeClass('active'); audioPlayer.find('figure').eq(trackNumber).addClass('active'); audioPlayer.find('.selected').removeClass('selected'); audioPlayer.find('.track-menu div').eq(trackNumber).addClass('selected'); } $('*[data-track]').each(function(trackNumber) { var audio = $('<audio></audio>') .prop('controls', true) .attr('controlsList', 'nodownload') .attr('preload', 'auto') .attr('src', $(this).attr('data-track')); $('<figure></figure>') .addClass(trackNumber === 0? 'active': '') .append($('<figcaption></figcaption>') .text($(this).attr('data-track-title'))) .appendTo(audioPlayer) .append(audio); function onPlay(event) { if (playbackData.playingAudioIndex == null) { selectAudioPlayer(trackNumber); } else if (playbackData.playingAudioIndex !== trackNumber) { playbackData.htmlAudios[playbackData.playingAudioIndex].pause(); selectAudioPlayer(trackNumber); } playbackData.playingAudioIndex = trackNumber; } function onPause(event) { if (playbackData.playingAudioIndex === trackNumber) { playbackData.playingAudioIndex = null; playbackData.autostopTime = null; } playbackData.closestPlayableSegIndex = 0; } function onSeeked(event) { playbackData.closestPlayableSegIndex = 0; playbackData.segments.forEach(function(s) { s.disable(); }); } function onEnded(event) { var nextPlayingAudioIndex = playbackData.playingAudioIndex + 1; onPause(event); onSeeked(event); if (nextPlayingAudioIndex < playbackData.htmlAudios.length) { var nextAudio = playbackData.htmlAudios[nextPlayingAudioIndex]; nextAudio.currentTime = 0; nextAudio.play(); } } audio.on('play', onPlay); audio.on('pause', onPause); audio.on('ended', onEnded); audio.on('seeked', onSeeked); playbackData.htmlAudios.push(audio.get(0)); }); if ($('*[data-track]').length > 1) { var trackMenu = $('<div class="track-menu"></div>').appendTo(audioPlayer); $('*[data-track]').each(function(trackNumber) { $('<div></div>') .text(trackNumber + 1) .click(function() { selectAudioPlayer(trackNumber); } ) .addClass(trackNumber === 0? 'selected': '') .prop('title', audioPlayer.find('figcaption').eq(trackNumber).text()) .tooltip() .appendTo(trackMenu); }); } // Create hyperlinks out of reconstruction markup $('*[data-def]').each(function() { var url = location.href; var anchor = "#" + encodeURIComponent($(this).attr('data-def').toLowerCase()) if ($(anchor).length === 0) { $(this).addClass('broken-ref-link'); } else { $(this).click(function(evt) { location.href = anchor; history.replaceState(null, null, url); evt.stopPropagation(); // Inner markup has priority over outer markup }); } }); // Create context menu for music references function playSampleFromMenu(menuItem, segment) { var m = /\[(\-?\d+)s \+(\d+)s\]/.exec(menuItem.text()); if (m) { var lowerBound = parseInt(m[1]); var upperBound = parseInt(m[2]); var trackIndex = segment.trackIndex; playSample2(segment.trackIndex, segment.startTime + lowerBound, segment.endTime + upperBound); } } var soundContextMenu = $('<div class="sound-meta-menu meta-menu">') .append($('<div>\u25B6 [-0s +0s]</div>')) .append($('<div>\u25B6 [-0s +5s]</div>')) .append($('<div>\u25B6 [-5s +0s]</div>')) .append($('<div>\u25B6 [-2s +2s]</div>')) .append($('<div>\u25B6 [-5s +5s]</div>')) .append($('<div>\u25B6 [-10s +10s]</div>')); // Store timing data and attach context menu $('*[data-segment]').each(function() { var segment = buildTimeSegmentFromSpec($(this).attr('data-segment'), $(this).find('.meta-progress').eq(0)); if (segment) { if (playbackData.htmlAudios.length <= segment.trackIndex) { $(this).append('Track number does not refer to existing track (note that the index starts with 0)'); } else { function attachSoundContextMenu() { soundContextMenu.detach().appendTo($(this)); soundContextMenu.children('div').off('click').click(function () { playSampleFromMenu($(this), segment); }); } var contextMenuAnchor = $('<span class="context-menu-anchor">\u23F5</span>').insertBefore($(this)); if (!isMobile) { contextMenuAnchor.hover(attachSoundContextMenu); } else { contextMenuAnchor.click(attachSoundContextMenu); } playbackData.segments.push(segment); } } else { $(this).append('Invalid syntax for segment time'); } }); playbackData.segments.sort(function(a, b) { return a.trackIndex - b.trackIndex || a.startTime - b.startTime; }); // Playing tick window.setInterval(function() { if (playbackData.playingAudioIndex === null) { return; } var playingAudio = playbackData.htmlAudios[playbackData.playingAudioIndex]; var currentTime = playingAudio.currentTime; var previouSegmentActive = false; var newActiveSegment = null; for(var segIndex = playbackData.closestPlayableSegIndex; segIndex < playbackData.segments.length; segIndex++) { var segment = playbackData.segments[segIndex]; var wasDisabled = segment.isDisabled(); segment.update(playbackData.playingAudioIndex, currentTime); if (segment.isDisabled()) { if (segment.startTime > currentTime && segment.trackIndex === playbackData.playingAudioIndex) { break; } continue; } if (!previouSegmentActive) { playbackData.closestPlayableSegIndex = segIndex; } previouSegmentActive = true; if (wasDisabled && newActiveSegment == null) { newActiveSegment = segment; } } function ensureVisible(element) { var offset = element.offset().top - $(window).scrollTop(); var SCROLL_OFFSET = 150; if (offset > window.innerHeight) { $('html,body').animate({ scrollTop: Math.max(0, offset - SCROLL_OFFSET) }, 500); } else if (offset < 0) { $('html,body').animate({ scrollTop: Math.max(0, element.offset().top - SCROLL_OFFSET) }, 500); } } if (newActiveSegment != null && !playbackData.noAutoscroll) { ensureVisible(newActiveSegment.element); } if (playbackData.autostopTime != null && currentTime >= playbackData.autostopTime) { playingAudio.pause(); return; } }, 50); });