/*!
* jQuery Color Utilities
*
* Released under the MIT and GPL licenses.
* Copied from MediaWiki 1.40. Removed in MediaWiki 1.41 (https://phabricator.wikimedia.org/T335723).
*/
EditToolsColorUtil = {
/**
* Parse CSS color strings looking for color tuples
*
* Based on highlightFade by Blair Mitchelmore
* <http://jquery.offput.ca/highlightFade/>
*
* @param {Array|string} color
* @return {Array}
*/
getRGB: function ( color ) {
var result;
// Check if we're already dealing with an array of colors
if ( color && Array.isArray( color ) && color.length === 3 ) {
return color;
}
if ( typeof color !== 'string' ) {
return undefined;
}
// Look for rgb(num,num,num)
// eslint-disable-next-line no-cond-assign
if ( result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec( color ) ) {
return [
parseInt( result[ 1 ], 10 ),
parseInt( result[ 2 ], 10 ),
parseInt( result[ 3 ], 10 )
];
}
// Look for rgb(num%,num%,num%)
// eslint-disable-next-line no-cond-assign, security/detect-unsafe-regex
if ( result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec( color ) ) {
return [
parseFloat( result[ 1 ] ) * 2.55,
parseFloat( result[ 2 ] ) * 2.55,
parseFloat( result[ 3 ] ) * 2.55
];
}
// Look for #a0b1c2
// eslint-disable-next-line no-cond-assign
if ( result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec( color ) ) {
return [
parseInt( result[ 1 ], 16 ),
parseInt( result[ 2 ], 16 ),
parseInt( result[ 3 ], 16 )
];
}
// Look for #fff
// eslint-disable-next-line no-cond-assign
if ( result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec( color ) ) {
return [
parseInt( result[ 1 ] + result[ 1 ], 16 ),
parseInt( result[ 2 ] + result[ 2 ], 16 ),
parseInt( result[ 3 ] + result[ 3 ], 16 )
];
}
// Look for rgba(0, 0, 0, 0) == transparent in Safari 3
// eslint-disable-next-line no-cond-assign
if ( result = /rgba\(0, 0, 0, 0\)/.exec( color ) ) {
return EditToolsColorUtil.colors.transparent;
}
// Otherwise, we're most likely dealing with a named color
return EditToolsColorUtil.colors[ color.trim().toLowerCase() ];
},
/**
* Named color map
*
* Based on Interface by Stefan Petre
* <http://interface.eyecon.ro/>
*
* @property {Object}
*/
colors: {
aqua: [ 0, 255, 255 ],
azure: [ 240, 255, 255 ],
beige: [ 245, 245, 220 ],
black: [ 0, 0, 0 ],
blue: [ 0, 0, 255 ],
brown: [ 165, 42, 42 ],
cyan: [ 0, 255, 255 ],
darkblue: [ 0, 0, 139 ],
darkcyan: [ 0, 139, 139 ],
darkgrey: [ 169, 169, 169 ],
darkgreen: [ 0, 100, 0 ],
darkkhaki: [ 189, 183, 107 ],
darkmagenta: [ 139, 0, 139 ],
darkolivegreen: [ 85, 107, 47 ],
darkorange: [ 255, 140, 0 ],
darkorchid: [ 153, 50, 204 ],
darkred: [ 139, 0, 0 ],
darksalmon: [ 233, 150, 122 ],
darkviolet: [ 148, 0, 211 ],
fuchsia: [ 255, 0, 255 ],
gold: [ 255, 215, 0 ],
green: [ 0, 128, 0 ],
indigo: [ 75, 0, 130 ],
khaki: [ 240, 230, 140 ],
lightblue: [ 173, 216, 230 ],
lightcyan: [ 224, 255, 255 ],
lightgreen: [ 144, 238, 144 ],
lightgrey: [ 211, 211, 211 ],
lightpink: [ 255, 182, 193 ],
lightyellow: [ 255, 255, 224 ],
lime: [ 0, 255, 0 ],
magenta: [ 255, 0, 255 ],
maroon: [ 128, 0, 0 ],
navy: [ 0, 0, 128 ],
olive: [ 128, 128, 0 ],
orange: [ 255, 165, 0 ],
pink: [ 255, 192, 203 ],
purple: [ 128, 0, 128 ],
violet: [ 128, 0, 128 ],
red: [ 255, 0, 0 ],
silver: [ 192, 192, 192 ],
white: [ 255, 255, 255 ],
yellow: [ 255, 255, 0 ],
transparent: [ 255, 255, 255 ]
},
/**
* Convert an RGB color value to HSL.
*
* Conversion formula based on
* <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
*
* Adapted from <https://en.wikipedia.org/wiki/HSL_color_space>.
*
* Assumes `r`, `g`, and `b` are contained in the set `[0, 255]` and
* returns `h`, `s`, and `l` in the set `[0, 1]`.
*
* @param {number} r The red color value
* @param {number} g The green color value
* @param {number} b The blue color value
* @return {number[]} The HSL representation
*/
rgbToHsl: function ( r, g, b ) {
var d, h, s, l, min, max;
r = r / 255;
g = g / 255;
b = b / 255;
max = Math.max( r, g, b );
min = Math.min( r, g, b );
l = ( max + min ) / 2;
if ( max === min ) {
// achromatic
h = s = 0;
} else {
d = max - min;
s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min );
switch ( max ) {
case r:
h = ( g - b ) / d + ( g < b ? 6 : 0 );
break;
case g:
h = ( b - r ) / d + 2;
break;
case b:
h = ( r - g ) / d + 4;
break;
}
h /= 6;
}
return [ h, s, l ];
},
/**
* Convert an HSL color value to RGB.
*
* Conversion formula based on
* <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
*
* Adapted from <https://en.wikipedia.org/wiki/HSL_color_space>.
*
* Assumes `h`, `s`, and `l` are contained in the set `[0, 1]` and
* returns `r`, `g`, and `b` in the set `[0, 255]`.
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {number[]} The RGB representation
*/
hslToRgb: function ( h, s, l ) {
var r, g, b, hue2rgb, q, p;
if ( s === 0 ) {
r = g = b = l; // achromatic
} else {
hue2rgb = function ( t ) {
if ( t < 0 ) {
t += 1;
}
if ( t > 1 ) {
t -= 1;
}
if ( t < 1 / 6 ) {
return p + ( q - p ) * 6 * t;
}
if ( t < 1 / 2 ) {
return q;
}
if ( t < 2 / 3 ) {
return p + ( q - p ) * ( 2 / 3 - t ) * 6;
}
return p;
};
q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s;
p = 2 * l - q;
r = hue2rgb( h + 1 / 3 );
g = hue2rgb( h );
b = hue2rgb( h - 1 / 3 );
}
return [ r * 255, g * 255, b * 255 ];
},
/**
* Get a brighter or darker rgb() value string.
*
* Usage:
*
* getColorBrightness( 'red', +0.1 );
* // > "rgb(255,50,50)"
* getColorBrightness( 'rgb(200,50,50)', -0.2 );
* // > "rgb(118,29,29)"
*
* @param {Mixed} currentColor Current value in css
* @param {number} mod Wanted brightness modification between -1 and 1
* @return {string} Like `'rgb(r,g,b)'`
*/
getColorBrightness: function ( currentColor, mod ) {
var rgbArr = EditToolsColorUtil.getRGB( currentColor ),
hslArr = EditToolsColorUtil.rgbToHsl( rgbArr[ 0 ], rgbArr[ 1 ], rgbArr[ 2 ] );
rgbArr = EditToolsColorUtil.hslToRgb( hslArr[ 0 ], hslArr[ 1 ], hslArr[ 2 ] + mod );
return 'rgb(' +
[ parseInt( rgbArr[ 0 ], 10 ), parseInt( rgbArr[ 1 ], 10 ), parseInt( rgbArr[ 2 ], 10 ) ].join( ',' ) +
')';
}
};
function spEscaped(s, i){ // /-escaped
var escSymbols = 0;
i--;
while ((i > -1) && (s.charAt(i) == '/')){
escSymbols++;
i--;
}
return (escSymbols % 2 == 1)
}
function spIndexOfUnescaped(s, symbol){
var index = -1;
for (var i=0; i < s.length; i++)
if ((s.charAt(i) == symbol) && !spEscaped(s, i))
{index = i; break};
return index;
}
//replace $ with selection
function spReplaceSpecsymbols(s, symbols, toFunc){
var res = '', c;
for (var i = 0; i < s.length; i++){
c = s.charAt(i);
if (spEscaped(s, i))
res += c;
else
if (symbols.indexOf(c) > -1)
res += toFunc(c);
else
res += c;
}
return res;
}
jQuery.fn.extend({
//handles $ symbol as selection
insertTag: function(beginTag, endTag){
return this.each(function(){
var sel = $(this).textSelection('getSelection');
beginTag = spSelReplace(beginTag);
endTag = endTag ? spSelReplace(endTag) : '';
$(this).textSelection( 'encapsulateSelection', {
pre: beginTag || '',
peri: '',
post: endTag || '',
replace: true
});
//fix caret position after wrapping
if (endTag && (sel !== '')){
var pos = $(this).textSelection('getCaretPosition');
$(this).textSelection('setSelection', {start:pos - endTag.length});
}
function spSelReplace(s){
return spReplaceSpecsymbols(s, '/$', function(c){if (c=='/') return ''; else if (c=='$') return sel});
};
}); //this.each(function()
}, // InsertTag
setSelection: function(text){
return this.textSelection('encapsulateSelection', {
post: text,
replace: true
});
},
getSelection: function(text){
return this.textSelection('getSelection');
}
});
var EditTools =
{
functions: [], //"functional" insertion-links have attribute with index of it's function
charinsertDivider : "\240",
extraCSS : '\
.specialchars-table{\
margin-bottom:7px; margin-top:5px;\
border:1px solid #aaaaaa; border-spacing:0px;\
}\
.editpage-specialchars-selected{\
}\
table.specialchars {border: 1px solid #aaaaaa; border-collapse: collapse; width:100%;}\
table.specialchars td {border: 2px solid #aaaaaa; padding-left: 0.2em; padding-right: 0.2em; vertical-align: top;}\
.specialchars-table a {\
cursor: pointer;\
}\
#editpage-specialchars a:hover {\
}\
#specialchars-tablist{font-size:80%; background-color: #f3f3f3; border:1px solid #bbbbbb;\
position:absolute; right:99.9%; top:-1px; z-index:2}\
',
appendExtraCSS : function (){
mw.util.addCSS(this.extraCSS);
},
chars : function(section){return document.getElementById('specialchars-subsets' + section)},
subset: function(index){return document.getElementById('spchars' + index)},
tablist : function(){return document.getElementById('specialchars-tablist')},
tableId : 'editpage-specialchars',
cookieName : 'edittoolscharsubset',
tabPause : 600,
clearPause : function(){
clearTimeout(window.spTimeout);
},
tab : function(index){return $('#spTabDiv' + index)},
createEditTools : function ($placeholder){
var table = document.createElement("table");
table.setAttribute('class', 'specialchars-table');
table.setAttribute('id', this.tableId);
$placeholder.replaceWith(table);
var cookieRe, m;
for (var i=0; i < window.spOptions.sections.length; i++){
cookieRe = new RegExp("(?:^|;)\\s*" + EditTools.cookieName + i + "=(\\d+)\\s*(?:;|$)");
m = cookieRe.exec(document.cookie);
if (m && m.length > 1)
window.spOptions.sections[i].selected = m[1];
else window.spOptions.sections[i].selected = window.spOptions.sections[i].defaultSubset;
}
$(table).on('click', 'a', function($e){
var f = EditTools.functions[$(this).attr('data-func')];
if (typeof f == 'function') {
f.apply(this);
};
});
},
tablistUpdateSelection : function(){
if (!window.spActiveTab) return;
var tabs = this.tablist().getElementsByTagName('div');
for (var i=0; i < tabs.length; i++)
{
$(tabs[i]).removeClass('editpage-specialchars-selected');
$(tabs[i]).removeAttr('style');
}
for (var i=0; i < window.spOptions.sections.length; i++)
{
$(tabs[window.spOptions.sections[i].selected]).addClass('editpage-specialchars-selected');
$(tabs[window.spOptions.sections[i].selected]).css('background-color', EditToolsColorUtil.getColorBrightness(window.spOptions.sections[i].tabColor, 0.15));
}
},
//rebuild table
refresh : function(){
if (!this.created) return;
var table = document.getElementById(this.tableId);
$(table).empty();
var tablist = document.createElement('div');
tablist.setAttribute('id', 'specialchars-tablist');
tablist.setAttribute('onmouseout', 'var children = EditTools.tablist().getElementsByTagName("*"); if (((!event.relatedTarget) || (event.relatedTarget.id != "specialchars-tablist")) && ($.inArray(event.relatedTarget, children) == -1)) {EditTools.clearPause(); window.spTimeout = setTimeout(function(){$(EditTools.tablist()).hide();}, ' + this.tabPause + ');}');
$(document).delegate('#specialchars-tablist', 'mouseenter', function(){EditTools.clearPause();});
for (var i=0; i < window.spOptions.subsets.length; i++){
var $a = $('<a>');
$a.text(window.spOptions.subsets[i].caption);
$a.attr('data-spindex', i);
$a.attr('onclick', 'EditTools.selectSubset(window.spActiveTab, ' + i + '); return false;');
var $div = $('<div>');
$div.attr('data-spindex', i);
$div.append($a);
$(tablist).append($div);
}
for (var i=0; i < window.spOptions.sections.length; i++){
var row = table.insertRow(-1);
var $tab = $(row.insertCell(-1));
$tab.attr('style', 'background-color: ' + window.spOptions.sections[i].tabColor + '; vertical-align: top;');
var $tabDiv = $('<div>');
$tabDiv.attr('id', 'spTabDiv' + i).attr('style', 'min-height: 1em; position:relative; padding:2px;').appendTo($tab);
$tab.attr('data-pane', i).attr('id', 'spTab' + i);
$(document).delegate('#spTab' + i, 'mouseenter', function(event){
if ($(event.target).attr('data-pane'))
window.spActiveTab = $(event.target).attr('data-pane');
else
return;
$(EditTools.tablist()).detach().appendTo(EditTools.tab(window.spActiveTab)).show();
EditTools.clearPause();
EditTools.tablistUpdateSelection();
});
$tab.attr('onmouseout', 'if ((event.relatedTarget != EditTools.tablist()) && ($(EditTools.tablist()).find($(event.relatedTarget)).length == 0)) {EditTools.clearPause(); window.spTimeout = setTimeout(function(){$(EditTools.tablist()).hide();}, ' + this.tabPause + ');};');
var $chars = $(row.insertCell(-1));
$chars.attr('title', 'Клацніть, щоб вставити символи у вікно редагування');
$chars.attr('style', 'padding-left:10px; width:100%');
$chars.attr('id', 'specialchars-subsets' + i);
}
$(tablist).detach().appendTo(this.tab(0)).hide();
for (var i=0; i < window.spOptions.sections.length; i++) {
this.selectSubset(i, window.spOptions.sections[i].selected);
}
},
createSubset: function(index){
var subset = $(document.createElement('div'));
subset.attr('id', 'spchars' + index);
subset.appendTo($('#' + this.tableId)).hide();
var tokens = window.spOptions.subsets[index].symbols;
this.parseTokens(subset, tokens);
return subset;
},
selectSubset : function(pane, newCurSubset){
if ((window.spOptions.subsets[newCurSubset] == undefined))
return;
$(this.subset(window.spOptions.sections[pane].selected)).detach().appendTo($('#' + this.tableId)).hide();
for (var i=0; i < window.spOptions.sections.length; i++)
if (window.spOptions.sections[i].selected == newCurSubset)
window.spOptions.sections[i].selected = -1;
window.spOptions.sections[pane].selected = newCurSubset;
//save into cookies for persistence
try {
var expires = new Date ();
expires.setTime( expires.getTime() + 30 * 24 * 60 * 60 * 1000 ); // + 30 days
document.cookie = this.cookieName + pane +"="+ newCurSubset + ";path=/;expires=" + expires.toUTCString();
} catch (err) { }
var pp = document.getElementById(this.tableId).getElementsByTagName('div');
this.tablistUpdateSelection();
var index = newCurSubset;
var $p = $('#spchars' + index);
if (!$p.length)
$p = this.createSubset(index);
$p.show();
$(this.chars(pane)).append($p);
},
parseTokens : function (paragraph, arr){
var i, len = arr.length;
for (i = 0; i < len; i += 1){
if (typeof arr[i] == 'string')
this.parseStr(paragraph, arr[i])
else
if (Object.prototype.toString.call(arr[i]) == '[object Array]')
this.parseArr(paragraph, arr[i]);
else
if (typeof arr[i] == 'object')
this.parseObj(paragraph, arr[i]);
}
},
parseArr: function(paragraph, arr){
this.addLink(paragraph, arr[0], arr[1] || '', {});
},
parseObj: function(paragraph, obj){
if (obj.plain)
this.addText(paragraph, obj.cap || obj.caption, {bold: obj.b || obj.bold, italic: obj.i || obj.italic});
else
if (obj.html)
this.addHtml(paragraph, obj.html);
else {
var $a;
if (obj.ins || obj.insert)
$a = this.addLink(paragraph,
obj.ins || obj.insert,
obj.cap || obj.caption,
{bold: obj.b || obj.bold, italic: obj.i || obj.italic});
else
if (obj.func || obj.function){
$a = this.addFunc(paragraph,
obj.func || obj.function,
obj.cap || obj.caption || obj.ins,
{bold: obj.b || obj.bold, italic: obj.i || obj.italic});
}
if (obj.key){
if (typeof obj.key == 'string')
$a.attr('data-key', obj.key[0].toUpperCase());
else
if (Object.prototype.toStribg.apply(obj.key) == '[object Array]')
$a.attr('data-key', obj.key.join(' ').toUpperCase());
}
}
},
parseStr : function (paragraph, str){
var tokens = str.split(' '), i;
for (i = 0; i < tokens.length; i++)
this.addToken(paragraph, tokens[i]);
},
lineReplace: function(c){
if (c =='/') return '/';
else if (c == '_') return ' '
},
addToken: function(paragraph, token){
function readModifiers(){
var res = {bold: false, plain: false}, i = token.length-1, c;
while ((i > -1) && !spEscaped(token, i)){
c = token.charAt(i).toLowerCase();
if (c == 'ж')
res.bold = true;
else
if (c == 'н')
res.italic = true;
else
if (c == 'п')
res.plain = true;
else
break;
token = token.substring(0, i);
i--;
};
return res;
};
var modifiers = readModifiers();
if (modifiers.plain)
this.addText(paragraph, token, modifiers);
else if (token == '' || token == '_')
this.addText(paragraph, EditTools.charinsertDivider + ' ', modifiers);
else
this.addLink(paragraph, token, '', modifiers);
},
insertFunc: function(e){
EditTools.it($(this).attr('data-open').replace("'", "\'"), $(this).attr('data-close').replace("'", "\'"));
return false;
},
addLink: function(paragraph, token, name, mods){
var tagOpen = token, tagClose = '';
var n = spIndexOfUnescaped(token, '+');
if (n > -1) {
tagOpen = token.substring(0, n);
tagClose = token.substring(n+1);
}
tagOpen = spReplaceSpecsymbols(tagOpen, '/_', this.lineReplace);
tagClose = spReplaceSpecsymbols(tagClose, '/_', this.lineReplace);
if (!name){
name = tagOpen + tagClose;
name = spReplaceSpecsymbols(name, '/$', function(c){if (c == '$') return ''; else if (c =='/') return ''; });
}
var $a = this.addFunc(paragraph, this.insertFunc, name, mods);
$a.attr('data-open', tagOpen).attr('data-close', tagClose);
return $a;
},
addText: function(paragraph, txt, mods){
var elem = $(document.createTextNode(spReplaceSpecsymbols(txt, '/_', this.lineReplace)+' '));
paragraph.append(elem);
if (mods.bold)
elem.wrap('<b></b>');
if (mods.italic)
elem.wrap('<i></i>');
},
addHtml: function(paragraph, elem){
paragraph.append(elem);
},
addFunc: function(paragraph, func, caption, mods){
var $a = $('<a>');
var funcIndex = $.inArray(func, EditTools.functions);
if (funcIndex < 0)
funcIndex = EditTools.functions.push(func) - 1;
$a.attr('data-func', funcIndex);
$a.text(caption).appendTo(paragraph);
if (mods.bold)
$a.wrap('<b></b>');
if (mods.italic)
$a.wrap('<i></i>');
this.addText(paragraph, ' ', mods);
return $a;
},
enableForAllFields : function (){
if (typeof (insertTags) != 'function' || window.WikEdInsertTags)
return;
var texts = document.getElementsByTagName ('textarea');
for (var i = 0; i < texts.length; i++) {
addHandler (texts[i], 'focus', EditTools.registerTextField);
}
// While we're at it, also enable it for input fields
texts = document.getElementsByTagName ('input');
for (var i = 0; i < texts.length; i++)
if (texts[i].type == 'text') {
addHandler(texts[i], 'focus', EditTools.registerTextField);
}
},
last_active_textfield : null,
registerTextField : function (evt){
var e = evt || window.event;
var node = e.target || e.srcElement;
if (!node)
return;
EditTools.last_active_textfield = node.id;
return true;
},
getTextArea : function (){
var txtarea = null;
if (EditTools.last_active_textfield && EditTools.last_active_textfield != "")
txtarea = document.getElementById (EditTools.last_active_textfield);
if (!txtarea) {
// Fallback option: old behaviour
if (document.editform)
txtarea = document.editform.wpTextbox1;
else {
// Some alternate form? Take the first one we can find
txtarea = document.getElementsByTagName ('textarea');
if (txtarea.length > 0) txtarea = txtarea[0]; else txtarea = null;
}
}
return txtarea;
},
it : function (beginTag, endTag){
var textarea = EditTools.getTextArea();
if (textarea != undefined){
$(textarea).insertTag(beginTag, endTag);
}
},
setup : function (){
var $placeholder = $('#' + EditTools.tableId);
if (!$placeholder.length) return;
EditTools.appendExtraCSS();
$('.editOptions').before($placeholder);
$('input#wpSummary').attr('style', 'margin-bottom:3px;'); //fix margins after moving placeholder
EditTools.createEditTools($placeholder);
EditTools.enableForAllFields();
EditTools.created = true;
EditTools.refresh();
}
}; // end EditTools
function spTextArea(){
return $(EditTools.getTextArea());
}
$(function(){
if ((mw.config.get('wgAction') == 'edit') || (mw.config.get('wgAction') == 'submit'))
EditTools.setup();
});