Jump to content

User:Gary Queen/layout.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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);
		}
	}
}