User:Bosco/Unsigned helper.js: Difference between revisions
From Test Wiki
Content deleted Content added
re-do Special:Diff/1229376883 in another place where `appendToEditSummary` is called |
overhaul |
||
| Line 1: | Line 1: | ||
/* |
|||
* This is a fork of https://en.wikipedia.org/w/index.php?title=User:Anomie/unsignedhelper.js&oldid=1219219971 |
|||
*/ |
|||
(function () { |
(function () { |
||
const LOG_PREFIX = `[Unsigned Helper]:`; |
|||
function error(...toLog) { |
|||
console.error(LOG_PREFIX, ...toLog); |
|||
} |
|||
function warn(...toLog) { |
|||
console.warn(LOG_PREFIX, ...toLog); |
|||
} |
|||
function info(...toLog) { |
|||
console.info(LOG_PREFIX, ...toLog); |
|||
} |
|||
function debug(...toLog) { |
|||
console.debug(LOG_PREFIX, ...toLog); |
|||
} |
|||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; |
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; |
||
info('Loading...'); |
|||
function formatErrorSpan(errorMessage) { |
|||
return `<span style="color:maroon;"><b>Error:</b> ${errorMessage}</span>`; |
|||
} |
|||
const LAZY_REVISION_LOADING_INTERVAL = 50; |
|||
/** |
|||
* Lazily loads revision IDs for a page. |
|||
* Gives zero-indexed access to the revisions. Zeroth revision is the newest revision. |
|||
*/ |
|||
class LazyRevisionIdsLoader { |
|||
#pagename; |
|||
#indexedRevisionPromises = []; |
|||
/** |
|||
* We are loading revision IDs per LAZY_REVISION_LOADING_INTERVAL |
|||
* Each of requests gives us LAZY_REVISION_LOADING_INTERVAL revision IDs. |
|||
*/ |
|||
#historyIntervalPromises = []; |
|||
#api = new mw.Api(); |
|||
constructor(pagename) { |
|||
this.#pagename = pagename; |
|||
} |
|||
#getLastLoadedInterval(upToIndex) { |
|||
let i = 0; |
|||
while (this.#historyIntervalPromises[i] != undefined && i <= upToIndex) { |
|||
i++; |
|||
} |
|||
return [i, this.#historyIntervalPromises[i - 1]]; |
|||
} |
|||
#createIntervalFromResponse(response) { |
|||
if ('missing' in response.query.pages[0]) { |
|||
return undefined; |
|||
} |
|||
return { |
|||
rvcontinue: response.continue?.rvcontinue, |
|||
revisions: response.query.pages[0].revisions, |
|||
}; |
|||
} |
|||
async #loadIntervalsRecursive(index, upToIndex, rvcontinue) { |
|||
return new Promise(async (resolve, reject) => { |
|||
// reference documentation: https://en.wikipedia.org/w/api.php?action=help&modules=query%2Brevisions |
|||
const intervalQuery = { |
|||
action: 'query', |
|||
prop: 'revisions', |
|||
rvlimit: LAZY_REVISION_LOADING_INTERVAL, |
|||
rvprop: 'ids|user', // no 'content' here; 'user' is just for debugging purposes |
|||
rvslots: 'main', |
|||
formatversion: 2, // v2 has nicer field names in responses |
|||
titles: this.#pagename, |
|||
}; |
|||
if (rvcontinue) { |
|||
intervalQuery.rvcontinue = rvcontinue; |
|||
} |
|||
debug('loadIntervalsRecursive Q: index =', index, 'upToIndex =', upToIndex, 'intervalQuery =', intervalQuery); |
|||
this.#api.get(intervalQuery).then(async (response) => { |
|||
try { |
|||
// debug('loadIntervalsRecursive R:', response); |
|||
const interval = this.#createIntervalFromResponse(response); |
|||
this.#historyIntervalPromises[index] = Promise.resolve(interval); |
|||
if (index == upToIndex) { |
|||
// we've hit the limit of what we want to load so far |
|||
resolve(interval); |
|||
return; |
|||
} |
|||
if (response.batchcomplete) { |
|||
for (let i = index; i <= upToIndex; i++) { |
|||
this.#historyIntervalPromises[i] = Promise.resolve(undefined); |
|||
} |
|||
// we've asked for an interval of history which doesn't exist |
|||
resolve(undefined); |
|||
return; |
|||
} |
|||
// recursive call for one more interval |
|||
const ignored = await this.#loadIntervalsRecursive(index + 1, upToIndex, interval.rvcontinue); |
|||
if (this.#historyIntervalPromises[upToIndex] == undefined) { |
|||
resolve(undefined); |
|||
return; |
|||
} |
|||
this.#historyIntervalPromises[upToIndex].then( |
|||
result => resolve(result), |
|||
rejection => reject(rejection) |
|||
); |
|||
} catch (e) { |
|||
reject('loadIntervalsRecursive: ' + e); |
|||
} |
|||
}, rejection => { |
|||
reject('loadIntervalsRecursive via api: ' + rejection); |
|||
}); |
|||
}); |
|||
} |
|||
async #loadInterval(intervalIndex) { |
|||
const [firstNotLoadedIntervalIndex, latestLoadedInterval] = this.#getLastLoadedInterval(intervalIndex); |
|||
if (firstNotLoadedIntervalIndex > intervalIndex) { |
|||
return this.#historyIntervalPromises[intervalIndex]; |
|||
} |
|||
const rvcontinue = latestLoadedInterval?.rvcontinue; |
|||
return this.#loadIntervalsRecursive(firstNotLoadedIntervalIndex, intervalIndex, rvcontinue); |
|||
} |
|||
#indexToIntervalIndex(index) { |
|||
return Math.floor(index / LAZY_REVISION_LOADING_INTERVAL); |
|||
} |
|||
#indexToIndexInInterval(index) { |
|||
return index % LAZY_REVISION_LOADING_INTERVAL; |
|||
} |
|||
/** |
|||
* @param index zero-based index of a revision to load |
|||
*/ |
|||
async loadRevision(index) { |
|||
if (this.#indexedRevisionPromises[index]) { |
|||
return this.#indexedRevisionPromises[index]; |
|||
} |
|||
const promise = new Promise(async (resolve, reject) => { |
|||
const intervalIndex = this.#indexToIntervalIndex(index); |
|||
try { |
|||
const interval = await this.#loadInterval(intervalIndex); |
|||
if (interval == undefined) { |
|||
resolve(undefined); |
|||
return; |
|||
} |
|||
const theRevision = interval.revisions[this.#indexToIndexInInterval(index)]; |
|||
debug('loadRevision: loaded revision', index, theRevision); |
|||
resolve(theRevision); |
|||
} catch (e) { |
|||
reject('loadRevision: ' + e); |
|||
} |
|||
}); |
|||
this.#indexedRevisionPromises[index] = promise; |
|||
return promise; |
|||
} |
|||
} |
|||
/** |
|||
* Lazily loads full revisions (wikitext, user, revid, tags, edit summary, etc) for a page. |
|||
* Gives zero-indexed access to the revisions. Zeroth revision is the newest revision. |
|||
*/ |
|||
class LazyFullRevisionsLoader { |
|||
#pagename; |
|||
#revisionsLoader; |
|||
#indexedContentPromises = []; |
|||
#api = new mw.Api(); |
|||
constructor(pagename) { |
|||
this.#pagename = pagename; |
|||
this.#revisionsLoader = new LazyRevisionIdsLoader(pagename); |
|||
} |
|||
/** |
|||
* Returns a {@link Promise} with full revision for given index. |
|||
*/ |
|||
async loadContent(index) { |
|||
if (this.#indexedContentPromises[index]) { |
|||
return this.#indexedContentPromises[index]; |
|||
} |
|||
const promise = new Promise(async (resolve, reject) => { |
|||
try { |
|||
const revision = await this.#revisionsLoader.loadRevision(index); |
|||
if (revision == undefined) { |
|||
// this revision doesn't seem to exist |
|||
resolve(undefined); |
|||
return; |
|||
} |
|||
// reference documentation: https://en.wikipedia.org/w/api.php?action=help&modules=query%2Brevisions |
|||
const contentQuery = { |
|||
action: 'query', |
|||
prop: 'revisions', |
|||
rvlimit: 1, // load the big wikitext only for the revision |
|||
rvprop: 'ids|user|timestamp|tags|parsedcomment|content', |
|||
rvslots: 'main', |
|||
formatversion: 2, // v2 has nicer field names in responses |
|||
titles: this.#pagename, |
|||
rvstartid: revision.revid, |
|||
}; |
|||
debug('loadContent: contentQuery = ', contentQuery); |
|||
this.#api.get(contentQuery).then(response => { |
|||
try { |
|||
const theRevision = response.query.pages[0].revisions[0]; |
|||
resolve(theRevision); |
|||
} catch (e) { |
|||
// just in case the chain `response.query.pages[0].revisions[0]` |
|||
// is broken somehow |
|||
error('loadContent:', e); |
|||
reject('loadContent:' + e); |
|||
} |
|||
}, rejection => { |
|||
reject('loadContent via api:' + rejection); |
|||
}); |
|||
} catch (e) { |
|||
error('loadContent:', e); |
|||
reject('loadContent: ' + e); |
|||
} |
|||
}); |
|||
this.#indexedContentPromises[index] = promise; |
|||
return promise; |
|||
} |
|||
async loadRevisionId(index) { |
|||
return this.#revisionsLoader.loadRevision(index); |
|||
} |
|||
} |
|||
function midPoint(lower, upper) { |
|||
return Math.floor(lower + (upper - lower) / 2); |
|||
} |
|||
/** |
|||
* Based on https://en.wikipedia.org/wiki/Module:Exponential_search |
|||
*/ |
|||
async function exponentialSearch(lower, upper, candidateIndex, testFunc) { |
|||
const progressMessage = `Examining [${lower}, ${upper ? upper : '...'}]. Current candidate: ${candidateIndex}`; |
|||
if (await testFunc(candidateIndex, progressMessage)) { |
|||
if (candidateIndex + 1 == upper) { |
|||
return candidateIndex; |
|||
} |
|||
lower = candidateIndex; |
|||
if (upper) { |
|||
candidateIndex = midPoint(lower, upper); |
|||
} else { |
|||
candidateIndex = candidateIndex * 2; |
|||
} |
|||
return exponentialSearch(lower, upper, candidateIndex, testFunc); |
|||
} else { |
|||
upper = candidateIndex; |
|||
candidateIndex = midPoint(lower, upper); |
|||
return exponentialSearch(lower, upper, candidateIndex, testFunc); |
|||
} |
|||
} |
|||
class PageHistoryContentSearcher { |
|||
#pagename; |
|||
#contentLoader; |
|||
#progressCallback; |
|||
constructor(pagename, progressCallback) { |
|||
this.#pagename = pagename; |
|||
this.#contentLoader = new LazyFullRevisionsLoader(this.#pagename); |
|||
this.#progressCallback = progressCallback; |
|||
} |
|||
setProgressCallback(progressCallback) { |
|||
this.#progressCallback = progressCallback; |
|||
} |
|||
async findRevisionWhenTextAdded(text, startIndex) { |
|||
info('findRevisionWhenTextAdded: searching for', text); |
|||
return new Promise(async (resolve, reject) => { |
|||
try { |
|||
if (startIndex === 0) { |
|||
const latestFullRevision = await this.#contentLoader.loadContent(startIndex); |
|||
if (latestFullRevision == undefined) { |
|||
reject("Cannot find the latest revision. Does this page exist?"); |
|||
return; |
|||
} |
|||
if (!latestFullRevision.slots.main.content.includes(text)) { |
|||
reject("Cannot find text in the latest revision. Did you edit it?"); |
|||
return; |
|||
} |
|||
} |
|||
const foundIndex = await exponentialSearch(startIndex, null, startIndex + 10, async (candidateIndex, progressInfo) => { |
|||
try { |
|||
this.#progressCallback(progressInfo); |
|||
const candidateFullRevision = await this.#contentLoader.loadContent(candidateIndex); |
|||
if (candidateFullRevision == undefined) { |
|||
return false; |
|||
} |
|||
// debug('testFunc: checking text of revision:', candidateFullRevision, candidateFullRevision?.slots, candidateFullRevision?.slots?.main); |
|||
return candidateFullRevision.slots.main.content.includes(text); |
|||
} catch (e) { |
|||
reject('testFunc: ' + e); |
|||
} |
|||
}); |
|||
const foundFullRevision = await this.#contentLoader.loadContent(foundIndex); |
|||
resolve({ |
|||
fullRevision: foundFullRevision, |
|||
index: foundIndex, |
|||
}); |
|||
} catch (e) { |
|||
reject(e); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
function isRevisionARevert(fullRevision) { |
|||
if (fullRevision.tags.includes('mw-rollback')) { |
|||
return true; |
|||
} |
|||
if (fullRevision.tags.includes('mw-undo')) { |
|||
return true; |
|||
} |
|||
if (fullRevision.parsedcomment.includes('Undid')) { |
|||
return true; |
|||
} |
|||
if (fullRevision.parsedcomment.includes('Reverted')) { |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
function chooseTemplateFromRevision(fullRevision) { |
|||
if (typeof (fullRevision.anon) !== 'undefined') { |
|||
return 'Unsigned IP'; |
|||
} else if (typeof (fullRevision.temp) !== 'undefined') { |
|||
// Seems unlikely "temporary" users will have a user page, so this seems the better template for them for now. |
|||
return 'Unsigned IP'; |
|||
} else { |
|||
return 'Unsigned'; |
|||
} |
|||
} |
|||
function makeUnsignedTemplate(user, timestamp, template) { |
function makeUnsignedTemplate(user, timestamp, template) { |
||
const ts = new Date(timestamp); |
const ts = new Date(timestamp); |
||
let h = ts.getUTCHours(); |
let h = ts.getUTCHours(); |
||
if (h < 10) |
if (h < 10) { |
||
h = '0' + h; |
h = '0' + h; |
||
} |
|||
let m = ts.getUTCMinutes(); |
let m = ts.getUTCMinutes(); |
||
if (m < 10) |
if (m < 10) { |
||
m = '0' + m; |
m = '0' + m; |
||
} |
|||
const formattedTimestamp = `${h}:${m}, ${ts.getUTCDate()} ${months[ts.getUTCMonth()]} ${ts.getUTCFullYear()} (UTC)`; |
const formattedTimestamp = `${h}:${m}, ${ts.getUTCDate()} ${months[ts.getUTCMonth()]} ${ts.getUTCFullYear()} (UTC)`; |
||
return '{{subst:' + template + '|' + user + '|' + formattedTimestamp + '}}'; |
return '{{subst:' + template + '|' + user + '|' + formattedTimestamp + '}}'; |
||
| Line 17: | Line 361: | ||
const editSummaryField = $("#wpSummary:first"); |
const editSummaryField = $("#wpSummary:first"); |
||
if (editSummaryField.length == 0) { |
if (editSummaryField.length == 0) { |
||
warn('Cannot find edit summary text field.'); |
|||
return; |
return; |
||
} |
} |
||
| Line 34: | Line 378: | ||
} |
} |
||
// kept outside of doAddUnsignedTemplate() to keep all the caches |
|||
function formatErrorSpan(errorMessage) { |
|||
let searcher; |
|||
return `<span style="color:maroon;"><b>Error:</b> ${errorMessage}</span>`; |
|||
function getSearcher() { |
|||
if (searcher) { |
|||
return searcher; |
|||
} |
|||
const pagename = mw.config.get('wgPageName'); |
|||
searcher = new PageHistoryContentSearcher(pagename, progressInfo => { |
|||
info('Default progress callback', progressInfo); |
|||
}); |
|||
return searcher; |
|||
} |
} |
||
function doAddUnsignedTemplate() { |
async function doAddUnsignedTemplate() { |
||
const form = document.getElementById('editform'); |
|||
const wikitextEditor = form.elements.wpTextbox1; |
|||
let pos = $(wikitextEditor).textSelection('getCaretPosition', { startAndEnd: true }); |
|||
let txt; |
|||
if (pos[0] != pos[1]) { |
if (pos[0] != pos[1]) { |
||
txt = |
txt = wikitextEditor.value.substring(pos[0], pos[1]); |
||
pos = pos[1]; |
pos = pos[1]; |
||
} else { |
} else { |
||
pos = pos[1]; |
pos = pos[1]; |
||
if (pos <= 0) |
if (pos <= 0) { |
||
pos = |
pos = wikitextEditor.value.length; |
||
} |
|||
txt = e.value.substr(0, pos); |
|||
txt = wikitextEditor.value.substr(0, pos); |
|||
txt = txt.replace(new RegExp('[\\s\\S]*\\d\\d:\\d\\d, \\d+ (' + months.join('|') + ') \\d\\d\\d\\d \\(UTC\\)'), ''); |
txt = txt.replace(new RegExp('[\\s\\S]*\\d\\d:\\d\\d, \\d+ (' + months.join('|') + ') \\d\\d\\d\\d \\(UTC\\)'), ''); |
||
txt = txt.replace(/[\s\S]*\n=+.*=+\s*\n/, ''); |
txt = txt.replace(/[\s\S]*\n=+.*=+\s*\n/, ''); |
||
| Line 56: | Line 410: | ||
txt = txt.replace(/^\s+|\s+$/g, ''); |
txt = txt.replace(/^\s+|\s+$/g, ''); |
||
// TODO maybe migrate to https://www.mediawiki.org/wiki/OOUI/Windows/Message_Dialogs |
|||
var rvct = 1; |
|||
const mainDialog = $('<div>Examining...</div>').dialog({ |
|||
buttons: { |
buttons: { |
||
Cancel: function () { |
Cancel: function () { |
||
mainDialog.dialog('close'); |
|||
} |
} |
||
}, |
|||
modal: true, |
modal: true, |
||
title: 'Adding {{unsigned}}' |
title: 'Adding {{unsigned}}' |
||
}); |
}); |
||
getSearcher().setProgressCallback(debugInfo => { |
|||
var revid, user, ts, comment, template; |
|||
/* progressCallback */ |
|||
var q = { |
|||
info('Showing to user:', debugInfo); |
|||
url: mw.util.wikiScript('api'), |
|||
mainDialog.html(debugInfo); |
|||
dataType: 'json', |
|||
}); |
|||
type: 'POST', |
|||
data: { |
|||
format: 'json', |
|||
action: 'query', |
|||
titles: mw.config.get('wgPageName'), |
|||
prop: 'revisions', |
|||
rvprop: 'ids|timestamp|user|parsedcomment|content', |
|||
rvlimit: 1, |
|||
rawcontinue: 1 |
|||
}, |
|||
success: function (r, sts, xhr) { |
|||
if (!dialog.dialog('isOpen')) |
|||
return; |
|||
function applySearcherResult(searcherResult) { |
|||
if (!r.query || !r.query.pages) { |
|||
const fullRevision = searcherResult.fullRevision; |
|||
dialog.html(formatErrorSpan('Bad response from API')); |
|||
const template = chooseTemplateFromRevision(fullRevision); |
|||
if (window.console && typeof (window.console.error) == 'function') |
|||
const templateWikitext = makeUnsignedTemplate( |
|||
window.console.error("Bad response", r); |
|||
fullRevision.user, |
|||
return; |
|||
fullRevision.timestamp, |
|||
template |
|||
); |
|||
const newWikitextTillSelection = wikitextEditor.value.substr(0, pos).replace(/\s*$/, ' ') + templateWikitext; |
|||
wikitextEditor.value = newWikitextTillSelection + wikitextEditor.value.substr(pos); |
|||
$(wikitextEditor).textSelection('setSelection', { start: newWikitextTillSelection.length }); |
|||
appendToEditSummary(`mark unsigned [[Special:Diff/${fullRevision.revid}]]`); |
|||
mainDialog.dialog('close'); |
|||
} |
|||
function reportPossibleRevertToUser(searcherResult, useCb, keepLookingCb, cancelCb) { |
|||
const fullRevision = searcherResult.fullRevision; |
|||
const revid = fullRevision.revid; |
|||
const comment = fullRevision.parsedcomment; |
|||
const suspicionDialog = $('<div>') |
|||
.append( |
|||
"The ", |
|||
$('<a>').prop({ |
|||
href: '/w/index.php?diff=prev&oldid=' + revid, |
|||
target: '_blank' |
|||
}).text(`found revision (index=${searcherResult.index})`), |
|||
" may be a revert: ", |
|||
comment |
|||
) |
|||
.dialog({ |
|||
title: "Possible revert!", |
|||
modal: true, |
|||
buttons: { |
|||
"Use that revision": function () { |
|||
suspicionDialog.dialog('close'); |
|||
useCb(); |
|||
}, |
|||
"Keep looking": function () { |
|||
suspicionDialog.dialog('close'); |
|||
keepLookingCb(); |
|||
}, |
|||
"Cancel": function () { |
|||
suspicionDialog.dialog('close'); |
|||
cancelCb(); |
|||
}, |
|||
} |
} |
||
}); |
|||
} |
|||
function searchFromIndex(index) { |
|||
for (var k in r.query.pages) { |
|||
searcher.findRevisionWhenTextAdded(txt, index).then(searcherResult => { |
|||
if ('missing' in r.query.pages[k]) { |
|||
if (!mainDialog.dialog('isOpen')) { |
|||
continue; |
|||
// user clicked [cancel] |
|||
} |
|||
return; |
|||
var rr = r.query.pages[k].revisions[0]; |
|||
} |
|||
var cont = function () { |
|||
info('Searcher found:', searcherResult); |
|||
if (r['query-continue'] && r['query-continue'].revisions) { |
|||
if (isRevisionARevert(searcherResult.fullRevision)) { |
|||
dialog.html('Evaluating revision ' + (++rvct) + '...'); |
|||
reportPossibleRevertToUser( |
|||
q.data.rvcontinue = r['query-continue'].revisions.rvcontinue; |
|||
searcherResult, |
|||
() => { /* use */ |
|||
applySearcherResult(searcherResult); |
|||
var t = makeUnsignedTemplate(user, ts, template); |
|||
}, |
|||
var tt = e.value.substr(0, pos).replace(/\s*$/, ' ') + t; |
|||
() => { /* keep looking */ |
|||
// recursive call from a differfent index: `+1` is very important here |
|||
$(e).textSelection('setSelection', { start: tt.length }); |
|||
searchFromIndex(searcherResult.index + 1); |
|||
appendToEditSummary(`mark unsigned [[Special:Diff/${revid}]]`); |
|||
}, |
|||
dialog.dialog('close'); |
|||
() => { /* cancel */ |
|||
mainDialog.dialog('close'); |
|||
}; |
|||
if (typeof (rr['*']) != 'undefined' && rr['*'].indexOf(txt) < 0) { |
|||
if (!user) { |
|||
dialog.html(formatErrorSpan('Text was not found in the starting revision! Did you edit it?')); |
|||
return; |
|||
} |
} |
||
); |
|||
var t = makeUnsignedTemplate(user, ts, template); |
|||
var tt = e.value.substr(0, pos).replace(/\s*$/, ' ') + t; |
|||
e.value = tt + e.value.substr(pos); |
|||
$(e).textSelection('setSelection', { start: tt.length }); |
|||
appendToEditSummary(`mark unsigned [[Special:Diff/${revid}]]`); |
|||
dialog.dialog('close'); |
|||
}; |
|||
if (/reverted|undid/i.test(comment)) { |
|||
var dialog2 = $('<div>') |
|||
.append( |
|||
'The ', |
|||
$('<a>').prop({ |
|||
href: '/w/index.php?diff=prev&oldid=' + revid, target: '_blank' }).text('found revision'), |
|||
' may be a revert: ', |
|||
comment |
|||
) |
|||
.dialog({ |
|||
title: "Possible revert!", |
|||
modal: true, |
|||
buttons: { |
|||
"Use that revision": function () { |
|||
dialog2.dialog('close'); |
|||
cb(); |
|||
}, |
|||
"Keep looking": function () { |
|||
dialog2.dialog('close'); |
|||
cont(); |
|||
}, |
|||
"Cancel": function () { |
|||
dialog2.dialog('close'); |
|||
dialog.dialog('close'); |
|||
}, |
|||
} |
|||
}); |
|||
} else { |
|||
cb(); |
|||
} |
|||
} else { |
|||
revid = rr.revid; |
|||
user = rr.user; |
|||
ts = rr.timestamp; |
|||
comment = rr.parsedcomment; |
|||
if (typeof (rr.anon) !== 'undefined') { |
|||
template = 'Unsigned IP'; |
|||
} else if (typeof (rr.temp) !== 'undefined') { |
|||
// Seems unlikely "temporary" users will have a user page, so this seems the better template for them for now. |
|||
template = 'Unsigned IP'; |
|||
} else { |
|||
template = 'Unsigned'; |
|||
} |
|||
cont(); |
|||
} |
|||
return; |
return; |
||
} |
} |
||
applySearcherResult(searcherResult); |
|||
dialog.html(formatErrorSpan('No revisions found in the page!')); |
|||
}, |
}, rejection => { |
||
error(`Searcher cannot find requested index=${index}. Got error:`, rejection); |
|||
error: function (xhr, textStatus, errorThrown) { |
|||
if (! |
if (!mainDialog.dialog('isOpen')) { |
||
// user clicked [cancel] |
|||
return; |
return; |
||
} |
|||
dialog.html(formatErrorSpan(textStatus + ' ' + errorThrown)); |
|||
mainDialog.html(formatErrorSpan(`${rejection}`)); |
|||
} |
|||
}; |
}); |
||
} |
|||
if (f.elements.baseRevId) |
|||
q.data.rvstartid = f.elements.baseRevId.value; |
|||
searchFromIndex(0); |
|||
} |
} |
||
| Line 190: | Line 519: | ||
return false; |
return false; |
||
} |
} |
||
if (!window.charinsertCustom) |
if (!window.charinsertCustom) { |
||
window.charinsertCustom = {}; |
window.charinsertCustom = {}; |
||
} |
|||
if (!window.charinsertCustom['Insert']) |
|||
if (!window.charinsertCustom.Insert) { |
|||
window.charinsertCustom.Insert = ''; |
|||
} |
|||
if (!window.charinsertCustom['Wiki markup']) |
|||
window.charinsertCustom.Insert += ' {{unsigned}}\x10unsignedHelperAddUnsignedTemplate'; |
|||
if (!window.charinsertCustom['Wiki markup']) { |
|||
window.charinsertCustom['Wiki markup'] = ''; |
window.charinsertCustom['Wiki markup'] = ''; |
||
} |
|||
window.charinsertCustom['Wiki markup'] += ' {{unsigned}}\x10unsignedHelperAddUnsignedTemplate'; |
window.charinsertCustom['Wiki markup'] += ' {{unsigned}}\x10unsignedHelperAddUnsignedTemplate'; |
||
if (window.updateEditTools) |
if (window.updateEditTools) { |
||
window.updateEditTools(); |
window.updateEditTools(); |
||
} |
|||
info('Added edit tools.'); |
|||
})(); |
})(); |
||