User:Kephir/gadgets/rater/goldfish.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:Kephir/gadgets/rater/goldfish. This user script seems to have an accompanying .css page at User:Kephir/gadgets/rater/goldfish.css. |
/*
* Goldfish
* Copyright © 2013 Keφr at the English Wikipedia
*
* Goldfish is released under the terms of the GNU GPL version 2 or later,
* with the exception of a few specific functions released to the public domain.
* For the purpose of licensing, the various data used should not be considered
* a part of the script.
*
* "Goldfish" suggests this is something small, but this does not seem to be the case, actually.
* I should have probably called it "Moby Dick". After my own.
*/
// <nowiki>
mw.loader.using([
'mediawiki.util',
'mediawiki.api',
'mediawiki.Title',
/* XXX: drop these two dependencies */
'jquery.ui',
'jquery.ui'
], function () {
"use strict";
if (wgNamespaceNumber < 0)
return;
var GOLDFISH_VERSION = '2013-02-24';
var GOLDFISH_ADVERT = ' (with Goldfish)';
var settings = window.kephirGoldfish || {
promptToAssess: 0
// 0 = do not prompt, do not check even
// 1 = prompt to assess: highlight icon
// 2 = obnoxious prompt to assess: that, and display a popup message
};
importStylesheet('User:Kephir/gadgets/rater/goldfish.css');
// UI helper functions
//{ THESE FUNCTIONS ARE PUBLIC DOMAIN
var sh = {
el: function (tag, child, attr, events) {
var node = document.createElement(tag);
if (child) {
if ((typeof child === 'string') || (typeof child.length !== 'number'))
child = [child];
for (var i = 0; i < child.length; ++i) {
var ch = child[i];
if ((ch === void(null)) || (ch === null))
continue;
else if (typeof ch !== 'object')
ch = document.createTextNode(String(ch));
node.appendChild(ch);
}
}
if (attr) for (var key in attr) {
if ((attr[key] === void(0)) || (attr[key] === null))
continue;
node.setAttribute(key, String(attr[key]));
}
if (events) for (var key in events) {
node.addEventListener(key, events[key], false);
}
return node;
},
link: function (child, href, attr, ev) {
attr = attr || {};
ev = ev || {};
if (typeof attr === 'string') {
attr = { "title": attr };
}
if (typeof href === 'string')
attr.href = href;
else {
attr.href = 'javascript:void(null);';
ev.click = href;
}
return sh.el('a', child, attr, ev);
},
item: function (label, href, attr, ev, clbutt) {
return sh.el('li', [
sh.link(label, href, attr, ev)
], { "class": clbutt });
},
clear: function (node) {
while (node.hasChildNodes())
node.removeChild(node.firstChild);
}
};
// data grabber
var dataCache = { };
function grabData(kind) {
if (dataCache[kind] === void(null)) {
try {
$.ajax({ // XXX: jQuery sucks
'url': wgScript + '?action=raw&ctype=application/json&title=User:Kephir/gadgets/rater/' + kind + '.js',
'dataType': 'json',
'async': false, // fuck you, Douglas Crockford
'success': function (data) {
dataCache[kind] = data;
},
'error': function (xhr, message) {
throw new Error(message);
}
});
} catch (e) {
mw.util.jsMessage('Error retrieving "' + kind + '" data: ' + e.message + '. Goldfish will probably fail to work.');
dataCache[kind] = null;
}
}
return dataCache[kind];
}
//} END OF PUBLIC DOMAIN CODE
// completion helper
function attachCompletion(entry, callback) {
var tmout;
var uiCompleter = sh.el('ul', null, {
"class": "kephir-completion"
});
function generateList() {
var items = callback(entry.value);
while (uiCompleter.hasChildNodes())
uiCompleter.removeChild(uiCompleter.firstChild);
for (var i = 0; i < items.length; ++i) {
uiCompleter.appendChild(sh.el('li', [
sh.link(items[i].contents, function () {
items[i].callback.call(items[i]);
})
]));
}
}
uiCompleter.style.display = 'none';
uiCompleter.style.position = 'absolute';
uiCompleter.style.left = entry.offsetLeft + 'px';
uiCompleter.style.top = (entry.offsetTop + entry.offsetHeight) + 'px';
uiCompleter.style.minWidth = entry.offsetWidth + 'px';
entry.offsetParent.appendChild(uiCompleter);
entry.addEventListener('keypress', function () {
uiCompleter.style.display = 'none';
clearTimeout(tmout);
tmout = setTimeout(function () {
generateList();
uiCompleter.style.display = '';
}, 500);
}, false);
entry.addEventListener('blur', function () {
uiCompleter.style.display = 'none';
}, false);
}
// markup parser and editing model
MarkupError.prototype = Error.prototype;
function MarkupError(message, line) {
this.name = 'MarkupError';
this.message = message + ' at line ' + line;
this.line = line;
this.toString = function () {
return this.name + (this.message ? ': ' + this.message : '');
};
return this;
}
/*
* How markup parsing works
*
* The function below, parseMarkup() is a tokenizer: it takes raw markup
* and calls appropriate handlers when encountering meaningful fragments.
*
* The function below it, blobifyMarkup() calls parseMarkup() with handlers which
* build a markup block object containing blobs. A blob is an object which
* understands the structure of a specific markup fragment
* (a template invocation or a comment) and enables its easy manipulation.
* Markup block objects, besides containing blobs and simplifying serialisation
* (just call .toString()) maintain a list of marks - named pointers
* to specific locations within markup, easing template insertion.
*
* This is not a very good parser of MediaWiki markup. It is everything else
* which is good at avoiding feeding it with pathological input.
*/
function parseMarkup(code, handlers) {
var m, ms;
var stack = [];
var curline = 1;
function advance(n) {
curline += (code.substr(0, n).match(/\n/g) || []).length;
code = code.substr(n);
}
handlers.init(stack);
while (m = /^([^]*?)(\{\{\{?|}}}?|\||:|\[\[|]]|=|<!--|<(?:nowiki|pre|includeonly)(?=[\s>]))/.exec(code)) {
handlers.text(stack, m[1]);
if (stack.length) {
if ((stack[0].mode === 'tname') || (stack[0].mode === 'pname')) {
stack[0].name += m[1];
}
}
advance(m[1].length);
switch (m[2]) {
case '[[':
if (/^\[\[(?:\s*<!--[^]*?-->)*\s*[^\|<\[\]](?:[^\|<\[\]]+?|<!--[^]*?-->)*(?:\||]])/.test(code)) {
stack.unshift({
"mode": 'lpage'
});
handlers.linkStart(stack, m[2]);
} else {
handlers.text(stack, m[2]);
}
advance(m[2].length);
break;
case ']]':
switch (stack.length ? stack[0].mode : null) {
case 'lpage':
handlers.linkPage(stack, m[2]);
case 'ltext':
handlers.linkEnd(stack, m[2]);
stack.shift();
break;
default:
handlers.text(stack, m[2]);
}
advance(m[2].length);
break;
case '{{{':
if (/^\{\{\{(?!\{)/.test(code)) {
stack.unshift({
'mode': 'pname',
'name': ''
});
handlers.paramRefStart(stack);
advance(m[2].length);
break;
}
m[2] = '{{';
/* fallthrough */
case '{{':
if (/^\{\{(?:\s*<!--[^]*?-->)*\s*#(?:[^<\|:]+?|<!--[^]*?-->)*:/.test(code)) {
switch (stack.length ? stack[0].mode : null) {
case 'pname':
case 'fname':
case 'tname':
throw new MarkupError('Cannot accept a ParserFunction inside a ' + stack[0].mode);
default:
}
stack.unshift({
'mode': 'fname'
});
handlers.funcStart(stack);
advance(m[2].length);
} else if (/^\{\{(?:\s*<!--[^]*?-->)*\s*[^\s\|<](?:[^\|<]+?|<!--[^]*?-->)*(?:\||}})/.test(code)) {
switch (stack.length ? stack[0].mode : null) {
case 'pname':
case 'fname':
case 'tname':
throw new MarkupError('Cannot accept a template inside a ' + stack[0].mode);
default:
}
stack.unshift({
'mode': 'tname',
'name': '',
'line': curline
});
handlers.templateStart(stack);
advance(/^\{\{\s*/.exec(code)[0].length);
} else {
handlers.text(stack, m[2]);
advance(m[2].length);
}
break;
case '}}}':
if (stack.length && ((stack[0].mode === 'pname') || (stack[0].mode === 'pvalue') || (stack[0].mode === 'pignore'))) {
switch (stack[0].mode) {
case 'pname':
handlers.paramRefName(stack);
break;
case 'pvalue':
handlers.paramRefDefault(stack);
break;
case 'pignore':
handlers.paramRefPipe(stack);
break;
}
handlers.paramRefEnd(stack);
advance(m[2].length);
stack.shift();
break;
}
m[2] = '}}';
/* fallthrough */
case '}}':
switch (stack.length ? stack[0].mode : null) {
case 'tname':
stack[0].name = stack[0].name.replace(/\s*$/, "");
handlers.templateName(stack);
handlers.templateEnd(stack);
stack.shift();
break;
case 'tkey':
case 'tvalue':
handlers.templateParam(stack);
handlers.templateEnd(stack);
stack.shift();
break;
case 'fname':
handlers.funcName(stack);
handlers.funcEnd(stack);
stack.shift();
break;
case 'fparam':
handlers.funcParam(stack);
handlers.funcEnd(stack);
stack.shift();
break;
default:
handlers.text(stack, m[2]);
}
advance(m[2].length);
break;
case '|':
switch (stack.length ? stack[0].mode : null) {
case 'tname':
stack[0].name = stack[0].name.replace(/\s*$/, "");
handlers.templateName(stack);
stack[0].mode = 'tkey';
break;
case 'tkey':
case 'tvalue':
handlers.templateParam(stack);
stack[0].mode = 'tkey';
break;
case 'pname':
handlers.paramRefName(stack);
stack[0].mode = 'pvalue';
break;
case 'pvalue':
handlers.paramRefDefault(stack);
stack[0].mode = 'pignore';
break;
case 'pignore':
handlers.paramRefPipe(stack);
break;
case 'lpage':
stack[0].mode = 'ltext';
handlers.linkPage(stack, m[2]);
break;
case 'fparam':
handlers.funcParam(stack);
stack[0].fpnum++;
break;
default:
handlers.text(stack, m[2]);
}
advance(m[2].length);
break;
case ':':
switch (stack.length ? stack[0].mode : null) {
case 'fname':
handlers.funcName(stack);
stack[0].mode = 'fparam';
stack[0].fpnum = 0;
break;
default:
handlers.text(stack, m[2].length);
}
advance(m[2].length);
break;
case '=':
switch (stack.length ? stack[0].mode : null) {
case 'tkey':
handlers.templateEqual(stack);
stack[0].mode = 'tvalue';
break;
default:
handlers.text(stack, m[2]);
}
advance(m[2].length);
break;
case '<includeonly':
case '<nowiki':
case '<pre':
if (ms = /^<(nowiki|pre|includeonly)(?:\s+([a-z]=".*?"\s*)*)?>([^]*?)<\/\1>/.exec(code)) {
handlers.noWiki(stack, ms[0], ms[3]);
advance(ms[0].length);
} else {
throw new MarkupError("Broken <nowiki> or <pre> tag", curline);
}
break;
case '<!--':
if (ms = /^<!--([^]*?)-->/.exec(code)) {
handlers.comment(stack, ms[0], ms[1]);
advance(ms[0].length);
} else
throw new MarkupError("Broken comment", curline);
break;
default:
throw new MarkupError('"Should not happen" error - got "' + m[2] + '" from parser', curline);
}
}
handlers.text(stack, code);
handlers.end(stack);
if (stack.length !== 0) {
throw new Error("Broken invocation for {{" + stack[0].name + "}} (started at line " + stack[0].line + ") at line " + curline);
}
}
function CommentBlob(content) {
this.getContent = function () {
return content;
}
this.setContent = function (newContent) {
return content = newContent;
};
this.toString = function (plain) {
if (plain)
return '';
else
return '<!--' + content + '-->';
}
}
function MarkupBlock() {
var contents = [];
var marks = {};
this.push = function () {
contents.push.apply(contents, arguments);
this.length = contents.length;
};
this.hasMark = function (name) {
return name in marks;
}
this.setMark = function (name) {
marks[name] = contents.length;
};
this.item = function (i) {
return contents[i];
};
this.removeMark = function (name) {
if (typeof marks[name] !== 'number') {
return this.iterateBlocks(function (block) {
if (block === this)
return false;
if (block.removeMark(name))
return true;
});
}
delete marks[name];
return true;
};
this.remove = function (index, count) {
if ((count === void(0)) || (count === null))
count = 1;
for (var key in marks) {
if (typeof marks[key] === 'number')
if (marks[key] > index)
marks[key] -= count;
}
contents = contents.slice(0, index).concat(contents.slice(index + count));
this.length = contents.length;
};
this.insertBefore = function (item, mark) {
if (typeof marks[mark] !== 'number') {
if (marks[mark]) {
return marks[mark].insertBefore(item, mark);
}
return this.iterateBlocks(function (block) {
if (block === this)
return false;
if (block.insertBefore(item, mark)) {
marks[mark] = block;
return true;
}
});
}
contents.splice(marks[mark], 0, item);
for (var key in marks) {
if (typeof marks[key] === 'number')
if (marks[key] >= marks[mark])
marks[key]++;
}
return true;
};
this.trim = function () {
var adjust = 0;
while ((typeof contents[0] === 'string') && /^\s*$/.test(contents[0])) {
contents[i].shift();
adjust++;
}
while ((typeof contents[contents.length - 1] === 'string') && /^\s*$/.test(contents[contents.length - 1])) {
contents[i].pop();
adjust++;
}
if (typeof contents[0] === 'string')
contents[0] = contents[0].replace(/^\s*/, '');
if (typeof contents[contents.length - 1] === 'string')
contents[contents.length - 1] = contents[contents.length - 1].replace(/\s*$/, '');
for (var key in marks) {
if (typeof marks[key] === 'number')
if (marks[key] >= marks[mark])
if ((marks[key] -= adjust) > contents.length) {
marks[key] = contents.length;
}
}
};
this.clone = function () {
var that = new MarkupBlock();
for (var i = 0; i < contents.length; ++i) {
that.push(contents[i]);
}
return that;
};
this.iterateBlocks = function (iterator, andSelf) {
if (andSelf && iterator(this))
return true;
for (var i = 0; i < contents.length; ++i) {
if (contents[i].iterateBlocks)
if (contents[i].iterateBlocks(iterator, true))
return true;
}
};
this.iterateBlobs = function (iterator) {
for (var i = 0; i < contents.length; ++i) {
if (iterator(contents[i], i))
return true;
if (contents[i].iterateBlobs)
if (contents[i].iterateBlobs(iterator))
return true;
}
};
this.toString = function (plain) {
if (plain) {
var r = '';
for (var i = 0; i < contents.length; ++i) {
if ((typeof contents[i] !== 'number') && contents[i].toString)
r += contents[i].toString(true);
else
r += String(contents[i]);
}
return r.replace(/^\s+|\s+$/g, "");
} else
return contents.join("");
};
this.push.apply(this, arguments);
}
MarkupBlock.fromString = function () {
var block = new MarkupBlock();
block.push.apply(block, arguments);
return block;
};
function TemplateBlob(nameBlock) {
var paramBlocks = [];
var associations = {};
this.hasKey = function (key) {
return key in associations;
}
this.setNameBlock = function (block) {
nameBlock = block;
};
this.getName = function () {
var t = new mw.Title(nameBlock.toString(true));
if (t.ns === 0) {
if (!/^:/.test(t.name)){
t.ns = 10;
}
}
return t.toText();
};
this.getPlainValue = function (key) {
key = String(key);
return key in associations ? associations[key].value.toString(true) : null;
}
this.setPlainValue = function (key, value) {
key = String(key);
if (key in associations) {
// XXX: try to preserve whitespace
associations[key].value = MarkupBlock.fromString(value);
} else {
this.associate(MarkupBlock.fromString(key), MarkupBlock.fromString(value));
}
};
this.associate = function (key, value) {
paramBlocks.push(associations[(typeof key === 'number') ? String(key) : key.toString(true)] = {
"key": key,
"value": value
});
};
this.iterateBlocks = function (iterator) {
if (nameBlock.iterateBlocks(iterator, true))
return true;
for (var i = 0; i < paramBlocks.length; ++i) {
if (paramBlocks[i].key.iterateBlocks)
if (paramBlocks[i].key.iterateBlocks(iterator, true))
return true;
if (paramBlocks[i].value.iterateBlocks(iterator, true))
return true;
}
}
this.iterateBlobs = function (iterator) {
if (nameBlock.iterateBlobs(iterator))
return true;
for (var i = 0; i < paramBlocks.length; ++i) {
if (paramBlocks[i].key.iterateBlobs)
if (paramBlocks[i].key.iterateBlobs(iterator))
return true;
if (paramBlocks[i].value.iterateBlobs(iterator))
return true;
}
}
this.toString = function (plain) {
var r = '{{' + nameBlock.toString(plain);
for (var i = 0; i < paramBlocks.length; ++i) {
r += '|' + (typeof paramBlocks[i].key !== 'number' ? paramBlocks[i].key.toString(plain) + '=' : '') + paramBlocks[i].value.toString(plain);
}
return r + '}}';
};
}
function ParamRefBlob(nameBlock) {
this.getName = function () {
return nameBlock.toString(true);
};
}
function blobifyMarkup(code, handlers) {
var block = new MarkupBlock();
var curblock = block;
parseMarkup(code, {
init: function (stack) {
block.setMark("last-template");
if (handlers.init)
handlers.init(stack, curblock, block);
},
text: function (stack, raw) {
curblock.push(raw);
},
comment: function (stack, raw, content) {
curblock.push(new CommentBlob(content));
},
templateStart: function (stack) {
stack[0].curValue = new MarkupBlock();
curblock.push(stack[0].blob = new TemplateBlob(stack[0].curValue));
curblock = stack[0].curValue;
},
templateName: function (stack) {
if (handlers.templateName)
handlers.templateName(stack, curblock, block);
stack[0].curKey = stack[0].curPos = 1;
curblock = stack[0].curValue = new MarkupBlock();
},
templateParam: function (stack) {
if (handlers.templateParam)
handlers.templateParam(stack, curblock, block);
stack[0].blob.associate(stack[0].curKey, stack[0].curValue);
stack[0].curKey = ++stack[0].curPos;
curblock = stack[0].curValue = new MarkupBlock();
},
templateEqual: function (stack) {
stack[0].curKey = stack[0].curValue;
stack[0].curPos--;
curblock = stack[0].curValue = new MarkupBlock();
},
templateEnd: function (stack) {
if (handlers.templateEnd)
handlers.templateEnd(stack, curblock, block);
curblock = stack[1] ? stack[1].curValue : block;
if (!stack[1]) {
block.setMark("last-template");
}
},
paramRefStart: function (stack) {
stack[0].curValue = curblock = new MarkupBlock();
curblock.push(stack[0].blob = new ParamRefBlob(curblock));
},
paramRefName: function (stack) {
if (handlers.paramRefName)
handlers.paramRefName(stack, curblock, block);
curblock = stack[0].curValue = new MarkupBlock();
},
paramRefDefault: function (stack) {
if (handlers.paramRefDefault)
handlers.paramRefDefault(stack, curblock, block);
stack[0].blob.setDefault(curblock);
curblock = stack[0].curValue = new MarkupBlock();
},
paramRefPipe: function (stack) {
stack[0].blob.addExtra(curblock);
curblock = stack[0].curValue = new MarkupBlock();
},
paramRefEnd: function (stack) {
curblock = stack[1] ? stack[1].curValue : block;
},
linkStart: function (stack, raw) {
stack[0].curValue = curblock;
curblock.push(raw);
},
linkPage: function (stack, raw) {
curblock.push(raw);
},
linkEnd: function (stack, raw) {
curblock.push(raw);
},
funcStart: function (stack) {
stack[0].curValue = curblock = new MarkupBlock();
curblock.push(stack[0].blob = new ParserFunctionBlob(curblock));
},
funcName: function (stack) {
stack[0].curValue = curblock = new MarkupBlock();
},
funcParam: function (stack) {
stack[0].blob.pushArg(curblock);
},
funcEnd: function (stack) {
curblock = stack[1] ? stack[1].curValue : block;
},
noWiki: function (stack, raw, contents) {
curblock.push(raw);
},
end: function (stack) {
if (handlers.end)
handlers.end(stack, curblock, block);
}
});
return block;
}
// editing modules
var editModules = { };
(function () { // zoo - talk page notices
// [[Do not feed the animals]]
editModules.zoo = {
editor: {
init: function (state, ui) {
},
templateName: function (state, ui) {
},
templateParam: function (state, ui) {
},
templateEnd: function (state, ui) {
},
end: function (state, ui) {
}
},
checker: {
init: function (state, ui) {
},
templateName: function (state, ui) {
},
templateParam: function (state, ui) {
},
templateEnd: function (state, ui) {
},
end: function (state, ui) {
}
}
}
})();
(function () { // aquarium - Article Quality Rating Metric
function createTemplateUI(templ, state, ui) {
var uiRating, lastRated = null;
var sumdata = { };
function computeRating(scores) {
if (scores.compr === null)
return null;
if (scores.compr < 3)
return 'Stub';
if ((scores.compr >= 7) && (scores.sourc >= 4) && (scores.reada >= 2) && (scores.neutr >= 2))
return 'B';
if ((scores.compr >= 4) && (scores.sourc >= 2))
return 'C';
return 'Start';
}
function updateScore() {
uiRating.data = computeRating({
compr: templ.getPlainValue("comprehensiveness"),
sourc: templ.getPlainValue("sourcing"),
reada: templ.getPlainValue("readability"),
neutr: templ.getPlainValue("neutrality")
}) || 'none';
state.aquarium.uiSection.setSummary('rating: ' + uiRating.data);
}
function createScoreControl(parm, desc, min, max) {
var sel;
var item = sh.el('li', [
sh.el('label', [desc, sel = sh.el('select', [
sh.el('option', '?', { "value": "" })
], null, {
"change": function () {
templ.setPlainValue("rater", '{{subst' + ':REVISIONUSER}}');
templ.setPlainValue("time", '~~' + '~' + '~~');
templ.setPlainValue("oldid", state.page.getLastRevision());
templ.setPlainValue(parm, this.value == '' ? null : this.value);
sumdata[parm] = this.value;
updateScore();
ui.makeEditorDirty();
ui.refreshSummary();
}
})])
]);
for (var i = min; i <= max; ++i) {
sel.appendChild(sh.el('option', String(i), { "value": String(i) }));
}
sel.value = parseInt(templ.getPlainValue(parm), 10);
return item;
}
ui.addSummaryHook(function () {
var r = [];
if ('comprehensiveness' in sumdata) {
r[r.length] = 'Comp=' + sumdata.comprehensiveness;
}
if ('sourcing' in sumdata) {
r[r.length] = 'Src=' + sumdata.sourcing;
}
if ('neutrality' in sumdata) {
r[r.length] = 'Neut=' + sumdata.neutrality;
}
if ('readability' in sumdata) {
r[r.length] = 'Read=' + sumdata.readability;
}
if ('formatting' in sumdata) {
r[r.length] = 'Fmt=' + sumdata.formatting;
}
if ('illustrations' in sumdata) {
r[r.length] = 'Illu=' + sumdata.illustrations;
}
if (r.length) {
var rating = computeRating({
compr: templ.getPlainValue("comprehensiveness"),
sourc: templ.getPlainValue("sourcing"),
reada: templ.getPlainValue("readability"),
neutr: templ.getPlainValue("neutrality")
});
return '[[Wikipedia:Ambassadors/Research/Article quality|AQRM]]: ' + r.join(", ") + (rating ? ' (' + rating + '-class)' : '');
} else
return;
});
lastRated = {};
if (templ.hasKey("rater") && templ.hasKey("oldid") && templ.hasKey("time")) {
lastRated.user = templ.getPlainValue("rater");
lastRated.oldid = templ.getPlainValue("oldid");
lastRated.time = templ.getPlainValue("time");
if ((lastRated.time === ('~~' + '~' + '~~')) || (lastRated.user === ('{{subst' + ':REVISIONUSER}}'))) {
lastRated = null;
}
} else {
lastRated = null;
}
var uiTempl = sh.el('div', [
sh.el('ul', [
createScoreControl("comprehensiveness", "Comprehensiveness", 1, 10),
createScoreControl("sourcing" , "Sourcing" , 0, 6),
createScoreControl("neutrality" , "Neutrality" , 0, 3),
createScoreControl("readability" , "Readability" , 0, 3),
createScoreControl("formatting" , "Formatting" , 0, 2),
createScoreControl("illustrations" , "Illustrations" , 0, 2),
], { "class": "aqrm-scores" }),
sh.el('p', ['Computed rating: ', sh.el('strong', [uiRating = document.createTextNode('none')])]),
lastRated ? sh.el('p', [
'This page was last rated by ',
sh.link(lastRated.user, mw.util.getUrl('User:' + lastRated.user)),
' on ',
sh.el('strong', lastRated.time),
' at revision ',
sh.link(String(lastRated.oldid), wgScript + '?oldid=' + lastRated.oldid)
]) : void(0)
]);
updateScore();
return uiTempl;
}
editModules.aquarium = {
editor: {
init: function (state, ui, block) {
state.aquarium = {};
state.aquarium.uiSection = ui.addEditorSection([
sh.link('Article quality rating metric',
mw.util.getUrl('Wikipedia:Ambassadors/Research/Article quality')
)
], 'aqrm');
state.aquarium.uiSection.setSummary('no template present');
state.aquarium.uiSection.body.appendChild(
state.aquarium.uiMsg = sh.el('p', [
'No scoring template present. ',
sh.link('Add template', function () {
state.aquarium.templ = new TemplateBlob(MarkupBlock.fromString("Quality assessment"));
block.insertBefore(state.aquarium.templ, "last-template"); // XXX
sh.clear(state.aquarium.uiMsg);
state.aquarium.uiSection.setSummary('');
state.aquarium.uiSection.body.appendChild(
createTemplateUI(state.aquarium.templ, state, ui)
);
ui.makeEditorDirty();
})
])
);
},
templateName: function (state, ui, stack, block) {
},
templateParam: function (state, ui) {
},
templateEnd: function (state, ui, topblock, curblock, stack) {
if (stack[0].blob.getName() === 'Template:Quality assessment') {
sh.clear(state.aquarium.uiMsg)
state.aquarium.uiSection.setSummary('');
if (state.aquarium.templ) {
state.aquarium.uiMsg.appendChild(sh.el('span', [
sh.el('strong', 'Warning'), ': ',
'There is more than one assessment template. Will only take care of the last one.'
]));
}
state.aquarium.uiSection.body.appendChild(
createTemplateUI(state.aquarium.templ = stack[0].blob, state, ui)
);
}
},
end: function (state, ui) {
}
}
};
})();
(function () { // jungle - Wikipedia 1.0 Assessment
var projList = grabData('project-list');
for (var key in projList) {
if (!projList[key])
continue;
var aliases = projList[key].aliases;
for (var i = 0; i < aliases.length; ++i) {
projList[aliases[i]] = projList[key];
}
}
var projData = { };
var projDataSrc = { };
var bannerShells = [
"Template:WikiProjectBannerShell",
// {{WikiProjectBannerShell}}
"Template:Shell",
"Template:WBPS",
"Template:WikiProject",
"Template:WikiProject",
"Template:Wikiprojectbannershell",
"Template:WPBannerShell",
"Template:Wpbs",
"Template:WPBS",
// {{WikiProject Banners}}
"Template:WikiProject Banners",
"Template:WPB",
"Template:Wpb",
"Template:Wikiprojectbanners"
];
var bannerMeta = [
"Template:Metabanner",
"Template:WikiProject Notice",
"Template:WikiProjectBannerMeta",
"Template:WikiProjectNotice",
"Template:WPBM",
"Template:WPStructure",
"Wikipedia:Wpbm",
"Wikipedia:WPBM"
];
function generateData(source) {
var legit = false;
var params = {
};
var data = {
stdParams: {}
};
var buffer;
blobifyMarkup(source, {
init: function (stack) {
// XXX
},
templateName: function (stack, curblock, block) {
if (bannerMeta.indexOf(stack[0].blob.getName()) !== -1) {
legit = true;
}
},
templateParam: function (stack, curblock, block) {
var pname = typeof stack[0].curKey === 'number' ? stack[0].curKey : stack[0].curKey.toString(true);
var pvalue = stack[0].curValue;
var pparam;
pvalue.trim();
switch (tname) {
case 'Template:WPBannerMeta':
switch (pname) {
case 'small':
case 'auto':
case 'class':
case 'importance':
case 'priority':
case 'listas':
case 'attention':
case 'infobox':
pparam = pvalue.item(0);
if (pparam instanceof ParamRefBlob) {
data.stdParams[pname] = pparam.getName();
}
encounters[pparam.getName()] = true;
break;
case 'PROJECT': // the name of the project
data.project = 'WikiProject ' + pvalue.toString(true);
break;
case 'PROJECT_NAME': // project name (if it does not start with "WikiProject ")
data.project = pvalue.toString(true);
break;
case 'QUALITY_SCALE':
data.qualityScale = pvalue.toString(true); // standard/extended/inline/subpage
break;
case 'IMPORTANCE_SCALE':
data.importanceScale = pvalue.toString(true); // standard/inline/subpage
break;
}
break;
case 'Template:WPBannerMeta/hooks/notes':
break;
case 'Template:WPBannerMeta/hooks/bchecklist':
break;
case 'Template:WPBannerMeta/hooks/collaboration':
break;
case 'Template:WPBannerMeta/hooks/taskforces':
break;
}
},
templateEnd: function (stack, curblock, block) {
var name = stack[0].blob.getName();
// commit data to
},
paramRefName: function (stack, curblock, block) {
var pname = curblock.toString(true);
if (!(pname in encounters))
encounters[pname] = false;
},
end: function (curblock, block) {
}
});
if (!legit)
return null;
return data;
}
function grabProjectData(name, handlers) {
if (name in projData) {
if (projData[name] === null) {
handlers.nak();
} else {
handlers.ack(projData[name], projDataSrc[name]);
}
return;
}
// XXX: no data yet - download it
$.ajax({ // XXX: jQuery sucks
'url': wgScript + '?action=raw&ctype=application/json&title=' + name + '/rater.json',
'dataType': 'json'
}).done(function (result) {
handlers.ack(
projData[name] = result,
projDataSrc[name] = 'json'
);
}).fail(function () {
// XXX: check what error happened first
$.ajax({ // XXX: jQuery sucks
'url': wgScript + '?action=raw&ctype=application/json&title=' + name,
'dataType': 'text'
}).done(function (result) {
var data;
try {
data = projData[name] = generateData(result);
} catch (e) {
handlers.error(e); // XXX
return;
}
if (data === null)
if (name in projList) {
handlers.error(); // XXX
} else {
handlers.nak();
}
else
handlers.ack(
data,
projDataSrc[name] = 'generated'
);
}).fail(function () { // jQuery sucks even more than I thought
// XXX: error details
handlers.error();
});
});
// 3. if a positive entry, download the template's data
// 1. if no data available, download its source and autogenerate data
// 4. if no, download its source and check if it calls {{WPBannerMeta}}
// 1. if it does not, console.info("suggest negative entry for {{xxx}}") and pass
// 2. if it does, autogenerate data
}
// a jungle of templates, that is what WP:1.0 is.
// some hints, so you will not get apeshit:
// ayeaye - assessment
// capuchin - checklists
// rhesus - requests
// tarsier - task forces
// orangutan - other
var monkey = {
};
// each template is scanned, and the monkeys generate appropriate interface for parameters encountered.
editModules.jungle = {
editor: {
init: function (state, ui) {
var uiNewBannerInput;
state.jungle = {};
state.jungle.shell = null;
state.jungle.section = ui.addEditorSection([
sh.link('Version 1.0 Editorial Team assessment scheme',
mw.util.getUrl('Wikipedia:Version 1.0 Editorial Team/Assessment')
)
], 'jungle');
state.jungle.section.body.appendChild(
state.jungle.uiList = sh.el('ul', [
// empty
])
);
state.jungle.section.body.appendChild(
sh.el('form', [
uiNewBannerInput = sh.el('input', null, {
"class": "name",
"size": "48",
"placeholder": "new banner"
}),
sh.el('input', null, {
"type": "submit",
"value": "add"
})
], {
"action": "javascript:void(0);",
"class": "new-banner"
}, {
"submit": function (ev) {
ev.preventDefault();
// XXX:
// 1. if there is a shell, add template at the end of the shell
// 2. if there is no shell and there are two templates already, wrap them into a shell and put the new template into it
// 3. otherwise put after the last template
uiNewBannerInput.value = '';
}
})
);
},
templateName: function (state, ui, topblock, curblock, stack) {
var name = stack[0].blob.getName();
if (bannerShells.indexOf(name) !== -1) {
state.jungle.shell = stack[0].blob;
// XXX: how to handle multiple banner shells?
}
},
templateParam: function (state, ui) {
},
templateEnd: function (state, ui, topblock, curblock, stack) {
var blob = stack[0].blob;
var name = blob.getName();
var uiItem, uiWait, uiName;
if (name in projList)
if (projList[name] === null)
return;
state.jungle.uiList.appendChild(uiItem = sh.el('li', [
sh.el('span', [
sh.link(
[uiName = document.createTextNode(
(projList[name] ? projList[name].project : null) || name.replace(/^Template:(WikiProject ?|WP ?)?/, '')
)],
mw.util.getUrl(name)
),
': '
], { "class": "" }),
uiWait = sh.el('span', ['loading data...'], { "class": "wait" })
]));
grabProjectData(name, {
ack: function (data) {
var paramsHandled = {};
uiWait.parentNode.removeChild(uiWait);
uiItem.appendChild(sh.el(
));
blob.enumParams(function (key) {
paramsHandled[key] = false;
});
if (data.project)
uiName.project = data.project;
for (var key in monkey) {
monkey[key](state, paramsHandled, data, uiItem);
}
for (var key in paramsHandled) {
if (!paramsHandled[key]) {
// XXX
}
}
},
nak: function () {
console.info('suggesting negative entry for: ' + name);
uiItem.parentNode.removeChild(uiItem);
},
error: function (err) {
console.info(err);
uiWait.parentNode.removeChild(uiWait);
uiItem.appendChild(sh.el('span', [
'error'
]));
}
});
},
end: function (state, ui) {
}
},
checker: {
init: function (state, ui) {
},
templateName: function (state, ui) {
},
templateParam: function (state, ui) {
},
templateEnd: function (state, ui) {
},
end: function (state, ui) {
}
}
};
})();
// general backend code
var api = new mw.Api();
var talkPageHeader = null;
function Page(mdata) {
this.grabTalkHeader = function (handlers, force) {
if (!force && talkPageHeader) {
handlers.ok(talkPageHeader);
return true;
}
api.get({
action: 'query',
prop: 'info|revisions',
rvprop: 'timestamp|content',
rvsection: 0,
rvlimit: 1,
rvdir: 'older',
intoken: 'edit',
titles: mdata.talkPageName
}).done(function (result) {
var tpgpid = Object.keys(result.query.pages)[0];
var tpg = result.query.pages[tpgpid];
handlers.ok(talkPageHeader = result.query.pages[tpgpid]);
}).fail(handlers.error);
};
this.saveTalkHeader = function (markup, summary, handlers) {
api.post({
action: 'edit',
section: 0,
title: mdata.talkPageName,
basetimestamp: talkPageHeader.revisions ? talkPageHeader.revisions[0].timestamp : void(0),
starttimestamp: talkPageHeader.starttimestamp,
token: talkPageHeader.edittoken,
notminor: true,
summary: summary,
watchlist: 'nochange',
text: markup
}).done(function (result) {
talkPageHeader = null;
handlers.ok(result);
}).fail(handlers.error);
};
this.getTalkPageName = function () {
return mdata.talkPageName;
}
this.getLastRevision = function () {
if (typeof mdata.lastRevision !== 'number') {
api.get({
action: 'query',
prop: 'ids|revisions',
rvprop: 'ids|timestamp',
rvsection: 0,
rvlimit: 1,
rvdir: 'older',
titles: mdata.pageName,
async: false
}).done(function (result) {
var pageid = Object.keys(result.query.pages)[0];
mdata.lastRevision = result.query.pages[pageid].revisions[0].revid;
})
}
return mdata.lastRevision;
};
}
// general UI code
function UserInterface(page) {
var self = this;
var uiEditorTab, uiSourceTab, uiPreviewTab, uiCurrentTab, uiStatusBar;
var uiSourceBox, uiSourceErr, uiPreviewBin, uiEditor, uiSummary;
var dirtySource = false, dirtyEditor = false, dirtySummary = false;
var block;
var uiTabLabel = [], uiTab = [];
var uiActiveTabLabel, uiActiveTab;
var talkPageData;
var summaryHooks = [];
function setActiveTab(i) {
if (uiActiveTabLabel)
uiActiveTabLabel.classList.remove("active");
if (uiActiveTab)
uiActiveTab.style.display = 'none';
uiTabLabel[i].classList.add("active");
uiTab[i].style.display = 'block';
uiActiveTab = uiTab[i];
uiActiveTabLabel = uiTabLabel[i];
}
function bailOutParsing(message) {
sh.clear(uiSourceErr);
uiSourceErr.appendChild(document.createTextNode(message)); // XXX: prettier?
dirtyEditor = false;
dirtySource = true;
setActiveTab(1);
}
this.makeEditorDirty = function () {
dirtyEditor = true;
};
this.clearEditor = function () {
block = null;
summaryHooks = [];
sh.clear(uiEditor);
};
this.setStatusBar = function (msg) {
sh.clear(uiStatusBar);
uiStatusBar.appendChild(sh.el('span', msg));
}
this.addEditorSection = function (title, clbutt) {
var summaryNode;
var summarySpan;
var hidden = false;
var sectBody = sh.el('dd', null, { "class": clbutt });
var sectHead = sh.el('dt', [
sh.el('span', ['[',
sh.link('hide', function () {
hidden = !hidden;
sectBody.style.display = hidden ? 'none' : '';
summarySpan.style.display = hidden ? '' : 'none';
this.firstChild.data = hidden ? 'show' : 'hide';
}),
']'], { "class": "hide-link" }),
sh.el('span', title, { "class": "title" }),
summarySpan = sh.el('span', [summaryNode = document.createTextNode('')]),
]);
summarySpan.style.display = 'none';
uiEditor.appendChild(sectHead);
uiEditor.appendChild(sectBody);
return {
"head": sectHead,
"body": sectBody,
setSummary: function (text) {
summaryNode.data = (text && ': ') + text;
}
};
}
this.refreshSummary = function () {
if (dirtySummary)
return;
var sum = summaryHooks.map(function (hook) {
return hook();
}).filter(function (item) {
return item !== void(0);
});
uiSummary.value = sum.length ? sum.join("; ") + GOLDFISH_ADVERT : '';
};
this.addSummaryHook = function (hook) {
summaryHooks.push(hook);
};
this.prepareEditor = function (source) {
var state = {
"page": page
};
this.clearEditor();
return block = blobifyMarkup(source, {
init: function (curblock, topblock) {
for (var key in editModules) {
if (editModules[key].editor && editModules[key].editor.init) {
editModules[key].editor.init(state, self, topblock);
}
}
},
templateName: function (stack, curblock, topblock) {
for (var key in editModules) {
if (editModules[key].editor && editModules[key].editor.templateName) {
editModules[key].editor.templateName(state, self, topblock, curblock, stack);
}
}
},
templateParam: function (stack, curblock, topblock) {
for (var key in editModules) {
if (editModules[key].editor && editModules[key].editor.templateParam) {
editModules[key].editor.templateParam(state, self, topblock, curblock, stack);
}
}
},
templateEnd: function (stack, curblock, topblock) {
for (var key in editModules) {
if (editModules[key].editor && editModules[key].editor.templateEnd) {
editModules[key].editor.templateEnd(state, self, topblock, curblock, stack);
}
}
},
end: function (curblock, topblock) {
for (var key in editModules) {
if (editModules[key].editor && editModules[key].editor.end) {
editModules[key].editor.end(state, self, topblock);
}
}
self.refreshSummary();
}
});
};
this.prepare = function (talkHeader, keepTab) {
var source = talkHeader.revisions ? talkHeader.revisions[0]['*'] : '';
uiSourceBox.value = source;
dirtySummary = false;
uiSummary.value = '';
try {
this.prepareEditor(source);
} catch (e) {
bailOutParsing(e.message);
debugger;
return;
}
dirtySource = false;
dirtyEditor = false;
if (!keepTab) {
setActiveTab(0);
}
};
var uiTitle, uiContent, uiFooter;
var uiBox = this.box = sh.el('div', [
uiTitle = sh.el('div', [
sh.el('span', [
sh.el('strong', "Goldfish"),
" version ",
GOLDFISH_VERSION,
" by ",
sh.link("Keφr", mw.util.getUrl('User:Kephir'))
]),
sh.el('ul', [
sh.item("Feedback",
wgScript + '?title=' + mw.util.wikiUrlencode('User talk:Kephir/gadgets/rater') +
'&action=edit§ion=new&preloadtitle=Feedback&editintro=' +
mw.util.wikiUrlencode('User:Kephir/gadgets/rater/feedback-editintro')
),
sh.item("×", function (ev) {
ev.preventDefault();
self.show(false);
}, "Close")
], { "class": "link-list buttons" }),
sh.el('br')
], { "class": "title" }),
sh.el('ul', [
sh.item("Reload", function (ev) {
page.grabTalkHeader({
ok: function (talkHeader) {
self.prepare(talkHeader, true);
},
error: function () {
mw.util.jsMessage('Error grabbing talk page revisions. See console for details.');
console.error(arguments);
}
}, true);
}, null, null, "item-reload"),
uiTabLabel[0] = sh.item("Editor", function (ev) {
if (dirtySource) {
try {
self.prepareEditor(uiSourceBox.value);
} catch (e) {
bailOutParsing(e.message);
debugger;
return;
}
dirtySource = false;
}
setActiveTab(0);
}),
uiTabLabel[1] = sh.item("Source", function (ev) {
sh.clear(uiSourceErr);
if (dirtyEditor) {
uiSourceBox.value = block.toString();
dirtyEditor = false;
}
setActiveTab(1);
}),
uiTabLabel[2] = sh.item("Preview", function (ev) {
if (dirtyEditor) {
uiSourceBox.value = block.toString();
dirtyEditor = false;
}
var source = uiSourceBox.value;
api.post({
'action': 'parse',
'title': page.getTalkPageName(),
'text': source,
'pst': '1',
'prop': 'text',
'disablepp': 1
}).done(function (result) {
uiPreviewBin.innerHTML = result.parse.text['*'];
setActiveTab(2);
}).fail(function () {
// XXX: show error message besides uiPreviewBin
console.error(arguments);
});
})
], { "class": "link-list tabs" }),
uiContent = sh.el('div', [
uiTab[0] = uiEditorTab = sh.el('div', [
uiEditor = sh.el('dl', [], {
"class": "editor"
})
]),
uiTab[1] = uiSourceTab = sh.el('div', [
uiSourceErr = sh.el('p'),
uiSourceBox = sh.el('textarea', null, {
"rows": 12,
"cols": 40
}, {
// XXX: other events?
"change": function () {
dirtySource = true;
dirtySummary = true;
uiSummary.value = 'Updated talk page header' + GOLDFISH_ADVERT;
}
})
], { "class": "source-tab" }),
uiTab[2] = uiPreviewTab = sh.el('div', [
uiPreviewBin = sh.el('div', null, {
"class": "preview-bin"
})
], { "class": "preview-tab" })
], { "class": "content" }),
uiFooter = sh.el('div', [
sh.el('div', [
'Edit summary: ',
uiSummary = sh.el('input', null, { }, {
"change": function () { // XXX: other events
dirtySummary = true;
}
})
], { "class": "summary-area" }),
sh.el('input', null, {
"type": "button",
"value": "Submit"
}, {
"click": function (ev) {
if (dirtyEditor) {
uiSourceBox.value = block.toString();
dirtyEditor = false;
}
return; // XXX: disabled before everything is done
self.setStatusBar(['Please wait...']);
page.saveTalkHeader(uiSourceBox.value, uiSummary.value, {
ok: function () {
uiActiveTab.style.display = 'none';
self.setStatusBar([
'Changes saved. ',
sh.link(
'Close', function () {
self.show(false);
}
)
]);
// PST has probably occured, so refresh
page.grabTalkHeader({
ok: function () {
self.prepare(talkHeader);
},
error: function () {
// XXX
}
}, true);
},
error: function () {
// XXX
}
});
}
}),
uiStatusBar = sh.el('span', null, {
"class": "status-bar"
})
], { "class": "footer" })
], { "class": "kephir-goldfish" });
this.show = function (state) {
uiBox.style.display = state ? 'block': 'none';
};
uiSourceTab.style.display = 'none';
uiEditorTab.style.display = 'none';
uiPreviewTab.style.display = 'none';
uiBox.style.display = 'none';
uiBox.style.position = 'absolute';
uiBox.style.top = '20%';
uiBox.style.right = '10%';
uiBox.style.width = '50%';
uiContent.style.height = '30em';
$(uiBox).draggable({
handle: uiTitle
}).resizable({
alsoResize: uiContent
}); // XXX: did I mention jQuery sucks?
}
var t = new mw.Title(wgPageName);
var page = new Page({
pageName: (t.ns &= ~1, t.toString()),
talkPageName: (t.ns = (t.ns & ~1) + 1, t.toString()),
lastRevision: !(wgNamespaceNumber % 2) ? wgCurRevisionId : void(0)
});
var ui = new UserInterface(page);
document.body.appendChild(ui.box);
// go through modules and let each hook up the editor tab
// glue code
var link = mw.util.addPortletLink(mw.config.get('skin') === 'vector' ? 'p-views' : 'p-cactions',
'javascript:void(0);', '◉', 'p-kephir-goldfish', 'Goldfish', '~'
);
link.addEventListener('click', function (ev) {
ev.preventDefault();
page.grabTalkHeader({
ok: function (talkHeader) {
ui.prepare(talkHeader);
ui.show(true);
},
error: function () {
mw.util.jsMessage('Error grabbing talk page revisions. See console for details.');
console.error(arguments);
}
});
}, false);
// test if we enabled autochecking for missing assessment
if (settings.promptToAssess) {
page.grabTalkHeader({
ok: function (talkHeader) {
var missingMsgs = [];
var state = {};
var blobs = blobifyMarkup(talkHeader.revisions ? talkHeader.revisions[0]['*'] : '', {
init: function () {
// ...
},
templateName: function (stack) {
// ...
},
templateParam: function (stack) {
// ...
},
templateEnd: function (stack) {
// ...
},
end: function () {
// ...
}
});
if (missingMsgs.length) {
var msgDiv = sh.el('div', [
'The rating information for this article is incomplete:'
], { "class": "kephir-goldfish-msg-missing" });
msgDiv.style.display = 'none';
msgDiv.style.position = 'absolute';
msgDiv.style.right = (link.offsetLeft + link.offsetWidth) + 'px';
msgDiv.style.top = (link.offsetTop + link.offsetHeight) + 'px';
link.offsetParent.appendChild(msgDiv);
// in Soviet Russia, article rates YOU!!
link.style.background = 'red';
link.getElementsByTagName('a')[0].style.color = 'black';
if (settings.promptToAssess > 1) {
mw.util.jsMessage([
sh.el('p', sh.el('strong', "This article has incomplete assessment information.")),
sh.el('p', "Hover over the icon for more details.")
]);
}
for (var i = 0; i < missingMsgs.length; ++i) {
// XXX: append to msgDiv (or maybe some ul within)
}
link.addEventListener('mouseenter', function () {
msgDiv.style.display = '';
}, false);
link.addEventListener('mouseleave', function () {
msgDiv.style.display = 'none';
}, false);
}
},
error: function () {
mw.util.jsMessage('Error grabbing talk page revisions. See console for details.');
console.error(arguments);
}
});
}
});
// </nowiki>