User:Andrybak/Scripts/Unsigned helper.js
Appearance
From Wikipedia, the free encyclopedia
< User:Andrybak | Scripts
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.
This code will be executed when previewing this page.
This user script seems to have a documentation page at User:Andrybak/Scripts/Unsigned helper.
/* * This is a fork of https://en.wikipedia.org/w/index.php?title=User:Anomie/unsignedhelper.js&oldid=1219219971 */ (function(){ constDEBUG=false; constLOG_PREFIX=`[Unsigned Helper]:`; functionerror(...toLog){ console.error(LOG_PREFIX,...toLog); } functionwarn(...toLog){ console.warn(LOG_PREFIX,...toLog); } functioninfo(...toLog){ console.info(LOG_PREFIX,...toLog); } functiondebug(...toLog){ console.debug(LOG_PREFIX,...toLog); } constmonths=['January','February','March','April','May','June','July','August','September','October','November','December']; constCONFIG={ undated:'Undated',// [[Template:Undated]] unsigned:'Unsigned',// [[Template:Unsigned]] }; if(mw.config.get('wgAction')!=='edit'&&mw.config.get('wgAction')!=='submit'&&document.getElementById("editform")==null){ return; } info('Loading...'); functionformatErrorSpan(errorMessage){ return`<span style="color:maroon;"><b>Error:</b> ${errorMessage}</span>`; } /** * Batch size for {@link LazyRevisionIdsLoader}. */ constLAZY_REVISION_LOADING_INTERVAL=50; /** * Lazily loads revision IDs for a page. The loading is done linearly, * in batches of the size of {@link LAZY_REVISION_LOADING_INTERVAL}. * This is relatively fast because we are not loading the heavy contents * of the page, only the metadata. * Gives zero-indexed access to the revisions. Zeroth revision is the newest revision. */ classLazyRevisionIdsLoader{ #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=newmw.Api(); constructor(pagename){ this.#pagename=pagename; } #getLastLoadedInterval(upToIndex){ debug(`#getLastLoadedInterval(${upToIndex}): `,this.#historyIntervalPromises.length); leti=0; while(this.#historyIntervalPromises[i]!=undefined&&i<=upToIndex){ i++; } debug(`#getLastLoadedInterval(${upToIndex}) = ${i}`); return[i,this.#historyIntervalPromises[i-1]]; } #createIntervalFromResponse(response){ if('missing'inresponse.query.pages[0]){ returnundefined; } constinterval={ rvcontinue:response.continue?.rvcontinue, revisions:response.query.pages[0].revisions, }; if(response.batchcomplete){ // remember that MediaWiki has no more revisions to return interval.batchcomplete=true; }else{ interval.batchcomplete=false; } returninterval; } async#loadIntervalsRecursive(startIndex,targetIndex,rvcontinue){ constlogMsgPrefix=`#loadIntervalsRecursive(${startIndex}, ${targetIndex}, '${rvcontinue}')`; returnnewPromise(async(resolve,reject)=>{ // reference documentation: https://en.wikipedia.org/w/api.php?action=help&modules=query%2Brevisions constintervalQuery={ 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(`${logMsgPrefix} Q =`,intervalQuery); this.#api.get(intervalQuery).then(async(response)=>{ try{ if(DEBUG){ debug(`${logMsgPrefix} R =`,response); } constinterval=this.#createIntervalFromResponse(response); this.#historyIntervalPromises[startIndex]=Promise.resolve(interval); if(startIndex==targetIndex){ // we've hit the limit of what we want to load so far resolve(interval); return; } if(interval.batchcomplete){ // reached the end of batch loading => cannot ask for one more // for convenience, fill the rest of the array with undefined for(leti=startIndex+1;i<=targetIndex;i++){ this.#historyIntervalPromises[i]=Promise.resolve(undefined); } info(`${logMsgPrefix}: This is the last batch returned by MediaWiki`); if(targetIndex<=startIndex){ error(`${logMsgPrefix}: something went very wrong`); } resolve(undefined); return; } // .batchcomplete has not been reached, call for one more interval (recursive) constignored=awaitthis.#loadIntervalsRecursive(startIndex+1,targetIndex,interval.rvcontinue); if(this.#historyIntervalPromises[targetIndex]==undefined){ resolve(undefined); return; } this.#historyIntervalPromises[targetIndex].then( result=>resolve(result), rejection=>reject(rejection) ); }catch(e){ reject('loadIntervalsRecursive: '+e); } },rejection=>{ reject('loadIntervalsRecursive via api: '+rejection); }); }); } async#loadInterval(intervalIndex){ const[firstNotLoadedIntervalIndex,latestLoadedIntervalPromise]=this.#getLastLoadedInterval(intervalIndex); if(firstNotLoadedIntervalIndex>intervalIndex){ returnthis.#historyIntervalPromises[intervalIndex]; } if(awaitlatestLoadedIntervalPromise?.then(interval=>interval.batchcomplete)){ // latest request returned the last batch in the batch loading of revisions returnPromise.resolve(undefined); } constrvcontinue=awaitlatestLoadedIntervalPromise?.then(interval=>interval.rvcontinue); debug(`#loadInterval(${intervalIndex}): ${firstNotLoadedIntervalIndex}, ${rvcontinue}`); returnthis.#loadIntervalsRecursive(firstNotLoadedIntervalIndex,intervalIndex,rvcontinue); } #indexToIntervalIndex(index){ returnMath.floor(index/LAZY_REVISION_LOADING_INTERVAL); } #indexToIndexInInterval(index){ returnindex%LAZY_REVISION_LOADING_INTERVAL; } #revisionsToString(revisions){ if(!revisions){ return"<undefined revisions>"; } returnArray.from(revisions).map((revision,index)=>{ return`[${index}]={revid=${revision.revid} by User:${revision.user}}` }).join(", "); } /** * @param index zero-based index of a revision to load */ asyncloadRevision(index){ if(this.#indexedRevisionPromises[index]){ returnthis.#indexedRevisionPromises[index]; } constpromise=newPromise(async(resolve,reject)=>{ constintervalIndex=this.#indexToIntervalIndex(index); debug(`loadRevision: loading from interval #${intervalIndex}...`); try{ constinterval=awaitthis.#loadInterval(intervalIndex); if(DEBUG){ debug(`loadRevision: loaded the interval#${intervalIndex} with revisions: (length=${interval?.revisions?.length}) ${this.#revisionsToString(interval?.revisions)}`); } if(interval==undefined){ resolve(undefined); return; } constindexInInterval=this.#indexToIndexInInterval(index); if(DEBUG){ debug(`loadRevision: from the above interval, looking at [${indexInInterval}]`); } consttheRevision=interval.revisions[indexInInterval]; debug('loadRevision: loaded revision',index,theRevision); resolve(theRevision); }catch(e){ reject('loadRevision: '+e); } }); this.#indexedRevisionPromises[index]=promise; returnpromise; } } /** * Lazily loads full revisions (full wikitext and metadata) for a page. * Gives zero-indexed access to the revisions. Zeroth revision is the newest revision. * Loaded revisions are cached to speed up consecutive requests about the * same page. */ classLazyFullRevisionsLoader{ #pagename; #revisionsLoader; #indexedContentPromises=[]; #api=newmw.Api(); constructor(pagename){ this.#pagename=pagename; this.#revisionsLoader=newLazyRevisionIdsLoader(pagename); } /** * Returns a {@link Promise} with full revision for given index. */ asyncloadContent(index){ if(this.#indexedContentPromises[index]){ returnthis.#indexedContentPromises[index]; } constpromise=newPromise(async(resolve,reject)=>{ try{ constrevision=awaitthis.#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 constcontentQuery={ 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{ consttheRevision=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; returnpromise; } asyncloadRevisionId(index){ returnthis.#revisionsLoader.loadRevision(index); } } functionmidPoint(lower,upper){ returnMath.floor(lower+(upper-lower)/2); } /** * Based on https://en.wikipedia.org/wiki/Module:Exponential_search */ asyncfunctionexponentialSearch(lower,upper,candidateIndex,testFunc){ if(upper===null&&lower===candidateIndex){ thrownewError(`Wrong arguments for exponentialSearch (${lower}, ${upper}, ${candidateIndex}).`); } if(lower===upper&&lower===candidateIndex){ thrownewError("Cannot find it"); } constprogressMessage=`Examining [${lower}, ${upper?upper:'...'}]. Current candidate: ${candidateIndex}`; if(awaittestFunc(candidateIndex,progressMessage)){ if(candidateIndex+1==upper){ returncandidateIndex; } lower=candidateIndex; if(upper){ candidateIndex=midPoint(lower,upper); }else{ candidateIndex=candidateIndex*2; } returnexponentialSearch(lower,upper,candidateIndex,testFunc); }else{ upper=candidateIndex; candidateIndex=midPoint(lower,upper); returnexponentialSearch(lower,upper,candidateIndex,testFunc); } } classPageHistoryContentSearcher{ #pagename; #contentLoader; #progressCallback; constructor(pagename,progressCallback){ this.#pagename=pagename; this.#contentLoader=newLazyFullRevisionsLoader(this.#pagename); this.#progressCallback=progressCallback; } setProgressCallback(progressCallback){ this.#progressCallback=progressCallback; } getContentLoader(){ returnthis.#contentLoader; } /** * Uses an exponential initial search followed by a binary search to find * a snippet of text, which was added to the page. */ asyncfindRevisionWhenTextAdded(text,startIndex){ info(`findRevisionWhenTextAdded(startIndex=${startIndex}): searching for '${text}'`); returnnewPromise(async(resolve,reject)=>{ try{ conststartRevision=awaitthis.#contentLoader.loadRevisionId(startIndex); if(startRevision==undefined){ if(startIndex===0){ reject("Cannot find the latest revision. Does this page exist?"); }else{ reject(`Cannot find the start revision (index=${startIndex}).`); } return; } if(startIndex===0){ constlatestFullRevision=awaitthis.#contentLoader.loadContent(startIndex); if(!latestFullRevision.slots.main.content.includes(text)){ reject("Cannot find text in the latest revision. Did you edit it?"); return; } } constfoundIndex=awaitexponentialSearch(startIndex,null,startIndex+10,async(candidateIndex,progressInfo)=>{ try{ this.#progressCallback(progressInfo); constcandidateFullRevision=awaitthis.#contentLoader.loadContent(candidateIndex); if(candidateFullRevision?.slots?.main?.content==undefined){ /* * TODO can we distinguish between * - `candidateIndex` is out of bounds of the history * vs * - `candidateIndex` is obscured from current user ([[WP:REVDEL]] or [[WP:SUPPRESS]]) * ? */ warn('testFunc: Cannot load the content for candidateIndex = '+candidateIndex); returnundefined; } // debug('testFunc: checking text of revision:', candidateFullRevision, candidateFullRevision?.slots, candidateFullRevision?.slots?.main); returncandidateFullRevision.slots.main.content.includes(text); }catch(e){ reject('testFunc: '+e); } }); if(foundIndex===undefined){ reject("Cannot find this text."); return; } constfoundFullRevision=awaitthis.#contentLoader.loadContent(foundIndex); resolve({ fullRevision:foundFullRevision, index:foundIndex, }); }catch(e){ reject(e); } }); } } functionisRevisionARevert(fullRevision){ if(fullRevision.tags.includes('mw-rollback')){ returntrue; } if(fullRevision.tags.includes('mw-undo')){ returntrue; } if(fullRevision.parsedcomment.includes('Undid')){ returntrue; } if(fullRevision.parsedcomment.includes('Reverted')){ returntrue; } returnfalse; } functionchooseTemplate(selectedText,fullRevision){ constuser=fullRevision.user; if(selectedText.includes(`[[User talk:${user}|`)||selectedText.includes(`[[user talk:${user}|`)){ /* * assume that presense of something that looks like a wikilink to the user's talk page * means that the message is just undated, not unsigned * NB: IP editors have `Special:Contributions` and `User talk` in their signature. */ returnCONFIG.undated; } if(selectedText.includes(`[[User:${user}`)||selectedText.includes(`[[user:${user}`)){ // some ancient undated signatures have only `[[User:` links returnCONFIG.undated; } returnCONFIG.unsigned; } functioncreateTimestampWikitext(timestamp){ /* * Format is from https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time_format_like_in_signatures * * The unicode escapes are needed to avoid actual substitution, see * https://en.wikipedia.org/w/index.php?title=User:Andrybak/Scripts/Unsigned_generator.js&diff=prev&oldid=1229098580 */ return`\u007B\u007Bsubst:#time:H:i, j xg Y "(UTC)"|${timestamp}}}`; } functionmakeTemplate(user,timestamp,template){ // <nowiki> constformattedTimestamp=createTimestampWikitext(timestamp); if(template==CONFIG.undated){ return'{{subst:'+template+'|'+formattedTimestamp+'}}'; } return'{{subst:'+template+'|'+user+'|'+formattedTimestamp+'}}'; // </nowiki> } functionconstructAd(){ return" (using [[w:User:Andrybak/Scripts/Unsigned helper|Unsigned helper]])"; } functionextractDiffIds(template,summary){ constregex=newRegExp(`mark \\[\\[Template:${template}\\|\\{\\{${template}\\}\\}\\]\\] ((\\[\\[Special:Diff\\\/[0-9]+\\]\\][ ,]*)+)`,'gm'); letmatch; constdiffs=newSet(); while((match=regex.exec(summary))!==null){ constdiffRegex=/\[\[Special:Diff\/[0-9]+\]\]/gm; letdiffMatch; while((diffMatch=diffRegex.exec(match[0]))!==null){ diffs.add(diffMatch[0]); } } returndiffs; } functionextractAccountDiffIds(summary){ returnextractDiffIds(CONFIG.unsigned,summary); } functionextractUndatedDiffIds(summary){ returnextractDiffIds(CONFIG.undated,summary); } functioncreateEditSummary(template,diffs){ constdiffsStr=Array.from(diffs).join(", "); if(diffsStr.length===0){ return""; } return`mark [[Template:${template}|{{${template}}}]] ${diffsStr}`; } functioncreateUnsignedEditSummary(diffs){ returncreateEditSummary(CONFIG.unsigned,diffs) } functioncreateUndatedEditSummary(diffs){ returncreateEditSummary(CONFIG.undated,diffs) } functionregenerateNewSummary(oldText){ constreplaceRegex=/mark (.*) \(using[^)]*\)/gm; constaccountDiffs=extractAccountDiffIds(oldText); constnewAccountText=createUnsignedEditSummary(accountDiffs); constundatedDiffs=extractUndatedDiffIds(oldText); constnewUndatedText=createUndatedEditSummary(undatedDiffs); debug('newAccountText',newAccountText); debug('newUndatedText',newUndatedText); if(newAccountText.length===0&&newUndatedText.length===0){ returnoldText; }elseif(newAccountText.length===0){ returnoldText.replace(replaceRegex,newUndatedText); }elseif(newUndatedText.length===0){ returnoldText.replace(replaceRegex,newAccountText); }else{ returnoldText.replace(replaceRegex,newAccountText+", "+newUndatedText); } } functionappendToEditSummary(newSummary){ consteditSummaryField=$("#wpSummary:first"); if(editSummaryField.length===0){ warn('Cannot find edit summary text field.'); return; } // get text without trailing whitespace letoldText=editSummaryField.val().trimEnd(); constad=constructAd(); letnewText=""; debug('oldText',oldText); if(oldText.match(/[*]\/$/)){ debug('Section name found'); // check if "/* section name */" is present newText=oldText+" "+newSummary; }elseif(oldText.length!==0){ debug('Some old text found'); newText=oldText.replace(ad,'')+", "+newSummary; newText=regenerateNewSummary(newText+ad); }else{ debug('Old text not found'); newText=newSummary; } editSummaryField.val(newText+ad); } // kept outside of doAddUnsignedTemplate() to keep all the caches letsearcher; functiongetSearcher(){ if(searcher){ returnsearcher; } constpagename=mw.config.get('wgPageName'); searcher=newPageHistoryContentSearcher(pagename,progressInfo=>{ info('Default progress callback',progressInfo); }); returnsearcher; } asyncfunctiondoAddUnsignedTemplate(){ constform=document.getElementById('editform'); constwikitextEditor=form.elements.wpTextbox1; /* * https://doc.wikimedia.org/mediawiki-core/master/js/module-jquery.textSelection.html * We cannot use wikitextEditor.value here, because this textarea is hidden and * is not updated with CodeMirror. Therefore, the selection in CodeMirror becomes * desynced from the text in wikitextEditor. * However, CodeMirror does respond to textSelection "commands" sent to wikitextEditor. * The responses correspond with up-to-date wikitext in CodeMirror. * For reference, see https://en.wikipedia.org/wiki/MediaWiki:Gadget-charinsert-core.js#L-251--L-258 */ const$editor=$(wikitextEditor); while($editor.textSelection('getSelection').endsWith('\n')){ const[selectionStart,selectionEnd]=$editor.textSelection('getCaretPosition',{startAndEnd:true}); $editor.textSelection('setSelection',{start:selectionStart,end:(selectionEnd-1)}); } constoriginalSelection=$(wikitextEditor).textSelection('getSelection'); letselection=originalSelection; debug(`doAddUnsignedTemplate: getSelection: '${selection}'`); selection=selection.replace(newRegExp('[\\s\\S]*\\d\\d:\\d\\d, \\d+ ('+months.join('|')+') \\d\\d\\d\\d \\(UTC\\)([<]/small[>])?'),''); selection=selection.replace(/[\s\S]*\n=+.*=+\s*\n/,''); selection=selection.replace(/^\s+|\s+$/g,''); debug(`doAddUnsignedTemplate: getSelection filtered: '${selection}'`); // TODO maybe migrate to https://www.mediawiki.org/wiki/OOUI/Windows/Message_Dialogs constmainDialog=$('<div>Examining...</div>').dialog({ buttons:{ Cancel:function(){ mainDialog.dialog('close'); } }, modal:true, title:'Adding {{unsigned}}' }); getSearcher().setProgressCallback(debugInfo=>{ /* progressCallback */ info('Showing to user:',debugInfo); mainDialog.html(debugInfo); }); functionapplySearcherResult(searcherResult){ constfullRevision=searcherResult.fullRevision; consttemplate=chooseTemplate(selection,fullRevision); consttemplateWikitext=makeTemplate( fullRevision.user, fullRevision.timestamp, template ); // https://doc.wikimedia.org/mediawiki-core/master/js/module-jquery.textSelection.html $(wikitextEditor).textSelection( 'encapsulateSelection',{ post:" "+templateWikitext } ); appendToEditSummary(createEditSummary(template,[`[[Special:Diff/${fullRevision.revid}]]`])); mainDialog.dialog('close'); } functionreportSearcherResultToUser(searcherResult,dialogTitle,useCb,keepLookingCb,cancelCb,createMainMessageDivFn){ constfullRevision=searcherResult.fullRevision; constrevid=fullRevision.revid; constcomment=fullRevision.parsedcomment; constquestionDialog=createMainMessageDivFn() .dialog({ title:dialogTitle, minWidth:document.body.clientWidth/5, modal:true, buttons:{ "Use that revision":function(){ questionDialog.dialog('close'); useCb(); }, "Keep looking":function(){ questionDialog.dialog('close'); keepLookingCb(); }, "Cancel":function(){ questionDialog.dialog('close'); cancelCb(); }, } }); } functionreportPossibleRevertToUser(searcherResult,useCb,keepLookingCb,cancelCb){ constfullRevision=searcherResult.fullRevision; constrevid=fullRevision.revid; constcomment=fullRevision.parsedcomment; reportSearcherResultToUser(searcherResult,"Possible revert!",useCb,keepLookingCb,cancelCb,()=>{ return$('<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 ); }); } functionformatTimestamp(timestamp){ // return new Date(timestamp).toLocaleString(); returntimestamp; } functionrevisionByteSize(fullRevision){ if(fullRevision==null){ returnnull; } returnnewBlob([fullRevision.slots.main.content]).size; } functionformatSizeChange(afterSize,beforeSize){ constchange=afterSize-beforeSize; consttitle=`${afterSize} bytes after change of this size`; consttitleAttribute=`title="${title}"`; letstyle=''; letchangeText=""+change; if(change>0){ changeText="+"+change; style='color: var(--color-content-added,#006400);'; } if(change<0){ // use proper minus sign ([[Plus and minus signs#Minus sign]]) changeText="−"+Math.abs(change); style='color: var(--color-content-removed,#8b0000);'; } if(Math.abs(change)>500){ // [[Help:Watchlist#How to read a watchlist (or recent changes)]] style=style+"font-weight:bold;"; } changeText=`(${changeText})`; return$('<span>').text(changeText).attr('style',style).attr('title',title); } asyncfunctionreportNormalSearcherResultToUser(searcherResult,useCb,keepLookingCb,cancelCb){ constfullRevision=searcherResult.fullRevision; constuser=fullRevision.user; constrevid=fullRevision.revid; constcomment=fullRevision.parsedcomment; constafterSize=revisionByteSize(fullRevision); constbeforeSize=revisionByteSize(awaitsearcher.getContentLoader().loadContent(searcherResult.index+1)); reportSearcherResultToUser(searcherResult,"Do you want to use this?",useCb,keepLookingCb,cancelCb,()=>{ return$('<div>').append( "Found a revision: ", $('<a>').prop({ href:'/w/index.php?diff=prev&oldid='+revid, target:'_blank' }).text(`[[Special:Diff/${revid}]] (index=${searcherResult.index})`), $('<br/>'),'• ',formatTimestamp(fullRevision.timestamp), $('<br/>'),"• by ",$('<a>').prop({ href:'/wiki/Special:Contributions/'+user.replaceAll(' ','_'), target:'_blank' }).text(`User:${user}`), $('<br/>'),"• ",formatSizeChange(afterSize,beforeSize), $('<br/>'),"• edit summary: ",$('<i>').html(comment) ); }); } functionsearchFromIndex(index){ if(selection==undefined||selection==''){ mainDialog.html(formatErrorSpan("Please select an unsigned message.")+ " Selected: <code>"+originalSelection+"</code>"); return; } searcher.findRevisionWhenTextAdded(selection,index).then(searcherResult=>{ if(!mainDialog.dialog('isOpen')){ // user clicked [cancel] return; } info('Searcher found:',searcherResult); constuseCallback=()=>{/* use */ applySearcherResult(searcherResult); }; constkeepLookingCallback=()=>{/* keep looking */ // recursive call from a differfent index: `+1` is very important here searchFromIndex(searcherResult.index+1); }; constcancelCallback=()=>{/* cancel */ mainDialog.dialog('close'); }; if(isRevisionARevert(searcherResult.fullRevision)){ reportPossibleRevertToUser(searcherResult,useCallback,keepLookingCallback,cancelCallback); return; } reportNormalSearcherResultToUser(searcherResult,useCallback,keepLookingCallback,cancelCallback); },rejection=>{ error(`Searcher cannot find requested index=${index}. Got error:`,rejection); if(!mainDialog.dialog('isOpen')){ // user clicked [cancel] return; } mainDialog.html(formatErrorSpan(`${rejection}`)); }); } searchFromIndex(0); } window.unsignedHelperAddUnsignedTemplate=function(event){ event.preventDefault(); event.stopPropagation(); mw.loader.using(['mediawiki.util','jquery.ui'],doAddUnsignedTemplate); returnfalse; } if(!window.charinsertCustom){ window.charinsertCustom={}; } if(!window.charinsertCustom.Insert){ window.charinsertCustom.Insert=''; } window.charinsertCustom.Insert+=' {{unsigned}}\x10unsignedHelperAddUnsignedTemplate'; if(!window.charinsertCustom['Wiki markup']){ window.charinsertCustom['Wiki markup']=''; } window.charinsertCustom['Wiki markup']+=' {{unsigned}}\x10unsignedHelperAddUnsignedTemplate'; if(window.updateEditTools){ window.updateEditTools(); } })();