Difference between revisions of "MediaWiki:Common.js"

From Conceptual Reconstructionism Project
Tag: Reverted
Tag: Reverted
Line 241: Line 241:
}
}
var contextMenuAnchor = $('<span class="context-menu-anchor">\u23F5</span><nobr>').insertBefore($(this));
var contextMenuAnchor = $('<span class="context-menu-anchor">\u23F5\u00A0</span>').insertBefore($(this));
if (!isMobile) {
if (!isMobile) {
contextMenuAnchor.hover(attachSoundContextMenu);
contextMenuAnchor.hover(attachSoundContextMenu);

Revision as of 17:53, 15 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'));

		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\u00A0</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);

});