User:Nardog/SmartDiff.js
Appearance
From Wikipedia, the free encyclopedia
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:Nardog/SmartDiff.
mw.loader.using([ 'mediawiki.util','mediawiki.Title','mediawiki.api' ],functionsmartDiff(){ mw.loader.addStyleTag('.smartdiff-link.extiw, .smartdiff-link.external{color:var(--color-progressive,#36c)} .smartdiff-link.extiw:visited, .smartdiff-link.external:visited{color:#795cb2} .smartdiff-link.extiw:active, .smartdiff-link.external:active{color:#faa700}'); classSmartDiff{ constructor($diff){ this.$diff=$diff; this.isSpecial=mw.config.get('wgNamespaceNumber')===-1; this.isView=mw.config.get('wgAction')==='view'&& newURLSearchParams(location.search).get('diffonly')!=='1'; this.magicWords=[ '!','BASEPAGENAME','BASEPAGENAME:','BASEPAGENAMEE','BASEPAGENAMEE:', 'canonicalurl:','CURRENTDAY','CURRENTDAY2','CURRENTDAYNAME', 'CURRENTDOW','CURRENTHOUR','CURRENTMONTH','CURRENTMONTH1', 'CURRENTMONTHABBREV','CURRENTMONTHNAME','CURRENTMONTHNAMEGEN', 'CURRENTTIME','CURRENTTIMESTAMP','CURRENTVERSION','CURRENTWEEK', 'CURRENTYEAR','DEFAULTCATEGORYSORT:','DEFAULTSORT:','DEFAULTSORTKEY:', 'DISPLAYTITLE:','filepath:','formatnum:','FULLPAGENAME', 'FULLPAGENAME:','FULLPAGENAMEE','FULLPAGENAMEE:','fullurl:', 'gender:','int:','lc:','lcfirst:','LOCALDAY','LOCALDAY2', 'LOCALDAYNAME','LOCALDOW','LOCALHOUR','LOCALMONTH','LOCALMONTH1', 'LOCALMONTHABBREV','LOCALMONTHNAME','LOCALMONTHNAMEGEN','LOCALTIME', 'LOCALTIMESTAMP','LOCALWEEK','LOCALYEAR','msg:','msgnw:', 'NAMESPACE','NAMESPACE:','NAMESPACEE','NAMESPACEE:','NAMESPACENUMBER', 'NAMESPACENUMBER:','ns:','NUMBEROFACTIVEUSERS','NUMBEROFARTICLES', 'NUMBEROFEDITS','NUMBEROFFILES','NUMBEROFPAGES','NUMBEROFUSERS', 'padleft:','PAGENAME','PAGENAMEE','PAGESINCAT:','PAGESINCATEGORY:', 'plural:','REVISIONDAY','REVISIONDAY:','REVISIONDAY2','REVISIONDAY2:', 'REVISIONID','REVISIONID:','REVISIONMONTH','REVISIONMONTH:', 'REVISIONMONTH1','REVISIONMONTH1:','REVISIONSIZE','REVISIONTIMESTAMP', 'REVISIONTIMESTAMP:','REVISIONUSER','REVISIONUSER:','REVISIONYEAR', 'REVISIONYEAR:','ROOTPAGENAME','ROOTPAGENAME:','ROOTPAGENAMEE', 'ROOTPAGENAMEE:','SHORTDESC:','SUBJECTPAGENAME','SUBJECTPAGENAME:', 'SUBJECTPAGENAMEE','SUBJECTPAGENAMEE:','SUBJECTSPACE','SUBJECTSPACE:', 'SUBJECTSPACEE','SUBJECTSPACEE:','SUBPAGENAME','SUBPAGENAME:', 'SUBPAGENAMEE','SUBPAGENAMEE:','TALKPAGENAME','TALKPAGENAME:', 'TALKPAGENAMEE','TALKPAGENAMEE:','TALKSPACE','TALKSPACE:', 'TALKSPACEE','TALKSPACEE:','uc:','ucfirst:','urlencode:' ]; if(window.smartdiffMagicWords){ this.magicWords.push(...window.smartdiffMagicWords); } try{ this.subNs=mw.config.get('wgVisualEditorConfig').namespacesWithSubpages; }catch(e){} if(!this.subNs){ this.subNs=Object.keys(mw.config.get('wgFormattedNamespaces')) .map(k=>Number(k)).filter(ns=>![0,6,8].includes(ns)); } this.re=/((?:\[(?:<[^>]*>)?\[|(?<!{(?:<[^>]*>)?){(?:<[^>]*>)?{(?:<[^>]*>)?(?:(?:#(?:<[^>]*>)?invoke|(?:safe)?subst|msg(?:nw)?|raw|int)(?:<[^>]*>)?:)?)(?:\s*(?:<[^>]*>)?<(?:<[^>]*>)?tvar(?:<[^>]*>)?\s(?!>).*?>)?\s*)((?:(?!&[gl]t;)[^\[\]{|}])+?)(?=\s*(?:(?:<[^>]*>)?<(?:<[^>]*>)?\/(?:<[^>]*>)?tvar(?:<[^>]*>)?>(?:<[^>]*>)?\s*)?(?:\||\](?:<[^>]*>)?\]|}(?:<[^>]*>)?}|$))/g; this.headRe=/^((?:(?:<[^>]*>)*=){1,6}(?:<[^>]*>)?\s*)((?:(?!&[gl]t;).)+?)(?=\s*(?:(?:<[^>]*>)?=){1,6}(?:<[^>]*>|\s)*(?:<|$))/g; this.urlRe=/(?:https?(?:<[^>]*>)?:(?:<[^>]*>)?|(?<=\[(?:<[^>]*>)?))\/(?:<[^>]*>)?\/(?:[-\dA-Za-z]+|<[^>]*>)+\.(?:[-.\d:A-Za-z]+|<[^>]*>)+(?:\/(?:(?:[!#-%(-;=?-Z_a-z~]+|&|<[^>]*>)*(?:[#-%(+\-\/-9=?-Z_a-z~]|&)(?:<[^>]*>)?)?)?/g; if(window.smartdiffTemplates){ this.tempRe=/( data-smartdiff-temp="(\d+)">[^{|}]+)(\|(?:(?!&[gl]t;)[^\[\]{}]|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?})+)(?=}(?:<[^>]*>)?}|$)/g; this.tempSubRe=/((?:\s|{(?:<[^>]*>)?{(?:<[^>]*>)?!(?:<[^>]*>)?}(?:<[^>]*>)?}[^<>|]*|<[^>]*>)*(?:\|(?:\s|(?:<[^>]*>)|\d+(?:\s|<[^>]*>)*=|[^\d<=>|](?:[^<=>|]|<[^>]*>)*=(?:[^<=>|]|<[^>]*>)*\|?)*|$))/; this.templates=window.smartdiffTemplates; } ['rep','headRep','urlRep','tempRep'].forEach(fn=>{ this[fn]=this[fn].bind(this); }); this.side='old'; $diff.find('.diff-deletedline > div').get().forEach(this.processDiv,this); this.side='new'; $diff.find('.diff-addedline > div').get().forEach(this.processDiv,this); let$contexts=$diff.find('.diff-context > div'); $contexts.each((i,div)=>{ if(i%2){ this.side='new'; if(this.propUsed&&this.getProp()!==this.getProp('pn','old')){ this.processDiv(div); }else{ $contexts.eq(i).replaceWith($contexts.eq(i-1).clone()); } }else{ this.side='old'; this.propUsed=false; this.processDiv(div); } }); this.links={}; $diff.find('.smartdiff-link:not(.external)').each((i,link)=>{ lettitle=link.title; if(!title)return; if(!this.links.hasOwnProperty(title)){ this.links[title]=[]; } this.links[title].push(link); }); this.query(Object.keys(this.links).slice(0,500)); if(this.hasError){ mw.notify('SmartDiff error',{type:'warn'}); } } processDiv(div){ if(div.querySelector('a[href]'))return; letorigHtml=div.innerHTML; letnewHtml=origHtml.replace(this.urlRe,this.urlRep) .replace(this.re,this.rep).replace(this.headRe,this.headRep); if(this.tempRe){ newHtml=newHtml.replace(this.tempRe,this.tempRep); } if(newHtml===origHtml)return; let$newDiv=$('<div>').html(newHtml); if(this.detectErrors($newDiv,newHtml,origHtml,div))return; div.textContent=''; $newDiv.contents().appendTo(div); } rep(0ドル,1ドル,2ドル){ if(0ドル.includes('<a class="smartdiff-link'))return0ドル; let[s,pre,mid,post]=this.stripTags(2ドル,true,1ドル); lett=mw.Title.newFromText(s),isTemp; if(t){ if(1ドル.includes('invoke')){ t=mw.Title.makeTitle(828,s); }elseif(s[0]==='/'){ if(this.subNs.includes(this.getProp('ns'))){ t=mw.Title.newFromText( this.getProp()+s.replace(/\/+$/,'') ); }elseif(1ドル[0]==='{'){ t.namespace=10; } }elseif(1ドル[0]==='{'){ if(s[0]==='#')return0ドル; if(1ドル.includes('int')){ t=mw.Title.makeTitle(8,s); }elseif(!t.namespace&&s[0]!==':'){ if(!1ドル.includes('msg')&&!1ドル.includes('raw')){ letmatch=s.match(/^[^:]+(?::(?=.)|$)/); if(match&&this.magicWords.includes(match[0])){ return0ドル; } } t.namespace=10; isTemp=true; } }elseif((this.isSpecial||!this.isView)&&s[0]==='#'){ t.title=this.getProp(); } }elseif(s.startsWith('../')&&this.subNs.includes(this.getProp('ns'))){ letchunks=s.split('/'); letlevelCount=chunks.findIndex(v=>v!=='..'); letsup=this.getProp().split('/').slice(0,-levelCount).join('/'); if(sup){ letsub=chunks.slice(levelCount).join('/').replace(/\/+$/,''); t=mw.Title.newFromText(sub?sup+'/'+sub:sup); } } if(!t)return0ドル; letattrs={ class:'smartdiff-link', href:t.getUrl() }; if(this.isSpecial||!this.isView||s[0]!=='#'){ attrs.title=t.toText(); } if(isTemp&&this.tempRe){ letname=t.getMainText(); letidx=this.templates.findIndex(temp=>temp.names.includes(name)); if(idx!==-1){ attrs['data-smartdiff-temp']=idx; } } returnpre+$('<a>').attr(attrs).html(mid)[0].outerHTML+post; } stripTags(s,decode,pre='',post=''){ letmid=s,tags=s.match(/<\/?(?:ins|del)[^>]*>/g); s=$($.parseHTML(s.replace(/&/g,'&'))).text(); if(decode){ try{ s=decodeURIComponent(s); }catch(e){} } if(tags){ if(tags[0][1]==='/'){ pre+=tags[0]; mid=`<${tags[0].slice(2,5)} class="diffchange diffchange-inline">`+mid; } letlastTag=tags.pop(); if(lastTag[1]!=='/'){ mid+=`</${lastTag.slice(1,4)}>`; post=lastTag+post; } } return[s,pre,mid,post]; } headRep(0ドル,1ドル,2ドル){ if(0ドル.includes('<a class="smartdiff-link'))return0ドル; let[s,pre,mid,post]=this.stripTags(2ドル,true,1ドル); s=s.replace(/'''(.+?)'''|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>/gi,'1ドル') .replace(/''(.+?)''/g,'1ドル') .replace(/^_+|_+$/g,''); lett=mw.Title.newFromText( `${this.isSpecial||!this.isView?this.getProp():''}#${s}` ); if(!t)return0ドル; letattrs={ class:'smartdiff-link', href:t.getUrl() }; if(this.isSpecial||!this.isView){ attrs.title=t.toText(); } returnpre+$('<a>').attr(attrs).html(mid)[0].outerHTML+post; } urlRep(0ドル){ let[url,pre,mid,post]=this.stripTags(0ドル); returnpre+$('<a>').attr({ class:'smartdiff-link external', href:url, rel:'nofollow' }).html(mid)[0].outerHTML+post; } tempRep(0ドル,1ドル,2ドル,3ドル){ if(3ドル.includes('<a class="smartdiff-link'))return0ドル; lettemp=this.templates[2ドル]; return1ドル+3ドル.split(this.tempSubRe).map((os,i)=>{ if(!os||i%2)returnos; letj=i/2; if(j<temp.start||j>temp.end|| temp.skipOdd&&j%2||temp.skipEven&&j%2===0 ){ returnos; } let[s,pre,mid,post]=this.stripTags(os,true); if(temp.prefix){ s=temp.prefix+s; } if(temp.suffix){ s+=temp.suffix; } lett=temp.forceNs ?mw.Title.makeTitle(temp.namespace,s) :mw.Title.newFromText(s,temp.namespace); if(!t)returnos; letparams=(j>=temp.noRedirectStart||j<=temp.noRedirectEnd)&& {redirect:'no'}; returnpre+$('<a>').attr({ class:'smartdiff-link', href:t.getUrl(params), title:t.toText() }).html(mid)[0].outerHTML+post; }).join(''); } getProp(n='pn',side=this.side){ this.propUsed=true; if(this[side]){ if(this[side][n]){ returnthis[side][n]; } }else{ this[side]={}; letlink=this.$diff[0].querySelector( side==='old' ?'#mw-diff-otitle1 a, #differences-prevlink' :'#mw-diff-ntitle1 a, #differences-nextlink' ); if(link){ letpn=mw.util.getParamValue('title',link.search); this[side].pn=pn; this[side].ns=mw.Title.newFromText(pn).namespace; returnthis[side][n]; } } if(this[n]){ returnthis[n]; } if(this.isSpecial){ this.pn=''; this.ns=0; }else{ this.pn=mw.config.get('wgPageName'); this.ns=mw.config.get('wgNamespaceNumber'); } returnthis[n]; } query(titles){ if(!titles.length)return; newmw.Api().post({ action:'query', titles:titles.slice(0,50), iwurl:1, prop:'info', inprop:'linkclasses', inlinkcontext:this.getProp(), formatversion:2 },{ headers:{'Promise-Non-Write-API-Action':1} }).then(response=>{ letquery=response&&response.query; if(!query)return; letdata={}; (query.pages||[]).forEach(page=>{ letobj={classes:page.linkclasses||[]}; if(page.missing&&!page.known){ obj.classes.push('new'); obj.params={action:'edit',redlink:1}; } data[page.title]=obj; }); (query.interwiki||[]).forEach(interwiki=>{ data[interwiki.title]={ classes:['extiw'], url:interwiki.url }; }); (query.normalized||[]).forEach(entry=>{ if(!data.hasOwnProperty(entry.to))return; letobj=data[entry.to]; obj.canonical=entry.to; if(!obj.url){ obj.url=mw.util.getUrl(entry.to,obj.params); } data[entry.from]=obj; }); Object.entries(data).forEach(([title,obj])=>{ if(!this.links.hasOwnProperty(title))return; let$links=$(this.links[title]).addClass(obj.classes) .attr('title',obj.canonical); if(obj.url){ $links.attr('href',function(){ returnobj.url+this.hash; }); } }); this.query(titles.slice(50)); }); } detectErrors($newDiv,newHtml,origHtml,div){ letcomp=$newDiv.html(); if(comp!==newHtml){ console.warn( 'SmartDiff syntax error at:\n', div, `\nNew HTML:\n${newHtml}\nCompared against:\n${comp}` ); this.hasError=true; returntrue; } let$comp=$newDiv.clone(); $comp.find('.smartdiff-link').contents().unwrap(); comp=$comp.html().replace(/<\/(ins|del)><1円[^>]*>/g,''); if(comp!==origHtml){ console.warn( 'SmartDiff mutation error at:\n', div, `\nOriginal HTML:\n${origHtml}\nCompared against:\n${comp}` ); this.hasError=true; returntrue; } } } mw.hook('wikipage.diff').add($diff=>{ newSmartDiff($diff); }); });