Jump to content
Wikipedia The Free Encyclopedia

User:Nardog/SmartDiff.js

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 user script seems to have a documentation page at User:Nardog/SmartDiff.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
 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*(?:<[^>]*>)?&lt;(?:<[^>]*>)?tvar(?:<[^>]*>)?\s(?!&gt;).*?&gt;)?\s*)((?:(?!&[gl]t;)[^\[\]{|}])+?)(?=\s*(?:(?:<[^>]*>)?&lt;(?:<[^>]*>)?\/(?:<[^>]*>)?tvar(?:<[^>]*>)?&gt;(?:<[^>]*>)?\s*)?(?:\||\](?:<[^>]*>)?\]|}(?:<[^>]*>)?}|$))/g;
 this.headRe=/^((?:(?:<[^>]*>)*=){1,6}(?:<[^>]*>)?\s*)((?:(?!&[gl]t;).)+?)(?=\s*(?:(?:<[^>]*>)?=){1,6}(?:<[^>]*>|\s)*(?:&lt;|$))/g;
 this.urlRe=/(?:https?(?:<[^>]*>)?:(?:<[^>]*>)?|(?<=\[(?:<[^>]*>)?))\/(?:<[^>]*>)?\/(?:[-\dA-Za-z]+|<[^>]*>)+\.(?:[-.\d:A-Za-z]+|<[^>]*>)+(?:\/(?:(?:[!#-%(-;=?-Z_a-z~]+|&amp;|<[^>]*>)*(?:[#-%(+\-\/-9=?-Z_a-z~]|&amp;)(?:<[^>]*>)?)?)?/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(/&amp;/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);
 });
 });

AltStyle によって変換されたページ (->オリジナル) /