User:MintCandy/rater/goldfish.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// <nowiki>
/*
* Goldfish
* Copyright © 2013 Keφr at the English Wikipedia
* 原始地址:en:User:Kephir/gadgets/rater/goldfish.js
* 本地版本:2013-05-16
*/
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:Chiefwei/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:Chiefwei/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:Chiefwei'))
]),
sh.el('ul', [
sh.item("Feedback",
wgScript + '?title=' + mw.util.wikiUrlencode('User talk:Chiefwei/rater') +
'&action=edit§ion=new&preloadtitle=Feedback&editintro=' +
mw.util.wikiUrlencode('User:Chiefwei/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>