Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
- Opera: Strg+F5
/** Description: AUTOMATIC SIGNING: if not sure, warn.
/** Description: AUTOMATIC SIGNING: if not sure, warn.
*** aut. signing / (automatische Unterschrift) ***
* @documentation: see [[w:de:User:Perhelion/signing]]
* @revision: 17:09, 6. Dez. 2017 (CET)
* @authors:
* created 23.04.2006 [[w:de:User:Olliminatore]] version 1.56 13.03.2007
* updated 23.04.2006 [[w:en:User:Ilmari Karonen]]
* updated 19.09.2011 – 29.12.2011 [[User:Perhelion]], FIX for non Gecko
* updated 31.12.2011 [[w:de:User:PerfektesChaos]], some code improvements
* updated 19.06.2014 Perhelion, fixes (code tidy up, jshint) gimmicks added
* updated 09.09.2014 [[w:de:User:Schnark]] (code cosmetic)
* –2016 Perhelion
* tested only on modern browsers <nowiki>
* @license released dual-licensed under the terms of the GFDL v1.2 or the GPL v2.
* required modules: 'jquery.textSelection','mediawiki.language','mediawiki.util', 'mediawiki.action.edit.preview'
* ToDo: exlcude LivePreview with session save, use hook content
**/
/*global mediaWiki:false, jQuery:false, alert:false, OO */
/*jshint bitwise:true, curly:false, eqeqeq:true, forin:true, laxbreak:true,
trailing:true, undef:true, unused:true */
(function ($, mw) {
'use strict';
if (!mw.libs || mw.libs.threadSign) throw new Error('Initializing of threadSign stopped because it was already executed');
var name = 'AutoSign',
ns = mw.config.get('wgNamespaceNumber'),
$textarea,
$editform,
minoredit,
txtOld = '', // Original text container
CmtE = '', // Comment
txtLen,
lrLen,
txtEndLen,
txtOldEnd,
reUser = '',
signTxt = [], // Contains all local text (only En and De current)
txtOldL;
var c = $.extend({
usersignature: window.usersignature || '-- ~~\~~', // If both are declared the config is prior!
sigAccessKey: '>',
sigText: null // Different styled sig for non automatic
}, mw.config.get([
'wgDBname',
'wgAction',
'wgContentLanguage', // Only for hi
'wgRelevantUserName', // ''
'wgFormattedNamespaces', // ''
'wgUserName', // ''
'wgPageName',
'wgIsArticle'
// "wgUserLanguage",
// "wgTitle"
]));
var ts = {
version: 'v1.93d',
$sigBox: $(),
$label: $(),
onoff: $.noop,
init: function () {
if (ts.config instanceof Object)
c = $.extend(c, ts.config);
if (mw.config.get('wgIsArticle')) return; // Only if editing
var newSection = /section=new/.test(location.search); // Newsections are potential (not always) talkpages
if (!c.sigText) // Not change sig
c.sigText = c.usersignature;
c.sigText = ' ' + $.trim(c.sigText) + '\n'; // Add whitespace
var regpages = null, // New Array(); used in 2 different ways
whitelist = [], // String list
blacklist = [], // Regexp list
p,
len;
$textarea = $('#wpTextbox1');
if (!$textarea.length) return;
minoredit = document.forms.editform.wpMinoredit;
txtOld = $.trim($textarea.val());
txtOldL = txtOld.length;
if (!minoredit || newSection || !txtOldL)
c.dSum = ''; // No auto-summary
if (ns === 4) {
if (c.regpages) { // Option for affected pages
regpages = c.regpages;
if (!$.isArray(regpages) || typeof regpages[0] !== 'string')
return alert('Config error: "' + regpages + '" must be of type string array.');
}
blacklist = [// Default
/\/[Ii]ntro$/, // Intro subpage
/\/[Hh]eader$/, // Header subpage
/\/[Aa]rchive?[ _/]/, // Archiv subpage
// :DR/2016/03/23
/\/\d{4}\/\d\d\/\d\d$/
];
switch (c.wgDBname) { // Search forum pages TODO: API query from Wikidata could be for internationalization
case 'enwiki': // Defaults for en:Wikipedia
whitelist = [// List of all local non-talk-pages, -nns = no new section
':Requests for undeletion', // -nns
':Requests for adminship/', // -nns
':Requests for arbitration/', // -nns
':Requests for permissions', // -nns
' for deletion', // Articles , Miscellany, Templates // -nns
' for discussion', // -nns
':Dispute resolution noticeboard', // -nns
':Adminship survey', // -nns
':Deletion review', // -nns
':Cleanup', // -nns
':SVG help', // -nns
':Arbitration/', // -nns
':Village pump',
':Articles for deletion',
'.*noticeboard.*',
':page protection',
'mediation)',
':Bot requests',
':Help desk',
':Editor review',
':Media copyright questions'
];
break;
case 'dewiki': // Defaults for de:Wikipedia
whitelist = [
':Löschkandidaten/', // -nns
'erkstatt', //Grafik -nns
'wünsche', // Entsperr, Bilder -nns
':Auskunft',
':Café',
':Fragen von Neulingen',
':Fragen zur Wikipedia',
':Verbesserungsvorschläge',
':Urheberrechtsfragen',
':Kandidat', //en, uren
':Löschprüfung',
':Sperrprüfung',
'Meinung', //sbilder, Dritte
':Qualitätssicherung/',
' Bilder', // Diskussionen über, , Redaktion
':Review',
':Vandalismusmeldung',
'/Anfragen', //WP:A/A Bots/
'Notizen', //WP:A/N
':Tellerrand',
':WikiProjekt Vorlagen',
':Projektdiskussion',
':WikiProjekt Wappen',
':Redaktion '
];
break;
case 'commonswiki': // Wikimedia Commons // TODO: ATTENTION on whole project should ns default on, because multilingual
whitelist = [
'illage pump', // En
':Forum', // De
':Help desk',
':Bots/',
' requests/', // Deletion
' candidates/', // Quality
':Administrators\' ',
':Translators\' noticeboard',
' for discussion/',
':Graphic Lab'
];
break;
}
for (p = 0, len = blacklist.length; p < len; p++) { // If page in blacklist empty whitelist
if (blacklist[p].test(c.wgPageName)) {
whitelist = '';
break;
}
}
regpages = (regpages && whitelist) ? whitelist.concat(regpages) : whitelist;
if (newSection /*|| $('#ca-addsection').length*/)
regpages = ''; // Talkpage
if (whitelist) { // Not on blacklist
if ($.isArray(regpages)) // need check
for (p = 0, len = regpages.length; p < len; p++) {
if (c.wgPageName.indexOf(regpages[p].replace(/ /g, '_')) !== -1) {
regpages = '';
break;
}
}
if (c.wgDBname === 'enwiki') { // special cat for enwiki
$('.hiddencats li').children('a').each(function () { // check first for cat
if (this.text && /Non-talk pages that are automatically signed$/.test(this.text))
regpages = '';
});
}
/** Only on ns:4 in edit on "non" talk pages (and "non" new-section and not at blacklist) to check possible forum page. NOTE: [[phab:T22177]]
Get all included "pageprops" from the wiki page to recognize pages with newsectionlink. **/
if (regpages) { // Not registered // anyway if not check per api??? should never happen
$.when(mw.loader.load('mediawiki.util'), $.ready).then(function () {
$.getJSON(mw.util.wikiScript('api'), {
action: 'query',
prop: 'pageprops',
indexpageids: true,
titles: c.wgPageName,
cache: true,
format: 'json',
timeout: 3000
}).done(function (json) {
if (!json || !json.query || !json.query.pages)
return console.log('could not get page info!');
json = json.query.pages[json.query.pageids];
if (json && json.pageprops && typeof json.pageprops.newsectionlink !== 'undefined') { // Newsectionlink=""
if ('Perhelion' === c.wgUserName) alert('AutoSign: Page was still not registered!');
c.regpages = '';
}
}).always(function () {
ts.exec();
});
});
} else {
c.regpages = '';
return ts.exec();
}
}
} else {
if (ns % 2 === 1)
regpages = ''; // Always talk-pages
c.regpages = regpages; // If regpages is null, ns is not valid
if (regpages === '' || ns === 2)
return ts.exec();
}
},
/** Count occurrences
* @para {string}
* @return single tuple {array of numbers}
**/
_cntOcc: function (txt) {
// Sig-counter
var sig = /~{3}/g,
falseRe = /(\{\{subst\:unsig.*?\}\})|(<(nowiki|pre)>[\s\S]*?<\/(nowiki|pre)>)|(<!--[\s\S]*?-->)/ig,
count = 0, // counter (falseRe)
cnt2 = 0, // False sig-counter (count)
cnt = 0; // General sig-counter (sig)
while (sig.test(txt))
cnt++;
count = txt.match(falseRe) || [];
for (var i = 0; i < count.length; i++) {
while (sig.test(count[i]))
cnt2++;
}
mw.log(cnt, cnt2);
return [cnt, cnt2];
},
setIndentation: function (e) {
var $textarea = $(e.target);
var txt = $textarea.val();
// lrI = txt.lastIndexOf(r) + 1, // Last line index
var pos = $textarea.textSelection('getCaretPosition'); // current linebreak
if (!pos)
return;
var txtE = txt.slice(pos);
txt = txt.slice(0, pos); // all text before
var lrI = txt.lastIndexOf('\n');
var line = txt.slice(lrI + 1, pos);
this.indent = ""; // Gimmik for remove last indent
if (line) {
var lrM = line.match(/^[:*#]+ ?/); // last indent match
if (lrM) {
var lrLen = lrM[0].length;
//log("line pos: '%s'", line, line[0],lrLen, pos , lrM);
if (lrLen) {
$textarea.val(txt + '\n' + lrM[0] + txtE);
this.indent = [pos, lrLen];
lrLen++;
$textarea.textSelection('setSelection', {
start: pos + lrLen
});
e.preventDefault();
}
}
}
return;
},
rmvIndentation: function (e) {
var $textarea = $(e.target);
var txt = $textarea.val();
var pos = this.indent[0];
if (pos) {
pos++;
var txtE = txt.slice(pos + this.indent[1]);
txt = txt.slice(0, pos); // trim last indentation
this.indent = "";
$textarea.val(txt + txtE);
$textarea.textSelection('setSelection', {
start: pos
});
return e.preventDefault();
}
},
/**
* @brief Search Html comment span at text-end
*
* @param [string] txt
* @return stript txt and CmtE
*/
stripeCmt: function (txt) {
var cmtRe = /(<!--[\s\S]*?-->)(?!\n*<!--)/g;
var cmtRight = '';
CmtE = ''; // restet global var
while (cmtRe.test(txt)) { // Really html comment
CmtE = '\n' + RegExp.lastMatch;
}
if (CmtE) {
var cmtEndI = cmtRe.lastIndex;
//log("Comment at text end found. " , "/(<!--[^]*?-->)(?!\n*<!--)/.test(txt)", txt.length, CmtE, cmtEndI );
if (cmtEndI < txt.length && cmtEndI > txtLen) { // some added rigth
cmtRight = txt.slice(cmtEndI); // RegExp.rightContext
}
txt = $.trim(txt.slice(0, cmtEndI - CmtE.length));
CmtE += cmtRight;
}
return txt;
},
/** Remove last trailing indent and auto. salute and save txt to DOM **/
rmvIndent: function (txt, cmtE, pos) {
var lrLi = 0;
// txt = $.trim(txt); // need trim entire content? can't because we need trim reUser too
//log('txt before rmvIndent', txt, "\n--SPLIT--\n", cmtE,"\nlrLen:", lrLen, " pos:", pos, reUser);
if (lrLen) { // Only if was set
if (cmtE) { // Remove indent before comment
txt = ts.stripeCmt(txt);
cmtE = CmtE; // CmtE has higher scope
/* var txtStr = stripeCmt(txt);
if ( !/-->$/.test(txt) ) {
if ( ts.cmtEndI > txtLen ) {// post after Cmt ???
var cmtRight = txt.slice(ts.cmtEndI);
console.log( "Where is the Cmt? txt.length: %i, txtLen: %i, cmtEndI: %i, cmtE.length: %i", txt.length, txtLen, ts.cmtEndI, cmtE.length, txt, "\n--SPLIT--\n", cmtE)
cmtE += cmtRight; // add text after cmt
//if (! cmtE) $textarea.val(txt);
//return txt;
} else cmtE = ''; // Comment removed or not anymore at end
txt = txtStr;
} */
}
lrLi = txt.lastIndexOf('\n'); // Last reply-line index
//log( "Trimmed last indent line. ",lrLi, new RegExp("\\n[:*# ]+\\s*" + $.trim(reUser) + "\\s*").test(txt.slice(lrLi)) + '\n"'+ txt.slice(lrLi) + '"' );
txt = txt.slice(0, lrLi) + txt.slice(lrLi).replace(new RegExp('\\n[:*# ]+ *' + $.trim(reUser) + ' *$'), '') + cmtE;
reUser = '';
lrLen = ''; // global reset
}
$textarea.val(txt);
if (pos)
$textarea.textSelection('setSelection', {
start: pos // - 1 ?
}).textSelection('scrollToCaretPosition').focus();
return txt;
},
/**
* @brief Get users on topic text
* @return [array tuple [string]] posterList (last first)
**/
getUser: function () {
// lrR = $textarea.textSelection('getCaretPosition');
var txt = txtOld;
// \[\[(?:user(?:in)?|User talk|User(?:[ _]talk)?): ?([^\]\|[]+)(?:\|([^\]\[]+))?\]\]
var strRe = new RegExp('\\[\\[(?:' + c.wgFormattedNamespaces[2] +
'(?:in)?|' + c.wgFormattedNamespaces[3] +
'|User(?:[ _]talk)?): ?([^\\]\\|[]+)(?:\\|([^\\]\[]+))?\\]\\]', 'ig');
var topicPosters = txt.match(strRe) || [];
var posterList = [];
// Unique
topicPosters = $.grep(topicPosters.reverse(), function (p, i) {
return $.inArray(p, topicPosters) === i;
});
var u = topicPosters.length,
reUser,
userDict = {}; // : [id, name]
while (u--) // (reverse order)
while (strRe.test(topicPosters[u])) {
reUser = RegExp.$1;
if (!mw.util.isIPAddress(reUser, false))
posterList.push([RegExp.lastMatch, reUser, RegExp.$2]);
}
// console.log(posterList);
posterList = $.grep(posterList, function (p, i) { // Unique
return $.inArray(p, posterList) === i;
});
u = posterList.length;
var d = 0; // delete counter
while (u--) {
reUser = posterList[u];
var userMatch = reUser.shift();
if (reUser[1]) { // detailed check
if (new RegExp(c.wgFormattedNamespaces[3] + '|User[ _]talk', 'i').test(userMatch))
reUser.pop();
else {
reUser[1] = $.trim(reUser[1]);
if (reUser[1].indexOf(' ') > 3)
reUser.pop();
}
}
var user = reUser[0] || '';
user = $.trim(user.replace(strRe, '$1'));
if (user) {
// console.log(u, user, reUser[1]);
if (user.indexOf('/') !== -1)
user = user.split('/')[0];
else if (user.indexOf('#') !== -1) {
user = user.split('#')[0];
}
if (reUser[0] !== user) {
reUser = [user]; // remove 2.
} else if (reUser[1] && reUser[1] === user)
reUser.pop(); // remove 2.
if (user === c.wgUserName || userDict[user] && (!reUser[1] || userDict[user][1])) {
// console.log("splice", u, d, posterList[u],"==",user, userDict[user]);
posterList.splice(u, 1);
d++;
} else if (userDict[user]) { // Strong dupe with 2., so replace
posterList.splice(userDict[user][0] - d, 1);
d++;
// console.log("Strong dupe",u, d, userDict[user][0] - d, userDict[user]);
posterList[userDict[user][0] - d] = reUser;
} else {
userDict[user] = [u, reUser[0]];
posterList[u] = reUser;
}
}
} // @reverse only yet because dupe filter
// return posterList.reverse();
return posterList.reverse();
},
/**
* Converts the raw data to DOM objects
* @param {event} e
* @param {string} userName
* @param {string} userNick
**/
createDropDown: function (e) {
e.stopPropagation();
e.preventDefault();
var tar = e.target, userName, userNick;
var selOkTxt = '--- OK ---'; // Button option for multiple select
var posterList = this.getUser();
// console.log("posterList", posterList.length, posterList);
posterList = $.grep(posterList, function (p) {
return (p && p[0] !== c.wgRelevantUserName);
});
var u = posterList.length;
var posterLen = u;
while (u--) {
var reUser = posterList[u];
userName = reUser[0];
userNick = (reUser[1]) ? reUser[1] : '';
// log(u, reUser, reUser[1], userName, userNick);
// FIXME: sometimes userName is empty on clutter load!?
userNick = (userNick || userName).replace(/\w\-+\w/g, ' ').replace(/(\w)[\d-\.]+|[\d-\.]+(\w)/g, '$1$2'); // cleanup
posterList[u] = $('<option>', {
value: userName,
text: userNick
});
}
/**
* Get selected drop-down values
* @param {HTMLobject} tar
* @return {array} userName
**/
var getSelectedOptions = function getSelectedOptions(tar, event) {
var sel = this || document.getElementById('mention-select'),
opts = sel.options,
oLen = opts.length - 1,
bOk;
// console.log("getSelectedOptions", tar, userName);
ts.userNames = '';
// if (!userName || !$.isArray(userName)) // change to array
userName = [];
if (oLen > 0) {
var ind = opts[opts.selectedIndex];
if (ind && ind.value !== selOkTxt) {
if ( tar.title === 'Ping' && event.ctrlKey ) {
bOk = opts[oLen].selected;
for (var i = 0; i < oLen ; i++) {
var opt = opts[i];
if (opt.selected)
userName.push(opt.value);
}
} else {
var $sel = $(sel).hide();
userName = [ind.value, ind.text];
if (tar.title === 'Ping') {// stripe nick
userName.pop();
ind.value = opts[oLen].value;
ind.selected = false;
}
// console.log(tar.title, userName, sel, $sel);
window.setTimeout(function () {
$sel.show();
}, 200);
}
if (!event.ctrlKey || bOk)
return tar.onclick(userName);
ts.userNames = userName;
}
else return tar.onclick('');
}
};
if (posterLen < 2) {
userName = [userName];
if (tar.title !== 'Ping')
userName.push(userNick);
return tar.onclick(userName);
}
var $sel = $('<select>', {
id: 'mention-select',
multiple: 1
}).on('click', function (e) {
getSelectedOptions(tar, e);
}).append(posterList).on("blur", function (e) {
if (!e.ctrlKey)
$(this).parent().remove();
});
if (tar.title === 'Ping')
$sel.append($('<option>', {
style: 'text-align:center',
text: selOkTxt
}));
$sel.attr("size", Math.min(6, posterLen));
$sel = $('<div>').css({
position:"absolute",
top: tar.offsetTop - 22,
left: tar.offsetLeft + 24,
zIndex: 77
}).append($sel);
$(tar).parent().append($sel);
$sel.children().first().focus().find("option:first").prop("selected", 1);
// return false;
},
/** Ping template (per button) in textarea
* @param {event/array} e/userName
* @param {array} ts.userNames (multiple)
**/
setPing: function (e) {
if (e.target && !($textarea.textSelection('getSelection') || ts.userNames))
return this.createDropDown(e);
else if (ts.userNames && e.target) {
e = ts.userNames;
ts.userNames = '';
}
e = (e && e[0]) ? e.join('|') : '';
return $textarea.textSelection('encapsulateSelection', {
pre: '{{Ping|',
peri: e,
post: '}}'
});
},
/** Link user
* @param {event/array} e/userName
**/
mentionUser: function (e) {
var isSel = $textarea.textSelection('getSelection');
if (e.target && !(isSel || e.ctrlKey))
return this.createDropDown(e);
return $textarea.textSelection('encapsulateSelection', {
pre: '[[' + c.wgFormattedNamespaces[2] + ':',
peri: (e[0] || '') + '|' + (e[1] || ''),
post: isSel ? '|]]' : ']]'
});
},
enable: function () {
if (this.enabled)
return;
this.toggleSigBox();
this.enabled = true;
this.onoff(1);
},
disable: function () {
if (!this.enabled)
return;
this.toggleSigBox();
this.enabled = 0;
this.onoff(0);
},
exec: function () {
$editform = $('#editform');
var r = '\n',
sum = document.forms.editform.wpSummary, // oojs
n = /\n/,
//maxlLength = 40, // CUSTOM: line-length for trim sigtext css
bSig = !(c.regpages !== '' || ns === 2); // Recognize on non talk-pages
if (window.wikEd)
return alert(name, 'is not compatible with wikEd');
if (bSig)
ts.useLivePreview();
$textarea.on('keypress', function (e) { // Gimmik automatic indention
if (e.which === 13) // enter
return ts.setIndentation(e);
if (e.which === 8 && ts.indent) // backspace
return ts.rmvIndentation(e);
ts.indent = ""; // reset
});
/** Localized strings **/
if (mw.language && $.inArray('de', mw.language.getFallbackLanguageChain()) !== -1) // Local translations
// FIXME: could use int: mw.msg( 'prefs-signature') still not exists !?
signTxt = {
boxTitle: 'Signiere automatisch. ',
boxText: 'Signieren',
//'formWarn' : "Automatische Signatur: Kein Bearbeitungsfeld gefunden!",
btnAlt: 'Manuelle Signatur',
confirm: 'Es wurde keine Signierung gefunden. Trotzdem fortfahren?'
};
else
signTxt = {
boxTitle: 'Sign this edit automatic. ',
boxText: 'AutoSign',
//'formWarn' : 'Automatic thread signing: No edit field found!',
btnAlt: 'Manual signing',
confirm: 'No signing was found. Continue anyway?'
};
signTxt.hi = ($.inArray(c.wgContentLanguage, ['de', 'nn', 'da']) !== -1) ? 'Hallo ' : 'Hey ';
if (!$editform.length)
return;
/** Put outent string (per button) in textarea **/
ts.setOutdent = function () {
var txt = $textarea.val();
var outdent = (c.wgDBname === 'enwiki') ? '\n{{od}}' : '\n:┌{{padleft:┘|22|─}}\n'; // ToDo: 22 could be dynamically?
var pos = $textarea.textSelection('getCaretPosition');
if (pos < 0) // Go text-end, no caret position found
pos = txt.length;
//log('the last 2 signs before and last 3 signs after setOutdent: %O%O%O%O%O',txt[pos-2],txt[pos-1],txt[pos],txt[pos+1],txt[pos+2]);
if (n.test(txt[pos]) && !n.test(txt[pos - 1]))
pos++; // Trim
if (txt[pos + 1] && n.test(txt[pos + 1]))
pos++; // Redundant new line
ts.rmvIndent($.trim(txt.slice(0, pos)) + outdent + txt.slice(pos), CmtE);
};
/** Put sig in textarea **/
this.doSig = function (e, pos) {
var txt = $textarea.val();
var sigText = c.sigText;
if (!pos) { // Post-end (from button)
pos = $textarea.textSelection('getCaretPosition');
// sigText = sigText.replace( /\n+$/, '' );
} else {
if (pos < 0) // Go text-end, no caret position found
pos = txt.length;
var tmpRe = /\}\}\s*$/;
if (tmpRe.test(txt.slice(pos - 4, pos))) { // If a template on post end
var lrIndex = txt.slice(0, pos).replace(/\s+$/, '').lastIndexOf(r); // Last post line
var tmpTxt = txt.slice(lrIndex, pos);
//log('Template found on post ', lrIndex, tmpTxt );
if (/\|1\s*=[^\n]*(\}\})\s*$/.test(tmpTxt)) { // Check last line for propper template
tmpRe = tmpTxt.search(tmpRe);
//log('Propper template on post end? %i : "%s"', tmpRe , RegExp.$1, tmpTxt.length );
pos = lrIndex + tmpRe;
sigText = sigText.replace(/\n+$/, '');
}
}
}
/*sigText = ( pos > maxlLength && !n.test( txt.slice( pos - maxlLength, pos ).replace( /\n+$/, '' ) ) ) ? // Check line length (*it's more a gimmick: <br> and *, #, : are not recognized)
sigText : sigText.replace( /white-space:nowrap;?/, '' ).replace( ' style=""', '' ); // If short then not needed (only simple remove for default sigText) */
//log('the last 2 signs before and last 3 signs after signing: "%O%O" : "%O%O%O"',txt[pos-2],txt[pos-1],txt[pos],txt[pos+1],txt[pos+2]);
//log('txt.slice(pos %i, pos) %o length: %o , pos: %i', txtEndLen, txt.slice(pos-txtEndLen, pos), txt.slice(pos-txtEndLen, pos).length, pos);
if (txt[pos] && n.test(txt[pos]) && !n.test(txt[pos - 1]))
pos++; // Trim
if (txt[pos + 1] && n.test(txt[pos + 1]))
pos++; // Redundant new line
this.rmvIndent(txt.slice(0, pos).replace(/\s+$/, '') + sigText + txt.slice(pos), CmtE, pos);
if (c.dSum) {
if (!/^(?:(\/\*.+?\*\/ *))([^ ])|^([^/]+?)(?!\/\*.+?\*\/ *)/.test(sum.value)) { // If nothing, exept section title
sum.value += c.dSum; // Add a optional default summary
}
c.dSum = ''; // Only once
}
return e;
};
/* For POST get checkbox value in preview- and diff- form submit.
Save only if should no sig set (with 'true').
Load previous checkbox setting from wpAutoSummary (hack); true means not checked */
// the sig checkbox.
var oBox = {
'type': 'checkbox',
'name': 'wpAutoSign',
'id': 'wpAutoSign',
'checked': bSig,
'accessKey': c.sigAccessKey,
selected: bSig
};
var oLabel = {
'for': 'wpAutoSign',
title: signTxt.boxTitle + ts.version,
align: 'inline',
// text: signTxt.boxText,
label: signTxt.boxText
};
var cURL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/';
var $sBtn = $('<img>', { // New sig button
src: cURL + '5/52/Tango_style_insert-signature_icon.svg/22px-Tango_style_insert-signature_icon.svg.png',
'title': 'AutoSign',
'rel': 'AutoSign',
'alt': signTxt.btnAlt,
'width': '22',
'onclick': 'mw.libs.threadSign.doSig(event)', // Attention: validity get not checked as with submit buttons
'height': '22',
'style': 'cursor:pointer',
'class': 'tool'
}); // New sig button
var $pBtn = $sBtn.clone().attr({
src: cURL + 'f/fa/Tango_style_ping_icon.svg/22px-Tango_style_ping_icon.svg.png',
title: 'Ping',
alt: '',
rel: 'Ping',
onclick: 'mw.libs.threadSign.setPing(event)'
}); // Ping button
var $mBtn = $sBtn.clone().attr({
src: cURL + '6/6b/Flow-mention.svg/22px-Flow-mention.svg.png',
title: 'Mention',
alt: '',
rel: 'Mention',
onclick: 'mw.libs.threadSign.mentionUser(event)'
}).css({
opacity: '.6'
}); // Mention button
var $oBtn = $sBtn.clone().attr({
src: cURL + 'b/bb/Tango_style_outdent_icon.svg/22px-Tango_style_outdent_icon.svg.png',
title: 'Outdent',
alt: '',
rel: 'Outdent',
onclick: 'mw.libs.threadSign.setOutdent()'
}); // Outdent button
var threadSignBtns = [
$sBtn,
$oBtn,
$pBtn,
$mBtn
];
/** Check for HTML comment at end (like in graphic-labs) **/
var txt = txtOld; // Make copy
if (!txt) {
if (ns === 3 && !minoredit && c.wgRelevantUserName !== c.wgUserName) { // Greetings gimmick, only on new section
txt = signTxt.hi + c.wgRelevantUserName + ',\n';
$textarea.val(txt).textSelection('setSelection', {
start: txt.length - 1
}).focus(); // Set caret at end
}
} else if (/-->$/.test(txt)) // Check last 3 chars
txt = ts.stripeCmt(txt); // Comment found
/* Gimmick: Put automatically indent text for reply if possible
(if not used, rmvIndent() executed in doSign() or doSig()) */
txtLen = txt.length;
var lrI = txt.lastIndexOf(r) + 1, // Last line index
lrR = txt.slice(lrI),
lrM = lrR.match(/^[:*#]+/); // Last reply-line match
lrLen = (lrM) ? lrM[0].length : 0; // new reply indent length
// Text-End-Length: CUSTOM negative, max 18 characters
txtEndLen = (Math.min(18, lrR.length) + lrLen) * -1;
txtOldEnd = txtOld.slice(txtEndLen);
//log("last line: ",txtEndLen, lrR,lrLen,lrM, c.autoSalut);
if (c.wgAction === 'edit') { // Do only on first load
if (lrLen && lrI + lrLen + 1 < txtLen) { // Only if text on indent
lrI = ':';
lrLen++;
if (lrLen === 2 && lrM[0] !== ':') { // Mainly no indent raise for votes
lrI = '';
lrLen = 1;
}
$textarea.val(txt + r + lrM[0] + lrI + ' ' + CmtE);
//log("Last indent reply line setted at. ", txtLen, lrLen);
txtLen += lrLen + 2;
} else if (!lrLen && c.autoSalut && txtOldEnd && /\d{4} \((?:CES?T|UTC)\)[^\d]*?$/.test(lrR)) { // New thread salut only Opt-in
lrLen++;
reUser = this.getUser().pop() || "";
lrI = '';
if (reUser) {
lrI = ': ';
reUser = (reUser[1] && /\w+/.test(reUser[1])) ? reUser[1] : reUser[0];
// FIXME: reUser[1] could included HTML?
if (reUser)
reUser = signTxt.hi + reUser.replace(/\d+$/g, '') + ', '; // one wspace
lrLen += reUser.length;
}
txtLen += lrLen + lrI.length;
$textarea.val(txt + r + lrI + reUser + CmtE);
} else {
lrLen = 0;
if (txt) { // Start always with new line?
txtLen++;
$textarea.val(txt + r + CmtE);
}
}
}
if (bSig && txt)
$(window).on('load', function () { // Scroll bottom on start
$textarea.textSelection('setSelection', {
start: txtLen
})
.textSelection('scrollToCaretPosition').focus();
});
//log("INIT: txtOldEnd: %s, lrLen = %s", txtOldEnd, lrLen);
if (minoredit) {
this.offSigBox(minoredit); // Run function
$(minoredit).on('change', this.offSigBox); // Add function
}
$('#wpSave, #wpPreview, #wpDiff, #wpMinoredit').on("click", {
name: name
}, ts.doSign);
var makeCheckbox = function ($label) {
// ts.$sigBox.on('change', ts.toggleSigBox);
return $label
.css({
'padding': '2px 6px',
'border-color':(!bSig? '#C00;color:#A00':'')
})
.addClass((bSig? 'usermessage' : ''))
.after(threadSignBtns);
};
var makeCheckboxOOUI = function () {
mw.loader.using('oojs-ui-core').done(function () {
var checkbox = new OO.ui.CheckboxInputWidget(oBox);
var field = new OO.ui.FieldLayout(checkbox, oLabel);
ts.onoff = function (on) {
checkbox.setSelected(on);
};
ts.$sigBox = checkbox.$element.find('input').updateTooltipAccessKeys();
ts.$label = makeCheckbox(field.$element.find('label'));
ts.$sigBox.on('change', ts.toggleSigBox);
checkbox.on('change', function () {
if (checkbox.isSelected())
ts.enable();
else
ts.disable();
});
// oo-ui-fieldLayout-body
$('.editCheckboxes > .oo-ui-layout').append(field.$element);
});
};
var makeCheckboxCommon = function () {
oLabel.text = oLabel.label; // Use OOUI label
var $label = $('<label>', oLabel),
$checkbox = $('<span>').addClass('oo-ui-fieldLayout-body');
ts.$sigBox = $('<input>', oBox)
.on('change', ts.toggleSigBox).updateTooltipAccessKeys();
ts.$sigBox.on('change', ts.toggleSigBox);
$checkbox.append(ts.$sigBox, $label);
ts.$label = makeCheckbox($label);
$('.editCheckboxes').append($checkbox);
};
// Add sig controls to the bottom Wiki-edit-options (if all code passed).
if (!ts.$sigBox.length) { // Only once
// Insert signing checkbox in ".editCheckboxes"
if ($('#wpSummaryLabel').hasClass('oo-ui-layout'))
makeCheckboxOOUI();
else
makeCheckboxCommon();
}
//log(name, bSig,c.regpages,$("#wpAutoSign").prop('checked', bSig));
}, // exec end
useLivePreview: function () { // LivePreview needed
if (((minoredit && !minoredit.checked) || ts.$sigBox.prop('checked'))
&& mw.user.options.get('uselivepreview') != 1) {
mw.user.options.set('uselivepreview', 1);
mw.loader.load('mediawiki.action.edit.preview');
}
},
toggleSigBox: function () {
ts.useLivePreview();
ts.$label.toggleClass('usermessage');
},
offSigBox: function (e) { // Switch enable box
if (e.type && e.type === 'change')
e = e.target;
// if (e === minoredit) {
if (!ts.$sigBox.prop('checked'))
return;
// console.log (ts.$sigBox.prop('disabled'));
// } else ts.toggleSigBox();
ts.$sigBox.prop('disabled', e.checked);
},
doSign: function (e) {
/**
* @brief Try check edit for sure: return pos
*
* @param [string] c for check
* @param [number] pos for position
* @return pos
*/
function _chkEdit(c, pos) {
pos = txt.indexOf(c, pos--); // Go line-end (caret, really in last line in post)
if (pos > 1) { // not last line
var txtEnd = txt.substr(pos, 24).replace(/(^\s+)/, ''); // CUSTOM value 24? try after post-end; trimLeft = RegExp.$1
var txtNewEnd = txt.slice(pos - 16, pos); // CUSTOM for compare only: 16 chars tolerance before post-end
var oldp = (c === '\n') ? txtOld.lastIndexOf(txtEnd) : 0; // Old pos off test-end, not if CmtE
if (!oldp && /-->$/.test(txtEnd)) { // test pos is not in HTML comment
c = txt.lastIndexOf('<!--');
if (c < pos) // pos is illegal in cmt
pos = c--;
//oldp = 0;
}
// if some added
if (oldp !== -1 && oldp < pos - 3 && txtOld.indexOf(txtNewEnd + RegExp.$1 + txtEnd) === -1) {
//log('Check edit? txtEnd: "%s",\ntxtNewEnd: %s, oldp: %i, pos: %i, RegExp.$1: "%s" ', txtEnd, txtNewEnd, oldp, pos, RegExp.$1);
return pos;
}
}
}
var tolerance = 4;
var txt = $.trim($textarea.val());
if ((minoredit && minoredit.checked) || !ts.$sigBox.prop('checked')) // minor edits get not signed
return ts.rmvIndent(txt, CmtE); // Remove indent always?
if (!txt)
return; // Text deleted?
var cOld = ts._cntOcc(txtOld),
cNew = ts._cntOcc(txt), // Occurrences count
oldp, // Old pos off post-end
txtEnd, // Array of 2 possibilities: with and without indent
pos;
//log(" new general sig counter: %d, %d; old false: %d, %d", cNew[0], cNew[1], cOld[0], cOld[1]);
/*If (cNew[0] >= cOld[1]) { // if there is an a sign added, check for true
cNew[0] -= cNew[1]; // evaluate hit count, a true sig?
// cOld[0] -= cOld[1]; // FIXME: old sig-counter, must be theoretically always 0; NOT working because on submit the old value get lost
console.assert(cOld[0] - cOld[1], " ERROR: the RegExp sig-counter must been wrong ", cOld[0]);
log("old false sigs = %s new sig counter %s new false sigs: %d", cOld[1], cNew[0], cNew[1]);
}*/
if (cNew[0] - cNew[1]) // There is already a sig
return ts.rmvIndent(txt, CmtE);
/** IF no sig then search a set position **/
if (cNew[0] <= cOld[1]) {
/** First try compare old and new last line **/
if (!CmtE) { //FIXME: this first check for CmtE not supported yet
// lrLen = ; Don't compare aut. indent
mw.log("lrLen,txtEndLen", lrLen, txtEndLen);
txtEnd = [txt.substr(txtEndLen - (lrLen ? lrLen + 1 : 0), -txtEndLen), txt.slice(txtEndLen)]; // CUSTOM txtEndLen txt-length, must be same length as txtOldEnd; txtEndLen lrLen is auto indent-length
oldp = txtOldL + txtEndLen;
//log('txtOldEnd %i: "%s" ; txtNewEnd %i: "%s"',txtOldEnd.length,txtOldEnd,txtEnd.length,txtEnd, oldp, txt.lastIndexOf(txtOldEnd)); // HINT: DEBUG CRITICAL;
// Compare last line
mw.log('Is last line identical?\n"%s"\n"%s"', txtOldEnd, txtEnd, txtEnd[1]);
if (!txtOld || $.inArray(txtOldEnd, txtEnd) < 0) { // && oldp > txt.lastIndexOf(txtOldEnd) - tolerance // then some txt between
return ts.doSig(e, -1); // Aut. underwrite on last line
}
}
// if not the post is between, because last line is identical
pos = $textarea.textSelection('getCaretPosition'); // MW: 1.18 Get the caret position in a txtarea
pos = _chkEdit('\n', pos);
if (pos) {
return ts.doSig(e, pos); // Aut. underwrite
}
/** THEN the caret is NOT in the NEW txt **/
// Alert('caret is NOT in the NEW txt') // DEBUG
txt = ts.rmvIndent(txt, CmtE); // Remove auto indent (only if not used)
if (txt.length > (txtOldL + tolerance)) { // If more then the tolerance amount chars are added, find the last new line
var lines = txt.match(/.+/gm) || [],
linesOld = txtOld.match(/.+/gm) || [];
//log('lines.length = %i; txt.length = %i; linesOld.length = %i; txtOldL = %i;',lines.length,txt.length,linesOld.length,txtOldL+tolerance);
if (lines.length > linesOld.length) { // Only if line added
lines = lines.filter(function (x) {
return linesOld.indexOf(x) < 0;
}).pop() || []; // Last changed line
// The .get(-1) and not() method works here too, but is not documented for non DOM elements!?
if (lines.length) { // If empty the changed line was exactly also in the old txt
pos = txt.lastIndexOf(lines);
//log("txt.length = " + txt.length + "; caret at: " + pos + " old= " + txtOldL + " : " + txtOld.indexOf(lines));
return ts.doSig(e, pos + lines.length);
}
/* Fixme?: need fallback case?
This case can only happen, if the only new line (without caret on it) is a fully duplicate of another in the txt. */
} // Else return; // no new line, do nothing (also no warning)!?
}
/* FIXME?: need fallback case?
This case can only happen, if some txt was removed and the new comment was not at the end and the caret was not in the new comment. */
// Rare fallback case: if the last txt is a HTML-comment, check the beginning of the comment.
if (CmtE) {
pos = _chkEdit('<!--', txtLen);
//log("Rare fallback case: %i ", txtLen,txt.slice(txtLen,txt.indexOf('<!--', txtLen)) );
if (pos) // Post-end
return ts.doSig(e, pos); // Aut. underwrite
}
}
// FIXME DEBUG: not for Diff and Preview, values are reseted
// if ('Perhelion' === c.wgUserName && /^wp(Diff|Preview)$/.test(e.target.name)) alert(name + ' main-edit, but no auto-signature position found!');
if (e.target.name === 'wpSave') { // Warn only on save submit!?
$editform.submit(function (e) {
if (!confirm(signTxt.confirm)) {
e.preventDefault();
e.stopPropagation();
mw.log.warn(name, txtOld, txt);
return false;
}
});
}
return e;
}
};
mw.libs.threadSign = ts;
if (mw.config.get('wgPageContentModel') === 'wikitext') {
$(document).trigger('loadWikiScript', ['Perhelion/signing.js', ts]); // For bind config
$.when(mw.loader.using(['jquery.textSelection', 'mediawiki.language', 'user.options', 'mediawiki.util']), $.ready).then(function () {
$(ts.init);
});
}
}
(jQuery, mediaWiki));
// EOF </nowiki>