MediaWiki:Common.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
if (mw.config.get('wgPageName' ) === 'Krishna_says_in_Bhagavad-gita') {
/* switch tooltip texts from content-div to label-div */
const v_labels = Array.from(document.getElementsByClassName("tabs-label"));
const v_content = Array.from(document.getElementsByClassName("tabs-content"));
for (var i = 0; i < v_labels.length; i++) {
v_labels[i].title = v_content[i].title;
v_content[i].title = "";
}
/* adjust wikitables */
var arr = Array.from(document.getElementsByClassName("wikitable"));
for (var t = 0; t < arr.length; t++) {
/* insert THEAD in wikitable */
var thead = document.createElement("thead");
thead.style.display = "block";
var wtbl = arr[t];
wtbl.insertBefore(thead, wtbl.firstChild);
/* move first row of TBODY to THEAD */
thead.appendChild(wtbl.rows[0]);
/* make TBODY scrollable */
var tbody = wtbl.tBodies[0];
tbody.style.display = "block";
tbody.style.overflowY = "auto";
tbody.style.maxHeight = "500px";
for (var r = 0; r < wtbl.rows.length; r++) {
var row = wtbl.rows[r];
/* adjust column widths and hide from-until columns */
row.cells[0].classList = "cw_0";
row.cells[1].classList = "cw_150";
row.cells[2].classList = "cw_500";
/* mark the text */
if (r == 0) continue;
var v_text = row.cells[2].textContent;
var v_html = ""; var v_from = 0;
var marks = JSON.parse(row.cells[0].textContent.replaceAll("O", "[").replaceAll("C", "]"));
for (var m = 0; m < marks.length; m++) {
v_html +=
v_text.substr(v_from,marks[m][0]-v_from) + "<mark>" +
v_text.substr(marks[m][0],marks[m][1]-marks[m][0]+1) + "</mark>";
v_from = marks[m][1]+1;
}
v_html += v_text.substr(v_from);
row.cells[2].innerHTML = v_html;
}
}
}
// install Google Search
// document.write('<script type="text/javascript" src="'
// + 'http://vanipedia.org/w/index.php?title=MediaWiki:GoogleSearch.js'
// + '&action=raw&ctype=text/javascript"></' + 'script>');
// install JQuery
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:JQuery.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install WikEd toolbar and Vanify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vaniquotes.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Replace
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:TextboxReplace.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Unify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Unify.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Vanify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vanify.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Vani_suggest for diacritics auto-suggest pulldown on the search bar
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vani_suggest.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install previous/next links for video categories in all languages
//document.write('<script type="text/javascript" src="/navcat/navcat.js"></' + 'script>');
// Buttons
//addOnloadHook(function() {
//if (document.editform) {
//addPortletLink("p-cactions", "javascript:Unify()", "unify", "ca-unify", "Convert Balarama fonts to Unicode", "");
// addPortletLink("p-cactions", "javascript:Vanify()", "vanify", "ca-vanify", "Corrects vani markup", "");
// addPortletLink('p-cactions', 'javascript:format()', 'format', 'ca-format', 'Format article', '');
//addPortletLink('p-cactions', 'javascript:wpTextboxReplace()', 'Replace', 'ca-replace', 'Replaces text in the edit window', 'R');
//}
//});
// end Buttons
// --- YouTube Random Shorts (Kasya) ---
$(document).ready(function() {
var $widget = $('.vani-shorts-viewport');
if ($widget.length) {
var $viewport = $widget;
var $titleFrame = $widget.find('.vani-shorts-title');
new mw.Api().get({
action: 'query',
prop: 'revisions',
titles: 'VanimediaMayapur YouTube SHORTS List',
rvprop: 'content',
format: 'json'
}).done(function(data) {
try {
var pages = data.query.pages;
var pageId = Object.keys(pages)[0];
if (pageId == "-1" || !pages[pageId].revisions) {
$viewport.text("Database list page not found.");
return;
}
// FIXED: Corrected MediaWiki revision array syntax lookup
var rawWikiText = pages[pageId].revisions[0]['*'];
var lines = rawWikiText.split('\n');
var validShorts = [];
for (var i = 0; i < lines.length; i++) {
var line = $.trim(lines[i]);
if (line.indexOf('File:') === 0 && line.toLowerCase().indexOf('link=') !== -1) {
validShorts.push(line);
}
}
if (validShorts.length === 0) {
$viewport.text("No gallery entries recognized.");
return;
}
var randomLine = validShorts[Math.floor(Math.random() * validShorts.length)];
var parts = randomLine.split('|');
// Reset defaults before parsing line elements
var rawUrl = "";
for (var j = 0; j < parts.length; j++) {
var part = $.trim(parts[j]);
if (part.toLowerCase().indexOf('link=') === 0) {
rawUrl = part.substring(5).trim();
}
}
// Clean all template formatting brackets, space characters, and hidden control markers
var urlCleaned = rawUrl.replace(/[\u200e\u200f\s\}\}\]]/g, '');
// Direct YouTube Alphanumeric Pattern Matcher
var idRegex = /(?:shorts\/|v=|embed\/|\/)([a-zA-Z0-9_-]{11})/;
var matchResults = urlCleaned.match(idRegex);
var videoId = "";
if (matchResults && matchResults[1]) {
// FIXED: Grabbed index 1 to pull the standalone 11-digit ID without any slashes
videoId = matchResults[1].trim();
}
if (videoId && videoId.length === 11) {
// FIXED: Isolated to inject ONLY the video player iframe at the correct 280px width
var embedUrl = "https://www.youtube.com/embed/" + videoId;
$viewport.html('<iframe width="280" height="225" src="' + embedUrl + '" frameborder="0" allowfullscreen style="border-radius: 8px; border: none;"></iframe>');
} else {
$viewport.html("<div style='color:#c0392b; padding:10px; font-size:11px;'>Invalid video ID sequence extracted.</div>");
}
} catch (err) {
console.error("Shorts engine exception:", err);
$viewport.text("Error handling text structure values.");
}
}).fail(function() {
$viewport.text("API connection failed.");
});
}
});
// Kasya
/* Open Timeline links in a new tab ONLY on the Chapters page */
mw.hook('wikipage.content').add(function($content) {
// Check if we are exactly on the timeline page
if (mw.config.get('wgPageName') === "Chapters_in_Srila_Prabhupada's_Life") {
// If yes, apply the new tab rule to the table
$content.find('#mp-tfp a').attr('target', '_blank').attr('rel', 'noopener noreferrer');
}
});
// Wait for MediaWiki core modules and the DOM to load
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
$(document).ready(function () {
// 1. FIND THE "102 Languages" SIDEBAR SECTION
var $sidebarMenu = $('#p-102_Languages ul').first();
// Fallback in case your specific MediaWiki version formats the ID differently
if ($sidebarMenu.length === 0) {
$('.vector-menu-heading, .portal h3').each(function() {
// Looks for the heading text directly
if ($(this).text().trim() === '102 Languages') {
$sidebarMenu = $(this).closest('.vector-menu, .portal').find('ul').first();
}
});
}
// Exit if the 102 Languages menu isn't found on the page
if (!$sidebarMenu || $sidebarMenu.length === 0) return;
// 2. CREATE THE SEARCH BAR AS A MENU ITEM
var $searchLi = $('<li id="n-language-search" style="margin-top: 10px; margin-bottom: 5px; list-style: none;"></li>');
var $searchBox = $('<input type="text" id="sidebarLanguageSearch" placeholder="Search 102 Languages..." style="width: 100%; box-sizing: border-box; padding: 5px; font-size: 0.85em; border: 1px solid #a2a9b1; border-radius: 2px;" autocomplete="off" />');
// Container for the results (hidden by default)
var $resultsContainer = $('<ul id="sidebarLanguageResults" style="list-style: none; padding: 0; margin: 4px 0 0 0; max-height: 200px; overflow-y: auto; border: 1px solid #eaecf0; background: #ffffff; display: none;"></ul>');
// Build the menu item and append it to the sidebar
$searchLi.append($searchBox).append($resultsContainer);
$sidebarMenu.prepend($searchLi);
var api = new mw.Api();
var languageList = [];
// 3. FETCH LANGUAGES FROM THE CATEGORY VIA API IN THE BACKGROUND
api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:Languages_with_Total_Pages',
cmlimit: 'max',
format: 'json'
}).done(function (data) {
if (data.query && data.query.categorymembers) {
languageList = data.query.categorymembers.map(function (member) {
return {
title: member.title,
url: mw.util.getUrl(member.title)
};
});
}
});
// 4. ADD SEARCH FUNCTIONALITY TO THE SIDEBAR BOX
$('#sidebarLanguageSearch').on('keyup', function (e) {
e.stopPropagation();
var searchTerm = $(this).val().toLowerCase();
$resultsContainer.empty();
// Hide the results box if the search is empty
if (searchTerm.length === 0) {
$resultsContainer.hide();
return;
}
// Filter the fetched languages
var filteredLanguages = languageList.filter(function (lang) {
return lang.title.toLowerCase().indexOf(searchTerm) > -1;
});
// Show results container
$resultsContainer.show();
// Append results using Vector's standard dark text color
if (filteredLanguages.length > 0) {
filteredLanguages.forEach(function (lang) {
$resultsContainer.append('<li style="padding: 4px 6px; border-bottom: 1px solid #f8f9fa;"><a href="' + lang.url + '" style="text-decoration: none; color: #0645ad; display: block; font-size: 0.9em;">' + lang.title + '</a></li>');
});
} else {
$resultsContainer.append('<li style="padding: 4px 6px; color: #72777d; font-size: 0.9em;">No languages found</li>');
}
});
// Prevent typing from accidentally triggering global keyboard shortcuts
$('#sidebarLanguageSearch').on('keydown', function(e) {
e.stopPropagation();
});
// Optional: Hide results when clicking outside the sidebar search
$(document).on('click', function(e) {
if (!$(e.target).closest('#n-language-search').length) {
$resultsContainer.hide();
}
});
// Re-show results if they click back into the search box
$('#sidebarLanguageSearch').on('click', function() {
if ($(this).val().length > 0) {
$resultsContainer.show();
}
});
});
});
// 5. SIDEBAR ACCORDION - STRICT LEFT-COLUMN ISOLATION & TRUE ACCORDION
(function() {
// 1. Tag ONLY menus physically located in the left sidebar
function tagSidebarMenus() {
var allMenus = document.querySelectorAll('.vector-menu');
for (var i = 0; i < allMenus.length; i++) {
var menu = allMenus[i];
// STRICT INCLUSION: Only tag menus inside the modern or legacy left sidebar containers
if (menu.closest('#vector-main-menu, #mw-panel, #mw-navigation')) {
menu.classList.add('vanipedia-sidebar-menu');
// Collapse them by default if they have a heading
if (menu.querySelector('.vector-menu-heading') && !menu.classList.contains('vanipedia-collapsed')) {
menu.classList.add('vanipedia-collapsed');
}
}
}
}
// Run tagging immediately, and again on load as a failsafe
tagSidebarMenus();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', tagSidebarMenus);
}
// 2. Inject CSS targeted STRICTLY at our newly tagged custom class
var style = document.createElement('style');
style.textContent =
/* Widen only our tagged sidebar menus */
'.vanipedia-sidebar-menu { width: 185px !important; } ' +
/* Core functional hiding rule */
'.vanipedia-sidebar-menu.vanipedia-collapsed .vector-menu-content { display: none !important; } ' +
/* Bold, Dark Header */
'.vanipedia-sidebar-menu .vector-menu-heading { ' +
' cursor: pointer !important; ' +
' background-color: #2c4c50 !important; ' +
' border: 1px solid #1b3033 !important; ' +
' border-radius: 4px !important; ' +
' margin-bottom: 4px !important; ' +
' padding: 10px 12px !important; ' +
' font-weight: 600 !important; ' +
' color: #ffffff !important; ' +
' display: flex !important; ' +
' justify-content: space-between !important; ' +
' align-items: center !important; ' +
' gap: 10px !important; ' +
' transition: all 0.2s ease; ' +
'} ' +
/* Fix header corners when expanded */
'.vanipedia-sidebar-menu:not(.vanipedia-collapsed) .vector-menu-heading { ' +
' border-radius: 4px 4px 0 0 !important; ' +
' margin-bottom: 0 !important; ' +
'} ' +
/* Hover effect */
'.vanipedia-sidebar-menu .vector-menu-heading:hover { background-color: #385e63 !important; } ' +
/* The dropdown caret */
'.vanipedia-sidebar-menu .vector-menu-heading::after { ' +
' content: "▼"; ' +
' font-size: 0.75em; ' +
' color: #ffffff !important; ' +
' transition: transform 0.2s ease; ' +
'} ' +
/* Rotate the arrow when collapsed */
'.vanipedia-sidebar-menu.vanipedia-collapsed .vector-menu-heading::after { ' +
' transform: rotate(-90deg); ' +
'} ' +
/* The Expanded Content Area */
'.vanipedia-sidebar-menu .vector-menu-content { ' +
' background-color: #eaeff0 !important; ' +
' border: 1px solid #2c4c50 !important; ' +
' border-top: none !important; ' +
' border-radius: 0 0 4px 4px !important; ' +
' padding: 0 0 4px 0 !important; ' +
' margin-bottom: 6px !important; ' +
' box-shadow: 0 3px 6px rgba(0,0,0,0.15) !important; ' +
'} ' +
/* Reset the list styles */
'.vanipedia-sidebar-menu .vector-menu-content ul { ' +
' list-style: none !important; ' +
' margin: 0 !important; ' +
' padding: 0 !important; ' +
'} ' +
/* Menu links */
'.vanipedia-sidebar-menu .vector-menu-content li a { ' +
' display: block !important; ' +
' padding: 8px 12px 8px 16px !important; ' +
' color: #1b3033 !important; ' +
' text-decoration: none !important; ' +
' border-left: 3px solid transparent !important; ' +
' border-bottom: 1px solid rgba(44, 76, 80, 0.1) !important; ' +
' transition: all 0.15s ease !important; ' +
'} ' +
/* Remove separator from last item */
'.vanipedia-sidebar-menu .vector-menu-content li:last-child a { ' +
' border-bottom: none !important; ' +
'} ' +
/* Hover effect for individual menu items */
'.vanipedia-sidebar-menu .vector-menu-content li a:hover { ' +
' background-color: #dbe4e6 !important; ' +
' border-left: 3px solid #2c4c50 !important; ' +
' color: #000000 !important; ' +
'} ';
document.head.appendChild(style);
// 3. Helper function to collapse all of our securely tagged menus
function collapseAllSidebarMenus() {
var menus = document.querySelectorAll('.vanipedia-sidebar-menu');
for (var i = 0; i < menus.length; i++) {
menus[i].classList.add('vanipedia-collapsed');
}
}
// 4. Intercept clicks for True Accordion Behavior
document.addEventListener('click', function(e) {
var heading = e.target.closest('.vector-menu-heading');
if (!heading) return;
// ONLY react if this click belongs to one of our tagged sidebar menus
var navContainer = heading.closest('.vanipedia-sidebar-menu');
if (navContainer) {
var isAlreadyOpen = !navContainer.classList.contains('vanipedia-collapsed');
// Forcefully collapse ALL left sidebar menus first
collapseAllSidebarMenus();
// Open the clicked one if it wasn't already open
if (!isAlreadyOpen) {
navContainer.classList.remove('vanipedia-collapsed');
}
e.stopPropagation();
e.preventDefault();
}
}, true);
})();
mw.loader.using('mediawiki.user').then(function() {
// Fetch the user's assigned groups
var userGroups = mw.config.get('wgUserGroups') || [];
// Define the roles that SHOULD see the administrative tabs
var isAuthorized = userGroups.includes('sysop') ||
userGroups.includes('interface-admin') ||
userGroups.includes('Editor');
// If the user is not in one of those groups, restrict the UI
if (!isAuthorized) {
document.body.classList.add('restricted-ui');
}
});
// Function to open accordion from TOC and jump there //
mw.hook('wikipage.content').add(function() {
function handleAccordionTOC(hash) {
if (!hash || hash === '#') return false;
try {
var targetId = hash.substring(1);
var targetElement = document.getElementById(targetId) || document.getElementById(decodeURIComponent(targetId));
if (!targetElement) return false;
var $target = $(targetElement);
// Match either h2 or h3
var $heading = $target.is('h2, h3') ? $target : $target.closest('h2, h3');
if (!$heading.length) return false;
var $accordion = $heading.next('.book-accordion');
// If the heading itself is not followed directly by an accordion,
// check if the heading is located INSIDE a parent accordion (e.g., nested h3)
var $parentAccordions = $heading.parents('.book-accordion');
// If there's no direct accordion next to it and it's not inside any accordion,
// let the browser handle standard jump.
if (!$accordion.length && !$parentAccordions.length) return false;
// 1. Open all ancestor accordions (if target heading is nested inside one)
$parentAccordions.each(function() {
var $parentCollapsible = $(this).children('.mw-collapsible');
if ($parentCollapsible.length && $parentCollapsible.hasClass('mw-collapsed')) {
var pId = $parentCollapsible.attr('id');
if (pId) {
var pToggleClass = pId.replace('mw-customcollapsible-', 'mw-customtoggle-');
$('.' + pToggleClass).first().click();
}
}
});
// 2. Open the accordion immediately following this heading (if present)
if ($accordion.length) {
var $collapsible = $accordion.children('.mw-collapsible, .mw-collapsible-content').first();
if (!$collapsible.length) {
$collapsible = $accordion.find('.mw-collapsible');
}
if ($collapsible.length && $collapsible.hasClass('mw-collapsed')) {
var collapsibleId = $collapsible.attr('id');
if (collapsibleId) {
var toggleClass = collapsibleId.replace('mw-customcollapsible-', 'mw-customtoggle-');
$('.' + toggleClass).first().click();
}
}
}
// Target element to scroll to (the accordion, or the heading itself if accordion is missing)
var $scrollTarget = $accordion.length ? $accordion : $heading;
// 3. Scroll to the visible section
setTimeout(function() {
$('html, body').animate({
scrollTop: $scrollTarget.offset().top - 75
}, 300);
}, 100);
// Return true to indicate we successfully handled the TOC accordion click
return true;
} catch (e) {
console.error("TOC Accordion script error:", e);
return false;
}
}
$(document).off('click.tocAccordion').on('click.tocAccordion', 'a[href^="#"]', function(e) {
if ($(this).closest('#toc, .toc, .vector-toc').length > 0) {
var href = $(this).attr('href');
// Run our function to see if this is an accordion/heading link
var isAccordion = handleAccordionTOC(href);
// ONLY stop the default browser jump if we handled it
if (isAccordion) {
e.preventDefault();
// Update the URL manually so the user can still share the link
if (history.pushState) {
history.pushState(null, null, href);
} else {
window.location.hash = href;
}
}
}
});
if (window.location.hash) {
setTimeout(function() {
handleAccordionTOC(window.location.hash);
}, 500);
}
});
/* Sticky burger menu - Pure Floating Dropdown (Forces Unpinned & Full Title) */
(function () {
let isMenuOpen = false;
// 1. Force the sidebar out of pinned mode and return menu into the dropdown container
function forceUnpinSidebar() {
document.documentElement.classList.remove('vector-feature-main-menu-pinned-enabled', 'vector-feature-main-menu-pinned-clientpref-1');
document.documentElement.classList.add('vector-feature-main-menu-pinned-disabled', 'vector-feature-main-menu-pinned-clientpref-0');
if (document.body) {
document.body.classList.remove('vector-feature-main-menu-pinned-enabled', 'vector-feature-main-menu-pinned-clientpref-1');
document.body.classList.add('vector-feature-main-menu-pinned-disabled', 'vector-feature-main-menu-pinned-clientpref-0');
}
// Clear stored pin preference
document.cookie = "vector-feature-main-menu-pinned=0; path=/; max-age=31536000";
try { localStorage.setItem('vector-feature-main-menu-pinned', '0'); } catch(e) {}
// If #vector-main-menu was moved into the pinned container, move it back into dropdown
const mainMenu = document.getElementById('vector-main-menu');
const unpinnedContainer = document.getElementById('vector-main-menu-unpinned-container') ||
document.querySelector('#vector-main-menu-dropdown .vector-dropdown-content');
const pinnedContainer = document.getElementById('vector-main-menu-pinned-container');
if (mainMenu && unpinnedContainer && pinnedContainer && pinnedContainer.contains(mainMenu)) {
unpinnedContainer.appendChild(mainMenu);
}
}
function getDropdownElement() {
return document.querySelector('#vector-main-menu-dropdown .vector-dropdown-content') ||
document.getElementById('vector-main-menu-dropdown-content') ||
document.getElementById('vector-main-menu-unpinned-container') ||
document.getElementById('vector-main-menu');
}
function openMenu(menuBtn) {
forceUnpinSidebar();
const checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
const dropdown = getDropdownElement();
if (!dropdown) return;
if (checkbox) {
checkbox.checked = true;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
const rect = menuBtn.getBoundingClientRect();
dropdown.style.setProperty('position', 'fixed', 'important');
dropdown.style.setProperty('top', rect.bottom + 'px', 'important');
dropdown.style.setProperty('left', rect.left + 'px', 'important');
dropdown.style.setProperty('z-index', '99999', 'important');
dropdown.style.setProperty('max-height', (window.innerHeight - rect.bottom - 16) + 'px', 'important');
dropdown.style.setProperty('overflow-y', 'auto', 'important');
dropdown.style.setProperty('display', 'block', 'important');
dropdown.style.setProperty('visibility', 'visible', 'important');
dropdown.style.setProperty('opacity', '1', 'important');
dropdown.style.setProperty('background-color', 'var(--background-color-base, #ffffff)', 'important');
dropdown.style.setProperty('box-shadow', '0 4px 16px rgba(0, 0, 0, 0.2)', 'important');
dropdown.style.setProperty('border-radius', '4px', 'important');
isMenuOpen = true;
}
function closeMenu() {
const checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
const dropdown = getDropdownElement();
if (!dropdown) return;
if (checkbox) {
checkbox.checked = false;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
dropdown.style.removeProperty('position');
dropdown.style.removeProperty('top');
dropdown.style.removeProperty('left');
dropdown.style.removeProperty('z-index');
dropdown.style.removeProperty('max-height');
dropdown.style.removeProperty('overflow-y');
dropdown.style.removeProperty('background-color');
dropdown.style.removeProperty('box-shadow');
dropdown.style.removeProperty('border-radius');
dropdown.style.setProperty('display', 'none', 'important');
dropdown.style.setProperty('visibility', 'hidden', 'important');
dropdown.style.setProperty('opacity', '0', 'important');
isMenuOpen = false;
}
function initStickyBurger() {
forceUnpinSidebar();
const stickyHeader = document.getElementById('vector-sticky-header');
if (!stickyHeader) return false;
if (document.getElementById('vanipedia-sticky-main-menu')) return true;
// 1. Create burger button
const menuBtn = document.createElement('div');
menuBtn.id = 'vanipedia-sticky-main-menu';
menuBtn.title = 'Main menu';
menuBtn.style.cursor = 'pointer';
menuBtn.className = 'vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--icon-only';
// 2. Add hamburger icon
const iconSpan = document.createElement('span');
iconSpan.className = 'vector-icon mw-ui-icon-menu mw-ui-icon-wikimedia-menu';
menuBtn.appendChild(iconSpan);
// 3. Toggle click handler (1st click opens, 2nd click rolls back)
menuBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
if (isMenuOpen) {
closeMenu();
} else {
openMenu(menuBtn);
}
});
// 4. Inject into start container
let startContainer = stickyHeader.querySelector('.vector-sticky-header-start');
if (!startContainer) {
startContainer = document.createElement('div');
startContainer.className = 'vector-sticky-header-start';
stickyHeader.insertBefore(startContainer, stickyHeader.firstChild);
}
startContainer.insertBefore(menuBtn, startContainer.firstChild);
return true;
}
// 5. Close when clicking anywhere outside
document.addEventListener('click', function (e) {
if (!isMenuOpen) return;
const menuBtn = document.getElementById('vanipedia-sticky-main-menu');
const dropdown = getDropdownElement();
if (dropdown && menuBtn && !menuBtn.contains(e.target) && !dropdown.contains(e.target)) {
closeMenu();
}
});
// 6. Bootstrap initializer
if (!initStickyBurger()) {
const checkTimer = setInterval(function () {
if (initStickyBurger()) {
clearInterval(checkTimer);
}
}, 100);
setTimeout(function () { clearInterval(checkTimer); }, 4000);
}
mw.hook('wikipage.content').add(initStickyBurger);
$(initStickyBurger);
})();
/* Floating "Back to Top" Button Controller */
(function () {
function initBackToTop() {
if (document.getElementById('vanipedia-back-to-top')) return;
// 1. Create button element with an up-arrow SVG icon
const btn = document.createElement('button');
btn.id = 'vanipedia-back-to-top';
btn.title = 'Back to top';
btn.setAttribute('aria-label', 'Back to top');
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
`;
// 2. Smooth scroll to top on click
btn.addEventListener('click', function (e) {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// 3. Show button only after scrolling down 300px
function toggleVisibility() {
if (window.scrollY > 300) {
btn.classList.add('visible');
} else {
btn.classList.remove('visible');
}
}
window.addEventListener('scroll', toggleVisibility, { passive: true });
toggleVisibility();
document.body.appendChild(btn);
}
$(initBackToTop);
mw.hook('wikipage.content').add(initBackToTop);
})();
(function () {
if (!document.getElementById('vani-home')) {
return;
}
/* =====================================================
ROTATING HERO PHOTOGRAPHS
===================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const vaniHeroImages = [
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-01.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-02.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-03.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-04.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-05.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-06.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-07.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-08.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-09.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-10.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-11.jpg',
'/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-12.jpg'
];
const heroA = document.querySelector('.vh-photo-a');
const heroB = document.querySelector('.vh-photo-b');
if (heroA && heroB && vaniHeroImages.length) {
let currentImage = -1;
let activeLayer = heroA;
function chooseHeroImage() {
let next;
do {
next = Math.floor(
Math.random() * vaniHeroImages.length
);
} while (
next === currentImage &&
vaniHeroImages.length > 1
);
return next;
}
function changeHero(firstLoad) {
const next = chooseHeroImage();
const image = vaniHeroImages[next];
if (firstLoad) {
heroA.style.backgroundImage =
'url("' + image + '")';
heroA.style.opacity = '1';
heroB.style.opacity = '0';
activeLayer = heroA;
currentImage = next;
return;
}
const incoming =
activeLayer === heroA ? heroB : heroA;
incoming.style.backgroundImage =
'url("' + image + '")';
incoming.style.opacity = '1';
activeLayer.style.opacity = '0';
activeLayer = incoming;
currentImage = next;
}
changeHero(true);
window.setInterval(function () {
changeHero(false);
}, 10000);
}
}
/* =====================================================
ROTATING ŚRĪLA PRABHUPĀDA SUBTITLE
===================================================== */
const vaniSubtitles = [
'His words still speak to us today.',
'His vāṇī brings closer to Kṛṣṇa.<br>His vision gives direction in lives.',
'Words spoken decades ago.<br>Wisdom that speaks personally today.',
'His vāṇī lives when we hear it and share with others.',
'There is always more to discover.<br>More to hear. More to understand. More to live.',
'His teachings give direction.<br>His example gives courage to follow.',
'From his heart —<br>a treasure of teachings for every generation.',
'His vāṇī. His compassion. His example.',
'His vāṇī lives on. His teachings guide us.<br>His mission continues.',
'His words cross decades and continents —<br>still reaching hearts, still changing lives.',
'His words continue to speak.<br>His teachings continue to guide.',
'He gave us a great treasure.<br>Vanipedia invites you to enter it.',
'A lifetime of teachings.<br>Invitation to hear him with an open heart.',
'His words illuminate the path.<br>His example inspires us to walk it.',
'He carried Kṛṣṇa consciousness across the world.<br>His vāṇī continues that journey today.',
'He lives on through his words,<br>his teachings and the mission he gave us.',
'His words continue to speak.<br>His teachings continue to guide.',
'Preserving and sharing Śrīla Prabhupāda’s vāṇī<br>so that generations may continue to hear from him.'
];
const vaniSubtitle =
document.getElementById('vh-subtitle-rotation');
if (vaniSubtitle) {
let currentSubtitle = -1;
function chooseSubtitle() {
let next;
do {
next = Math.floor(
Math.random() * vaniSubtitles.length
);
} while (
next === currentSubtitle &&
vaniSubtitles.length > 1
);
return next;
}
function showSubtitle(firstLoad) {
const next = chooseSubtitle();
if (firstLoad) {
currentSubtitle = next;
vaniSubtitle.innerHTML = vaniSubtitles[next];
vaniSubtitle.classList.remove('is-changing');
return;
}
// Fade out
vaniSubtitle.classList.add('is-changing');
window.setTimeout(function () {
vaniSubtitle.innerHTML = vaniSubtitles[next];
currentSubtitle = next;
requestAnimationFrame(function () {
requestAnimationFrame(function () {
vaniSubtitle.classList.remove('is-changing');
});
});
}, 450);
}
/* Initial sentence */
showSubtitle(true);
/* Change every 10 seconds */
window.setInterval(function () {
showSubtitle(false);
}, 12000);
}
/* ==========================================================
MAIN PAGE — REMOVE NATIVE LINK TOOLTIPS
========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
function removeMainPageLinkTitles() {
document
.querySelectorAll('#vani-home a[title]')
.forEach(function (link) {
link.removeAttribute('title');
});
}
$(removeMainPageLinkTitles);
mw.hook('wikipage.content').add(function () {
removeMainPageLinkTitles();
});
}
/* =====================================================
RANDOM THEMATIC ARTICLE SPOTLIGHT
===================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const discoverBox = document.getElementById('vh-random-article');
if (discoverBox) {
let articleLinks = [];
let currentHref = null;
const sourceUrl =
mw.util.getUrl('Category:Vanipedia Gemini - Articles');
function loadArticleLinks() {
return fetch(sourceUrl)
.then(function (response) {
return response.text();
})
.then(function (html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = Array.from(
doc.querySelectorAll('#mw-content-text a')
);
articleLinks = links
.map(function (a) {
return {
title: a.textContent.trim(),
href: a.getAttribute('href')
};
})
.filter(function (item) {
if (!item.title || !item.href) {
return false;
}
if (!item.href.startsWith('/wiki/')) {
return false;
}
if (item.href.includes('Category:')) {
return false;
}
if (item.href.includes('Special:')) {
return false;
}
return true;
});
console.log(
'Spotlight links loaded:',
articleLinks.length
);
});
}
function chooseRandomArticle() {
if (!articleLinks.length) {
return null;
}
let chosen;
do {
chosen =
articleLinks[
Math.floor(Math.random() * articleLinks.length)
];
} while (
chosen.href === currentHref &&
articleLinks.length > 1
);
return chosen;
}
function getShortTitle(youtubeUrl) {
const url =
'https://www.youtube.com/oembed' +
'?url=' +
encodeURIComponent(youtubeUrl) +
'&format=json';
return fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'YouTube title HTTP ' +
response.status
);
}
return response.json();
})
.then(function(data) {
return (
data &&
data.title
? data.title.trim()
: ''
);
})
.catch(function(error) {
console.warn(
'Could not load Short title:',
error
);
return '';
});
}
function showRandomArticle() {
const article = chooseRandomArticle();
if (!article) {
return;
}
currentHref = article.href;
const title =
decodeURIComponent(
article.href
.replace(/^\/wiki\//, '')
.replace(/_/g, ' ')
);
const apiUrl =
mw.util.wikiScript('api') +
'?action=query' +
'&format=json' +
'&prop=extracts' +
'&exintro=1' +
'&explaintext=1' +
'&exchars=260' +
'&titles=' +
encodeURIComponent(title);
fetch(apiUrl)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (!data.query || !data.query.pages) {
return;
}
const page = Object.values(data.query.pages)[0];
/* Do not display pages without a real extract */
if (
!page ||
!page.extract ||
page.extract.trim().length < 40
) {
showRandomArticle();
return;
}
const extract = page.extract.trim();
/* Fetch is finished. NOW begin the fade. */
discoverBox.classList.add('is-changing');
window.setTimeout(function () {
discoverBox.innerHTML =
'<a class="vh-random-article-link" href="' +
article.href +
'">' +
'<div class="vh-random-article-title">' +
escapeHtml(article.title) +
'</div>' +
'<div class="vh-random-article-extract">' +
escapeHtml(extract) +
'</div>' +
'<div class="vh-random-article-more">' +
'Read article →' +
'</div>' +
'</a>';
requestAnimationFrame(function () {
discoverBox.classList.remove('is-changing');
});
}, 500);
})
.catch(function () {
/* Simply try another article */
showRandomArticle();
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
loadArticleLinks()
.then(function () {
if (!articleLinks.length) {
return;
}
showRandomArticle();
window.setInterval(function () {
showRandomArticle();
}, 11000);
})
.catch(function (error) {
console.error(
'Spotlight failed:',
error
);
});
}
}
/* =====================================================
FROM HIS LETTERS
Random Śrīla Prabhupāda letter from Vanisource
===================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const letterBox =
document.getElementById('vh-random-letter');
if (letterBox) {
const vanisourceApi =
'https://vanisource.org/w/api.php';
const rootCategory =
'Category:Letters from Srila Prabhupada - by person';
let recipientCategories = [];
let currentLetter = '';
let letterChanging = false;
/* -------------------------------------------------
LOAD RECIPIENT CATEGORIES
------------------------------------------------- */
function loadLetterRecipients(cmcontinue) {
let url =
vanisourceApi +
'?action=query' +
'&format=json' +
'&list=categorymembers' +
'&cmtitle=' +
encodeURIComponent(rootCategory) +
'&cmtype=subcat' +
'&cmlimit=500';
if (cmcontinue) {
url +=
'&cmcontinue=' +
encodeURIComponent(cmcontinue);
}
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (
data.query &&
data.query.categorymembers
) {
data.query.categorymembers
.forEach(function (item) {
if (
item.title &&
item.title.endsWith(
' - Letters'
)
) {
recipientCategories.push(
item.title
);
}
});
}
/* Continue if more than 500 */
if (
data.continue &&
data.continue.cmcontinue
) {
return loadLetterRecipients(
data.continue.cmcontinue
);
}
});
}
/* -------------------------------------------------
CHOOSE RANDOM RECIPIENT
------------------------------------------------- */
function chooseRecipient() {
if (!recipientCategories.length) {
return null;
}
return recipientCategories[
Math.floor(
Math.random() *
recipientCategories.length
)
];
}
/* -------------------------------------------------
GET LETTERS FOR THAT RECIPIENT
------------------------------------------------- */
function getRandomLetter(category) {
const url =
vanisourceApi +
'?action=query' +
'&format=json' +
'&list=categorymembers' +
'&cmtitle=' +
encodeURIComponent(category) +
'&cmnamespace=0' +
'&cmtype=page' +
'&cmlimit=500';
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (
!data.query ||
!data.query.categorymembers ||
!data.query.categorymembers.length
) {
return null;
}
const letters =
data.query.categorymembers;
let letter;
do {
letter =
letters[
Math.floor(
Math.random() *
letters.length
)
];
} while (
currentLetter &&
letter.title === currentLetter &&
letters.length > 1
);
return letter;
});
}
/* -------------------------------------------------
RECIPIENT NAME
------------------------------------------------- */
function getRecipientName(category) {
return category
.replace(/^Category:/, '')
.replace(/ - Letters$/, '')
.trim();
}
/* -------------------------------------------------
TRY TO EXTRACT DATE FROM LETTER PAGE TITLE
Supports dates at beginning such as:
690101
740516
etc.
------------------------------------------------- */
function getLetterDate(title) {
const match =
title.match(
/(?:^|\/)(\d{2})(\d{2})(\d{2})(?:\D|$)/
);
if (!match) {
return '';
}
const yy =
parseInt(match[1], 10);
const month =
parseInt(match[2], 10);
const day =
parseInt(match[3], 10);
if (
month < 1 ||
month > 12 ||
day < 1 ||
day > 31
) {
return '';
}
const year =
1900 + yy;
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
return (
day +
' ' +
months[month - 1] +
' ' +
year
);
}
/* -------------------------------------------------
GET ACTUAL LETTER CONTENT
------------------------------------------------- */
function getLetterPassage(letterTitle) {
const url =
vanisourceApi +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(letterTitle) +
'&prop=text';
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (
!data.parse ||
!data.parse.text ||
!data.parse.text['*']
) {
return '';
}
const wrapper =
document.createElement('div');
wrapper.innerHTML =
data.parse.text['*'];
/*
* Remove things which definitely are
* not part of the actual letter.
*/
wrapper.querySelectorAll(
'table, .navbox, .toc, .mw-editsection, ' +
'.metadata, .noprint, style, script'
).forEach(function (element) {
element.remove();
});
/*
* Collect useful paragraphs.
*/
let paragraphs =
Array.from(
wrapper.querySelectorAll('p')
)
.map(function (p) {
return p.textContent
.replace(/\s+/g, ' ')
.trim();
})
.filter(function (text) {
if (text.length < 120) {
return false;
}
const lower = text.toLowerCase();
/* Standard opening/closing formalities */
if (
lower.startsWith('dear ') ||
lower.startsWith('my dear ') ||
lower.includes('please accept my blessings') ||
lower.includes('please accept my humble') ||
lower.includes('i beg to acknowledge receipt') ||
lower.includes('i am in due receipt') ||
lower.includes('your ever well-wisher') ||
lower.includes('hoping this will meet you') ||
lower.includes('yours in the service') ||
lower.includes('your servant')
) {
return false;
}
/* Addresses / letter headers */
if (
lower.includes('iskcon') ||
lower.includes('international society for krishna consciousness') ||
lower.includes('founder-acarya') ||
lower.includes('founder acharya') ||
lower.includes('a.c. bhaktivedanta swami') ||
lower.includes('tridandi goswami') ||
lower.includes('tridandi gosvami') ||
lower.includes('bhaktivedanta manor') ||
lower.includes('hare krishna land')
) {
return false;
}
/* Looks too much like an address */
const addressWords = [
'road',
'street',
'avenue',
'building',
'apartment',
'bombay',
'calcutta',
'los angeles',
'new york',
'london'
];
let addressHits = 0;
addressWords.forEach(function (word) {
if (lower.includes(word)) {
addressHits++;
}
});
if (addressHits >= 2) {
return false;
}
/*
* Require something resembling actual prose:
* at least two sentence endings.
*/
const sentences =
(text.match(/[.!?](?:\s|$)/g) || []).length;
if (sentences < 2) {
return false;
}
return true;
});
if (!paragraphs.length) {
return '';
}
/*
* Prefer reasonably substantial passages.
*/
let preferred =
paragraphs.filter(function (text) {
return (
text.length >= 180 &&
text.length <= 400
);
});
if (!preferred.length) {
preferred = paragraphs;
}
/*
* Random meaningful paragraph from
* within the letter.
*/
let passage =
preferred[
Math.floor(
Math.random() *
preferred.length
)
];
/*
* Prevent enormous passage in narrow
* left column.
*/
if (passage.length > 300) {
passage =
passage.substring(0, 300);
const lastSpace =
passage.lastIndexOf(' ');
if (lastSpace > 250) {
passage =
passage.substring(
0,
lastSpace
);
}
passage += '…';
}
return passage;
});
}
/* -------------------------------------------------
SAFE HTML
------------------------------------------------- */
function escapeLetterHtml(text) {
const div =
document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/* -------------------------------------------------
BUILD VANISOURCE URL
------------------------------------------------- */
function getLetterUrl(title) {
return (
'https://vanisource.org/wiki/' +
encodeURIComponent(
title.replace(/ /g, '_')
)
.replace(/%2F/g, '/')
.replace(/%3A/g, ':')
);
}
/* -------------------------------------------------
FIND A VALID RANDOM LETTER
------------------------------------------------- */
function prepareRandomLetter(attempt) {
attempt = attempt || 0;
/*
* Prevent an endless retry loop if Vanisource
* unexpectedly returns unusable pages.
*/
if (attempt > 15) {
return Promise.resolve(null);
}
const category =
chooseRecipient();
if (!category) {
return Promise.resolve(null);
}
return getRandomLetter(category)
.then(function (letter) {
if (!letter || !letter.title) {
return prepareRandomLetter(
attempt + 1
);
}
return getLetterPassage(
letter.title
)
.then(function (passage) {
if (
!passage ||
passage.length < 80
) {
return prepareRandomLetter(
attempt + 1
);
}
return {
title:
letter.title,
recipient:
getRecipientName(
category
),
date:
getLetterDate(
letter.title
),
passage:
passage,
href:
getLetterUrl(
letter.title
)
};
});
});
}
/* -------------------------------------------------
DISPLAY LETTER
------------------------------------------------- */
function showRandomLetter() {
if (letterChanging) {
return;
}
letterChanging = true;
/*
* Prepare the next letter while the current
* one remains fully visible.
*/
prepareRandomLetter(0)
.then(function (letter) {
if (!letter) {
letterChanging = false;
return;
}
currentLetter =
letter.title;
/*
* Only start fading AFTER the new
* content is ready.
*/
letterBox.classList.add(
'is-changing'
);
window.setTimeout(
function () {
let meta = '';
if (letter.date) {
meta =
'<div class="vh-random-letter-date">' +
escapeLetterHtml(
letter.date
) +
'</div>';
}
letterBox.innerHTML =
'<a class="vh-random-letter-link" ' +
'href="' +
letter.href +
'">' +
'<div class="vh-random-letter-person">' +
'To ' +
escapeLetterHtml(
letter.recipient
) +
'</div>' +
meta +
'<div class="vh-random-letter-text">' +
escapeLetterHtml(
letter.passage
) +
'</div>' +
'<div class="vh-random-letter-more">' +
'Read letter →' +
'</div>' +
'</a>';
requestAnimationFrame(
function () {
letterBox.classList.remove(
'is-changing'
);
letterChanging = false;
}
);
},
450
);
})
.catch(function (error) {
console.error(
'Letter feature failed:',
error
);
letterChanging = false;
});
}
/* -------------------------------------------------
START
------------------------------------------------- */
loadLetterRecipients()
.then(function () {
if (!recipientCategories.length) {
console.warn(
'No Vanisource letter recipients found.'
);
return;
}
console.log(
'Letter recipient categories loaded:',
recipientCategories.length
);
/*
* First letter immediately.
*/
showRandomLetter();
/*
* New letter every 10 seconds.
*/
window.setInterval(
showRandomLetter,
10000
);
})
.catch(function (error) {
console.error(
'Could not load Vanisource letters:',
error
);
});
}
}
/* ==========================================================
VIRTUAL CONTINUOUS ŚRĪLA PRABHUPĀDA TIMELINE
Includes 1896–1964 + 1965–1977
========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const timelineWindow =
document.querySelector('.vh-lifeline-window');
const timelineTrack =
document.getElementById('vh-timeline-track');
const yearButtons =
document.querySelectorAll(
'#vh-timeline-years .vh-year'
);
if (timelineWindow && timelineTrack) {
const API =
'https://vanipedia.org/w/api.php';
const ITEM_WIDTH = 220;
const AUTO_SPEED = 52;
const VISIBLE_ITEMS = 22;
let events = [];
let nodes = [];
let firstIndex = 0;
let offsetX = 0;
let dragging = false;
let dragMoved = false;
let pointerStartX = 0;
let previousPointerX = 0;
let lastFrame =
performance.now();
let animationId = null;
/* ==================================================
TEXT HELPERS
================================================== */
function cleanText(text) {
return text
.replace(/\s+/g, ' ')
.replace(/^[\s\-–—:,]+/, '')
.trim();
}
function shortenText(text) {
text =
cleanText(text);
if (text.length <= 95) {
return text;
}
let result =
text.substring(0, 95);
const space =
result.lastIndexOf(' ');
if (space > 70) {
result =
result.substring(
0,
space
);
}
return result + '…';
}
/* ==================================================
DEFAULT PAGE LINK
================================================== */
function makePageUrl(page) {
return (
'https://vanipedia.org/wiki/' +
encodeURIComponent(
page.replace(/ /g, '_')
)
.replace(/%2F/g, '/')
.replace(/%3A/g, ':')
);
}
/* ==================================================
FIND BEST LINK INSIDE ENTRY
================================================== */
function getUsefulLink(
element,
fallback
) {
const usefulLink =
Array
.from(
element.querySelectorAll('a')
)
.find(
function (a) {
const h =
a.getAttribute(
'href'
);
return (
h &&
!h.includes(
'action=edit'
) &&
!h.includes(
'redlink=1'
)
);
}
);
if (!usefulLink) {
return fallback;
}
const rawHref =
usefulLink.getAttribute(
'href'
);
if (!rawHref) {
return fallback;
}
try {
return new URL(
rawHref,
'https://vanipedia.org'
).href;
} catch (e) {
return fallback;
}
}
/* ==================================================
FETCH 1896–1964
Main timeline page
================================================== */
function fetchEarlyTimeline() {
const page =
"Timeline of Srila Prabhupada's Life";
const url =
API +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(page) +
'&prop=text';
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (
!data.parse ||
!data.parse.text ||
!data.parse.text['*']
) {
return [];
}
const temp =
document.createElement('div');
temp.innerHTML =
data.parse.text['*'];
const result = [];
temp
.querySelectorAll('li')
.forEach(function (li) {
const fullText =
cleanText(
li.textContent
);
/*
* Expected examples:
*
* 1896 - September 01 - Calcutta - ...
* 1922 - Meets ...
* 1944 - February 14 - Publishes ...
* 1959 - September 17 - ...
*
* We only keep 1896–1964.
*/
const match =
fullText.match(
/^((?:18|19)\d{2})\s*[-–—]\s*(.+)$/i
);
if (!match) {
return;
}
const year =
parseInt(
match[1],
10
);
if (
year < 1896 ||
year > 1964
) {
return;
}
let remainder =
cleanText(
match[2]
);
if (!remainder) {
return;
}
/*
* Try to extract a date
* from the remainder.
*/
let displayDate =
String(year);
let description =
remainder;
const monthMatch =
remainder.match(
/^(January|February|March|April|May|June|July|August|September|October|November|December)(?:\s+(\d{1,2}))?\s*[-–—]\s*(.+)$/i
);
if (monthMatch) {
const month =
monthMatch[1];
const day =
monthMatch[2]
? monthMatch[2]
: '';
description =
cleanText(
monthMatch[3]
);
displayDate =
month;
if (day) {
displayDate +=
' ' +
day;
}
displayDate +=
', ' +
year;
} else {
/*
* If no month is present,
* show year only.
*/
description =
remainder;
}
description =
shortenText(
description
);
if (!description) {
return;
}
const fallback =
makePageUrl(page);
const href =
getUsefulLink(
li,
fallback
);
result.push({
year:
year,
group:
'1896-1964',
date:
displayDate,
text:
description,
href:
href
});
});
return result;
});
}
/* ==================================================
FETCH ONE YEAR: 1965–1977
================================================== */
function fetchYear(year) {
const page =
"Timeline of Srila Prabhupada's Life - " +
year;
const url =
API +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(page) +
'&prop=text';
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (
!data.parse ||
!data.parse.text ||
!data.parse.text['*']
) {
return [];
}
const temp =
document.createElement('div');
temp.innerHTML =
data.parse.text['*'];
const result = [];
temp
.querySelectorAll('li')
.forEach(function (li) {
const fullText =
cleanText(
li.textContent
);
const match =
fullText.match(
/^(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})(?:,?\s+([0-9]{1,2}(?::[0-9]{2})?(?:\s*[AP]\.?M\.?)?))?\s*[-–—]\s*(.+)$/i
);
if (!match) {
return;
}
const month =
match[1];
const day =
match[2];
const time =
match[3]
? match[3].trim()
: '';
const description =
shortenText(
match[4]
);
if (!description) {
return;
}
const fallback =
makePageUrl(page);
const href =
getUsefulLink(
li,
fallback
);
let date =
month +
' ' +
day +
', ' +
year;
if (time) {
date +=
' · ' +
time;
}
result.push({
year:
year,
group:
String(year),
date:
date,
text:
description,
href:
href
});
});
return result;
});
}
/* ==================================================
CREATE NODE
================================================== */
function createNode() {
const link =
document.createElement('a');
link.className =
'vh-timeline-item';
link.draggable =
false;
const date =
document.createElement('div');
date.className =
'vh-timeline-date';
const dot =
document.createElement('span');
dot.className =
'vh-timeline-dot';
const text =
document.createElement('div');
text.className =
'vh-timeline-text';
link.appendChild(date);
link.appendChild(dot);
link.appendChild(text);
link.addEventListener(
'click',
function (event) {
if (dragMoved) {
event.preventDefault();
}
}
);
return link;
}
/* ==================================================
POPULATE NODE
================================================== */
function populateNode(
node,
eventIndex
) {
const total =
events.length;
eventIndex =
(
(
eventIndex %
total
) +
total
) %
total;
const item =
events[eventIndex];
node.href =
item.href;
node.dataset.year =
item.year;
node.dataset.group =
item.group;
node
.querySelector(
'.vh-timeline-date'
)
.textContent =
item.date;
node
.querySelector(
'.vh-timeline-text'
)
.textContent =
item.text;
}
/* ==================================================
BUILD VIRTUAL STRIP
================================================== */
function buildVirtualTimeline() {
timelineTrack.innerHTML = '';
nodes = [];
const needed =
Math.max(
VISIBLE_ITEMS,
Math.ceil(
timelineWindow.clientWidth /
ITEM_WIDTH
) + 6
);
for (
let i = 0;
i < needed;
i++
) {
const node =
createNode();
populateNode(
node,
firstIndex + i
);
timelineTrack.appendChild(
node
);
nodes.push(
node
);
}
timelineTrack.style.width =
(
needed *
ITEM_WIDTH
) +
'px';
offsetX =
-ITEM_WIDTH;
firstIndex =
(
firstIndex -
1 +
events.length
) %
events.length;
refreshAllNodes();
draw();
}
function refreshAllNodes() {
nodes.forEach(
function (
node,
position
) {
populateNode(
node,
firstIndex +
position
);
}
);
}
/* ==================================================
RECYCLE LEFT
================================================== */
function recycleLeft() {
const node =
nodes.shift();
nodes.push(
node
);
timelineTrack.appendChild(
node
);
firstIndex =
(
firstIndex +
1
) %
events.length;
populateNode(
node,
firstIndex +
nodes.length -
1
);
offsetX +=
ITEM_WIDTH;
}
/* ==================================================
RECYCLE RIGHT
================================================== */
function recycleRight() {
const node =
nodes.pop();
firstIndex =
(
firstIndex -
1 +
events.length
) %
events.length;
nodes.unshift(
node
);
timelineTrack.insertBefore(
node,
timelineTrack.firstChild
);
populateNode(
node,
firstIndex
);
offsetX -=
ITEM_WIDTH;
}
function normalizePosition() {
while (
offsetX <=
-ITEM_WIDTH * 2
) {
recycleLeft();
}
while (
offsetX > 0
) {
recycleRight();
}
}
/* ==================================================
DRAW
================================================== */
function draw() {
timelineTrack.style.transform =
'translate3d(' +
offsetX +
'px,0,0)';
}
/* ==================================================
ACTIVE YEAR BUTTON
================================================== */
function setActiveButton(group) {
yearButtons.forEach(
function (button) {
const value =
button.classList.contains(
'vh-year-early'
)
? '1896-1964'
: button.textContent.trim();
button.classList.toggle(
'is-active',
value === group
);
}
);
}
/* ==================================================
JUMP TO YEAR / PERIOD
================================================== */
function jumpToGroup(group) {
if (!events.length) {
return;
}
let targetIndex;
if (group === '1896-1964') {
targetIndex =
events.findIndex(
function (item) {
return (
item.group ===
'1896-1964'
);
}
);
} else {
const year =
parseInt(
group,
10
);
targetIndex =
events.findIndex(
function (item) {
return (
item.year ===
year
);
}
);
}
if (targetIndex === -1) {
return;
}
/*
* Put target near left side,
* with one virtual buffer item before it.
*/
firstIndex =
(
targetIndex -
1 +
events.length
) %
events.length;
offsetX =
-ITEM_WIDTH;
refreshAllNodes();
draw();
setActiveButton(
group
);
}
/* ==================================================
YEAR NAVIGATION
================================================== */
yearButtons.forEach(
function (button) {
button.addEventListener(
'click',
function () {
const value =
button.classList.contains(
'vh-year-early'
)
? '1896-1964'
: button.textContent.trim();
jumpToGroup(value);
}
);
}
);
/* ==================================================
ANIMATION
================================================== */
function animate(now) {
let elapsed =
(
now -
lastFrame
) /
1000;
lastFrame =
now;
if (elapsed > 0.05) {
elapsed =
0.05;
}
if (!dragging) {
offsetX -=
AUTO_SPEED *
elapsed;
normalizePosition();
draw();
}
animationId =
requestAnimationFrame(
animate
);
}
/* ==================================================
DRAGGING
================================================== */
timelineWindow.addEventListener(
'pointerdown',
function (event) {
if (
event.pointerType ===
'mouse' &&
event.button !== 0
) {
return;
}
dragging =
true;
dragMoved =
false;
pointerStartX =
event.clientX;
previousPointerX =
event.clientX;
timelineWindow.classList.add(
'is-dragging'
);
timelineWindow.setPointerCapture(
event.pointerId
);
event.preventDefault();
}
);
timelineWindow.addEventListener(
'pointermove',
function (event) {
if (!dragging) {
return;
}
const currentX =
event.clientX;
const difference =
currentX -
previousPointerX;
previousPointerX =
currentX;
if (
Math.abs(
currentX -
pointerStartX
) > 4
) {
dragMoved =
true;
}
offsetX +=
difference;
normalizePosition();
draw();
event.preventDefault();
}
);
function endDrag(event) {
if (!dragging) {
return;
}
dragging =
false;
timelineWindow.classList.remove(
'is-dragging'
);
try {
if (
timelineWindow.hasPointerCapture(
event.pointerId
)
) {
timelineWindow.releasePointerCapture(
event.pointerId
);
}
} catch (e) {}
if (dragMoved) {
window.setTimeout(
function () {
dragMoved =
false;
},
180
);
}
}
timelineWindow.addEventListener(
'pointerup',
endDrag
);
timelineWindow.addEventListener(
'pointercancel',
endDrag
);
timelineWindow.addEventListener(
'dragstart',
function (event) {
event.preventDefault();
}
);
/* ==================================================
RESIZE
================================================== */
let resizeTimer;
window.addEventListener(
'resize',
function () {
clearTimeout(
resizeTimer
);
resizeTimer =
setTimeout(
function () {
if (
events.length
) {
buildVirtualTimeline();
}
},
150
);
}
);
/* ==================================================
LOAD ALL TIMELINE DATA
================================================== */
const requests = [];
/*
* First: 1896–1964
*/
requests.push(
fetchEarlyTimeline()
);
/*
* Then: 1965–1977
*/
for (
let year = 1965;
year <= 1977;
year++
) {
requests.push(
fetchYear(year)
);
}
Promise
.all(requests)
.then(
function (results) {
events =
results.flat();
/*
* Ensure chronological order
* between groups.
*/
events.sort(
function (a, b) {
return (
a.year -
b.year
);
}
);
console.log(
'TIMELINE EVENTS:',
events.length
);
console.log(
'EARLY EVENTS:',
events.filter(
function (item) {
return (
item.group ===
'1896-1964'
);
}
).length
);
if (!events.length) {
console.warn(
'No timeline events found.'
);
return;
}
/*
* Start with 1965.
*/
const startIndex =
events.findIndex(
function (item) {
return (
item.year ===
1965
);
}
);
firstIndex =
startIndex >= 0
? startIndex
: 0;
buildVirtualTimeline();
setActiveButton(
'1965'
);
lastFrame =
performance.now();
animationId =
requestAnimationFrame(
animate
);
}
)
.catch(
function (error) {
console.error(
'Timeline loading failed:',
error
);
}
);
}
}
/* =========================================================
RANDOM NECTAR DROP — HOMEPAGE QUOTE
========================================================= */
if (mw.config.get('wgPageName') === 'Main_Page') {
const quoteWrap =
document.getElementById('vh-random-quote-wrap');
const quoteText =
document.getElementById('vh-random-quote');
const quoteSource =
document.getElementById('vh-random-quote-source');
if (quoteWrap && quoteText && quoteSource) {
const API =
'https://vanipedia.org/w/api.php';
const CATEGORY =
'Category:Nectar Drops from Srila Prabhupada';
let nectarPages = [];
let quoteChanging = false;
let currentPage = '';
/* -----------------------------------------
LOAD ALL NECTAR DROP PAGES
----------------------------------------- */
function loadNectarPages(cmcontinue) {
let url =
API +
'?action=query' +
'&format=json' +
'&list=categorymembers' +
'&cmtitle=' +
encodeURIComponent(CATEGORY) +
'&cmnamespace=0' +
'&cmlimit=max';
if (cmcontinue) {
url +=
'&cmcontinue=' +
encodeURIComponent(cmcontinue);
}
return fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'HTTP ' + response.status
);
}
return response.json();
})
.then(function(data) {
if (
data.query &&
data.query.categorymembers
) {
nectarPages =
nectarPages.concat(
data.query.categorymembers
);
}
if (
data.continue &&
data.continue.cmcontinue
) {
return loadNectarPages(
data.continue.cmcontinue
);
}
console.log(
'Nectar Drop pages loaded:',
nectarPages.length
);
});
}
/* -----------------------------------------
GET RANDOM NECTAR DROP
----------------------------------------- */
function getRandomNectarDrop() {
if (
quoteChanging ||
!nectarPages.length
) {
return;
}
quoteChanging = true;
let page;
for (
let attempt = 0;
attempt < 20;
attempt++
) {
const candidate =
nectarPages[
Math.floor(
Math.random() *
nectarPages.length
)
];
if (
candidate &&
candidate.title !== currentPage
) {
page = candidate;
break;
}
}
if (!page) {
quoteChanging = false;
return;
}
/* -------------------------------------
GET RAW WIKITEXT
------------------------------------- */
const url =
API +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(page.title) +
'&prop=wikitext';
fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'HTTP ' +
response.status
);
}
return response.json();
})
.then(function(data) {
if (
!data.parse ||
!data.parse.wikitext ||
!data.parse.wikitext['*']
) {
throw new Error(
'No wikitext returned.'
);
}
const wikitext =
data.parse.wikitext['*'];
/* ---------------------------------
FIND AUDIOBOX TEMPLATE
--------------------------------- */
const start =
wikitext.indexOf(
'{{Audiobox_NDrops|'
);
if (start === -1) {
quoteChanging = false;
window.setTimeout(
getRandomNectarDrop,
250
);
return;
}
const end =
wikitext.indexOf(
'}}',
start
);
if (end === -1) {
quoteChanging = false;
window.setTimeout(
getRandomNectarDrop,
250
);
return;
}
const template =
wikitext.substring(
start +
'{{Audiobox_NDrops|'.length,
end
);
/* ---------------------------------
SPLIT TEMPLATE PARAMETERS
--------------------------------- */
const parts =
template.split('|');
if (parts.length < 5) {
quoteChanging = false;
window.setTimeout(
getRandomNectarDrop,
250
);
return;
}
/*
* Parameter structure:
*
* 0 = Nectar Drops from...
* 1 = mp3 player
* 2 = QUOTE
* 3 = Vanisource page
* 4 = source label
*/
let quote =
parts[2]
.trim()
.replace(
/^["“]+/,
''
)
.replace(
/["”]+$/,
''
)
.trim();
const vanisourcePage =
parts[3]
.trim()
/* remove wiki-link brackets */
.replace(/^\[\[/, '')
.replace(/\]\]$/, '')
/* if link contains |display text, keep only target */
.split('|')[0]
/* remove namespace prefix */
.replace(/^Vanisource:/i, '')
/* remove any remaining stray brackets/braces */
.replace(/[\[\]\{\}]/g, '')
.trim();
const sourceLabel =
parts[4]
.trim();
if (!quote) {
quoteChanging = false;
window.setTimeout(
getRandomNectarDrop,
250
);
return;
}
/* ---------------------------------
CLEAN SIMPLE WIKITEXT
--------------------------------- */
quote =
quote
.replace(
/'''?/g,
''
)
.replace(
/\[\[([^|\]]+)\|([^\]]+)\]\]/g,
'$2'
)
.replace(
/\[\[([^\]]+)\]\]/g,
'$1'
)
.replace(
/\s+/g,
' '
)
.trim();
/* ---------------------------------
SOURCE LINK
--------------------------------- */
const href =
'https://vanisource.org/wiki/' +
vanisourcePage
.replace(/ /g, '_')
.split('/')
.map(encodeURIComponent)
.join('/');
/* ---------------------------------
DISPLAY
--------------------------------- */
currentPage = page.title;
quoteText.classList.add(
'is-changing'
);
quoteSource.classList.add(
'is-changing'
);
window.setTimeout(
function() {
const cleanQuote =
quote
.replace(
/\s*\(?\[\[Vanisource:.*$/i,
''
)
.trim();
quoteText.textContent =
cleanQuote;
quoteSource.innerHTML =
'';
const link =
document.createElement('a');
link.href =
href;
let cleanSource = page.title
.replace(/^\d{6}[a-z]?\s*-\s*/i, '')
.replace(
/\s*-\s*Srila Prabhupada Speaks a Nectar Drop in\s*/i,
', '
)
.trim();
link.textContent =
cleanSource;
quoteSource.appendChild(
link
);
/*
* Reveal only after the first
* random quote is ready.
*/
quoteWrap.style.visibility =
'visible';
requestAnimationFrame(
function() {
quoteText.classList.remove(
'is-changing'
);
quoteSource.classList.remove(
'is-changing'
);
quoteChanging = false;
}
);
},
450
);
})
.catch(function(error) {
console.error(
'Random Nectar Drop failed:',
error
);
quoteChanging = false;
});
}
/* -----------------------------------------
START
----------------------------------------- */
loadNectarPages()
.then(function() {
if (nectarPages.length) {
getRandomNectarDrop();
window.setInterval(
getRandomNectarDrop,
9000
);
}
})
.catch(function(error) {
console.error(
'Nectar Drop category failed:',
error
);
});
}
}
/* Vani Treasure (Featured) */
/* ==========================================================
HOMEPAGE — RANDOM SHORT
========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const shortCard =
document.getElementById('vh-random-short');
if (shortCard) {
const API =
'https://vanipedia.org/w/api.php';
const SHORTS_PAGE =
'VanimediaMayapur YouTube SHORTS List';
let shortItems = [];
let currentShort = -1;
/* --------------------------------------------------
LOAD SHORTS LIST
-------------------------------------------------- */
function loadShorts() {
const url =
API +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(SHORTS_PAGE) +
'&prop=wikitext';
fetch(url)
.then(function(response) {
return response.json();
})
.then(function(data) {
if (
!data.parse ||
!data.parse.wikitext ||
!data.parse.wikitext['*']
) {
throw new Error(
'Shorts wikitext not found'
);
}
const wiki =
data.parse.wikitext['*'];
/*
* Example:
*
* File:Short_1139.png
* |1139 - 21 August
* |link=https://www.youtube.com/shorts/_Wumpw8SAYs
*/
const regex =
/File:(Short_(\d+)\.(?:png|jpg|jpeg|webp))\s*\|\s*(\d+)\s*-\s*([^|\n]+?)\s*\|\s*link=(https:\/\/www\.youtube\.com\/shorts\/[^\s|\n]+)/gi;
let match;
while (
(match = regex.exec(wiki)) !== null
) {
shortItems.push({
file: match[1],
number: match[3],
date: match[4].trim(),
youtube: match[5].trim()
});
}
console.log(
'Homepage Shorts loaded:',
shortItems.length
);
if (shortItems.length) {
showRandomShort();
}
})
.catch(function(error) {
console.error(
'Homepage Shorts failed:',
error
);
});
}
/* --------------------------------------------------
GET ACTUAL THUMBNAIL URL
-------------------------------------------------- */
function getShortThumbnail(fileName) {
const url =
API +
'?action=query' +
'&format=json' +
'&prop=imageinfo' +
'&iiprop=url' +
'&iiurlwidth=400' +
'&titles=' +
encodeURIComponent(
'File:' + fileName
);
return fetch(url)
.then(function(response) {
return response.json();
})
.then(function(data) {
if (
!data.query ||
!data.query.pages
) {
return '';
}
const pages =
Object.values(
data.query.pages
);
if (
!pages.length ||
!pages[0].imageinfo ||
!pages[0].imageinfo.length
) {
return '';
}
const info =
pages[0].imageinfo[0];
return (
info.thumburl ||
info.url ||
''
);
});
}
/* --------------------------------------------------
DISPLAY RANDOM SHORT
-------------------------------------------------- */
function showRandomShort() {
if (!shortItems.length) {
return;
}
let index;
do {
index =
Math.floor(
Math.random() *
shortItems.length
);
}
while (
shortItems.length > 1 &&
index === currentShort
);
currentShort = index;
const item =
shortItems[index];
const media =
shortCard.querySelector(
'.vh-feature-media'
);
const copy =
shortCard.querySelector(
'.vh-feature-copy'
);
if (!media || !copy) {
return;
}
/* ---------- thumbnail ---------- */
getShortThumbnail(item.file)
.then(function(imageUrl) {
if (!imageUrl) {
return;
}
const preload = new Image();
preload.onload = function() {
media.style.backgroundImage =
'url("' + imageUrl + '")';
};
preload.src = imageUrl;
});
/* ---------- text ---------- */
copy.innerHTML = '';
const type =
document.createElement('small');
type.textContent =
'SHORT';
const title =
document.createElement('strong');
title.textContent =
'Loading title…';
copy.appendChild(type);
copy.appendChild(title);
getShortTitle(item.youtube)
.then(function(actualTitle) {
/*
* Make sure this card has not already
* rotated to another Short.
*/
if (
shortItems[currentShort] !== item
) {
return;
}
let cleanTitle = actualTitle
.replace(/\s*-\s*Prabhupada.*$/i, '')
.trim();
title.textContent =
cleanTitle ||
item.number;
});
/* ---------- click ---------- */
shortCard.onclick =
function() {
window.open(
item.youtube,
'_blank',
'noopener'
);
};
}
/* --------------------------------------------------
START
-------------------------------------------------- */
loadShorts();
/*
* Temporary individual rotation.
*
* Later we'll replace this with the common
* staggered controller for all four cards.
*/
window.setInterval(
function() {
if (shortItems.length) {
showRandomShort();
}
},
10500
);
}
}
/* ==========================================================
HOMEPAGE — RANDOM VIDEO
========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const videoCard =
document.getElementById('vh-random-video');
if (videoCard) {
const API =
'https://vanipedia.org/w/api.php';
const CATEGORY =
'Category:1080 English Pages with Videos';
let videoPages = [];
let currentVideo = '';
/* --------------------------------------------------
LOAD VIDEO PAGES
-------------------------------------------------- */
function loadVideoPages(cmcontinue) {
let url =
API +
'?action=query' +
'&format=json' +
'&list=categorymembers' +
'&cmtitle=' +
encodeURIComponent(CATEGORY) +
'&cmnamespace=0' +
'&cmtype=page' +
'&cmlimit=max';
if (cmcontinue) {
url +=
'&cmcontinue=' +
encodeURIComponent(cmcontinue);
}
return fetch(url)
.then(function(response) {
return response.json();
})
.then(function(data) {
if (
data.query &&
data.query.categorymembers
) {
data.query.categorymembers
.forEach(function(page) {
if (
/^Prabhupada \d{4}\s*-/i
.test(page.title)
) {
videoPages.push(page);
}
});
}
if (
data.continue &&
data.continue.cmcontinue
) {
return loadVideoPages(
data.continue.cmcontinue
);
}
});
}
/* --------------------------------------------------
CHOOSE RANDOM VIDEO
-------------------------------------------------- */
function chooseRandomVideoPage() {
if (!videoPages.length) {
return null;
}
let page;
do {
page =
videoPages[
Math.floor(
Math.random() *
videoPages.length
)
];
} while (
videoPages.length > 1 &&
page.title === currentVideo
);
return page;
}
/* --------------------------------------------------
CLEAN TITLE
-------------------------------------------------- */
function getVideoDisplayTitle(pageTitle) {
return pageTitle
.replace(
/^Prabhupada\s+\d{4}\s*-\s*/i,
''
)
.trim();
}
/* --------------------------------------------------
VANIPEDIA PAGE URL
-------------------------------------------------- */
function getVideoPageUrl(pageTitle) {
return (
'https://vanipedia.org/wiki/' +
encodeURIComponent(
pageTitle.replace(/ /g, '_')
)
.replace(/%2F/g, '/')
.replace(/%3A/g, ':')
);
}
/* --------------------------------------------------
GET YOUTUBE ID FROM PAGE WIKITEXT
-------------------------------------------------- */
function loadVideoInfo(page) {
const url =
API +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(page.title) +
'&prop=wikitext';
return fetch(url)
.then(function(response) {
return response.json();
})
.then(function(data) {
if (
!data.parse ||
!data.parse.wikitext ||
!data.parse.wikitext['*']
) {
return null;
}
const wiki =
data.parse.wikitext['*'];
/*
* Example:
* {{youtube_right|rEc1U_lsJ7s|Expand to Ten Million...}}
*/
const match =
wiki.match(
/\{\{youtube_right\s*\|\s*([A-Za-z0-9_-]{11})\s*\|/i
);
const videoId =
match
? match[1]
: '';
const thumbnailUrl =
videoId
? 'https://i.ytimg.com/vi/' +
videoId +
'/hqdefault.jpg'
: '';
return {
pageUrl:
getVideoPageUrl(
page.title
),
videoId:
videoId,
thumbnailUrl:
thumbnailUrl
};
});
}
/* --------------------------------------------------
SHOW RANDOM VIDEO
-------------------------------------------------- */
function showRandomVideo() {
const page =
chooseRandomVideoPage();
if (!page) {
return;
}
currentVideo =
page.title;
const media =
videoCard.querySelector(
'.vh-feature-media'
);
const copy =
videoCard.querySelector(
'.vh-feature-copy'
);
if (!media || !copy) {
return;
}
/* ---------- title ---------- */
copy.innerHTML = '';
const type =
document.createElement('small');
type.textContent =
'VIDEO';
const title =
document.createElement('strong');
title.textContent =
getVideoDisplayTitle(
page.title
);
copy.appendChild(type);
copy.appendChild(title);
/* ---------- thumbnail + click ---------- */
loadVideoInfo(page)
.then(function(info) {
if (
!info ||
currentVideo !== page.title
) {
return;
}
if (info.thumbnailUrl) {
const preload =
new Image();
preload.onload =
function() {
if (
currentVideo ===
page.title
) {
media.style.backgroundImage =
'url("' +
info.thumbnailUrl +
'")';
}
};
preload.src =
info.thumbnailUrl;
}
videoCard.onclick =
function() {
const target =
info.videoId
? 'https://www.youtube.com/watch?v=' +
info.videoId
: info.pageUrl;
window.open(
target,
'_blank',
'noopener'
);
};
})
.catch(function(error) {
console.error(
'Homepage Video failed:',
error
);
});
}
/* --------------------------------------------------
START
-------------------------------------------------- */
loadVideoPages()
.then(function() {
console.log(
'Homepage Video pages loaded:',
videoPages.length
);
if (videoPages.length) {
showRandomVideo();
window.setInterval(
showRandomVideo,
11500
);
}
})
.catch(function(error) {
console.error(
'Homepage Video category failed:',
error
);
});
}
}
/* ==========================================================
HOMEPAGE — RANDOM IMAGE QUOTE
========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const imageQuoteCard =
document.getElementById('vh-random-imagequote');
if (imageQuoteCard) {
const IMAGE_API =
'https://vanimedia.org/w/api.php';
const MONTH_CATEGORIES = [
'Category:January Quotes with Images',
'Category:February Quotes with Images',
'Category:March Quotes with Images',
'Category:April Quotes with Images',
'Category:May Quotes with Images',
'Category:June Quotes with Images',
'Category:July Quotes with Images',
'Category:August Quotes with Images',
'Category:September Quotes with Images',
'Category:October Quotes with Images',
'Category:November Quotes with Images',
'Category:December Quotes with Images'
];
let imageQuoteFiles = [];
let currentImageQuote = '';
/* ======================================================
LOAD ONE MONTH CATEGORY
====================================================== */
function loadImageQuoteCategory(
category,
cmcontinue
) {
let url =
IMAGE_API +
'?action=query' +
'&format=json' +
'&list=categorymembers' +
'&cmtitle=' +
encodeURIComponent(category) +
'&cmnamespace=6' +
'&cmtype=file' +
'&cmlimit=max';
if (cmcontinue) {
url +=
'&cmcontinue=' +
encodeURIComponent(cmcontinue);
}
return fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'Image Quote category HTTP ' +
response.status
);
}
return response.json();
})
.then(function(data) {
if (
data.query &&
data.query.categorymembers
) {
data.query.categorymembers
.forEach(function(file) {
if (file.title) {
imageQuoteFiles.push(
file
);
}
});
}
if (
data.continue &&
data.continue.cmcontinue
) {
return loadImageQuoteCategory(
category,
data.continue.cmcontinue
);
}
});
}
/* ======================================================
LOAD ALL MONTHS
====================================================== */
function loadAllImageQuotes() {
const jobs =
MONTH_CATEGORIES.map(
function(category) {
return loadImageQuoteCategory(
category
);
}
);
return Promise.all(jobs);
}
/* ======================================================
RANDOM FILE
====================================================== */
function chooseRandomImageQuote() {
if (!imageQuoteFiles.length) {
return null;
}
let file;
do {
file =
imageQuoteFiles[
Math.floor(
Math.random() *
imageQuoteFiles.length
)
];
} while (
imageQuoteFiles.length > 1 &&
file.title === currentImageQuote
);
return file;
}
/* ======================================================
GET THUMBNAIL + FULL IMAGE
====================================================== */
function getImageQuoteInfo(file) {
const url =
IMAGE_API +
'?action=query' +
'&format=json' +
'&prop=imageinfo' +
'&iiprop=url' +
'&iiurlwidth=500' +
'&titles=' +
encodeURIComponent(
file.title
);
return fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'Image Quote image HTTP ' +
response.status
);
}
return response.json();
})
.then(function(data) {
if (
!data.query ||
!data.query.pages
) {
return null;
}
const page =
Object.values(
data.query.pages
)[0];
if (
!page ||
!page.imageinfo ||
!page.imageinfo.length
) {
return null;
}
const info =
page.imageinfo[0];
const thumbnailUrl =
info.thumburl ||
info.url ||
'';
const fullUrl =
info.url ||
thumbnailUrl;
const pageUrl =
'https://vanimedia.org/wiki/' +
encodeURIComponent(
file.title.replace(
/ /g,
'_'
)
)
.replace(/%2F/g, '/')
.replace(/%3A/g, ':');
return {
thumbnailUrl:
thumbnailUrl,
fullUrl:
fullUrl,
pageUrl:
pageUrl
};
});
}
/* ======================================================
FILENAME → HUMAN DATE
072866 → July 28, 1966
====================================================== */
function getImageQuoteTitle(
fileTitle
) {
const clean =
fileTitle
.replace(
/^File:/i,
''
)
.replace(
/\.(jpg|jpeg|png|webp)$/i,
''
)
.replace(
/\s*Image-quote$/i,
''
)
.trim();
const match =
clean.match(
/^(\d{2})(\d{2})(\d{2})/
);
if (!match) {
return clean;
}
const month =
parseInt(
match[1],
10
);
const day =
parseInt(
match[2],
10
);
const year =
1900 +
parseInt(
match[3],
10
);
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
if (
month < 1 ||
month > 12 ||
day < 1 ||
day > 31
) {
return clean;
}
return (
months[month - 1] +
' ' +
day +
', ' +
year
);
}
/* ======================================================
LIGHTBOX
====================================================== */
function openImageQuoteLightbox(
imageUrl
) {
let lightbox =
document.getElementById(
'vh-imagequote-lightbox'
);
if (!lightbox) {
lightbox =
document.createElement(
'div'
);
lightbox.id =
'vh-imagequote-lightbox';
lightbox.innerHTML =
'<button ' +
'class="vh-imagequote-lightbox-close" ' +
'type="button" ' +
'aria-label="Close">×</button>' +
'<img ' +
'alt="Śrīla Prabhupāda Image Quote">';
document.body.appendChild(
lightbox
);
lightbox.addEventListener(
'mouseleave',
function() {
lightbox.classList.remove(
'is-open'
);
}
);
lightbox.addEventListener(
'click',
function(event) {
if (
event.target ===
lightbox ||
event.target
.classList
.contains(
'vh-imagequote-lightbox-close'
)
) {
lightbox.classList
.remove(
'is-open'
);
}
}
);
document.addEventListener(
'keydown',
function(event) {
if (
event.key ===
'Escape'
) {
lightbox.classList
.remove(
'is-open'
);
}
}
);
}
const image =
lightbox.querySelector(
'img'
);
image.src =
imageUrl;
const cardRect =
imageQuoteCard.getBoundingClientRect();
lightbox.classList.add(
'is-open'
);
requestAnimationFrame(function() {
const popupRect =
lightbox.getBoundingClientRect();
/*
* Place popup immediately to the
* right of the Image Quote card.
*/
let left =
cardRect.right - 240;
let top =
cardRect.top - 220;
/*
* If there is not enough room
* on the right, place it above.
*/
if (
left +
popupRect.width >
window.innerWidth - 10
) {
left =
cardRect.right -
popupRect.width;
top =
cardRect.top -
popupRect.height -
12;
}
/*
* Keep inside viewport.
*/
left =
Math.max(
10,
Math.min(
left,
window.innerWidth -
popupRect.width -
10
)
);
top =
Math.max(
10,
top
);
lightbox.style.left =
left + 'px';
lightbox.style.top =
top + 'px';
});
}
/* ======================================================
DISPLAY RANDOM IMAGE QUOTE
====================================================== */
function showRandomImageQuote() {
const file =
chooseRandomImageQuote();
if (!file) {
return;
}
currentImageQuote =
file.title;
const media =
imageQuoteCard.querySelector(
'.vh-feature-media'
);
const copy =
imageQuoteCard.querySelector(
'.vh-feature-copy'
);
if (!media || !copy) {
return;
}
/*
* Card text
*/
copy.innerHTML = '';
const type =
document.createElement(
'small'
);
type.textContent =
'IMAGE QUOTE';
const title =
document.createElement(
'strong'
);
title.textContent =
getImageQuoteTitle(
file.title
);
copy.appendChild(
type
);
copy.appendChild(
title
);
/*
* Load image
*/
getImageQuoteInfo(
file
)
.then(function(info) {
if (
!info ||
currentImageQuote !==
file.title
) {
return;
}
if (
info.thumbnailUrl
) {
const preload =
new Image();
preload.onload =
function() {
if (
currentImageQuote ===
file.title
) {
media.style
.backgroundImage =
'url("' +
info.thumbnailUrl +
'")';
}
};
preload.src =
info.thumbnailUrl;
}
/*
* Open full image
* in lightbox
*/
imageQuoteCard.onclick =
function() {
if (
info.fullUrl
) {
openImageQuoteLightbox(
info.fullUrl
);
}
};
})
.catch(function(error) {
console.error(
'Homepage Image Quote failed:',
error
);
});
}
/* ======================================================
START
====================================================== */
loadAllImageQuotes()
.then(function() {
console.log(
'Homepage Image Quotes loaded:',
imageQuoteFiles.length
);
if (
imageQuoteFiles.length
) {
showRandomImageQuote();
window.setInterval(
showRandomImageQuote,
9800
);
}
})
.catch(function(error) {
console.error(
'Homepage Image Quote categories failed:',
error
);
});
}
}
/* ==========================================================
HOMEPAGE — RANDOM NECTAR DROP
========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
const nectarCard =
document.getElementById('vh-random-nectar');
if (nectarCard) {
const NECTAR_API =
'https://vanipedia.org/w/api.php';
const NECTAR_CATEGORY =
'Category:Nectar Drops from Srila Prabhupada';
let nectarPages = [];
let currentNectar = '';
let nectarAudio = null;
let nectarLocked = false;
const audioArea =
nectarCard.querySelector(
'.vh-feature-audio'
);
const audioIcon =
nectarCard.querySelector(
'.vh-feature-audio-icon'
);
const nectarWave =
document.createElement(
'div'
);
nectarWave.className =
'vh-nectar-wave';
nectarWave.innerHTML =
'<span></span>' +
'<span></span>' +
'<span></span>' +
'<span></span>' +
'<span></span>' +
'<span></span>' +
'<span></span>';
audioArea.appendChild(
nectarWave
);
const copy =
nectarCard.querySelector(
'.vh-feature-copy'
);
/* ======================================================
LOAD ALL NECTAR DROP PAGES
====================================================== */
function loadNectarPages(
cmcontinue
) {
let url =
NECTAR_API +
'?action=query' +
'&format=json' +
'&list=categorymembers' +
'&cmtitle=' +
encodeURIComponent(
NECTAR_CATEGORY
) +
'&cmnamespace=0' +
'&cmtype=page' +
'&cmlimit=max';
if (cmcontinue) {
url +=
'&cmcontinue=' +
encodeURIComponent(
cmcontinue
);
}
return fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'Nectar category HTTP ' +
response.status
);
}
return response.json();
})
.then(function(data) {
if (
data.query &&
data.query.categorymembers
) {
data.query.categorymembers
.forEach(function(page) {
if (page.title) {
nectarPages.push(
page
);
}
});
}
if (
data.continue &&
data.continue.cmcontinue
) {
return loadNectarPages(
data.continue.cmcontinue
);
}
});
}
/* ======================================================
CHOOSE RANDOM NECTAR DROP
====================================================== */
function chooseRandomNectar() {
if (!nectarPages.length) {
return null;
}
let page;
do {
page =
nectarPages[
Math.floor(
Math.random() *
nectarPages.length
)
];
} while (
nectarPages.length > 1 &&
page.title === currentNectar
);
return page;
}
/* ======================================================
CREATE SHORT MEANINGFUL TITLE FROM QUOTE
====================================================== */
function getNectarShortText(
quote
) {
if (!quote) {
return 'Śrīla Prabhupāda speaks';
}
let text =
quote
.replace(
/^["“”]+|["“”]+$/g,
''
)
.replace(
/\s+/g,
' '
)
.trim();
/*
* Prefer first sentence when it is
* reasonably short.
*/
const sentenceMatch =
text.match(
/^(.+?[.!?])(?:\s|$)/
);
if (
sentenceMatch &&
sentenceMatch[1].length <= 85
) {
return sentenceMatch[1];
}
/*
* Otherwise take approximately
* the first 70 characters,
* ending at a full word.
*/
if (text.length > 72) {
text =
text.substring(
0,
72
);
const lastSpace =
text.lastIndexOf(
' '
);
if (lastSpace > 45) {
text =
text.substring(
0,
lastSpace
);
}
text += '…';
}
return text;
}
/* ======================================================
GET QUOTE TEXT + AUDIO URL
====================================================== */
function getNectarInfo(
page
) {
const url =
NECTAR_API +
'?action=parse' +
'&format=json' +
'&page=' +
encodeURIComponent(
page.title
) +
'&prop=text';
return fetch(url)
.then(function(response) {
if (!response.ok) {
throw new Error(
'Nectar page HTTP ' +
response.status
);
}
return response.json();
})
.then(function(data) {
if (
!data.parse ||
!data.parse.text ||
!data.parse.text['*']
) {
return null;
}
const wrapper =
document.createElement(
'div'
);
wrapper.innerHTML =
data.parse.text['*'];
/*
* Quote
*/
const quoteElement =
wrapper.querySelector(
'.nectardroptext'
);
const quote =
quoteElement
? quoteElement
.textContent
.replace(
/\s+/g,
' '
)
.trim()
: '';
/*
* MP3 source
*/
const source =
wrapper.querySelector(
'audio source'
);
const audioUrl =
source
? source.getAttribute(
'src'
)
: '';
return {
quote:
quote,
audioUrl:
audioUrl
};
});
}
/* ======================================================
RESET PLAY BUTTON
====================================================== */
function resetNectarPlayButton() {
if (audioIcon) {
audioIcon.textContent =
'▶';
}
}
/* ======================================================
PREPARE AUDIO
====================================================== */
function prepareNectarAudio(
audioUrl
) {
/*
* Dispose of previous audio object.
*/
if (nectarAudio) {
nectarAudio.pause();
nectarAudio.src = '';
nectarAudio = null;
}
resetNectarPlayButton();
if (!audioUrl) {
return;
}
nectarAudio =
new Audio(
audioUrl
);
nectarAudio.preload =
'metadata';
/*
* Audio finished naturally.
*/
/*
* Audio finished naturally.
*/
nectarAudio.addEventListener(
'ended',
function() {
resetNectarPlayButton();
nectarWave.classList.remove(
'is-playing'
);
nectarLocked =
false;
}
);
/*
* Audio starts / resumes.
*/
nectarAudio.addEventListener(
'play',
function() {
if (audioIcon) {
audioIcon.textContent =
'❚❚';
}
nectarWave.classList.add(
'is-playing'
);
}
);
/*
* Audio pauses.
*/
nectarAudio.addEventListener(
'pause',
function() {
if (
nectarAudio &&
!nectarAudio.ended &&
audioIcon
) {
audioIcon.textContent =
'▶';
}
nectarWave.classList.remove(
'is-playing'
);
}
);
}
/* ======================================================
PLAY / PAUSE
====================================================== */
function toggleNectarAudio(
event
) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
if (!nectarAudio) {
return;
}
/*
* Playing → pause
*/
if (!nectarAudio.paused) {
nectarAudio.pause();
nectarLocked =
false;
return;
}
/*
* Paused → play/resume
*/
nectarLocked =
true;
nectarAudio
.play()
.catch(function(error) {
console.error(
'Nectar Drop audio failed:',
error
);
});
}
/* ======================================================
DISPLAY RANDOM NECTAR DROP
====================================================== */
function showRandomNectar() {
/*
* Never change the card while
* user is listening or has paused it.
*/
if (nectarLocked) {
return;
}
const page =
chooseRandomNectar();
if (!page) {
return;
}
currentNectar =
page.title;
if (!copy) {
return;
}
copy.innerHTML = '';
const type =
document.createElement(
'small'
);
type.textContent =
'NECTAR DROP';
const title =
document.createElement(
'strong'
);
title.textContent =
'';
copy.appendChild(
type
);
copy.appendChild(
title
);
getNectarInfo(
page
)
.then(function(info) {
if (
!info ||
currentNectar !==
page.title
) {
return;
}
/*
* Display meaningful words
* directly from Nectar Drop.
*/
title.textContent =
getNectarShortText(
info.quote
);
/*
* Prepare MP3.
*/
prepareNectarAudio(
info.audioUrl
);
})
.catch(function(error) {
console.error(
'Homepage Nectar Drop failed:',
error
);
title.textContent =
'Śrīla Prabhupāda speaks';
});
}
/* ======================================================
PLAY BUTTON CLICK
====================================================== */
if (audioArea) {
audioArea.addEventListener(
'click',
toggleNectarAudio
);
}
/* ======================================================
START
====================================================== */
loadNectarPages()
.then(function() {
console.log(
'Homepage Nectar Drops loaded:',
nectarPages.length
);
if (
nectarPages.length
) {
showRandomNectar();
window.setInterval(
function() {
if (!nectarLocked) {
showRandomNectar();
}
},
12700
);
}
})
.catch(function(error) {
console.error(
'Homepage Nectar category failed:',
error
);
});
}
}
const networkMission =
document.getElementById('vh-network-mission');
if (networkMission) {
networkMission.addEventListener(
'click',
function() {
window.location.href =
mw.util.getUrl('Vanipedia_Vision');
}
);
}
/* CLOSE HOMEPAGE WRAPPER */
})();
/* =========================================================
VANIPEDIA FLIPBOOK POPUP
========================================================= */
(function () {
function createFlipbookOverlay() {
if (document.getElementById('vani-flipbook-overlay')) {
return;
}
var overlay = document.createElement('div');
overlay.id = 'vani-flipbook-overlay';
var modal = document.createElement('div');
modal.className = 'vani-flipbook-modal';
var closeButton = document.createElement('button');
closeButton.className = 'vani-flipbook-close';
closeButton.type = 'button';
closeButton.setAttribute('aria-label', 'Close flipbook');
closeButton.innerHTML = '×';
var frameHolder = document.createElement('div');
frameHolder.id = 'vani-flipbook-frame-holder';
modal.appendChild(closeButton);
modal.appendChild(frameHolder);
overlay.appendChild(modal);
document.body.appendChild(overlay);
closeButton.addEventListener('click', function () {
closeFlipbook();
});
overlay.addEventListener('click', function (event) {
if (event.target === overlay) {
closeFlipbook();
}
});
document.addEventListener('keydown', function (event) {
if (
event.key === 'Escape' &&
overlay.classList.contains('is-open')
) {
closeFlipbook();
}
});
}
function openFlipbook(book) {
createFlipbookOverlay();
var overlay =
document.getElementById('vani-flipbook-overlay');
var holder =
document.getElementById('vani-flipbook-frame-holder');
/*
* FIRST make popup visible.
*/
overlay.classList.add('is-open');
document.body.classList.add('vani-flipbook-open');
/*
* Remove any previous iframe.
*/
holder.innerHTML = '';
/*
* Wait until browser has calculated
* the real popup dimensions.
*/
requestAnimationFrame(function () {
requestAnimationFrame(function () {
var frame =
document.createElement('iframe');
frame.id =
'vani-flipbook-frame';
frame.title =
'Vanipedia Flipbook';
frame.setAttribute(
'allowfullscreen',
'true'
);
frame.src =
'https://dev.vanipedia.org/flipbook/?book=' +
encodeURIComponent(book);
holder.appendChild(frame);
});
});
}
function closeFlipbook() {
var overlay =
document.getElementById('vani-flipbook-overlay');
var holder =
document.getElementById('vani-flipbook-frame-holder');
if (!overlay) {
return;
}
overlay.classList.remove('is-open');
document.body.classList.remove('vani-flipbook-open');
if (holder) {
holder.innerHTML = '';
}
}
function initialiseFlipbooks() {
var triggers =
document.querySelectorAll(
'.vani-flipbook-trigger'
);
triggers.forEach(function (trigger) {
if (
trigger.getAttribute(
'data-flipbook-ready'
) === '1'
) {
return;
}
trigger.setAttribute(
'data-flipbook-ready',
'1'
);
trigger.addEventListener(
'click',
function (event) {
event.preventDefault();
var book =
trigger.getAttribute(
'data-book'
);
if (book) {
openFlipbook(book);
}
}
);
});
}
createFlipbookOverlay();
initialiseFlipbooks();
if (
window.mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(function () {
initialiseFlipbooks();
});
}
})();