Jump to content

User:Gary Queen/layout.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/*
	hook
*/
addOnloadHook(accessKeys);
addOnloadHook(pagesLayout);

/*
	useful functions
*/
function $(element)
{
	return document.getElementById(element);
}

function addClass(element, newClass)
{
	if (!element instanceof Object) return false;
	
	if (element.className)
	{
		var classes = element.className.split(' ');
		classes.push(newClass);
		return element.className = classes.join(' ');
	}
	else return element.className = newClass;	
}

function hasClass(element, classToCheck)
{
	if (!element instanceof Object || !element.className) return false;
	
	var classes = element.className.split(' ');
	for (var i = 0; i < classes.length; i++)
	{
		if (classes[i] == classToCheck)
			return true;
	}
	
	return false;	
}

function removeClass(element, oldClass)
{
	if (!element instanceof Object || !element.className) return false;
	
	var classes = element.className.split(' ');
	var newClasses = [];
	for (var i = 0; i < classes.length; i++)
	{
		if (classes[i] != oldClass)
			newClasses.push(classes[i]);
	}
	
	return element.className = newClasses;	
}

String.prototype.trim = function()
{
	return this.replace(/^[\s|\n]+|[\s|\n]+$/g, '');	
}

String.prototype.ltrim = function()
{
	return this.replace(/^[\s|\n]+/, '');
}

String.prototype.rtrim = function()
{
	return this.replace(/[\s|\n]+$/, '');
}

/*
	do access keys
*/
function accessKeys()
{
	var caEdit = $('ca-edit');
	var tPrint = $('t-print');
	var content = $('content');
	
	/*
		DISABLE ACCESS KEYS
	*/
	// Disable logo
	$('p-logo').childNodes[1].accessKey = null;
	
	// disable access keys, so quickedit can use them
	if (wgAction == 'view' || wgAction == 'purge')
	{
		if (caEdit) caEdit.firstChild.accessKey = null; // edit this page
		if (tPrint) tPrint.firstChild.accessKey = null; // printable version
	}
	
	/*
		ENABLE ACCESS KEYS
	*/
	// viewing a non-existent page
	if (caEdit && caEdit.firstChild && !caEdit.firstChild.accessKey && caEdit.firstChild.firstChild.nodeValue == 'Create this page')
		caEdit.firstChild.accessKey = 'e';
	
	// Diff navigation links
	// check if we're viewing an oldid page
	var revisionNav = $('mw-revision-nav');
	var mwPrevlink = document.getElementsByClassName('mw-prevlink');
	var mwNextlink = document.getElementsByClassName('mw-nextlink');
	var prevlink, nextlink;
	
	if (revisionNav)
	{
		if (revisionNav.childNodes[1].firstChild.nodeValue == 'diff')
		{
			// have both prev and next
			prevlink = revisionNav.childNodes[1];
			nextlink = revisionNav.childNodes[11];
		}
		else // only have next
			nextlink = revisionNav.childNodes[7];
	}
	else if (mwPrevlink.length > 0 || mwNextlink.length > 0)
	{
		prevlink = mwNextlink[0];
		nextlink = mwPrevlink[0];
	}
	else
	{	
		prevlink = $('differences-prevlink');
		nextlink = $('differences-nextlink');
	}
	
	// assign previous link
	if (prevlink && !$('wpPreview'))
	{
		if (tPrint) tPrint.firstChild.accessKey = null;
		prevlink.accessKey = 'p';
	}
	
	// assign next link
	if (nextlink)
	{
		$('pt-mytalk').firstChild.accessKey = null;
		nextlink.accessKey = 'n';
	}
	
	// Content accesskey
	var book = $('ca-nstab-book');
	var portal = $('ca-nstab-portal');
	var special = $('ca-nstab-special');
	var contentTab;
	
	if (book) contentTab = book;
	else if (portal) contentTab = portal;
	else if (special) contentTab = special;
	else contentTab = '';
	
	if (contentTab) contentTab.firstChild.accessKey = 'c';

	// QuickEdit link
	var contentSub = $('contentSub');
	
	if (contentSub.innerHTML != '' && !special)
	{
		var qeAccessKey = 'b';
		var qeLeadLink = $('sectionlink-0');
		if (qeLeadLink) qeLeadLink.accessKey = qeAccessKey;
		console.log('1: ' + qeLeadLink);
	}
	else if ($('t-find-edit'))
	{
		$('t-find-edit').firstChild.setAttribute('accesskey', 'b');
		console.log('2');
	}
	
	// creating new page, from search results page
	var newLinks = content.getElementsByClassName('new');
	
	if (wgPageName == 'Special:Search' && newLinks[0])
		newLinks[0].accessKey = 'e';
		
	// Sidebar links
	var drafts = $('t-drafts');
	var goals = $('t-goals');
	var pageSize = $('t-page-size');

	if (drafts) drafts.firstChild.setAttribute('accesskey', 'd'); // Drafts
	if (goals) goals.firstChild.setAttribute('accesskey', 'g'); // Goals
	// if (pageSize) pageSize.firstChild.setAttribute('accesskey', 'a'); // Page Size
	
	// access key for edit box on uneditable page
	var permissionErrors = content.getElementsByClassName('permissions-errors');
	if (permissionErrors.length > 0) $('wpTextbox1').accessKey = ',';
}

/*
	do page layout
*/
function pagesLayout()
{
	/*
		variables
	*/
	var content = $('content');
	var jumpToNav = $('jump-to-nav');
	var pPersonal = $('p-personal');
	
	var afterJumpToNav = jumpToNav.nextSibling.nextSibling.nextSibling.nextSibling;
	var bodyContent = $('bodyContent');
	var cactions = $('p-cactions');
	var caEdit = $('ca-edit');
	var caMain = $('ca-nstab-main');
	var contentSub = $('contentSub');
	var firstDiffElement = content.getElementsByClassName('diff')[0];
	var globalWrapper = $('globalWrapper');
	var h2 = content.getElementsByTagName('h2');
	var paragraphs = content.getElementsByTagName('p');
	var pBody = pPersonal.getElementsByClassName('pBody')[0];
	var pendingChanges = $('mw-fr-revisiontag');
	var relLinks = content.getElementsByClassName('rellink');
	var section0 = $('section-0');
	var siteSub = $('siteSub');
	var toc = $('toc');
	var tPrint = $('t-print');
	var userMessages = content.getElementsByClassName('usermessage');
	var wikiPreview = $('wikiPreview');
	var wikitables = content.getElementsByClassName('wikitable');
	
	/*
		Layout
	*/
	// Fatter pages except when it would exceed page width
	if (window.innerWidth > 1425) globalWrapper.style.width = cactions.style.width = pBody.style.width = '1400px';
	else globalWrapper.style.width = cactions.style.width = pBody.style.width = (window.innerWidth - 25) + 'px';
	
	// Thinner page width for articles (1000 pixels wide)
	var thinnerPage = false;
	var fatterPage = false;
	
	// TODO fatterPages
	
	// fatterPageTerms
	if (typeof(fatterPageTerms) == 'object' && fatterPageTerms.length > 0)
	{
		for (var i = 0; i < fatterPageTerms.length; i++)
		{
			if (wgPageName.replace(/_/g, ' ').indexOf(fatterPageTerms[i]) != -1)
			{
				fatterPage = true;
				break;
			}
		}
	}
	
	// thinnerPages
	if (typeof(thinnerPages) == 'object' && thinnerPages.length > 0)
	{
		for (var i = 0; i < thinnerPages.length; i++)
		{
			if (wgPageName.indexOf(thinnerPages[i].replace(/ /g, '_')) == 0)
			{
				thinnerPage = true;
				break;
			}
		}
	}
	
	// TODO thinnerPageTerms
	
	// Thinner pages for articles
	if (window.innerWidth > 1025 && fatterPage == false && (wgCanonicalNamespace == '' || thinnerPage) && (wgAction == 'view' || wgAction == 'submit' || wgAction == 'edit' || wgAction == 'purge') && (location.href.indexOf('title=') && location.href.indexOf('diff=')) == -1 && !(wgCanonicalNamespace == '' && wgTitle == wgMainPageTitle))
		globalWrapper.style.width = cactions.style.width = pBody.style.width = '1000px';
	
	// Forced page to use article styles
	if ($('thinner-page'))
	{
		globalWrapper.style.width = cactions.style.width = pBody.style.width = '1000px';
		appendCSS('#bodyContent > p, #wikiPreview > p, .text-indent { text-indent: 2em !important; }');
	}
		
	// Shorter search text (searchGoButton, mw-searchButton)
	$('searchGoButton').value = 'G';
	$('mw-searchButton').value = 'S';
	
	// remove the extra space after editsections
	var editSections = document.getElementsByClassName('editsection');
	var nextSibling;
	for (var i = 0; i < editSections.length; i++)
	{
		nextSibling = editSections[i].nextSibling;
		if (nextSibling && nextSibling.nodeType == 3 && nextSibling.nodeValue == ' ')
			nextSibling.parentNode.removeChild(nextSibling);
	}
	
	// adjust references
	var references = document.getElementsByClassName('references-small');
	for (var i = 0; i < references.length; i++)
	{
		var ref = references[i];
		var colCount = parseInt(ref.style.MozColumnCount || 1);
		var colWidth = ref.style.MozColumnWidth || '30em';
		if (colCount == 2 || (colWidth.substr(colWidth.length - 2, 2) == 'em' && parseInt(colWidth) >= 30)) ref.style.MozColumnCount = ref.style.MozColumnWidth = 'auto';
		if (ref.scrollHeight > 250) 
		{
			addClass(ref, 'grey-border');
			ref.style.clear = 'both';
		}
		
		// Indicate how many references there are in the box.
		var numberOfReferences = Math.floor(ref.childNodes[1].childNodes[1].childNodes.length / 2);
		var referencesHeading = ref.previousSibling.previousSibling;
		
		if (referencesHeading.nodeName == 'H2')
		{
			var referencesHeadline = referencesHeading.getElementsByClassName('mw-headline')[0];
			
			var newReferencesNode = document.createElement('span');
			newReferencesNode.style.fontSize = '75%';
			newReferencesNode.style.color = 'grey';
			newReferencesNode.appendChild(document.createTextNode(' (' + numberOfReferences + ' total)'));
			
			referencesHeadline.appendChild(newReferencesNode);
		}
	}
	
	// changes for non-discussion and discussion pages; covers discussion pages not in a "talk:" namespace, such as many noticeboards
	isDiscussionPage = ($('ca-addsection'));
	
	// don't add fancy text changes on pages with short paragraphs (discussions), AND better separate discussions
	if (isDiscussionPage)
	{
		importStylesheet('User:Gary King/short paragraphs.css');
		importStylesheet('User:Gary King/discussions.css');
	}
	
	// insert clear: right; after h3, h4, h5
	function addClears(elements)
	{
		for (var i = 0; i < elements.length; i++)
		{
			h = elements[i];
			if (!h.nextSibling) continue;
			div = document.createElement('div');
			addClass(div, 'clear-right');
			h.parentNode.insertBefore(div, h.nextSibling);
		}
	}
	
	h3 = content.getElementsByTagName('h3');
	h4 = content.getElementsByTagName('h4');
	h5 = content.getElementsByTagName('h5');
	
	/*addClears(h3);
	addClears(h4);
	addClears(h5);*/
	
	// adjust top icons distance from right
	var topIcons = document.getElementsByClassName('topicon');
	var numberOfTopIcons = topIcons.length;
	
	function getRightDist(element)
	{
		return getLengthInPixels(element.style.right)[0];
	}
	
	// loop through top icons and get the one with the largest style.right value
	var leftMostTopIconRightValue = 0;
	for (var i = 0; i < numberOfTopIcons; i++)
	{
		var rightDist = getRightDist(topIcons[i])
		
		if (rightDist > leftMostTopIconRightValue)
			leftMostTopIconRightValue = rightDist;
	}
	
	var trumpIconLength = getTrumpIconLength(), distanceRight;
	
	if (trumpIconLength) distanceRight = trumpIconLength;
	else if (leftMostTopIconRightValue) distanceRight = leftMostTopIconRightValue + 25;
	else distanceRight = 0;
	
	function getTrumpIconLength()
	{
		var trumpIcons = { 'status-top': 175, 'TemplateUserinfo': 225 };
		var trumpIconLength = 0;

		for (var icon in trumpIcons)
		{
			if ($(icon))
			{
				var trumpIconLength = trumpIcons[icon];
				break;
			}
		}
		
		return trumpIconLength;
	}
	
	/*var distanceRight = 0;
	
	// ordered from left to right
	var firstIcon = $('protected-icon') || $('status-top');
	var secondIcon = $('spoken-icon');
	var thirdIcon = $('commons-icon') || $('featured-star') || $('good-star') || $('rollback-icon') || $('script-icon');
	
	var trumpIcons = { 'status-top': 150, 'TemplateUserinfo': 225 };
	var trumpIconLength = getTrumpIconLength();
	
	if (trumpIconLength) distanceRight = trumpIconLength;
	else if (firstIcon) distanceRight = getRightDist(firstIcon) + 25;
	else if (secondIcon) distanceRight = getRightDist(secondIcon) + 25;
	else if (thirdIcon) distanceRight = getRightDist(thirdIcon) + 30;*/
	
	// move QuickEdit section-0 link to top-right corner of page
	sectionLink0 = $('sectionlink-0');
	
	// QE is adding an edit link to the lead section, so move it to the corner
	if (sectionLink0)
	{
		link = sectionLink0.parentNode;
		link.id = 'editsection-0';
		addClass(link, 'lead-qe-link');
		link.style.marginRight = distanceRight + 'px';
	
		// add new edit link into QE edit link
		newLink = document.createElement('a');
		newLink.href = wgScript + '?title=' + encodeURIComponent(mw.config.get('wgPageName')) + '&action=edit&section=0';
		newLink.title = 'Edit section';
		
		newLink.appendChild(document.createTextNode('edit'));
	
		link.insertBefore(newLink, sectionLink0);
		link.insertBefore(document.createTextNode('/'), sectionLink0);
		
		// move it
		content.insertBefore(link, section0);
	}
	
	// better padding for boxes aligned to the left and right
	toccolours = content.getElementsByClassName('toccolours');
	for (var i = 0; i < toccolours.length; i++)
	{
		tocColour = toccolours[i];
		textSideMargin = '1.5em';
		
		if (tocColour.style.cssFloat == 'right') 
		{
			tocColour.style.marginRight = 0;
			tocColour.style.marginLeft = textSideMargin;
		}
		else if (tocColour.style.cssFloat == 'left') 
		{
			tocColour.style.marginLeft = 0;
			tocColour.style.marginRight = textSideMargin;
		}
	}
	
	// don't indent lines in certain cases
	// italicized lines that are not indented and therefore look like hatnotes
	for (var i = 0; i < paragraphs.length; i++)
	{
		p = paragraphs[i];
		
		if (p.parentNode != bodyContent && p.parentNode != wikiPreview) continue;
		if (p.childNodes.length == 1 && p.firstChild.nodeName == 'I' && p.previousSibling.previousSibling.previousSibling.previousSibling != jumpToNav) p.style.textIndent = 0;
	}	
	
	// merge multiple hatnotes together
	// TODO Make these work together? i.e. if a dablink is followed by a reflink, merge them anyway.
	function mergeLinks(className)
	{
		links = content.getElementsByClassName(className);
		for (var i = links.length - 1; i >= 0; i--)
		{
			l = links[i];
			
			// give "title" attribute to node
			l.title = className;
			
			if (!l.nextSibling || !l.nextSibling.nextSibling || !hasClass(l.nextSibling.nextSibling, className)) continue;

			nextEl = l.nextSibling.nextSibling;
			addClass(nextEl, 'merged-hatnote');
		
			text = document.createTextNode(className == 'dablink' ? ' ' : '. ');
		
			l.appendChild(text);
			l.appendChild(nextEl);
		}
	}
	
	mergeLinks('dablink');
	mergeLinks('rellink');
	
	// contentSub (redirects, contribution page user info, etc.)
	/*if (contentSub.firstChild)
	{
		if (pendingChanges)
		{
			newPCDiv = document.createElement('div');
			addClass(newPCDiv, 'contentSub');
			newPCDiv.style.display = 'block';
			newPCDiv.appendChild(pendingChanges);
		
			contentSub.parentNode.insertBefore(newPCDiv, jumpToNav);
		}
		
		addClass(contentSub, 'merged-content-sub');
		if (contentSub.firstChild.nodeType == 3) contentSub.firstChild.nodeValue = contentSub.firstChild.nodeValue.replace(/^\s+/g, '');
		
		newDiv = document.createElement('div');
		newDiv.style.display = 'block';
		addClass(newDiv, 'contentSub');
		
		contentSub.parentNode.insertBefore(newDiv, contentSub.nextSibling);
	}
	else 
		addClass(contentSub, 'contentSub');*/

	// move a left-aligned thumb image to before any header that immediately precedes it
	leftAlignedThumb = 'tleft';
	thumbs = content.getElementsByClassName(leftAlignedThumb);
	for (var i = 0; i < thumbs.length; i++)
	{
		t = thumbs[i];
		movedText = 'This left-aligned image thumb was moved from the section below to the section above, in accordance with the Manual of Style (WP:MOS).';
		
		// immediately precedes it
		if (!t.previousSibling && !t.previousSibling.previousSibling) continue;		
		prev = t.previousSibling.previousSibling;
		if (prev.nodeName == 'H3' || prev.nodeName == 'H4' || prev.nodeName == 'H5') 
		{
			t.parentNode.insertBefore(t, prev);
			t.title = movedText;
		}
		
		// preceded by a rellink, then precedes it
		if (!prev.previousSibling && !prev.previousSibling.previousSibling && !hasClass(prev.previousSibling.previousSibling, 'rellink')) continue;		
		prev = prev.previousSibling.previousSibling;
		if (prev && (prev.nodeName == 'H3' || prev.nodeName == 'H4' || prev.nodeName == 'H5'))
		{
			t.parentNode.insertBefore(t, prev);
			t.title = movedText;
		}
	}
	
	// indent rellinks if an image is to the left of it
	for (var i = 0; i < relLinks.length; i++)
	{
		l = relLinks[i];
		
		if (!l.previousSibling || !l.previousSibling.previousSibling) continue;
		two = l.previousSibling.previousSibling;
		if (!two.previousSibling) continue;
		three = two.previousSibling;
		if (!three.previousSibling) continue;
		four = three.previousSibling;
		
		if ((two && hasClass(two, leftAlignedThumb)) || (three && hasClass(three, leftAlignedThumb)) || (four && hasClass(four, leftAlignedThumb))) addClass(l, 'text-indent');
	}

	// get length in pixels
	function getLengthInPixels(length, emInPixels)
	{
		// 1em ~ 13px
		if (typeof(emInPixels) == 'undefined') emInPixels = 13;
		
		var value = parseInt(length);
		var type = length.substring(length.length - 2, length.length);

		if (type == 'em') 
		{
			value = value * emInPixels;
			type = 'px';
		}
		
		return [value, type];
	}
	
	// proper padding for left- and right-aligned tables
	function checkAndFixTableMargins(table)
	{
		var alignment, result;
		
		// calculate "alignment"
		if (wikitables[i].align) alignment = wikitables[i].align;
		else if (wikitables[i].style.cssFloat) alignment = wikitables[i].style.cssFloat;
		
		if (alignment != 'left' && alignment != 'right') return false;
		
		var margin = alignment == 'left' ? table.style.marginRight : table.style.marginLeft;
		
		var pixelLengths = getLengthInPixels(margin)
		var value = pixelLengths[0];
		var type = pixelLengths[1];
		
		if (type == 'px' && value < 13) result = '13px';
		else if ((type == 'em' && value < 1) || !type) result = '1em';
		else result = '1em'; // may need to add checks for more "type"s
		
		if (alignment == 'left') table.style.marginRight = result;
		else table.style.marginLeft = result;

		return result;
	}
	
	for (var i = 0; i < wikitables.length; i++)
		checkAndFixTableMargins(wikitables[i])
		
	// add more space above .usermessage on Main Page
	if (wgCanonicalNamespace == '' && wgTitle == wgMainPageTitle && userMessages.length > 0)
		userMessages[0].style.margin = '2em 0 0 0';
	
	// have no max width for non-standard TOCs
	toctitle = $('toctitle');
	if (!toctitle) appendCSS('.toc { max-width: none; }');
	
	// add accesskeys for QE section links
	for (var i = 1; i <= 9; i++)
	{
		link = $('sectionlink-' + i);
		if (!link) break;
		
		link.accessKey = i;
	}
	
	// number h2 headers
	if (!(wgCanonicalNamespace == '' && wgTitle == wgMainPageTitle) && (wgAction == 'view' || wgAction == 'purge'))
	{
		if (firstDiffElement) start = 2;
		else if (toc) start = 1;
		else start = 0;
		
		begin = 0;
		for (var i = start; i < h2.length; i++)
		{
			number = document.createElement('span');
			addClass(number, 'heading-number');
			number.appendChild(document.createTextNode(begin + 1 + '. '));
			h2[i].insertBefore(number, h2[i].firstChild);
			begin++;
		}
	}
	
	// remove diff whitespace, around .diff-deletedline and .diff-addedline
	deletedLines = content.getElementsByClassName('diff-deletedline');
	addedLines = content.getElementsByClassName('diff-addedline');
	
	function trimDiffs(elements)
	{
		for (var i = 0; i < elements.length; i++)
		{
			if (!elements[i].firstChild) continue;
			elements[i].firstChild.innerHTML = elements[i].firstChild.innerHTML.trim();
		}
	}
	
	// FIXME Removes * and perhaps other characters at beginning of the line.
	/*trimDiffs(deletedLines);
	trimDiffs(addedLines);*/
	
	// TODO Use white-space: pre-wrap; on deletedLines[i].firstChild and strip whtiespace added 
	// by the software so that added/removed whitespaces are more clear?
	
	// remove extra line breaks - only in articles, for now at least
	if (wgCanonicalNamespace == '')
	{
		// at the beginning of the page
		next = afterJumpToNav;
		if (hasClass(next, 'dablink') || next.nodeName == 'DL')
		{
			next2 = next.nextSibling.nextSibling;
			next3 = next2.nextSibling.nextSibling;
			
			if (next2.nodeName == 'P' && next2.firstChild.nodeName == 'BR')
				next2.removeChild(next2.firstChild);
			else if (next3.nodeName == 'P' && next3.firstChild.nodeName == 'BR')
				next3.removeChild(next3.firstChild);
		}
		
		// before the TOC
		if (toc && toc.previousSibling.previousSibling)
		{
			beforeTOC = toc.previousSibling.previousSibling;
			if (beforeTOC.nodeName == 'P' && beforeTOC.childNodes.length == 1 && beforeTOC.firstChild.nodeName == 'BR')
				beforeTOC.parentNode.removeChild(beforeTOC);
		}
	}
}