mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-27 17:10:19 +00:00
d28f09df31
This is the final step of the process described at <https://www.mediawiki.org/wiki/Separating_skins_from_core_MediaWiki>. Corresponding core change: Idfc38503. Change-Id: I84fcf7ce6385b8323544cafe6912a00f1886d20d
284 lines
9.5 KiB
Plaintext
284 lines
9.5 KiB
Plaintext
<public:attach event="ondocumentready" onevent="CSSHover()" />
|
|
<script>
|
|
/**
|
|
* Whatever:hover - V3.11
|
|
* ------------------------------------------------------------
|
|
* Author - Peter Nederlof, http://www.xs4all.nl/~peterned
|
|
* License - http://creativecommons.org/licenses/LGPL/2.1
|
|
*
|
|
* Special thanks to Sergiu Dumitriu, http://purl.org/net/sergiu,
|
|
* for fixing the expression loop.
|
|
*
|
|
* Whatever:hover is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* Whatever:hover is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* howto: body { behavior:url("csshover3.htc"); }
|
|
* ------------------------------------------------------------
|
|
*/
|
|
|
|
window.CSSHover = (function(){
|
|
|
|
// regular expressions, used and explained later on.
|
|
var REG_INTERACTIVE = /(^|\s)((([^a]([^ ]+)?)|(a([^#.][^ ]+)+)):(hover|active|focus))/i;
|
|
var REG_AFFECTED = /(.*?)\:(hover|active|focus)/i;
|
|
var REG_PSEUDO = /[^:]+:([a-z\-]+).*/i;
|
|
var REG_SELECT = /(\.([a-z0-9_\-]+):[a-z]+)|(:[a-z]+)/gi;
|
|
var REG_CLASS = /\.([a-z0-9_\-]*on(hover|active|focus))/i;
|
|
var REG_MSIE = /msie (5|6|7)/i;
|
|
var REG_COMPAT = /backcompat/i;
|
|
|
|
// property mapping, real css properties must be used in order to clear expressions later on...
|
|
// Uses obscure css properties that no-one is likely to use. The properties are borrowed to
|
|
// set an expression, and are then restored to the most likely correct value.
|
|
var Properties = {
|
|
index: 0,
|
|
list: ['text-kashida', 'text-kashida-space', 'text-justify'],
|
|
get: function() {
|
|
return this.list[(this.index++)%this.list.length];
|
|
}
|
|
};
|
|
|
|
// camelize is used to convert css properties from (eg) text-kashida to textKashida
|
|
var camelize = function(str) {
|
|
return str.replace(/-(.)/mg, function(result, match){
|
|
return match.toUpperCase();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Local CSSHover object
|
|
* --------------------------
|
|
*/
|
|
|
|
var CSSHover = {
|
|
|
|
// array of CSSHoverElements, used to unload created events
|
|
elements: [],
|
|
|
|
// buffer used for checking on duplicate expressions
|
|
callbacks: {},
|
|
|
|
// init, called once ondomcontentready via the exposed window.CSSHover function
|
|
init:function() {
|
|
// don't run in IE8 standards; expressions don't work in standards mode anyway,
|
|
// and the stuff we're trying to fix should already work properly
|
|
if(!REG_MSIE.test(navigator.userAgent) && !REG_COMPAT.test(window.document.compatMode)) {
|
|
return;
|
|
}
|
|
|
|
// start parsing the existing stylesheets
|
|
var sheets = window.document.styleSheets, l = sheets.length;
|
|
for(var i=0; i<l; i++) {
|
|
this.parseStylesheet(sheets[i]);
|
|
}
|
|
},
|
|
|
|
// called from init, parses individual stylesheets
|
|
parseStylesheet:function(sheet) {
|
|
// check sheet imports and parse those recursively
|
|
if(sheet.imports) {
|
|
try {
|
|
var imports = sheet.imports;
|
|
var l = imports.length;
|
|
for(var i=0; i<l; i++) {
|
|
this.parseStylesheet(sheet.imports[i]);
|
|
}
|
|
} catch(securityException){
|
|
// trycatch for various possible errors
|
|
}
|
|
}
|
|
|
|
// interate the sheet's rules and send them to the parser
|
|
try {
|
|
var rules = sheet.rules;
|
|
var r = rules.length;
|
|
for(var j=0; j<r; j++) {
|
|
this.parseCSSRule(rules[j], sheet);
|
|
}
|
|
} catch(someException){
|
|
// trycatch for various errors, most likely accessing the sheet's rules.
|
|
}
|
|
},
|
|
|
|
// magic starts here ...
|
|
parseCSSRule:function(rule, sheet) {
|
|
|
|
// The sheet is used to insert new rules into, this must be the same sheet the rule
|
|
// came from, to ensure that relative paths keep pointing to the right location.
|
|
|
|
// only parse a rule if it contains an interactive pseudo.
|
|
var select = rule.selectorText;
|
|
if(REG_INTERACTIVE.test(select)) {
|
|
var style = rule.style.cssText;
|
|
|
|
// affected elements are found by truncating the selector after the interactive pseudo,
|
|
// eg: "div li:hover" >> "div li"
|
|
var affected = REG_AFFECTED.exec(select)[1];
|
|
|
|
// that pseudo is needed for a classname, and defines the type of interaction (focus, hover, active)
|
|
// eg: "li:hover" >> "onhover"
|
|
var pseudo = select.replace(REG_PSEUDO, 'on$1');
|
|
|
|
// the new selector is going to use that classname in a new css rule,
|
|
// since IE6 doesn't support multiple classnames, this is merged into one classname
|
|
// eg: "li:hover" >> "li.onhover", "li.folder:hover" >> "li.folderonhover"
|
|
var newSelect = select.replace(REG_SELECT, '.$2' + pseudo);
|
|
|
|
// the classname is needed for the events that are going to be set on affected nodes
|
|
// eg: "li.folder:hover" >> "folderonhover"
|
|
var className = REG_CLASS.exec(newSelect)[1];
|
|
|
|
// no need to set the same callback more than once when the same selector uses the same classname
|
|
var hash = affected + className;
|
|
if(!this.callbacks[hash]) {
|
|
|
|
// affected elements are given an expression under a borrowed css property, because fake properties
|
|
// can't have their expressions cleared. Different properties are used per pseudo, to avoid
|
|
// expressions from overwriting eachother. The expression does a callback to CSSHover.patch,
|
|
// rerouted via the exposed window.CSSHover function.
|
|
var property = Properties.get();
|
|
var atRuntime = camelize(property);
|
|
|
|
// because the expression is added to the stylesheet, and styles are always applied to html that is
|
|
// dynamically added to the dom, the expression will also trigger for those new elements (provided
|
|
// they are selected by the affected selector).
|
|
sheet.addRule(affected, property + ':expression(CSSHover(this, "'+pseudo+'", "'+className+'", "'+atRuntime+'"))');
|
|
|
|
// hash it, so an identical selector/class combo does not duplicate the expression
|
|
this.callbacks[hash] = true;
|
|
}
|
|
|
|
// duplicate expressions need not be set, but the style could differ
|
|
sheet.addRule(newSelect, style);
|
|
}
|
|
},
|
|
|
|
// called via the expression, patches individual nodes
|
|
patch:function(node, type, className, property) {
|
|
|
|
// restores the borrowed css property to the value of its immediate parent, clearing
|
|
// the expression so that it's not repeatedly called.
|
|
try {
|
|
var value = node.parentNode.currentStyle[property];
|
|
node.style[property] = value;
|
|
} catch(e) {
|
|
// the above reset should never fail, but just in case, clear the runtimeStyle if it does.
|
|
// this will also stop the expression.
|
|
node.runtimeStyle[property] = '';
|
|
}
|
|
|
|
// just to make sure, also keep track of patched classnames locally on the node
|
|
if(!node.csshover) {
|
|
node.csshover = [];
|
|
}
|
|
|
|
// and check for it to prevent duplicate events with the same classname from being set
|
|
if(!node.csshover[className]) {
|
|
node.csshover[className] = true;
|
|
|
|
// create an instance for the given type and class
|
|
var element = new CSSHoverElement(node, type, className);
|
|
|
|
// and store that instance for unloading later on
|
|
this.elements.push(element);
|
|
}
|
|
|
|
// returns a dummy value to the expression
|
|
return type;
|
|
},
|
|
|
|
// unload stuff onbeforeunload
|
|
unload:function() {
|
|
try {
|
|
|
|
// remove events
|
|
var l = this.elements.length;
|
|
for(var i=0; i<l; i++) {
|
|
this.elements[i].unload();
|
|
}
|
|
|
|
// and set properties to null
|
|
this.elements = [];
|
|
this.callbacks = {};
|
|
|
|
} catch (e) {
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* CSSHoverElement
|
|
* --------------------------
|
|
*/
|
|
|
|
// the event types associated with the interactive pseudos
|
|
var CSSEvents = {
|
|
onhover: { activator: 'onmouseenter', deactivator: 'onmouseleave' },
|
|
onactive: { activator: 'onmousedown', deactivator: 'onmouseup' },
|
|
onfocus: { activator: 'onfocus', deactivator: 'onblur' }
|
|
};
|
|
|
|
// CSSHoverElement constructor, called via CSSHover.patch
|
|
function CSSHoverElement(node, type, className) {
|
|
|
|
// the CSSHoverElement patches individual nodes by manually applying the events that should
|
|
// have fired by the css pseudoclasses, eg mouseenter and mouseleave for :hover.
|
|
|
|
this.node = node;
|
|
this.type = type;
|
|
var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g');
|
|
|
|
// store event handlers for removal onunload
|
|
this.activator = function(){ node.className += ' ' + className; };
|
|
this.deactivator = function(){ node.className = node.className.replace(replacer, ' '); };
|
|
|
|
// add the events
|
|
node.attachEvent(CSSEvents[type].activator, this.activator);
|
|
node.attachEvent(CSSEvents[type].deactivator, this.deactivator);
|
|
}
|
|
|
|
CSSHoverElement.prototype = {
|
|
// onbeforeunload, called via CSSHover.unload
|
|
unload:function() {
|
|
|
|
// remove events
|
|
this.node.detachEvent(CSSEvents[this.type].activator, this.activator);
|
|
this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator);
|
|
|
|
// and set properties to null
|
|
this.activator = null;
|
|
this.deactivator = null;
|
|
this.node = null;
|
|
this.type = null;
|
|
}
|
|
};
|
|
|
|
// add the unload to the onbeforeunload event
|
|
window.attachEvent('onbeforeunload', function(){
|
|
CSSHover.unload();
|
|
});
|
|
|
|
/**
|
|
* Public hook
|
|
* --------------------------
|
|
*/
|
|
|
|
return function(node, type, className, property) {
|
|
if(node) {
|
|
// called via the css expression; patches individual nodes
|
|
return CSSHover.patch(node, type, className, property);
|
|
} else {
|
|
// called ondomcontentready via the public:attach node
|
|
CSSHover.init();
|
|
}
|
|
};
|
|
|
|
})();
|
|
</script> |