MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
Preparation for better viewing of training videos - adds clickable timestamps |
No edit summary |
||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
/* ── Fix TimedMediaHandler: remove disabled, block popup ── */ | |||
$(function () { | $(function () { | ||
if ($('.training-video-wrap').length === 0) return; | if ($('.training-video-wrap').length === 0) return; | ||
/* ── | setTimeout(function () { | ||
var $video | /* Remove disabled attribute so video can play inline */ | ||
var $entries | $('.mw-file-element').each(function () { | ||
var $body | $(this).removeAttr('disabled'); | ||
var $markers | $(this).attr('controls', 'controls'); | ||
var $fill | }); | ||
var $timeDisp | |||
/* Block the popup link and play inline instead */ | |||
if ($video.length === 0) return; | $('a.mw-tmh-play').on('click', function (e) { | ||
e.preventDefault(); | |||
e.stopPropagation(); | |||
var video = $(this).closest('.mw-tmh-player').find('video')[0]; | |||
if (video) { | |||
video.play(); | |||
} | |||
}); | |||
/* Now initialise the transcript sync */ | |||
initTranscript(); | |||
}, 600); | |||
}); | |||
/* ── Transcript sync ── */ | |||
function initTranscript() { | |||
var $video = $('.training-video-wrap video'); | |||
var $entries = $('.ts-entry'); | |||
var $body = $('.ts-scroll-body'); | |||
var $markers = $('.ts-progress-marker'); | |||
var $fill = $('.ts-progress-fill'); | |||
var $timeDisp = $('.ts-time-display'); | |||
if ($video.length === 0) return; | |||
var videoEl = $video[0]; | var videoEl = $video[0]; | ||
/* ── Format seconds → m:ss ── */ | /* ── Format seconds → m:ss ── */ | ||
function fmt(s) { | function fmt(s) { | ||
| Line 23: | Line 47: | ||
return Math.floor(s / 60) + ':' + ('0' + (s % 60)).slice(-2); | return Math.floor(s / 60) + ':' + ('0' + (s % 60)).slice(-2); | ||
} | } | ||
/* ── Collect timestamp data from DOM ── */ | /* ── Collect timestamp data from DOM ── */ | ||
var timestamps = []; | var timestamps = []; | ||
| Line 29: | Line 53: | ||
timestamps.push(parseInt($(this).data('time'), 10)); | timestamps.push(parseInt($(this).data('time'), 10)); | ||
}); | }); | ||
/* ── Highlight the active transcript entry ── */ | /* ── Highlight the active transcript entry ── */ | ||
function highlightActive() { | function highlightActive() { | ||
| Line 43: | Line 67: | ||
var $active = $entries.eq(activeIdx); | var $active = $entries.eq(activeIdx); | ||
if ($active.length && $body.length) { | if ($active.length && $body.length) { | ||
var entryTop | var entryTop = $active.position().top; | ||
var bodyScrollTop = $body.scrollTop(); | var bodyScrollTop = $body.scrollTop(); | ||
var bodyHeight | var bodyHeight = $body.height(); | ||
if (entryTop < 0 || entryTop > bodyHeight - 60) { | if (entryTop < 0 || entryTop > bodyHeight - 60) { | ||
$body.animate({ scrollTop: bodyScrollTop + entryTop - 60 }, 200); | $body.animate({ scrollTop: bodyScrollTop + entryTop - 60 }, 200); | ||
| Line 51: | Line 75: | ||
} | } | ||
} | } | ||
/* ── Update progress bar ── */ | /* ── Update progress bar ── */ | ||
function updateProgress() { | function updateProgress() { | ||
| Line 59: | Line 83: | ||
$timeDisp.text(fmt(videoEl.currentTime) + ' / ' + fmt(videoEl.duration)); | $timeDisp.text(fmt(videoEl.currentTime) + ' / ' + fmt(videoEl.duration)); | ||
} | } | ||
/* ── Bind native video events ── */ | /* ── Bind native video events ── */ | ||
$video.on('timeupdate', function () { | $video.on('timeupdate', function () { | ||
| Line 65: | Line 89: | ||
updateProgress(); | updateProgress(); | ||
}); | }); | ||
$video.on('loadedmetadata', function () { | $video.on('loadedmetadata', function () { | ||
$timeDisp.text('0:00 / ' + fmt(videoEl.duration)); | $timeDisp.text('0:00 / ' + fmt(videoEl.duration)); | ||
$markers.each(function () { | $markers.each(function () { | ||
var t = parseInt($(this).data('time'), 10); | var t = parseInt($(this).data('time'), 10); | ||
| Line 74: | Line 97: | ||
}); | }); | ||
}); | }); | ||
/* If metadata already loaded */ | |||
if (videoEl.readyState >= 1) { | |||
$timeDisp.text('0:00 / ' + fmt(videoEl.duration)); | |||
$markers.each(function () { | |||
var t = parseInt($(this).data('time'), 10); | |||
$(this).css('left', ((t / videoEl.duration) * 100) + '%'); | |||
}); | |||
} | |||
/* ── Transcript entry click → seek video ── */ | /* ── Transcript entry click → seek video ── */ | ||
$entries.on('click', function () { | $entries.on('click', function () { | ||
| Line 82: | Line 114: | ||
highlightActive(); | highlightActive(); | ||
}); | }); | ||
/* ── Progress bar click → seek ── */ | /* ── Progress bar click → seek ── */ | ||
$('.ts-progress-track').on('click', function (e) { | $('.ts-progress-track').on('click', function (e) { | ||
| Line 89: | Line 121: | ||
videoEl.currentTime = ratio * videoEl.duration; | videoEl.currentTime = ratio * videoEl.duration; | ||
}); | }); | ||
/* ── Marker click → seek ── */ | /* ── Marker click → seek ── */ | ||
$markers.on('click', function (e) { | $markers.on('click', function (e) { | ||
| Line 97: | Line 129: | ||
if (videoEl.paused) videoEl.play(); | if (videoEl.paused) videoEl.play(); | ||
}); | }); | ||
} | |||
} | |||
Revision as of 07:18, 22 April 2026
/* Any JavaScript here will be loaded for all users on every page load. */
/* ── Fix TimedMediaHandler: remove disabled, block popup ── */
$(function () {
if ($('.training-video-wrap').length === 0) return;
setTimeout(function () {
/* Remove disabled attribute so video can play inline */
$('.mw-file-element').each(function () {
$(this).removeAttr('disabled');
$(this).attr('controls', 'controls');
});
/* Block the popup link and play inline instead */
$('a.mw-tmh-play').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
var video = $(this).closest('.mw-tmh-player').find('video')[0];
if (video) {
video.play();
}
});
/* Now initialise the transcript sync */
initTranscript();
}, 600);
});
/* ── Transcript sync ── */
function initTranscript() {
var $video = $('.training-video-wrap video');
var $entries = $('.ts-entry');
var $body = $('.ts-scroll-body');
var $markers = $('.ts-progress-marker');
var $fill = $('.ts-progress-fill');
var $timeDisp = $('.ts-time-display');
if ($video.length === 0) return;
var videoEl = $video[0];
/* ── Format seconds → m:ss ── */
function fmt(s) {
s = Math.floor(s);
return Math.floor(s / 60) + ':' + ('0' + (s % 60)).slice(-2);
}
/* ── Collect timestamp data from DOM ── */
var timestamps = [];
$entries.each(function () {
timestamps.push(parseInt($(this).data('time'), 10));
});
/* ── Highlight the active transcript entry ── */
function highlightActive() {
var cur = videoEl.currentTime;
var activeIdx = 0;
for (var i = 0; i < timestamps.length; i++) {
if (cur >= timestamps[i]) activeIdx = i;
}
$entries.each(function (i) {
$(this).toggleClass('ts-active', i === activeIdx);
});
/* Auto-scroll transcript panel to keep active line visible */
var $active = $entries.eq(activeIdx);
if ($active.length && $body.length) {
var entryTop = $active.position().top;
var bodyScrollTop = $body.scrollTop();
var bodyHeight = $body.height();
if (entryTop < 0 || entryTop > bodyHeight - 60) {
$body.animate({ scrollTop: bodyScrollTop + entryTop - 60 }, 200);
}
}
}
/* ── Update progress bar ── */
function updateProgress() {
if (!videoEl.duration) return;
var pct = (videoEl.currentTime / videoEl.duration) * 100;
$fill.css('width', pct + '%');
$timeDisp.text(fmt(videoEl.currentTime) + ' / ' + fmt(videoEl.duration));
}
/* ── Bind native video events ── */
$video.on('timeupdate', function () {
highlightActive();
updateProgress();
});
$video.on('loadedmetadata', function () {
$timeDisp.text('0:00 / ' + fmt(videoEl.duration));
$markers.each(function () {
var t = parseInt($(this).data('time'), 10);
$(this).css('left', ((t / videoEl.duration) * 100) + '%');
});
});
/* If metadata already loaded */
if (videoEl.readyState >= 1) {
$timeDisp.text('0:00 / ' + fmt(videoEl.duration));
$markers.each(function () {
var t = parseInt($(this).data('time'), 10);
$(this).css('left', ((t / videoEl.duration) * 100) + '%');
});
}
/* ── Transcript entry click → seek video ── */
$entries.on('click', function () {
var t = parseInt($(this).data('time'), 10);
videoEl.currentTime = t;
if (videoEl.paused) videoEl.play();
highlightActive();
});
/* ── Progress bar click → seek ── */
$('.ts-progress-track').on('click', function (e) {
var rect = this.getBoundingClientRect();
var ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
videoEl.currentTime = ratio * videoEl.duration;
});
/* ── Marker click → seek ── */
$markers.on('click', function (e) {
e.stopPropagation();
var t = parseInt($(this).data('time'), 10);
videoEl.currentTime = t;
if (videoEl.paused) videoEl.play();
});
}