More languages
More actions
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. */
//Matomo accurate tracking (no more 0 second visits)
//_paq.push(['enableHeartBeatTimer']);
/**
* Dynamic Navigation Bars. See [[Wikipedia:NavFrame]]
*
* Based on script from en.wikipedia.org, 2008-09-15.
*
* @source www.mediawiki.org/wiki/MediaWiki:Gadget-NavFrame.js
* @maintainer Helder.wiki, 2012–2013
* @maintainer Krinkle, 2013
*/
( function () {
// Set up the words in your language
var collapseCaption = 'hide';
var expandCaption = 'show';
var navigationBarHide = '[' + collapseCaption + ']';
var navigationBarShow = '[' + expandCaption + ']';
/**
* Shows and hides content and picture (if available) of navigation bars.
*
* @param {number} indexNavigationBar The index of navigation bar to be toggled
* @param {jQuery.Event} e Event object
*/
function toggleNavigationBar( indexNavigationBar, e ) {
var navChild,
navToggle = document.getElementById( 'NavToggle' + indexNavigationBar ),
navFrame = document.getElementById( 'NavFrame' + indexNavigationBar );
// Prevent browser from jumping to href "#"
e.preventDefault();
if ( !navFrame || !navToggle ) {
return false;
}
// If shown now
if ( navToggle.firstChild.data === navigationBarHide ) {
for ( navChild = navFrame.firstChild; navChild !== null; navChild = navChild.nextSibling ) {
if ( $( navChild ).hasClass( 'NavContent' ) || $( navChild ).hasClass( 'NavPic' ) ) {
navChild.style.display = 'none';
}
}
navToggle.firstChild.data = navigationBarShow;
// If hidden now
} else if ( navToggle.firstChild.data === navigationBarShow ) {
for ( navChild = navFrame.firstChild; navChild !== null; navChild = navChild.nextSibling ) {
if ( $( navChild ).hasClass( 'NavContent' ) || $( navChild ).hasClass( 'NavPic' ) ) {
navChild.style.display = 'block';
}
}
navToggle.firstChild.data = navigationBarHide;
}
}
/** Adds show/hide-button to navigation bars.
* @param {jQuery} $content
*/
function createNavigationBarToggleButton( $content ) {
var i, j, navChild, navToggle, navToggleText, isCollapsed,
indexNavigationBar = 0;
// iterate over all < div >-elements
var $divs = $content.find( 'div.NavFrame' );
$divs.each( function ( i, navFrame ) {
indexNavigationBar++;
navToggle = document.createElement( 'a' );
navToggle.className = 'NavToggle';
navToggle.setAttribute( 'id', 'NavToggle' + indexNavigationBar );
navToggle.setAttribute( 'href', '#' );
$( navToggle ).on( 'click', $.proxy( toggleNavigationBar, null, indexNavigationBar ) );
isCollapsed = $( navFrame ).hasClass( 'collapsed' );
// backwards compatibility for old technique where the collapsed class was not yet used
for ( navChild = navFrame.firstChild; navChild !== null && !isCollapsed; navChild = navChild.nextSibling ) {
if ( $( navChild ).hasClass( 'NavPic' ) || $( navChild ).hasClass( 'NavContent' ) ) {
if ( navChild.style.display === 'none' ) {
isCollapsed = true;
}
}
}
if ( isCollapsed ) {
for ( navChild = navFrame.firstChild; navChild !== null; navChild = navChild.nextSibling ) {
if ( $( navChild ).hasClass( 'NavPic' ) || $( navChild ).hasClass( 'NavContent' ) ) {
navChild.style.display = 'none';
}
}
}
navToggleText = document.createTextNode( isCollapsed ? navigationBarShow : navigationBarHide );
navToggle.appendChild( navToggleText );
// Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked)
for ( j = 0; j < navFrame.childNodes.length; j++ ) {
if ( $( navFrame.childNodes[j] ).hasClass( 'NavHead' ) ) {
navToggle.style.color = navFrame.childNodes[j].style.color;
navFrame.childNodes[j].appendChild( navToggle );
}
}
navFrame.setAttribute( 'id', 'NavFrame' + indexNavigationBar );
} );
}
mw.hook( 'wikipage.content' ).add( createNavigationBarToggleButton );
}());
/** Side bar customization */
function ModifySidebar( action, section, name, link ) {
try {
switch ( section ) {
case 'languages':
var target = 'p-lang';
break;
case 'toolbox':
var target = 'p-tb';
break;
case 'navigation':
var target = 'p-navigation';
break;
default:
var target = 'p-' + section;
break;
}
if ( action == 'add' ) {
var node = document.getElementById( target )
.getElementsByTagName( 'div' )[0]
.getElementsByTagName( 'ul' )[0];
var aNode = document.createElement( 'a' );
var liNode = document.createElement( 'li' );
aNode.appendChild( document.createTextNode( name ) );
aNode.setAttribute( 'href', link );
liNode.appendChild( aNode );
liNode.className = 'plainlinks';
node.appendChild( liNode );
}
if ( action == 'remove' ) {
var list = document.getElementById( target )
.getElementsByTagName( 'div' )[0]
.getElementsByTagName( 'ul' )[0];
var listelements = list.getElementsByTagName( 'li' );
for ( var i = 0; i < listelements.length; i++ ) {
if (
listelements[i].getElementsByTagName( 'a' )[0].innerHTML == name ||
listelements[i].getElementsByTagName( 'a' )[0].href == link
)
{
list.removeChild( listelements[i] );
}
}
}
} catch( e ) {
// let's just ignore what's happened
return;
}
}
function CustomizeModificationsOfSidebar() {
// adds [[Special:CategoryTree|Special:CategoryTree]] to toolbox
ModifySidebar( 'add', 'toolbox', 'List of uploaded files', 'https://en.prolewiki.org/wiki/Special:ListFiles' );
}
jQuery( CustomizeModificationsOfSidebar );
/** ==================== Citizen Skin Enhancements (sidebar icons, heading hyperlinks, collection refresh) ==================== **/
$(function() {
// Add sidebar icons in correct order
var $header = $('.citizen-drawer');
if ($header.length) {
var icons = [
{cls: 'sidebar-library', href: '/wiki/ProleWiki:Library', title: 'Library'},
{cls: 'sidebar-essays', href: '/wiki/ProleWiki:Essays', title: 'Essays'}
];
if (!mw.user.isAnon()) {
icons.push({cls: 'sidebar-upload', href: '/wiki/Special:Upload', title: 'Upload file'});
}
// Create elements in reverse order to ensure correct positioning
for (var i = icons.length - 1; i >= 0; i--) {
var icon = icons[i];
$header.after(
'<a class="citizen-header__item ' + icon.cls +
'" href="' + icon.href + '" title="' + icon.title + '">' +
'<div class="citizen-header__button"></div></a>'
);
}
}
// Heading hyperlinks
function setupHeadingLinks() {
var $headings = $('h2, h3, h4, h5').find('.mw-headline');
if (!$headings.length) return;
function smoothScroll(targetId) {
var target = document.getElementById(targetId);
if (target) {
window.scrollTo({
top: target.offsetTop,
behavior: 'smooth'
});
}
}
// Create hyperlink buttons with proper event handling
$headings.each(function() {
var $headline = $(this),
id = $headline.attr('id');
if (!id) return;
var $link = $(
'<a href="#' + id + '" class="heading-hyperlink citizen-ui-icon mw-ui-icon-wikimedia-link" ' +
'title="Direct link to this section" aria-label="Direct link to this section" data-no-preview="true"></a>'
);
// Add specific click handler to prevent section collapse
$link.on('click', function(e) {
e.preventDefault();
e.stopPropagation(); // Prevent event from bubbling to section toggle
smoothScroll(id);
history.replaceState(null, null, '#' + id);
});
$headline.append($link);
});
// Handle other anchor clicks (not the hyperlink buttons)
$(document).on('click', 'a[href^="#"]:not(.heading-hyperlink)', function(e) {
var href = this.href;
var id = href.substring(href.indexOf('#') + 1);
if (!id) return;
e.preventDefault();
smoothScroll(id);
history.replaceState(null, null, '#' + id);
});
}
setupHeadingLinks();
// Collection rendering check
if ($("#renderingProgress").length) {
setTimeout(function() {
location.reload();
}, 5000);
}
});
/** ==================== Combined Preferences Setup (custom typefaces, themes) ==================== **/
$(function() {
// Shared setup for both theme and font pickers
function initPreferences() {
setupThemePicker();
setupFontPicker();
}
/** ===== Theme Picker ===== **/
function setupThemePicker() {
var html = document.documentElement;
var currentMonth = new Date().getMonth();
var isJune = currentMonth === 5;
var themes = [
{value: 'day', label: 'Light', className: 'skin-citizen-light'},
{value: 'os', label: 'Auto', className: 'skin-citizen-auto'},
{value: 'night', label: 'Dark', className: 'skin-citizen-dark'},
{value: 'sepia', label: 'Sepia', className: 'skin-citizen-sepia', labelId: 'sepia-style-label'}
];
if (isJune) {
themes.push({
value: 'trans',
label: 'Trans',
className: 'skin-citizen-trans',
labelId: 'trans-style-label'
});
}
function applyTheme(themeValue) {
// Batch class operations by looping and removing each class
for (var i = 0; i < themes.length; i++) {
html.classList.remove(themes[i].className);
}
for (var j = 0; j < themes.length; j++) {
if (themes[j].value === themeValue) {
html.classList.add(themes[j].className);
break;
}
}
localStorage.setItem('skin-theme-clientpref', themeValue);
}
var storedTheme = localStorage.getItem('skin-theme-clientpref') || 'day';
if (storedTheme === 'trans' && !isJune) storedTheme = 'day';
applyTheme(storedTheme);
function addThemeOptions() {
var themeContainer = document.getElementById('skin-client-prefs-skin-theme');
if (!themeContainer) {
return setTimeout(addThemeOptions, 100);
}
var form = themeContainer.querySelector('form');
var template = form.querySelector('.citizen-client-prefs-radio');
if (!template) return;
for (var i = 0; i < themes.length; i++) {
var theme = themes[i];
var radioId = 'skin-client-pref-skin-theme-value-' + theme.value;
if (document.getElementById(radioId)) continue;
var radioElement = template.cloneNode(true);
var input = radioElement.querySelector('input');
var label = radioElement.querySelector('label');
input.id = radioId;
input.value = theme.value;
input.name = 'skin-client-pref-skin-theme-group';
input.checked = (storedTheme === theme.value);
label.setAttribute('for', radioId);
label.textContent = theme.label;
if (theme.labelId) {
label.id = theme.labelId;
}
form.appendChild(radioElement);
}
}
$(document).on('change', 'input[name="skin-client-pref-skin-theme-group"]', function() {
applyTheme(this.value);
});
addThemeOptions();
}
/** ===== Typeface Picker ===== **/
function setupFontPicker() {
var html = document.documentElement;
var FONT_STORAGE_KEY = 'skin-font-clientpref';
var TEXT_SIZE_ID = 'skin-client-prefs-citizen-feature-custom-font-size';
var fonts = [
{value: 'sans', label: 'Sans', className: 'font-sans'},
{value: 'serif', label: 'Serif', className: 'font-serif'},
{value: 'dyslexic', label: 'Accessibility', className: 'font-dyslexic'}
];
var activeFont = localStorage.getItem(FONT_STORAGE_KEY) || 'sans';
function applyFont(fontValue) {
for (var i = 0; i < fonts.length; i++) {
html.classList.remove(fonts[i].className);
}
if (fontValue !== 'sans') {
for (var j = 0; j < fonts.length; j++) {
if (fonts[j].value === fontValue) {
html.classList.add(fonts[j].className);
break;
}
}
}
localStorage.setItem(FONT_STORAGE_KEY, fontValue);
}
applyFont(activeFont);
function addFontOptions() {
var fontSection = document.getElementById('skin-client-prefs-font');
if (fontSection) return;
var prefsPanel = document.getElementById('citizen-client-prefs');
if (!prefsPanel) {
return setTimeout(addFontOptions, 100);
}
// Create new section using string HTML (ES5 safe)
var section = document.createElement('div');
section.className = 'mw-portlet mw-portlet-js citizen-menu';
section.id = 'skin-client-prefs-font';
section.innerHTML =
'<div class="citizen-menu__heading">Typeface</div>' +
'<div class="citizen-menu__content">' +
'<ul class="citizen-menu__content-list">' +
'<li class="mw-list-item mw-list-item-js">' +
'<form></form>' +
'</li>' +
'</ul>' +
'</div>';
var textSizeSection = document.getElementById(TEXT_SIZE_ID);
if (textSizeSection) {
if (textSizeSection.nextSibling) {
textSizeSection.parentNode.insertBefore(section, textSizeSection.nextSibling);
} else {
textSizeSection.parentNode.appendChild(section);
}
} else {
prefsPanel.appendChild(section);
}
var template = document.querySelector('.citizen-client-prefs-radio');
var form = section.querySelector('form');
if (!template || !form) return;
for (var i = 0; i < fonts.length; i++) {
var font = fonts[i];
var radioId = 'skin-client-pref-font-value-' + font.value;
var radioElement = template.cloneNode(true);
var input = radioElement.querySelector('input');
var label = radioElement.querySelector('label');
input.id = radioId;
input.value = font.value;
input.name = 'skin-client-pref-font-group';
input.checked = (activeFont === font.value);
label.setAttribute('for', radioId);
label.textContent = font.label;
// Add custom font class to the label
label.className = label.className + ' ' + font.className;
// Add ID for styling if needed
label.id = font.value + '-font-label';
form.appendChild(radioElement);
}
}
$(document).on('change', 'input[name="skin-client-pref-font-group"]', function() {
applyFont(this.value);
});
addFontOptions();
}
initPreferences();
});
/** ==================== Reading mode and features ==================== **/
/** (trigger, hoverline, progress bar) */
// Skip loading reading mode if not on desktop device:
if (!('ontouchstart' in window || navigator.maxTouchPoints > 0)) {
$(function() {
var html = document.documentElement,
readingModeClass = 'reading-mode',
isReadingMode = false;
// Toggle reading mode
function toggleReadingMode() {
isReadingMode = !isReadingMode;
html.classList.toggle(readingModeClass, isReadingMode);
if (isReadingMode && window._paq) {
_paq.push(['trackGoal', 12]);
}
}
// Keyboard shortcut (desktop only)
if (!('ontouchstart' in window)) {
$(document).on('keydown', function(e) {
if ((e.keyCode === 48 || e.keyCode === 96) && // 0 key
!$(e.target).is('input, textarea, [contenteditable]')) {
toggleReadingMode();
}
});
}
// Hover line feature
function setupHoverLine() {
var $line = $('<div id="hover-line"></div>').css({
position: 'fixed',
left: 0,
width: '100%',
height: '1.5em',
pointerEvents: 'none',
zIndex: 1,
transition: 'top 0.1s ease, background-color 0.3s ease',
display: 'none',
backgroundColor: 'rgba(255,255,150,0.2)'
}).appendTo('body');
$(document).on('mousemove', function(e) {
if (!isReadingMode) {
$line.hide();
return;
}
var el = document.elementFromPoint(e.clientX, e.clientY);
if (!el) {
$line.hide();
return;
}
var paragraph = $(el).closest('p, li')[0];
if (!paragraph) {
$line.hide();
return;
}
var range;
if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(e.clientX, e.clientY);
} else if (document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(e.clientX, e.clientY);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
range.collapse(true);
}
}
if (!range) {
$line.hide();
return;
}
var rect = range.getBoundingClientRect();
if (!rect || rect.height === 0) {
$line.hide();
return;
}
$line.css({
top: rect.top + 'px',
height: rect.height + 'px',
display: 'block'
});
});
}
setupHoverLine();
// Progress bar feature with hover tooltip
function setupProgressBar() {
// Create wrapper container
var $wrapper = $('<div id="reading-progress-wrapper"></div>').css({
position: 'fixed',
bottom: 0,
left: 0,
width: '100%',
height: '12px',
zIndex: 9999,
pointerEvents: 'auto'
}).appendTo('body');
// Create progress bar
var $bar = $('<div id="reading-progress-bar"></div>').css({
position: 'absolute',
bottom: 0,
left: 0,
height: '4px',
backgroundColor: 'var(--color-progress-bar)',
width: '0%',
transition: 'width 0.1s ease-out',
pointerEvents: 'none'
}).appendTo($wrapper);
// Create tooltip
var $tooltip = $('<div id="reading-progress-tooltip"></div>').css({
position: 'fixed',
padding: '2px 6px',
backgroundColor: '#333',
color: '#fff',
fontSize: '1em',
borderRadius: '3px',
pointerEvents: 'none',
zIndex: 10000,
display: 'none'
}).appendTo('body');
var lastPercent = 0;
// Update progress on scroll
$(window).on('scroll', function() {
if (!isReadingMode) {
$wrapper.hide();
return;
}
$wrapper.show();
var scrollTop = $(window).scrollTop(),
docHeight = $(document).height(),
winHeight = $(window).height(),
percent = Math.min(100, scrollTop / (docHeight - winHeight) * 100);
if (Math.abs(percent - lastPercent) > 0.5) {
$bar.width(percent + '%');
lastPercent = percent;
}
});
// Show/hide tooltip on hover
$wrapper.hover(
function() {
if (!isReadingMode) return;
$tooltip.show();
},
function() {
$tooltip.hide();
}
);
// Update tooltip position and text
$wrapper.on('mousemove', function(e) {
if (!isReadingMode) return;
var scrollTop = $(window).scrollTop(),
docHeight = $(document).height(),
winHeight = $(window).height(),
percent = Math.min(100, scrollTop / (docHeight - winHeight) * 100);
$tooltip.text(Math.round(percent) + '%')
.css({
left: (e.clientX + 8) + 'px',
bottom: '16px'
});
});
}
setupProgressBar();
});
} //end reading mode IF statement
/** ==================== Visual Editor Welcome Message ==================== **/
mw.loader.using(['ext.visualEditor.desktopArticleTarget.init']).then(function () {
mw.hook('ve.activationComplete').add(function () {
var checkDialog = setInterval(function () {
var $dialogContent = $('.ve-init-mw-welcomeDialog-content');
if ($dialogContent.length) {
clearInterval(checkDialog);
// Only append once
if (!$dialogContent.data('custom-appended')) {
$dialogContent.append(
'<br><br>Please read our <strong><a href="/wiki/Principles">Principles</a></strong> and <strong><a href="/wiki/Encyclopedic_tone_guide">Encyclopedic tone guide</a><strong> first to make sure your edit is approved!'
);
$dialogContent.data('custom-appended', true);
// Resize the OOUI dialog to fit new content
var dialog = OO.ui.WindowManager.static.currentWindow;
if (dialog && typeof dialog.updateSize === 'function') {
dialog.updateSize();
}
}
}
}, 200);
});
});
/** ==================== Accreq Form Autosave ==================== **/
$(function() {
var $textarea = $('#wpBio');
if (!$textarea.length) return;
var STORAGE_KEY = 'form-bio-autosave',
$status = $('<div id="autosave-status" style="font-size:0.8em;color:gray;height:1.2em;margin-top:5px;"></div>'),
lastValue = '';
$textarea.after($status);
// Restore saved content
var saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
$textarea.val(saved);
lastValue = saved;
showStatus('Restored previous work');
}
// Setup autosave
$textarea.on('input', function() {
var current = $textarea.val();
if (current === lastValue) return;
showStatus('Saving...');
setTimeout(function() {
localStorage.setItem(STORAGE_KEY, current);
lastValue = current;
showStatus('Saved locally');
}, 2000);
});
// Clear on form submit
$textarea.closest('form').on('submit', function() {
localStorage.removeItem(STORAGE_KEY);
});
function showStatus(msg) {
$status.text(msg).stop().fadeIn().delay(3000).fadeOut();
}
});
/** ==================== Join us homepage testimonials ==================== **/
$(function() {
var $joinUs = $('#joinUs'),
$testimonial = $('#homepage-testimonial');
if (!$joinUs.length || !$testimonial.length) return;
var testimonials = [
'<span>"The ProleWiki community is very welcoming and diverse..."</span><br><a href="https://en.prolewiki.org/wiki/Comrade:ComradeTrash">ComradeTrash</a>',
'<span>"Working with ProleWiki has given me space to learn..."</span><br><a href="https://en.prolewiki.org/wiki/Comrade:CriticalResist">CriticalResist</a>',
'<span>"ProleWiki is a lively environment for socialist education..."<br><a href="https://en.prolewiki.org/wiki/Comrade:Charhapiti">Charhapiti</a>',
'<span>"As a trans woman, I have never felt more accepted..."</span><br><a href="https://en.prolewiki.org/wiki/Comrade:Local_Gay_Communist_-_LGC">Local Gay Communist</a>'
];
if ($joinUs.height() >= 375) {
$testimonial.html(
testimonials[Math.floor(Math.random() * testimonials.length)]
).show();
}
});