Lunarity's Sandbox Wiki
Advertisement

//

/*! Copyright (C) 2012 Lunarity
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/*jshint browser:true, jquery:true, laxbreak:true, smarttabs:true */
/*global importArticle */

// Standard cite:
// <sup class="reference"><a href="#cite_note_0">[1]</a></sup>
// WARN: Format can be altered by a MediaWiki message

// TODO: Minify this once I lock down the interface
(function(w) {
	'use strict';
	var dev = w.dev = w.dev || {};
	if (jQuery.isFunction(dev.define) && dev.define.amd) {
		return;
	}
	dev.requireList = [];
	function makeQueueAction(what) {
		return function() {
			dev.requireList.push([what, arguments]);
		};
	}
	var require = makeQueueAction('require'),
	    define = makeQueueAction('define');
	require.define = define;
	require.checkStateOf = function() {
		return 'undefined';
	};
	define.require = require;
	define.alias = makeQueueAction('alias');
	define.configure = makeQueueAction('configure');
	define.amd = {
		jQuery: true,
		MediaWiki: true,
		recorder: true
	};

	dev.require = require;
	dev.define = define;

	importArticle({ type: 'script', article: 'w:dev:User:Lunarity/loader.js' });
})(this);
/*global dev */



// Reference logic that uses the popup to actual show the things
// This is the core. (In MVC terms, this is the Controller. The popup is the View, and
// the citation nodes in the DOM are the Model)
dev.require(
	['page!u:dev:ReferencePopups/popup.js', 'jquery', 'mediawiki',
	'page!u:dev:ReferencePopups/shared-config.js', 'flag!dom-ready',
	// We don't actually need colors, this is just an optimisation hack so that we
	// bulk load colors instead of having popup.js issue a separate request for it.
	'page!u:dev:Colors/code.js'],
function(Popup, $, mw, config) {
'use strict';

var i18n = {
	en: {
		coreConfigureText: 'Configure Reference Popups',
		coreConfigureHover: 'Change settings for Reference Popups'
	},
	it: {
		coreConfigureText: 'Configura le note a popup',
		coreConfigureHover: 'Cambia le impostazioni per le note a popup'
	},
	pl: {
		coreConfigureText: 'Skonfiguruj wyskakujące przypisy',
		coreConfigureHover: 'Zmień ustawienia dla wyskakujących przypisów'
	}
};
i18n = $.extend(i18n.en, i18n[mw.config.get('wgUserLanguage')]);


// This code creates and destroys popups as the references are interacted with.
// I do it this way as it minimises memory usage by ensuring only one popup
// will exist at any time.
// Armed is the currently active popup instance, open is the currently OPEN
// popup instance. The difference only matters when hovering, the previously
// open one will be killed and replaced by the Armed instance when armed opens.
var armedPop = null,
    openPop = null;
function cyclePopup(newTarget) {
	/*jshint boss:true */
	// Can't arm the open.
	if (openPop && openPop.element.is(newTarget)) {
		return;
	}
	if (armedPop) {
		// Don't do anything if we are arming already armed
		if (armedPop.element.is(newTarget)) {
			return;
		}
		armedPop.destroy();
	}
	return (armedPop = constructPopup($(newTarget)));
}
function cleanupPopups() {
	if (openPop) {
		openPop.destroy();
	}
	if (armedPop) {
		armedPop.destroy();
	}
	openPop = armedPop = null;
}


// Click processing functions
var lastTouch;
$('#mw-content-text').on({
	'touchEnd.RefPopups': function(ev) {
		// Fires when touch stops, always fired, even if finger wanders away
		lastTouch = ev.originalEvent;
	},
	'click.RefPopups': function(ev) {
		var userConfig = config.getConfig();
		// On touch screens, there is no hover so we always process clicks
		if (((!lastTouch || lastTouch.defaultPrevented) && userConfig.react !== 'click') || userConfig.disabled) {
			return;
		}
		lastTouch = null;
		var pop = cyclePopup(this);
		if (pop) {
			// The popup missed the event so signal it manually
			ev.preventDefault(); // Prevent link nav
			pop.show(ev);
		}
	},
	// Central state tracking, this is where the always-only-one happens
	// This is important for hover to avoid insta-kills when brushing
	// across references
	'referencepopupopen.RefPopups': function() {
		// Only react to popups created by us, not ones by other scripts
		if (!armedPop || !armedPop.element.is(this)) {
			return;
		}
		var oldOpen = openPop;
		openPop = armedPop;
		armedPop = null;
		if (oldOpen) { // Kill existing so only one is open
			// IMPORTANT: The destroy MAY trigger a close event which will
			//	invoke the handler below, that can cause TWO calls to
			//	destroy() which is bad. We switch the value of openPop
			//	BEFORE destroying to avoid that.
			oldOpen.destroy();
		}
	},
	'referencepopupclose.RefPopups': function() {
		if (!openPop || !openPop.element.is(this)) {
			return;
		}
		// If there is an armed popup then we just drop dead, otherwise
		// we shift ourself from open to armed.
		if (armedPop) {
			openPop.destroy();
		} else {
			armedPop = openPop;
		}
		openPop = null;
	}
}, '.reference');
// When the page is hidden (navigated away but held in cache) then we will
// close any open popups so that when the user hits back, they won't still
// be open.
$(window).on('pagehide.RefPopups', cleanupPopups);
// Install the hover events, if necessary.
applyHoverEvents();


// Installs hovering events if we're in hover mode, otherwise doesn't do anything
// It's called from the configuration save callback
function applyHoverEvents() {
	var $article = $('#mw-content-text');
	$article.off('mouseenter.RefPopups');
	var userConfig = config.getConfig();
	if (userConfig.disabled) {
		return;
	}
	if (userConfig.react === 'hover') {
		$article.on('mouseenter.RefPopups', '.reference', function() {
			var pop = cyclePopup(this);
			// Popup missed the hover, so cycle manually
			if (pop) { pop.triggerHover(); }
		});
	}
	if (openPop) {
		openPop.option('activateBy', userConfig.react);
	}
	if (armedPop) {
		armedPop.option('activateBy', userConfig.react);
	}
}

// Add configuration buttons to the interface.
// Lockdown prevents it at the admin's option (NOT RECOMMENDED)
if (!config.globalLockdown) {
	// We insert the configuration link below the categories, above article comments.
	// It displays as a float right, cleared block.
	$('#WikiaArticleCategories, #catlinks').first().after(
		$('<a href="#configure-refpopups" class="refpopups-configure-page" />')
		.html('[' + i18n.coreConfigureText + ']')
		.click(onClickConfigure)
	);
}

// Interfacing code to load and display the configuration interface.
// The interface is stored in a separate file to reduce the size.
function onClickConfigure(ev) {
	ev.preventDefault();
	config.showUI();
}
config.onChange.add(cleanupPopups, applyHoverEvents);

function constructPopup($ref) {
	var frag = $ref.find('a[href^="#cite_note"]').first();
	if (!frag.length) {
		return null; // Failsafe for crap wiki configurations
	}
	// The first step is to determine the Element for the reference node
	// NOTE: When a reference contains special HTML chars like '&', they get encoded
	//	to '.26', however the '.' marks the start of a class which is a problem...
	//   jQuery supports escaping the dot so we can work around this. There are other
	//   special characters but I think only dot will occur in cite's #ids.
	var $cite = $(frag.attr('href').replace(/\./g, '\\.'));
	if (!$cite.length) {
		// This happens when the ref tag is of the "name=X" form, but X doesn't exist
		return null;
	}

	// Create the content box. Sometimes people ram giant images in to the reference
	// so we wrap it in a scrollable box to avoid spill.
	var $content = $('<div style="overflow:auto">'),
	    // Configuration button
	    $conf = $('<a href="#" class="refpopups-configure" />')
	    .prop('title', i18n.coreConfigureHover)
	    .click(onClickConfigure);

	// We need to get just the reference body itself, without the backreference links
	$content.append($cite.find('.reference-text').clone());

	// And away we go
	// NOTE: We're not using the $.fn.referencePopup wrapper as it's safer to stay
	//	in our own namespace.
	var userConfig = config.getConfig();
	return new Popup.Popup({
		content: $conf.add($content),
		activateBy: userConfig.react,
		hoverDelay: userConfig.hoverDelay,
		animation: userConfig.animate,
		stickyHover: userConfig.stick,
		contentBoxClass: 'WikiaArticle'
		// Disabled. Attach to body so on top of everything.
		// NOTE: May cause CSS malfunctions in the popup due to not being descended
		//	from .WikiaPage.V2 or #WikiaMainContent
		//context: '#WikiaMainContent, #mw-content-text'
	}, $ref[0]);
}

}); // End Core

//
Advertisement