Movatterモバイル変換


[0]ホーム

URL:


Jump to content
WikipediaThe Free Encyclopedia
Search

User:JPxG/SPS.js

    From Wikipedia, the free encyclopedia
    <User:JPxG
    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 theappropriate village pump.
    This codewill be executed when previewing this page.
    Thisuser script seems to have a documentation page atUser:JPxG/SPS.
    Note: After saving, you have to bypass your browser's cache to see the changes.Google Chrome,Firefox,Microsoft Edge andSafari: Hold down the⇧ Shift key and click theReload toolbar button. For details and instructions about other browsers, seeWikipedia:Bypass your cache.
    /* Signpost Publishing Script (SPS) by Evad37 Forked by JPxG, 2022 ------------ Note 1: This script will only run for users specified in the publishers array. ------------ Note 2: This script assumes users have the following permissions - you must request them if you do not already have them. THIS IS IMPORTANT! THE SCRIPT WILL EAT SHIT AND MESS UP THE ISSUE IF YOU RUN IT WITHOUT THEM! * Page mover (or administrator) on English Wikipedia   - This ensures redirects are not left behind when moving pages during publication. * Mass message sender (or administrator) on English Wikipedia   - This allows posting the Signpost on the talkpages of English Wikipedia subscribers. * Mass message sender (or administrator) on Meta   - This allows posting the Signpost on the talkpages of subscribers on other projects.*//* jshint esversion: 6, esnext:false, laxbreak: true, undef: true, maxerr: 999 *//* globals console, window, document, $, mw, OO, extraJs */// <nowiki>/* ========== Dependencies and initial checks =================================================== */$.when(// Resource loader modulesmw.loader.using(['mediawiki.util','mediawiki.api','ext.gadget.libExtraUtil','oojs-ui-core','oojs-ui-widgets','oojs-ui-windows']),// Page ready$.ready).then(function(){varatNewsroom=mw.config.get('wgPageName').includes('Wikipedia:Wikipedia_Signpost/Newsroom');if(!atNewsroom){return;}varpublishers=['Evad37','Chris troutman','Smallbones','Bri','Eddie891','DannyS712','JPxG','EpicPupper'];varisApprovedUser=(publishers.indexOf(mw.config.get('wgUserName'))!==-1);if(!isApprovedUser){return;}// Script version and API config optionsvarscriptVersion='2.4.3';varapiConfig={ajax:{headers:{'Api-User-Agent':'SignpostPublishingScript/'+scriptVersion+' ( https://en.wikipedia.org/wiki/User:Evad37/SPS )'}}};window.SPSdebug=true;//On first run after page load, clear the cache in window.localStoragetry{window.localStorage.setItem('SignpostPubScript-titles','');window.localStorage.setItem('SignpostPubScript-selected-titles','');window.localStorage.setItem('SignpostPubScript-previousIssueDates','');window.localStorage.setItem('SignpostPubScript-info','');window.localStorage.setItem('SignpostPubScript-startAtZero','');}catch(e){}/* ========== Styles ============================================================================ */mw.util.addCSS('.SPS-dialog-heading { font-size: 115%; font-weight: bold; text-align: center; margin: -0.2em 0 0.2em; }'+'.SPS-dryRun { display: none; font-size: 88%; margin-left: 0.2em; }'+'.SPS-dialog-DraggablePanel { margin: 0.5em 0; }'+'.SPS-dialog-DraggablePanel .oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header { display: none; }'+'.SPS-dialog-item-section { font-weight: bold; }'+'.SPS-dialog-item-title { font-size: 92%; color: #333; margin-left: 0.1em; }'+'.SPS-dialog-item-blurb { font-size: 85%; color: #333; margin-left: 0.1em; }'+'.four ul li.SPS-task-waiting { color: #777; }'+'.four ul li.SPS-task-doing { color: #00F; }'+'.four ul li.SPS-task-done { color: #0A0; }'+'.four ul li.SPS-task-failed { color: #A00; }'+'.four ul li.SPS-task-skipped { color: #B0A; }'+'.four ul li .SPS-task-status { font-weight: bold; }'+'.four ul li .SPS-task-failed .SPS-task-errorMsg { font-weight: bold; }'+'.SPS-inlineButton { margin: 0.2em; padding: 0.3em 0.6em; font-size: 0.9em; }'+'.no-bold { font-weight: normal; }');/* ========== Utility functions ================================================================= *//** writeToCache * @param {String} key * @param {Array|Object} val */varwriteToCache=function(key,val){try{varstringVal=JSON.stringify(val);window.localStorage.setItem('SignpostPubScript-'+key,stringVal);}catch(e){}};/** readFromCache * @param {String} key * @returns {Array|Object|String|null} Cached array or object, or empty string if not yet cached, *          or null if there was error. */varreadFromCache=function(key){varval;try{varstringVal=window.localStorage.getItem('SignpostPubScript-'+key);if(stringVal!==''){val=JSON.parse(stringVal);}}catch(e){console.log('[SPS] error reading '+key+' from window.localStorage cache:');console.log('\t'+e.name+' message: '+e.message+(e.at?' at: '+e.at:'')+(e.text?' text: '+e.text:''));}returnval||null;};/** promiseTimeout * @param {Number} time - duration of the timeout in miliseconds * @returns {Promise} that will be resolved after the specified duration */varpromiseTimeout=function(time){vartimeout=$.Deferred();window.setTimeout(function(){returntimeout.resolve();},time);returntimeout;};/** reflect * @param {Promise|Any} Promise, or a value to treated as the result of a resolved promise * @returns {Promise} that always resolves to an object which wraps the values or errors from the *          resolved or rejected promise in a 'value' or 'error' array, along with a 'status' of *          "resolved" or "rejected" */varreflect=function(promise){varargsArray=function(args){return(args.length===1?[args[0]]:Array.apply(null,args));};return$.when(promise).then(function(){return{'value':argsArray(arguments),status:"resolved"};},function(){return{'error':argsArray(arguments),status:"rejected"};});};/** whenAll * Turns an array of promises into a single promise of an array, using $.when.apply * @param {Promise[]} promises * @returns {Promise<Array>} resolved promises */varwhenAll=function(promises){return$.when.apply(null,promises).then(function(){returnArray.from(arguments);});};/** getFullUrl * @param {String|null} page Page name. Defaults to the value of `mw.config.get('wgPageName')` * @param {Object|null} params A mapping of query parameter names to values, e.g. `{action: 'edit'}` * @retuns {String} Full url of the page */vargetFullUrl=function(page,params){return'https:'+mw.config.get('wgServer')+mw.util.getUrl(page,params);};/** approxPageSize * Calculates the approximate size of a page by adding up the size of images, * and rounding up to the nearest MB. * * @param {String} page Name of the page * @return {Promise<String>} Size */varapproxPageSize=function(page){return$.get(getFullUrl(page,{useskin:'vector'})).then(function(pageHtml){vardoc=document.implementation.createHTMLDocument("New Document");$(doc.body).append(pageHtml.replace(/<(link|meta|script.*?\>).*?\>/g,'<!-- $1 -->'));varimagesSizesPromises=Array.from(doc.images).map(function(image){return$.get(image.src).then(res=>{// Most image responses will have a .length prop (which slightly underestimates file size)if(res.length){varapproxSize=res.length*1.09;// .length slightly underestimates file sizeif(!isNaN(approxSize)){returnapproxSize;}}// Otherwise, we can try approximating the length of the root element (adjusted to account for compresison)varrootHtmlLength=res&&res.rootElement&&res.rootElement.outerHTML&&res.rootElement.outerHTML.length;if(rootHtmlLength){varapproxHTMLSize=rootHtmlLength*4.2/13.37;// Based on dev tools, 4.20 KB is transferred for a 13.37 KB svg file.if(!isNaN(approxHTMLSize)){returnapproxHTMLSize;}}// If all else fails, just ignore the filereturn0;});});return$.when.apply(null,imagesSizesPromises).then(function(){varimagesSizes=Array.from(arguments);vartotal_bytes=(200*1024)+// Non-image resources (scripts, css, etc)imagesSizes.reduce((a,b)=>a+b);vartotal_Mb=total_bytes/1024/1024;vartotal_Mb_rounded=Math.round(total_Mb*10)/10;vartotal_approx=(total_Mb<0.1)?"<0.1&nbsp;MB":total_Mb_rounded+"&nbsp;MB";returntotal_approx;});});};varremoveHtmlComments=function(wikitext,trim){varnewWikitext=wikitext.replace(/<!\-\-(.|\n)*?\-\->/g,'');if(window.SPSdebug){console.log("removed HTML comments");}returntrim?newWikitext.trim():newWikitext;};/* ========== Overlay Dialog ==================================================================== *//* ---------- OverlayDialog class --------------------------------------------------------------- */// Create OverlayDialog class that inherits from OO.ui.MessageDialogvarOverlayDialog=function(config){OverlayDialog.super.call(this,config);};OO.inheritClass(OverlayDialog,OO.ui.MessageDialog);// Give it a static name propertyOverlayDialog.static.name='overlayDialog';// Override clearMessageAndSetContent methodOverlayDialog.prototype.clearMessageAndSetContent=function(contentHtml){// Find the message label in the dialogthis.$element.find('label.oo-ui-messageDialog-message')// Insert the new content after it.after($('<div>').addClass('oo-ui-overlayDialog-content').append(contentHtml))// And empty the original message.empty();};// Override getTeardownProcess methodOverlayDialog.prototype.getTeardownProcess=function(data){// When closing, remove the content divthis.$element.find('div.oo-ui-overlayDialog-content').remove();// Call superclass methodreturnOverlayDialog.super.prototype.getTeardownProcess.call(this,data);};/* ---------- Window manager -------------------------------------------------------------------- *//* Factory.   Makes it easer to use the window manager: Open a window by specifiying the symbolic name of the   class to use, and configuration options (if any). The factory will automatically crete and add   windows to the window manager as needed. If there is an old window of the same symbolic name,   the new version will automatically replace old one.*/varovarlayWindowFactory=newOO.Factory();ovarlayWindowFactory.register(OverlayDialog);varovarlayWindowManager=newOO.ui.WindowManager({factory:ovarlayWindowFactory});ovarlayWindowManager.$element.attr('id','SPS-ovarlayWindowManager').addClass('sps-oouiWindowManager').appendTo('body');/** showOverlayDialog * @param {Promise} contentPromise - resolves to {String} of HTML to be displayed * @param {String} title - title of overlay dialog */varshowOverlayDialog=function(contentWikitext,parsedContentPromise,title,mode){varisWikitextMode=mode&&mode.wikitext;varcontentPromise=(isWikitextMode)?$.Deferred().resolve($('<pre>').text(contentWikitext)):parsedContentPromise;varinstance=ovarlayWindowManager.openWindow('overlayDialog',{title:title+(isWikitextMode?' wikitext':''),message:'Loading...',size:'larger',actions:[{action:'close',label:'Close',flags:'safe'},{action:'toggle',label:'Show '+(isWikitextMode?'preview':'wikitext')}]});instance.opened.then(function(){contentPromise.done(function(contentHtml){ovarlayWindowManager.getCurrentWindow().clearMessageAndSetContent(contentHtml);ovarlayWindowManager.getCurrentWindow().updateSize();}).fail(function(code,jqxhr){ovarlayWindowManager.getCurrentWindow().clearMessageAndSetContent('Preview failed.',(code==null)?'':extraJs.makeErrorMsg(code,jqxhr));});});instance.closed.then(function(data){if(!data||!data.action||data.action!=='toggle'){return;}showOverlayDialog(contentWikitext,parsedContentPromise,title,{'wikitext':!isWikitextMode});});};/* ========== Fake API class ==================================================================== */// For dry-run mode. Makes real read request to retrieve content, but logs write request to console.// Also handles previews of content.varFakeApi=function(apiConfig){this.realApi=newmw.Api(apiConfig);this.isFake=true;};FakeApi.prototype.abort=function(){this.realApi.abort();console.log('FakeApi was aborted');};FakeApi.prototype.get=function(request){returnthis.realApi.get(request);};FakeApi.prototype.preview=function(label,content,title){varself=this;$('#SPS-previewButton-container').append($('<button>').addClass('SPS-inlineButton mw-ui-button').text(label).click(function(){varparsedContentPromise=self.realApi.post({action:'parse',contentmodel:'wikitext',text:content,title:title,pst:1,prop:'text'}).then(function(result){if(!result||!result.parse||!result.parse.text||!result.parse.text['*']){return$.Deferred().reject('Empty result');}returnresult.parse.text['*'];});showOverlayDialog(content,parsedContentPromise,label);}));};FakeApi.prototype.postWithEditToken=function(request){console.log(request);// For occasional failures, for testing purposes, set the first Boolean value to trueif(false&&Math.random()>0.8){return$.Deferred().reject('Random failure');}// Show previews for key tasksif(request.title&&request.title==='Wikipedia:Wikipedia Signpost'){varpreviewMainWikitext=request.text.replace(// Use preview version of header"{{subst:Wikipedia:Wikipedia Signpost/Templates/Main page top}}","{{#invoke:Wikipedia Signpost/Main page top preview|top}}").replace(// Use today's datenewRegExp(mw.util.escapeRegExp("{{Wikipedia:Wikipedia Signpost/Templates/Issue|1}}"),"g"),newDate().toISOString().slice(0,10));this.preview('Preview main page',previewMainWikitext,request.title);}elseif(request.action&&request.action==='massmessage'&&request.spamlist==='Global message delivery/Targets/Signpost'){varpreviewMassMsgWikitext='{{Fake heading|1='+request.subject+'}}\n'+request.message;this.preview('Preview mass message',previewMassMsgWikitext,'User talk:Example');}returnpromiseTimeout(500).then(function(){return{'action':{'result':'Success'}};});};/* ========== Pre-publishing tasks / API requests =============================================== *//** getArticleTitles * Find page titles of next edition's articles. * @param {Object} api - real or fake API * @returns {Promise} of an {Array} of page titles */vargetArticleTitles=function(api){if(window.SPSdebug){console.log("Attempting to get pages from next issue");}returnapi.get({action:'query',generator:'allpages',gapprefix:'Wikipedia_Signpost/Next_issue/',gapnamespace:4,gapfilterredir:'nonredirects',gaplimit:'max',indexpageids:1}).then(function(result){if(window.SPSdebug){console.log("... retrieved!");}return$.map(result.query.pages,function(page){returnpage.title;});});};/** getPreviousIssueDates * Get the previous issue date that each section ran it. * @param {Object} api - real or fake API * @param {Number} year - the current year * @returns {Promise} of an {Object} of 'section':'previous issue date' pairs */vargetPreviousIssueDates=function(api,year){if(window.SPSdebug){console.log("Attempting to get previous issue dates");}returnapi.get({action:'query',list:'allpages',apfrom:'Wikipedia Signpost/'+(year+1),apnamespace:4,apfilterredir:'nonredirects',apminsize:'1500',aplimit:'500',apdir:'descending'}).then(function(result){if(window.SPSdebug){console.log("... retrieved!");}returnresult.query.allpages.map(function(page){returnpage.title;}).map(function(title){returntitle.split('/');}).reduce(function(prevIssueDates,titleParts){if(titleParts[1]&&titleParts[2]&&!(prevIssueDates[titleParts[2]])){prevIssueDates[titleParts[2]]=titleParts[1];}returnprevIssueDates;},{});});};/** getPageInfo * Get article information for a particular article, to be used for snippets etc. Used by #getInfo * * @param {Object} page Page information from Api query with prop: 'revisions', rvprop: 'content' * @returns {Promise<Object>} Relevant page information in key:value pairs */vargetPageInfo=function(page){if(window.SPSdebug){console.log("Getting page info for "+page.title);}// Page size approximation (promise)varsizePromise=approxPageSize(page.title);varwikitext=page.revisions[0]['*'];vartemplates=extraJs.parseTemplates(wikitext);// Title and blurb from Signpost draft templatevardraftTemplate=templates.find(t=>t.name==='Signpost draft');vartitle=draftTemplate&&draftTemplate.getParam('title')&&draftTemplate.getParam('title').value||'';varblurb=draftTemplate&&draftTemplate.getParam('blurb')&&draftTemplate.getParam('blurb').value||'';// Begin extraction of piccy params - JPxG, 2023 Dec 22varpiccyfilename=draftTemplate&&draftTemplate.getParam('piccyfilename')&&draftTemplate.getParam('piccyfilename').value||'';varpiccycredits=draftTemplate&&draftTemplate.getParam('piccy-credits')&&draftTemplate.getParam('piccy-credits').value||'';varpiccylicense=draftTemplate&&draftTemplate.getParam('piccy-license')&&draftTemplate.getParam('piccy-license').value||'';varpiccyscaling=draftTemplate&&draftTemplate.getParam('piccy-scaling')&&draftTemplate.getParam('piccy-scaling').value||'';varpiccyxoffset=draftTemplate&&draftTemplate.getParam('piccy-xoffset')&&draftTemplate.getParam('piccy-xoffset').value||'';varpiccyyoffset=draftTemplate&&draftTemplate.getParam('piccy-yoffset')&&draftTemplate.getParam('piccy-yoffset').value||'';// End hackjob// RSS feed description from the RSS description template,// or use title and blurb instead if no rss description is foundvarrssTemplate=templates.find(t=>t.name==='Wikipedia:Wikipedia Signpost/Templates/RSS description');varrss=(rssTemplate&&rssTemplate.getParam('1')&&rssTemplate.getParam('1').value&&removeHtmlComments(rssTemplate.getParam('1').value,true))||removeHtmlComments(title,true)+': '+removeHtmlComments(blurb,true);// Begin hackjob by JPxG to parse "by" params out of header template, 2023 Dec 3varheaderTemplate=templates.find(t=>t.name==='Wikipedia:Wikipedia Signpost/Templates/Signpost-article-header-v2'||t.name==='Wikipedia:Signpost/Template:Signpost-article-start');varby=headerTemplate&&headerTemplate.getParam('2')&&headerTemplate.getParam('2').value||'';by=by.toLowerCase().startsWith("by ")?by.slice(3):by;// End hackjobreturnsizePromise.then(function(size){return{'pageid':page.pageid,'section':page.title.replace("Wikipedia:Wikipedia Signpost/Next issue/",""),'title':removeHtmlComments(title,true),'blurb':removeHtmlComments(blurb,true),'rss':removeHtmlComments(rss,true),'wikitext':wikitext,'templates':templates,'size':size,'by':removeHtmlComments(by,true),'piccyfilename':removeHtmlComments(piccyfilename,true),'piccycredits':removeHtmlComments(piccycredits,true),'piccylicense':removeHtmlComments(piccylicense,true),'piccyscaling':removeHtmlComments(piccyscaling,true),'piccyxoffset':removeHtmlComments(piccyxoffset,true),'piccyyoffset':removeHtmlComments(piccyyoffset,true)};});};/** getInfo * Get article information for each section, to be used for snippets etc * @param {Object} api - real api or fake api * @param {Array} pagetitles * @param {Object} prevIssueDates - object with 'section':'previous issue date' pairs * @returns Promise of an Array of Objects */vargetInfo=function(api,pagetitles,prevIssueDates){if(window.SPSdebug){console.log("Getting info for pagetitles.");}returnapi.get({action:'query',titles:pagetitles,prop:'revisions',rvprop:'content',indexpageids:1}).then(function(result){varinfoPromises=$.map(result.query.pages,getPageInfo);returnwhenAll(infoPromises);}).then(function(infos){returninfos.map(function(info){info.prev=prevIssueDates[info.section]||'';returninfo;});});};/* ========== Publishing tasks / API requests =================================================== */// Step 0:/** makeIssuePage * Creates the main issue page * @param {Object} data - configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad *   @param {Array} data.articles *   @param {Number} data.firstItemIndex - 0 if there is a "From the editor(s)" section, otherwise 1 * * @returns Promise of a success or failure message */varmakeIssuePage=function(data){if(window.SPSdebug){console.log("Making issue page.");}vartoCoverItem=function(article,index){varstrToRet;strToRet="{{Signpost/item|{{{1}}}|";strToRet+=(index+data.firstItemIndex);strToRet+="|"+data.today.iso;strToRet+="|"+article.section;strToRet+="|"+article.title;strToRet+="|"+article.size;strToRet+="|sub="+article.blurb;strToRet+="|by="+article.by;strToRet+="|piccyfilename="+article.piccyfilename;strToRet+="|piccy-credits="+article.piccycredits;strToRet+="|piccy-license="+article.piccylicense;strToRet+="|piccy-scaling="+article.piccyscaling;strToRet+="|piccy-xoffset="+article.piccyxoffset;strToRet+="|piccy-yoffset="+article.piccyyoffset;strToRet+="}}\n";returnstrToRet;// This looks like it's the main page but it's not -- it's for the issue archive page!};if(window.SPSdebug){console.log("... made, now trying to save.");}returndata.api.postWithEditToken({action:'edit',title:data.path+"/"+data.today.iso,text:data.articles.map(toCoverItem).join(''),summary:"New edition"+data.script_ad});};// Step 1:/** cleanupWikitext * Prepare wikitext of an article for publication (remove draft template, add/edit footer template, replace /Next issue/ with date) * Used by #prepareArticles * @param {Object} article Data from #getInfo * @returns {String} wikitext for publication */varcleanupWikitext=function(article){if(window.SPSdebug){console.log("Cleaning up wikitext.");}// Replacement wikitext for top noinclude sectionvarnew_topNoincludeSection="<noinclude>{{Wikipedia:Wikipedia Signpost/Templates/RSS description|1="+article.rss+"}}{{Wikipedia:Signpost/Template:Signpost-header|||}}</noinclude>";// Replacement wikitext for article header templatevararticleHeaderTemplate=article.templates.find(t=>t.name==='Wikipedia:Wikipedia Signpost/Templates/Signpost-article-header-v2'||t.name==='Wikipedia:Signpost/Template:Signpost-article-start');varnew_headerWikitext=articleHeaderTemplate&&articleHeaderTemplate.wikitext.replace(articleHeaderTemplate.getParam('1').wikitext,'|'+"{{{1|"+article.title+"}}}");// Add piccy params to the new header wikitextvarnew_headerExtraParams=""new_headerExtraParams+="\n|piccyfilename = "+article.piccyfilename;new_headerExtraParams+="\n|piccy-credits = "+article.piccycredits;new_headerExtraParams+="\n|piccy-license = "+article.piccylicense;new_headerExtraParams+="\n|piccy-xoffset = "+article.piccyxoffset;new_headerExtraParams+="\n|piccy-yoffset = "+article.piccyyoffset;new_headerExtraParams+="\n|piccy-scaling = "+article.piccyscaling;new_headerWikitext=new_headerWikitext.slice(0,-2)+new_headerExtraParams+"\n}}"// Replacement wikitext for article footer templatevarfooterTemplate=article.templates.find(t=>t.name==='Wikipedia:Signpost/Template:Signpost-article-comments-end'||t.name==='Wikipedia:Wikipedia Signpost/Templates/Signpost-article-comments-end');varnew_footerWikitext="{{Wikipedia:Signpost/Template:Signpost-article-comments-end||"+article.prev+"|}}";// Signpost draft helper template - to be removedvarhelperTemplate=article.templates.find(t=>t.name==='Signpost draft helper');varhelperPatt=(helperTemplate)?newRegExp('\\n?'+mw.util.escapeRegExp(helperTemplate.wikitext)):null;vartoday=newDate().toISOString().slice(0,10);varupdatedWikitext=article.wikitext.replace(helperPatt,'').replace(/<noinclude>(?:.|\n)*?<\/noinclude>/,new_topNoincludeSection).replace(articleHeaderTemplate&&articleHeaderTemplate.wikitext,new_headerWikitext).replaceAll("/Next issue","/"+today).replaceAll("/Next_issue","/"+today);if(footerTemplate){if(window.SPSdebug){console.log("... cleaned (found footertemplate)");}returnupdatedWikitext.replace(footerTemplate.wikitext,new_footerWikitext);}else{if(window.SPSdebug){console.log("... cleaned (no footertemplate)");}returnupdatedWikitext.trim()+"\n<noinclude>"+new_footerWikitext+"</noinclude>";}};/** prepareArticles * Edit each article to prepare it for publication (remove draft template, add/edit footer template) * @param {Object} data - configuration and other data, including *   @param {Object} data.api - real api for editing, or fake api for logging proposed edit *   @param {String} data.script_ad *   @param {Array} data.articles * @returns {Promise} resolved if edits were successfull, or rejected with a failure message */varprepareArticles=function(data){if(window.SPSdebug){console.log("Attempting to prepare articles.");}vareditedArticlesPromises=data.articles.map(function(article){if(window.SPSdebug){console.log("... trying pageid "+article.pageid);}returndata.api.postWithEditToken({action:'edit',pageid:article.pageid,text:cleanupWikitext(article),summary:"Preparing for publication"+data.script_ad});});returnwhenAll(editedArticlesPromises);};// Step 2:varmaxMovesPerMinute=1;// Var placed outside function for reuse in dialog status panel// asinine hack from jpxg 2025-05-14: this is erroring out a lot so i'm just going to set it to 1, e.g. make the batch size one// then edit "minutesBetweenMoveBatches" to set timeouts for each move. for some reason batching them all is just being a pain in the ass idk why/** * moveArticles * * Move each article to the new issue subpage * * @param {Object} data - configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad *   @param {Array} data.articles * * @returns Promise of a success or failure message */varmoveArticles=function(data){if(window.SPSdebug){console.log("Attempting to move pages.");}varminutesBetweenMoveBatches=0.26;// The extra 0.1 is a safety factor.varmillisecondsBetweenMoveBatches=minutesBetweenMoveBatches*60*1000;varmovedArticlesPromises=data.articles.map(function(article,index){varnumberAlreadyMoved=index;varsleepMilliseconds=Math.floor(numberAlreadyMoved/maxMovesPerMinute)*millisecondsBetweenMoveBatches;returnpromiseTimeout(sleepMilliseconds).then(function(){if(window.SPSdebug){console.log("... moving pageid "+article.pageid);console.log("Old title: "+data.path+"/Next issue/"+article.section);console.log("New title: "+data.path+"/"+data.today.iso+"/"+article.section);}returndata.api.postWithEditToken({action:'move',fromid:article.pageid,to:data.path+"/"+data.today.iso+"/"+article.section,noredirect:1,reason:'Publishing'+data.script_ad});});});returnwhenAll(movedArticlesPromises);};// Step 3:/** * editIssueSubpage * * Update the page "Wikipedia:Wikipedia Signpost/Templates/Issue" * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad * * @returns Promise of both the previous date in ISO format, and a success or failure message */vareditIssueSubpage=function(data){if(window.SPSdebug){console.log("Attempting to update Wikipedia:Wikipedia Signpost/Templates/Issue.");}returndata.api.get({action:'query',titles:'Wikipedia:Wikipedia Signpost/Templates/Issue',prop:'revisions',rvprop:'content',indexpageids:1}).then(function(result){varpid=result.query.pageids[0];varoldWikitext=result.query.pages[pid].revisions[0]['*'];// Update the YYYY-MM-DD datesvaroldIsoDates=oldWikitext.match(/(\d{4}-\d{2}-\d{2})/g);varwikitext=oldWikitext.replace(oldIsoDates[0],data.today.iso).replace(oldIsoDates[1],oldIsoDates[0]);//Store previous edition date for later usevarprevious_iso=oldIsoDates[0];//Get the current volume and issue numbersvaredition,vol,iss;varedition_patt=/Volume (\d+), Issue (\d+)/;//If the previous edition was last year, increment volume and reset issue number to 1if(parseInt(previous_iso.slice(0,4))<data.today.year){edition=edition_patt.exec(wikitext);vol=(parseInt(edition[1])+1).toString();iss="1";}else{//increment issue numberedition=edition_patt.exec(wikitext);vol=edition[1];iss=(parseInt(edition[2])+1).toString();}//update volume and issue numbersreturn$.Deferred().resolve(wikitext.replace(/Volume (\d+), Issue (\d+)/,"Volume "+vol+", Issue "+iss),{"previousIssueDate":previous_iso,"vol":vol,"iss":iss});}).then(function(wikitext,editionInfo){if(window.SPSdebug){console.log("... attempting to save edit.");}vareditPromise=data.api.postWithEditToken({action:'edit',title:data.path+"/Templates/Issue",text:wikitext,summary:"Publishing new edition"+data.script_ad});return$.when(editPromise,editionInfo);});};// Step 4:/** * editMain * * Edit the main Signpost page * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {String} data.path *   @param {String} data.script_ad *   @param {Array} data.articles * * @returns Promise of a success or failure message */vareditMain=function(data){if(window.SPSdebug){console.log("Attempting to edit main Signpost page.");}vartopwikitext="{{subst:Wikipedia:Wikipedia Signpost/Templates/Main page top}}\n\n";varbottomwikitext="{{subst:Wikipedia:Wikipedia Signpost/Templates/Main page bottom}}";varmidwikitext=data.articles.map(function(article){varstrToRet="{{Signpost/snippet";strToRet+="|{{Wikipedia:Wikipedia Signpost/Templates/Issue|1}}";strToRet+="|"+article.section;strToRet+="|"+article.title;strToRet+="|"+article.blurb;strToRet+="|"+article.size;strToRet+="|sub="+article.size;strToRet+="|by="+article.by;strToRet+="|piccyfilename="+article.piccyfilename;strToRet+="|piccy-credits="+article.piccycredits;strToRet+="|piccy-license="+article.piccylicense;strToRet+="|piccy-scaling="+article.piccyscaling;strToRet+="|piccy-xoffset="+article.piccyxoffset;strToRet+="|piccy-yoffset="+article.piccyyoffset;strToRet+="}}\n\n";returnstrToRet;// This looks like it's the issue archive page but it's not -- it's for the main page!});if(window.SPSdebug){console.log("... attempting to save edit.");}returndata.api.postWithEditToken({action:'edit',title:data.path,text:topwikitext+midwikitext.join('')+bottomwikitext,summary:"Publishing new edition"+data.script_ad});};// Step 5:/** * makeSingle * * Create the single page edition * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad * * @returns Promise of a success or failure message */varmakeSingle=function(data){if(window.SPSdebug){console.log("Attempting to save single-page edition.");}returndata.api.postWithEditToken({action:'edit',title:data.path+"/Single/"+data.today.iso,text:"{{Wikipedia:Wikipedia Signpost/Single|issuedate="+data.today.iso+"}}",summary:"Publishing new single page edition"+data.script_ad});};// Step 6:/** * makeArchive * * Create this issue's archive page * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad *   @param {String} data.previousIssueDate -- in ISO format * * @returns Promise of a success or failure message */varmakeArchive=function(data){if(!data.previousIssueDate){return$.Deferred().reject('Previous issue date not found');}if(window.SPSdebug){console.log("Attempting to save archive page.");}returndata.api.postWithEditToken({action:'edit',title:data.path+"/Archives/"+data.today.iso,text:"{{Signpost archive|"+data.previousIssueDate+"|"+data.today.iso+"|}}",summary:"Publishing new single page edition"+data.script_ad});};// Step 7:/** * updatePrevArchive * * Update the previous issue's archive page with the next edition date * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edit *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad *   @param {String} data.previousIssueDate -- in ISO format * * @returns Promise of a success or failure message */varupdatePrevArchive=function(data){if(!data.previousIssueDate){return$.Deferred().reject('Previous issue date not found');}if(window.SPSdebug){console.log("Attempting to update previous archive.");}returndata.api.get({action:'query',titles:data.path+"/Archives/"+data.previousIssueDate,prop:'revisions',rvprop:'content',indexpageids:1}).then(function(result){varpid=result.query.pageids[0];varwikitext=result.query.pages[pid].revisions[0]['*'];returnwikitext.replace(/\|?\s*}}/,"|"+data.today.iso+"}}");}).then(function(wikitext){if(window.SPSdebug){console.log("... trying to save page.");}returndata.api.postWithEditToken({action:'edit',title:data.path+"/Archives/"+data.previousIssueDate,text:wikitext,summary:"Add next edition date"+data.script_ad});});};// Step 8:/** * purgePages * * Purge pages to ensure that latest versions are shown, following all the edits and page moves * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for purging, or fake api for logging proposed purge *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {Array} data.articles * * @returns Promise of a success or failure message */varpurgePages=function(data){if(window.SPSdebug){console.log("Purging pages.");}varpurgetitles=data.articles.map(function(article){returndata.path+"/"+data.today.iso+"/"+article.section;}).concat(["Wikipedia:Wikipedia Signpost/Templates/Issue","Wikipedia:Wikipedia Signpost","Wikipedia:Wikipedia Signpost/Single",data.path+"/Archives/"+data.today.iso,"Wikipedia:Wikipedia Signpost/"+data.today.iso]);returndata.api.postWithEditToken({action:'purge',titles:purgetitles});};// Step 9:/** * massmsg * * Mass-message enwiki subscribers * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edits *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *     @param {String} data.today.dmy *   @param {String} data.script_page *   @param {String} data.vol -- Volume number of current issue *   @param {String} data.iss -- Issue number of current issue * * @returns Promise of a success or failure message */varmassmsg=function(data){if(window.SPSdebug){console.log("Attempting to compose MassMessage.");}varvol=data.vol||'';variss=data.iss||'';varmsg_spamlist=data.path+"/Tools/Spamlist";varmsg_content='<div lang="en" dir="ltr" class="mw-content-ltr"><div style="column-count:2;"> '+'{{Wikipedia:Wikipedia Signpost/'+data.today.iso+'}} </div><!--Volume '+vol+', Issue '+iss+'--> '+'<div class="hlist" style="margin-top:10px; font-size:90%; padding-left:5px; font-family:Georgia, Palatino, Palatino Linotype, Times, Times New Roman, serif;"> '+'* \'\'\'[[Wikipedia:Wikipedia Signpost|Read this Signpost in full]]\'\'\' * [[Wikipedia:Wikipedia Signpost/Single/'+data.today.iso+'|Single-page]] * '+'[[Wikipedia:Wikipedia Signpost/Subscribe|Unsubscribe]] * [[User:MediaWiki message delivery|MediaWiki message delivery]] ([[User talk:MediaWiki message delivery|talk]]) ~~~~~ '+'<!-- Sent via script ([['+data.script_page+']]) --></div></div>';varmsg_subject="''The Signpost'': "+data.today.dmy;if(window.SPSdebug){console.log("... sending MassMessage.");}returndata.api.postWithEditToken({action:'massmessage',spamlist:msg_spamlist,subject:msg_subject,message:msg_content});};// Step 10:/** * gloablmassmsg * * Mass-message global subscribers * * @param {Object} data -- configuration and other data, including *   @param {Object} data.metaApi -- real (foreign) api for editing, or fake api for logging proposed edits *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *     @param {String} data.today.dmy *   @param {String} data.script_page *   @param {Array} data.articles * * @returns Promise of a success or failure message */varglobalmassmsg=function(data){if(window.SPSdebug){console.log("Attempting to compose global MassMessage.");}varmsg_spamlist="Global message delivery/Targets/Signpost";varmsg_subject="''The Signpost'': "+data.today.dmy;varmsg_top='<div lang="en" dir="ltr" class="mw-content-ltr" style="margin-top:10px; font-size:90%; padding-left:5px; font-family:Georgia, Palatino, Palatino Linotype, Times, Times New Roman, serif;">'+'[[File:WikipediaSignpostIcon.svg|40px|right]] \'\'News, reports and features from the English Wikipedia\'s newspaper\'\'</div>\n'+'<div style="column-count:2;">\n';varmsg_mid=data.articles.reduce(function(midtext,article){returnmidtext+"* "+article.section+": [[w:en:"+data.path+"/"+data.today.iso+"/"+article.section+"|"+article.title+"]]\n\n";},'');varmsg_bottom='</div>\n'+'<div style="margin-top:10px; font-size:90%; padding-left:5px; font-family:Georgia, Palatino, Palatino Linotype, Times, Times New Roman, serif;">'+'\'\'\'[[w:en:Wikipedia:Wikipedia Signpost|Read this Signpost in full]]\'\'\' · [[w:en:Wikipedia:Signpost/Single|Single-page]] · '+'[[m:Global message delivery/Targets/Signpost|Unsubscribe]] · [[m:Global message delivery|Global message delivery]] ~~~~~\n'+'<!-- Sent via script ([[w:en:'+data.script_page+']]) --></div>';if(window.SPSdebug){console.log("... sending global MassMessage.");}returndata.metaApi.postWithEditToken({action:'massmessage',assert:'user',spamlist:msg_spamlist,subject:msg_subject,message:msg_top+msg_mid+msg_bottom});};// Step 11:/** * updateYearArchive * * Update the current year's archive overview page * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edits *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *     @param {String} data.today.dmy *     @param {String} data.today.month *     @param {String|Number} data.today.year *   @param {String} data.script_ad *   @param {String} data.vol -- Volume number of current issue *   @param {String} data.iss -- Issue number of current issue * * @returns Promise of a success or failure message */varupdateYearArchive=function(data){if(!data.vol||!data.iss){varnotFound=(!data.vol?'Volume number ':'')+(!data.vol&&!data.iss?'and ':'')+(!data.iss?'Issue number ':'');return$.Deferred().reject(notFound+'not found');}if(window.SPSdebug){console.log("Attempting to update year archive.");}returndata.api.get({action:'query',titles:data.path+"/Archives/"+data.today.year,prop:'revisions',rvprop:'content',indexpageids:1}).then(function(result){// Zero-padded issue numbervarpadded_iss=(data.iss.length===1)?"0"+data.iss:data.iss;varnewContent="===[[Wikipedia:Wikipedia Signpost/Archives/"+data.today.iso+"|Volume "+data.vol+", Issue "+padded_iss+"]], "+data.today.dmy+"===\n{{Wikipedia:Wikipedia Signpost/"+data.today.iso+"}}\n\n";varemptyPageStartText="{{Wikipedia:Wikipedia Signpost/Archives|year="+data.today.year+"}}\n\n<br />\n{{Template:TOCMonths}}\n\n"+"{{Wikipedia:Signpost/Template:Signpost-footer}}\n"+"[[Category:Wikipedia Signpost archives|"+data.today.year+"]]\n[[Category:Wikipedia Signpost archives "+data.today.year+"| ]]";varpid=result.query.pageids[0];varpageDoesNotExist=(pid<0);varwikitext=(pageDoesNotExist)?emptyPageStartText:result.query.pages[pid].revisions[0]['*'];varneedsMonthHeading=(wikitext.indexOf(data.today.month)===-1);varnewContentHeading=(needsMonthHeading)?"== "+data.today.month+" ==\n":'';varinsertionPoint="";// a falsey valueif(wikitext.indexOf("{{Wikipedia:Signpost/Template:Signpost-footer}}")!==-1){insertionPoint="{{Wikipedia:Signpost/Template:Signpost-footer}}";}elseif(wikitext.indexOf('[[Category:')!==-1){// footer not found, insert new wikitext above categoriesinsertionPoint="[[Category:";}if(!insertionPoint){returnwikitext.trim()+newContentHeading+newContent.trim();}else{returnwikitext.replace(insertionPoint,newContentHeading+newContent+insertionPoint);}}).then(function(wikitext){if(window.SPSdebug){console.log("... saving edit.");}returndata.api.postWithEditToken({action:'edit',title:data.path+"/Archives/"+data.today.year,text:wikitext,summary:"Add next edition"+data.script_ad});});};// Step 12:/** * createArchiveCats * * Create archive categories for current month and year, if they don't already exist * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edits *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *     @param {String|Number} data.today.year *   @param {String} data.script_ad * * @returns Promise of a success or failure message */varcreateArchiveCats=function(data){if(window.SPSdebug){console.log("Attempting to create archive cats.");}// Ignore the articleexists errorvarpromiseWithoutExistsError=function(promise){returnpromise.then(function(v){returnv;},function(c,jqxhr){return(c==='articleexists')?c:$.Deferred().reject(c,jqxhr);});};varmonth_cat_title="Category:Wikipedia Signpost archives "+data.today.iso.slice(0,7);varmonth_cat_text="[[Category:Wikipedia Signpost archives "+data.today.year+"|"+data.today.iso.slice(5,7)+"]]";varyear_cat_title="Category:Wikipedia Signpost archives "+data.today.year;varyear_cat_text="{{Wikipedia:Wikipedia Signpost/Archives}}\n\n"+"[[Category:Wikipedia Signpost archives|"+data.today.year+"]]";varmonthCatPrmoise=promiseWithoutExistsError(data.api.postWithEditToken({action:'edit',title:month_cat_title,text:month_cat_text,summary:"Create"+data.script_ad,createonly:1}));varyearCatPromise=promiseWithoutExistsError(data.api.postWithEditToken({action:'edit',title:year_cat_title,text:year_cat_text,summary:"Create"+data.script_ad,createonly:1}));if(window.SPSdebug){console.log("... attempting to make cats meow");}return$.when(monthCatPrmoise,yearCatPromise);};// Step 13:/** * updateOldNextLinks * * Update previous issue's "next" links * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edits *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *   @param {String} data.script_ad *   @param {Array} data.articles *   @param {Object} data.previousIssueDates * * @returns Promise of a success or failure message */varupdateOldNextLinks=function(data){if(window.SPSdebug){console.log("Attempting to update old 'next' links.");}if(!data.previousIssueDates){return$.Deferred().reject('Previous issues not found');}varprevtitles=data.articles.map(function(article){if(!data.previousIssueDates[article.section]){return'';}returndata.path+"/"+data.previousIssueDates[article.section]+"/"+article.section;}).filter(function(v){returnv!=='';});if(prevtitles.length===0){return$.Deferred().reject('Previous issues not found');}if(window.SPSdebug){console.log("... saving edit.");}returndata.api.get({action:'query',titles:prevtitles,prop:'revisions',rvprop:'content',indexpageids:1}).then(function(result){varpageEditPromises=$.map(result.query.pages,function(page){varoldwikitext=page.revisions[0]['*'];varwikitext=oldwikitext.replace(/({{Wikipedia:Signpost\/Template:Signpost-article-comments-end\|\|\d{4}-\d{2}-\d{2}\|)}}/,"$1"+data.today.iso+"}}");returndata.api.postWithEditToken({action:'edit',title:page.title,text:wikitext,summary:"Add next edition"+data.script_ad});});returnwhenAll(pageEditPromises);});};// Step 14:/** * requestWatchlistNotification * * Request a new watchlist notification * * @param {Object} data -- configuration and other data, including *   @param {Object} data.api -- real api for editing, or fake api for logging proposed edits *   @param {String} data.path *   @param {Object} data.today *     @param {String} data.today.iso *     @param {String} data.today.month *   @param {String} data.script_ad * * @returns Promise of a success or failure message */varrequestWatchlistNotification=function(data){// return "Disabled, JPxG 2023-05-22"if(window.SPSdebug){console.log("Attempting to create request watchlist notice.");}returndata.api.postWithEditToken({action:'edit',title:'MediaWiki talk:Watchlist-messages',section:'new',sectiontitle:data.today.month+' Signpost notice',text:"{{sudo}}\nThe [["+data.path+"/Archives/"+data.today.iso+"|"+data.today.month+" edition]] is now out. ~~~~",summary:"Requesting a notice for this month's Signpost edition"+data.script_ad});};varcreateSingleTalk=function(data){if(window.SPSdebug){console.log("Attempting to create single-page talk.");}returndata.api.postWithEditToken({action:'edit',title:'Wikipedia talk:Wikipedia Signpost/Single/'+data.today.iso,text:"{{Wikipedia:Wikipedia Signpost/Templates/Single talk}}",summary:"Creating new single talk page with [[Wikipedia:Wikipedia Signpost/Templates/Single talk]]"+data.script_ad});};/* ========== Main Dialog ======================================================================= *//* ---------- DraggableGroupStack class---------------------------------------------------------- */varDraggableGroupStack=functionOoUiDraggableGroupStack(config){config=config||{};config.draggable=true;config.expanded=false;config.padded=true;config.framed=true;// Parent constructorDraggableGroupStack.parent.call(this,config);config.$group=this.$element;// Mixin constructorOO.ui.mixin.DraggableGroupElement.call(this,config);};OO.inheritClass(DraggableGroupStack,OO.ui.StackLayout);OO.mixinClass(DraggableGroupStack,OO.ui.mixin.DraggableGroupElement);DraggableGroupStack.prototype.removeItems=function(){returnthis;};//DraggableGroupStack.prototype.addItems = OO.ui.StackLayout.prototype.addItems;/* ---------- DraggablePanel class -------------------------------------------------------------- */varDraggablePanel=functionOoUiDraggablePanel(config){config=config||{};if(config.classes&&$.isArray(config.classes)){config.classes.push('SPS-dialog-DraggablePanel');}else{config.classes=['SPS-dialog-DraggablePanel'];}// Parent constructorDraggablePanel.parent.call(this,config);// Mixin constructorOO.ui.mixin.DraggableElement.call(this,config);this.$element.on('click',this.select.bind(this)).on('keydown',this.onKeyDown.bind(this))// Prevent propagation of mousedown.on('mousedown',function(e){e.stopPropagation();});// Initialization/*this.$element.append( this.$label, this.closeButton.$element );*/};OO.inheritClass(DraggablePanel,OO.ui.PanelLayout);OO.mixinClass(DraggablePanel,OO.ui.mixin.DraggableElement);/** * Handle a keydown event on the widget * * @fires navigate * @param {jQuery.Event} e Key down event * @return {boolean|undefined} false to stop the operation */DraggablePanel.prototype.onKeyDown=function(e){varmovement;if(e.keyCode===OO.ui.Keys.ENTER){this.select();returnfalse;}elseif(e.keyCode===OO.ui.Keys.LEFT||e.keyCode===OO.ui.Keys.RIGHT||e.keyCode===OO.ui.Keys.UP||e.keyCode===OO.ui.Keys.DOWN){if(OO.ui.Element.static.getDir(this.$element)==='rtl'){movement={left:'forwards',right:'backwards'};}else{movement={left:'backwards',right:'forwards'};}this.emit('navigate',e.keyCode===OO.ui.Keys.LEFT?movement.left:movement.right);returnfalse;}elseif(e.keyCode===OO.ui.Keys.UP||e.keyCode===OO.ui.Keys.DOWN){this.emit('navigate',e.keyCode===OO.ui.Keys.UP?'backwards':'forwards');returnfalse;}};/** * Select this item * * @fires select */DraggablePanel.prototype.select=function(){this.emit('select');};/* ---------- MainDialog class ------------------------------------------------------------------ */varMainDialog=functionOoUiMainDialog(config){MainDialog.super.call(this,config);};OO.inheritClass(MainDialog,OO.ui.ProcessDialog);MainDialog.static.name='mainDialog';/* ~~~~~~~~~~ Available actions (buttons) ~~~~~~~~~~ */MainDialog.static.actions=[{action:'close',modes:['welcome','choose','sort'],label:'Cancel',flags:'safe'},{action:'start',modes:'welcome',label:'Start',flags:['primary','progressive']},{action:'dryrun',modes:'welcome',label:'Dry-run only',flags:'progressive'},{action:'back-to-welcome',modes:'choose',label:'Back',flags:'safe'},{action:'next-to-sort',modes:'choose',label:'Next',flags:['primary','progressive']},{action:'back-to-choose',modes:'sort',label:'Back',flags:'safe'},{action:'next-to-status',modes:'sort',label:'Publish',flags:['primary','progressive']},{action:'next-to-finished',modes:'status',label:'Continue',flags:['primary','progressive'],disabled:true},{action:'abort',modes:'status',label:'Abort',flags:['safe','destructive']},{action:'close',modes:['finished','aborted'],label:'Close',flags:['primary','progressive']},{action:'back-to-status',modes:'finished',label:'Back',flags:'safe'}];// Get dialog height.MainDialog.prototype.getBodyHeight=function(){varheadHeight=this.$head.outerHeight(true);varfootHeight=this.$foot.outerHeight(true);varcurrentPanelContentsHeight=$(this.stackLayout.currentItem.$element).children().outerHeight(true);returnheadHeight+footHeight+currentPanelContentsHeight;};/* ~~~~~~~~~~ Initiliase the content and layouts ~~~~~~~~~~ */MainDialog.prototype.initialize=function(){MainDialog.super.prototype.initialize.apply(this,arguments);vardialog=this;this.panels={welcome:newOO.ui.PanelLayout({$content:$('<div>').append([$('<p>').addClass('SPS-dialog-heading').text('Welcome to the Signpost Publishing Script'),$('<p>').text('Make sure each article has a fully completed {{Signpost draft}} template, and is a subpage of Wikipedia:Wikipedia_Signpost/Next_issue/'),$('<p>').text('"Dry-run only" mode simulates the publising process without making any actual changes – actions are logged to the browser\'s javascript console instead; previews are also available.')]),classes:['one'],padded:true,scrollable:true,expanded:true}),choose:newOO.ui.PanelLayout({$content:$('<div>').append([$('<p>').addClass('SPS-dialog-heading').append(['Select articles',$('<span>').addClass('SPS-dryRun').text('[dry-run only]')])]),classes:['two'],padded:true,scrollable:true,expanded:true}),sort:newOO.ui.PanelLayout({$content:$('<div>').append([$('<p>').addClass('SPS-dialog-heading').append(['Check order, titles, and blurbs',$('<span>').addClass('SPS-dryRun').text('[dry-run only]')])]),classes:['three'],padded:true,scrollable:true,expanded:true}),status:newOO.ui.PanelLayout({$content:$('<div>').append([$('<p>').addClass('SPS-dialog-heading').append(['Status',$('<span>').addClass('SPS-dryRun').text('[dry-run only]')])]),classes:['four'],padded:true,scrollable:true,expanded:true}),finished:newOO.ui.PanelLayout({$content:$('<div>').append([$('<p>').addClass('SPS-dialog-heading').append(['Finished!',$('<span>').addClass('SPS-dryRun').text('[dry-run only]')]),$('<div>').attr('id','SPS-previewButton-container'),$('<p>').text('Remember to announce the new issue on the mailing list, Twitter, and Facebook.')]),classes:['five'],padded:true,scrollable:true,expanded:true}),aborted:newOO.ui.PanelLayout({$content:$('<div>').append([$('<p>').addClass('SPS-dialog-heading').append(['Aborted',$('<span>').addClass('SPS-dryRun').text('[dry-run only]')]),$('<p>').append(['Follow the manual steps at ',extraJs.makeLink('Wikipedia:Wikipedia Signpost/Newsroom/Resources','the resources page'),' to complete publiction.'])]),classes:['six'],padded:true,scrollable:true,expanded:true})};this.stackLayout=newOO.ui.StackLayout({items:$.map(dialog.panels,function(panel){returnpanel;})});this.$body.append(this.stackLayout.$element);};/* ~~~~~~~~~~ Set panels contents ~~~~~~~~~~ *//** @param {Array} titles **/MainDialog.prototype.setChoosePanelContent=function(titles){vardialog=this;varcachedSelectedTitles=readFromCache('selected-titles');vartitleCheckboxes=titles.map(function(title){returnnewOO.ui.CheckboxMultioptionWidget({data:title,selected:(cachedSelectedTitles)?cachedSelectedTitles.indexOf(title)!==-1:true,label:$('<span>').append([title,' ',extraJs.makeLink(title,'→')])});});this.widgets.titlesMultiselect=newOO.ui.CheckboxMultiselectWidget({items:titleCheckboxes,id:'SPS-dialog-titlesMultiselect'});this.getPanelElement('choose').find('#SPS-dialog-titlesMultiselect').remove().end().append(dialog.widgets.titlesMultiselect.$element);};/** @param {Array} selectedTitles, @param {Boolean} startAtZeroValue **/MainDialog.prototype.setSortPanelContent=function(articlesInfo,startAtZeroValue){varmakeItemContent=function(section,title,blurb){returnnewOO.ui.ActionFieldLayout(newOO.ui.LabelWidget({label:$('<div>').append([$('<span>').addClass('SPS-dialog-item-section').text(section+' ').append(extraJs.makeLink('Wikipedia:Wikipedia_Signpost/Next_issue/'+section,'→')),$('<div>').addClass('SPS-dialog-item-title').text(title),$('<div>').addClass('SPS-dialog-item-blurb').text(blurb)])}),newOO.ui.IconWidget({icon:'draggable',title:'Drag to reposition'})).$element;};vararticleItems=articlesInfo.map(function(info){returnnewDraggablePanel({expanded:false,framed:true,padded:false,data:info,$content:makeItemContent(info.section,info.title,info.blurb)});});this.widgets.sortOrderDraggabelGroup=newDraggableGroupStack({continuous:true,orientation:'vertical',id:'SPS-dialog-sortOrderDraggabelGroup',items:articleItems});$(this.widgets.sortOrderDraggabelGroup.$element).append(articleItems.map(function(item){returnitem.$element;}));this.widgets.startAtZeroCheckbox=newOO.ui.CheckboxMultioptionWidget({selected:startAtZeroValue,label:'Use announcement ("from the editors") formatting for first item',id:'SPS-dialog-startAtZeroCheckbox'});this.getPanelElement('sort').find('#SPS-dialog-sortOrderDraggabelGroup').remove().end().find('#SPS-dialog-startAtZeroCheckbox').remove().end().append([this.widgets.sortOrderDraggabelGroup.$element,this.widgets.startAtZeroCheckbox.$element,]);};MainDialog.prototype.setStatusPanelContent=function(){varmoveDelayNotice=(this.data.articles.length>maxMovesPerMinute)?'&#32;(Note: an approximately '+Math.floor(this.data.articles.length/maxMovesPerMinute)+' minute long delay is required due to technical limitations)&#32;':'';vartaskDescriptions=['Creating issue page','Preparing articles',$('<span>').text('Moving articles').append($('<span>').css('font-size','88%').append(moveDelayNotice)),'Editing issue page','Editing main Signpost page','Creating single page edition','Creating archive page','Updating previous edition\'s archive page','Purging pages','Mass-messaging enwiki subscribers','Mass-messaging global subscribers','Updating current year\'s archive overview page','Creating archive categories (if needed)','Updating "Next" links in previous issues','Requesting a new watchlist notification','Creating a single-talk page'];this.taskItems=taskDescriptions.map(function(description,index){returnnewOO.ui.Widget({$element:$('<li>'),id:'SPS-task-'+index,classes:['SPS-task'],$content:$('<span>').append([description,'... ',$('<span>').addClass('SPS-task-status').text('waiting')]),data:{completed:false}});});this.getPanelElement('status').find('#SPS-dialog-taskList').remove().end().append($('<ul>').attr('id','SPS-dialog-taskList').append(this.taskItems.map(function(item){returnitem.$element;})));};MainDialog.prototype.resetFinishedPanel=function(){$('#SPS-previewButton-container').empty();};/* ~~~~~~~~~~ Process panels (read their state, and set data based on it) ~~~~~~~~~~ */MainDialog.prototype.processWelcomePanel=function(action){if(action==='start'){this.data.api=newmw.Api(apiConfig);this.data.metaApi=newmw.ForeignApi('https://meta.wikimedia.org/w/api.php',apiConfig);$('.SPS-dryRun').hide();}else{this.data.api=newFakeApi(apiConfig);this.data.metaApi=newFakeApi(apiConfig);$('.SPS-dryRun').show();}};MainDialog.prototype.processChoosePanel=function(){if(!this.data||!this.data.api){return;}varoldCachedTitles=readFromCache('selected-titles');this.data.selectedTitles=this.getSelectedTitles();writeToCache('selected-titles',this.data.selectedTitles);returnoldCachedTitles;};MainDialog.prototype.processSortPanel=function(){if(!this.data||!this.data.api){returntrue;}this.data.articles=this.getArticleInfosInOrder();writeToCache('info',this.data.articles);varstartAtZero=this.getStartAtZeroBoolean();this.data.firstItemIndex=(startAtZero)?0:1;writeToCache('startAtZero',startAtZero);};/* ~~~~~~~~~~ Process actions ~~~~~~~~~~ */// Get the process (steps to be done) for each action (button click).// Generally, set the new mode, get the new panel ready, and display the new panel.MainDialog.prototype.getActionProcess=function(action){vardialog=this;if(action==='start'||action==='dryrun'){returnnewOO.ui.Process(function(){dialog.processWelcomePanel(action);}).next(function(){returndialog.getTitlesFromCacheOrApi().then(dialog.cacheTitlesIfNotCached).then(function(titles){dialog.setChoosePanelContent.call(dialog,titles);});}).next(function(){dialog.actions.setMode('choose');dialog.stackLayout.setItem(dialog.panels.choose);});}elseif(action==='next-to-sort'){returnnewOO.ui.Process(function(){return$.when(dialog.processChoosePanel()).then(function(oldCachedSelectedTitles){returndialog.getArticleAndIssueInfo.call(dialog,oldCachedSelectedTitles);}).then(function(articleInfos,prevIssueDates,startAtZero){dialog.setPrevIssueDates(prevIssueDates);returndialog.setSortPanelContent(articleInfos,startAtZero);});}).next(function(){dialog.actions.setMode('sort');dialog.stackLayout.setItem(dialog.panels.sort);varpublishButtonLabel=(dialog.data.api.isFake)?'Simulate publishing':'Publish';dialog.getActions().getSpecial().primary.setLabel(publishButtonLabel);})// Hack to calculate size from this panel, rather than the previous panel.next(function(){dialog.actions.setMode('sort');dialog.stackLayout.setItem(dialog.panels.sort);varpublishButtonLabel=(dialog.data.api.isFake)?'Simulate publishing':'Publish';dialog.getActions().getSpecial().primary.setLabel(publishButtonLabel);});}elseif(action==='next-to-status'){returnnewOO.ui.Process(function(){dialog.processSortPanel.call(dialog);}).next(dialog.setStatusPanelContent,this).next(function(){dialog.actions.setMode('status');dialog.stackLayout.setItem(dialog.panels.status);}).next(dialog.resetFinishedPanel).next(function(){dialog.doPublishing.call(dialog);});}elseif(action==='next-to-finished'){returnnewOO.ui.Process(function(){dialog.actions.setMode('finished');dialog.stackLayout.setItem(dialog.panels.finished);});}elseif(action==='abort'){returnnewOO.ui.Process(function(){dialog.data.api.abort();dialog.actions.setMode('aborted');dialog.stackLayout.setItem(dialog.panels.aborted);});}elseif(action==='back-to-welcome'||action==='welcome'){returnnewOO.ui.Process(dialog.processChoosePanel).next(function(){dialog.actions.setMode('welcome');dialog.stackLayout.setItem(dialog.panels.welcome);});}elseif(action==='back-to-choose'){returnnewOO.ui.Process(dialog.processSortPanel).next(function(){dialog.actions.setMode('choose');dialog.stackLayout.setItem(dialog.panels.choose);dialog.updateSize();});}elseif(action==='back-to-status'){returnnewOO.ui.Process(function(){dialog.actions.setMode('status');dialog.stackLayout.setItem(dialog.panels.status);})// Hack to calculate size from this panel, rather than the previous panel.next(function(){dialog.actions.setMode('status');dialog.stackLayout.setItem(dialog.panels.status);});}elseif(action==='close'){returnnewOO.ui.Process(dialog.processChoosePanel).next(dialog.processSortPanel).next(function(){dialog.close();});}returnMainDialog.super.prototype.getActionProcess.call(this,action);};// Set up the initial modeMainDialog.prototype.getSetupProcess=function(data){vardialog=this;data=data||{};dialog.data=data;returnMainDialog.super.prototype.getSetupProcess.call(this,data).next(function(){dialog.widgets={};dialog.actions.setMode('welcome');dialog.stackLayout.setItem(dialog.panels.welcome);dialog.updateSize();},this);};// Do the publishingMainDialog.prototype.doPublishing=function(){vardialog=this;vartaskComleted=function(taskNumber){returndialog.taskItems[taskNumber].getData().completed;};vartasksFunctions=[makeIssuePage,prepareArticles,moveArticles,editIssueSubpage,editMain,makeSingle,makeArchive,updatePrevArchive,purgePages,massmsg,globalmassmsg,updateYearArchive,createArchiveCats,updateOldNextLinks,requestWatchlistNotification,createSingleTalk];varallDonePromise=tasksFunctions.reduce(function(previousPromise,tasksFunction,taskNumber){returnpreviousPromise.then(function(){dialog.showTaskStarted(taskNumber);if(taskComleted(taskNumber)){returnreflect('skipped');}returnreflect(tasksFunction(dialog.getData()));}).then(function(reflectdPromise){returndialog.setTaskStatus(reflectdPromise,taskNumber);});},$.Deferred().resolve());returnallDonePromise.then(function(){vardialogSpecialActions=dialog.getActions().getSpecial();dialogSpecialActions.primary.setDisabled(false);dialogSpecialActions.safe.setDisabled(true);});};/* ~~~~~~~~~~ Helper functions: getters ~~~~~~~~~~ *//** * @param {Array} oldCachedSelectedTitles * @returns Promise of: *    {Array} of objects containing article info for each selected title *    {String} the previous issue date *    {Boolean} value for 'startAtZero' checkbox */MainDialog.prototype.getArticleAndIssueInfo=function(oldCachedSelectedTitles){vardialog=this;vardefaultStartAtZero=false;if(oldCachedSelectedTitles){varsameTitles=(this.data.selectedTitles.join('')===oldCachedSelectedTitles.join(''));varcachedPreviousIssueDates=readFromCache('previousIssueDates');varcachedInfo=readFromCache('info');varcachedStartAtZero=readFromCache('startAtZero')||false;if(sameTitles&&cachedPreviousIssueDates&&cachedInfo){//dialog.data.prevIssueDates = cachedPreviousIssueDates;// Get the latest info, but in the previously set orderreturngetInfo(this.data.api,oldCachedSelectedTitles,cachedPreviousIssueDates).then(function(newInfo){varsortingNewInfoNotPossible=newInfo.length!==cachedInfo.length;if(sortingNewInfoNotPossible){// Just use the new info in the default orderreturn$.Deferred().resolve(newInfo,cachedPreviousIssueDates,defaultStartAtZero);}// Sort the new info into the same order as the old infovarsortedNewInfo=cachedInfo.map(function(oldArticle){returnnewInfo.find(function(newArticle){returnnewArticle.section===oldArticle.section;});});varhasEmptySlots=sortedNewInfo.some(function(e){returne==null;});if(hasEmptySlots){// Just use the new info in the default orderreturn$.Deferred().resolve(newInfo,cachedPreviousIssueDates,defaultStartAtZero);}return$.Deferred().resolve(sortedNewInfo,cachedPreviousIssueDates,cachedStartAtZero);});}}returngetPreviousIssueDates(this.data.api,this.data.today.year).then(function(previousIssueDates){returngetInfo(dialog.data.api,dialog.data.selectedTitles,previousIssueDates).then(function(articlesInfos){return$.Deferred().resolve(articlesInfos,previousIssueDates,defaultStartAtZero);});});};MainDialog.prototype.getArticleInfosInOrder=function(){varitems=this.widgets.sortOrderDraggabelGroup.getItems();returnitems.map(function(item){returnitem.data;});};MainDialog.prototype.getPanelElement=function(panel){return$('#'+this.panels[panel].getElementId()).children().first();};/** * @returns {Array} Selected titles */MainDialog.prototype.getSelectedTitles=function(){returnthis.widgets.titlesMultiselect.findSelectedItemsData();};MainDialog.prototype.getStartAtZeroBoolean=function(){varcheckbox=this.widgets.startAtZeroCheckbox;returncheckbox.isSelected();};/** @returns Promise of an array of titles **/MainDialog.prototype.getTitlesFromCacheOrApi=function(){varcachedTitles=readFromCache('titles');if(cachedTitles){return$.Deferred().resolve(cachedTitles,true/* = alreadyCached */);}returngetArticleTitles(this.data.api);};/* ~~~~~~~~~~ Helper functions: setters ~~~~~~~~~~ *//** * @param {Array} titles * @param {Boolean} alreadyCached * @returns {Array} titles (passed through without modification) */MainDialog.prototype.cacheTitlesIfNotCached=function(titles,alreadyCached){if(!alreadyCached){writeToCache('titles',titles);}returntitles;};/** * @chainable * @param {Object} previousIssueDates - name:date pairs of section names and their previous issue date * @returns {Object} previousIssueDates */MainDialog.prototype.setPrevIssueDates=function(previousIssueDates){this.data.previousIssueDates=previousIssueDates;writeToCache('previousIssueDates',previousIssueDates);returnpreviousIssueDates;};/* ~~~~~~~~~~ Task-related functions (status panel) ~~~~~~~~~~ */MainDialog.prototype.showTaskStarted=function(i){vartask=this.taskItems[i];if(task.getData().completed){return;}task.$element.addClass('SPS-task-doing').find('.SPS-task-status').text('...doing...');};MainDialog.prototype.showTaskDone=function(i,skipped){vartask=this.taskItems[i];vartaskClass=(skipped)?'SPS-task-skipped':'SPS-task-done';varstatusText=(skipped)?'Skipped.':'Done!';task.$element.removeClass('SPS-task-doing').addClass(taskClass).find('.SPS-task-status').text(statusText);};MainDialog.prototype.showTaskFailed=function(i,reason,allowRetry,handledPromise){vardialog=this;vartask=dialog.taskItems[i];vartaskData=task.getData();varretryButton=$('<button>').addClass('SPS-inlineButton mw-ui-button mw-ui-progressive').text('Retry').click(function(){task.$element.removeClass('SPS-task-failed').find('.SPS-task-errorMsg, #SPS-errorbox-'+i).remove();taskData.completed=false;taskData.skipped=false;task.setData(taskData);dialog.doPublishing().always(function(){dialog.getActionProcess('next-to-finish');});});varskipButton=$('<button>').addClass('SPS-inlineButton mw-ui-button').append(['Continue ',$('<span>').addClass('no-bold').text('(after doing step manually)')]).click(function(){$('#SPS-errorbox-'+i).remove();taskData.completed=true;taskData.skipped=true;task.setData(taskData);dialog.showTaskDone(i,true);handledPromise.resolve();});varerrorActions=$('<div>').attr('id','SPS-errorbox-'+i).append([(allowRetry?retryButton:''),skipButton]);task.$element.removeClass('SPS-task-doing').addClass('SPS-task-failed').find('.SPS-task-status').empty().after(errorActions).after($('<span>').addClass('SPS-task-errorMsg').text(' Failed ('+reason+')'));};MainDialog.prototype.setTaskStatus=function(result,taskNumber){varhandledPromise=$.Deferred();vartask=this.taskItems[taskNumber];vartaskData=task.getData();if(result.status==="resolved"){taskData.completed=true;task.setData(taskData);this.showTaskDone(taskNumber,taskData.skipped);if(!taskData.skipped&&taskNumber===3){vareditionInfo=result.value[1];if(editionInfo){this.data.previousIssueDate=editionInfo.previousIssueDate;this.data.vol=editionInfo.vol;this.data.iss=editionInfo.iss;}}handledPromise.resolve();}else{vartasksWithoutRetryOption=[1,2,6];varallowRetry=(tasksWithoutRetryOption.indexOf(taskNumber)===-1);this.showTaskFailed(taskNumber,extraJs.makeErrorMsg(result.error[0],result.error[1]),allowRetry,handledPromise);}returnhandledPromise;};/* ~~~~~~~~~ Window management ~~~~~~~~~~ */varmainWindowFactory=newOO.Factory();mainWindowFactory.register(MainDialog);varmainWindowManager=newOO.ui.WindowManager({factory:mainWindowFactory});mainWindowManager.$element.addClass('sps-oouiWindowManager').insertBefore($('#SPS-ovarlayWindowManager'));/* ========== Portlet link ====================================================================== */// Add link to 'More' menu which starts everythingmw.util.addPortletLink('p-cactions','#','Publish next edition','ca-pubnext');$('#ca-pubnext').on('click',function(e){e.preventDefault();// Configuration valuesvarnewDate=newDate();varconfig={script_page:'User:JPxG/SPS',script_ad:" ([[User:JPxG/SPS|via script]])",path:"Wikipedia:Wikipedia Signpost",today:{date:newDate,iso:newDate.toISOString().slice(0,10),day:newDate.getUTCDate(),month:mw.config.get('wgMonthNames')[newDate.getUTCMonth()+1],year:newDate.getUTCFullYear()},size:'larger'};/*// Idiotic hack to make this issue be a specific day:// un comment this if an issue needs to be for a particular day// and it isn't that day (i.e. New Years', Leif Erikson Day, etc).// JPxG, 2024 January 11config = {script_page:'User:JPxG/SPS',script_ad:" ([[User:JPxG/SPS|via script]])",path:"Wikipedia:Wikipedia Signpost",today: {date:newDate,iso:"2024-01-10",day:"10",month:"January",year:"2024"},size: 'larger'};*/// iso   = "2024-01-10"// day   = "10"// month = "January"// year  = "2024"config.today.dmy=config.today.day+" "+config.today.month+" "+config.today.year;// Open the main dialog windowmainWindowManager.openWindow('mainDialog',config);});// End of full file closure wrappers});// </nowiki>
    Retrieved from "https://en.wikipedia.org/w/index.php?title=User:JPxG/SPS.js&oldid=1311024619"

    [8]ページ先頭

    ©2009-2025 Movatter.jp