//
/*! 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
//