Jump to content

User:Js/diffs.js

From Wikipedia, the free encyclopedia
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.
//dfPinWatchlist = true


$(function(){


var dfPopupSheet
 

mw.util.addCSS('\
td.diff-addedline:hover .diffchange, td.diff-deletedline:hover .diffchange {background:#fdd}\
')
 var localDomain = new RegExp('^https?:' + mw.config.get('wgServer').replace(/([\.\/])/g,'\\$1') + '\/')
 

$(function(){
 
 var $tbl = $('table.diff')
 if( $tbl.length ){
   improveTable($tbl)
   dfAddToolbar($tbl, '#contentSub')
 }
 
 mw.util.$content.click(dfClick)

 mw.util.addCSS('\
a[href*="diff="][href^="/w"],\
a[href*="diff="][href*="' + mw.config.get('wgServer') + '"]' +
 (window.dfDiffLinksCSS || '{font-style:italic}') +'\
a[href*="diff="][href^="/w"]:hover,\
a[href*="diff="][href*="' + mw.config.get('wgServer') + '"]:hover\
 {color:red !important}\
.df-popup {margin:0.5em}\
 ')
 
 
})
 
 $(document).keyup( function(e){ //close popup on Escape
   if( e.which == 27 ) popupClose('.df-instance:last')
 })



function dfClick(e){

 cursorWait() //cancel waiting indicator if something went wrong
 if( e.shiftKey || e.which != 1) return

 //is it a diff link?
 var lnk = $(e.target).closest('a')
 if( !lnk.length ) return
 var url = lnk.attr('href').replace(localDomain, '/')
 if( ! /^\//.test(url)  ) return //external URL
 
 var loadURL, dfContainer
 
 if( /[&?]diff=/.test(url) ){
 // diff
   if( /differences-(next|prev)link/.test( lnk.attr('id') ) )//prev/next link in diff table
      dfContainer = lnk.closest('table.diff').parent()
   else
      markClickedLink(lnk)
   if( /:Undelete/.test(url) )
     loadURL = url + '&useskin=myskin #content'
   else
     loadURL = url + '&action=render&diffonly=true'
   
 }else if( mw.config.get('wgCanonicalSpecialPageName') == 'Watchlist' 
        && window.dfPinWatchlist //popup for any link 
        && ! lnk.closest('fieldset').length ){

    if( ! /\?/.test(url) && ! /(special|служебная):/i.test(url) )
      loadURL = url + '?action=render'
    else 
      loadURL = url + '&useskin=myskin #content'
 
 }else{
   return
 }  
 
 //load diff
 e.preventDefault()
 cursorWait(true)
 dfContainer = dfContainer || popupCreate(e)
 dfContainer
 .attr('dfURL', url)
 .load( loadURL, afterDiffLoaded )

}




function afterDiffLoaded(){

 cursorWait()
 var $container = $(this)
 
 var url = $container.attr('dfURL')
 var dfLink = outputLink2('', $container.attr('dfURL'), 'Δ', 'current diff')

 var $tbl = $container.find('table.diff')
 if( $tbl.length ) improveTable( $tbl )

 if ( $container.hasClass('df-popup') ){

   var pgTitle = getTitleFromURL( $tbl.find('td.diff-ntitle a').attr('href') )
   var caption = $(
    '<div class=df-caption>' + 
    '<b>' + outputLink2(pgTitle) + '</b>' +
    ' (' + outputLink2(pgTitle, '?action=history', 'h') + ' ' + dfLink  + ')' +
    '</div>'
   )
   .prependTo($container)

   dfAddToolbar($tbl, caption)
   $container.parent().appendTo('body')
 
 }else{
   $('#contentSub')
   .children('.df-link').remove().end()
   .append(
     $('<span class=df-link style="margin-left:1em; font-weight:bold" />')
     .append( dfLink )
   )
 }

}






 
function popupCreate(e){ 

 //set CSS
 if( ! dfPopupSheet ){
   dfPopupSheet = mw.util.addCSS('\
  .df-clicked {background-color:#E0E0E0}\
  a.df-clicked-last {background-color:#FFDDDD}\
  .df-popup {position:absolute; z-index:5; width:95%; border:1px solid #000033; \
    font-size:110%; background-color:white; padding:0 9px 9px 9px }\
  .df-caption {background:#F0F0FF; border:1px outset gray; padding:2px; margin:0 -9px}\
  .df-closer {position:absolute; z-index:10; border:2px outset gray;\
    width:20px; height:20px; cursor:pointer; background:gray; opacity:0.5}')
   if( ! /[&?]diff=/.test(document.URL) ) 
     importStylesheetURI('//bits.wikimedia.org/ru.wikipedia.org/load.php?modules=mediawiki.action.history.diff&only=styles')
     //importStylesheetURI('/skins-1.5/common/diff.css')
 }

 //create popup
 var dfN = $('.df-popup').length
 var dfPopup = $('<div class=df-popup />')
 .css({
   top: $(window).scrollTop() + 30  + dfN * 5 + 'px',
   left: 10 + dfN * 5 + 'px'
 })
  .click( function(e){ //close popup when clicking on edges and and caption
    if( /df-(popup|caption)/.test(e.target.className) ) popupClose(this)
    else $(this).addClass('persistent')
  })
  .click(dfClick)
// .mouseleave( function(e){
//    if( ! /persistent/.test(this.className) ) popupClose(this) 
//  })

  //create 'closing' square on mouse position
 var dfCloser = $('<div class=df-closer title=close>&nbsp;</div>')
 .css({top: e.pageY-10, left: e.pageX-10})
 .mouseleave( function(){ $(this).remove() })
 .click( function(){ popupClose(this) } )

 $('<div class=df-instance />') //container for popup and closer
 .append(dfPopup, dfCloser)
 
 // if (isIE) hideAllSelectElements(true) // !!!
 return dfPopup

} 
 


function popupClose(el){
 $(el).closest('.df-instance').remove()
}



//******************************************************************************************

function cursorWait(isWait){
 if( window.dfNoWaitCursor ) return
 document.body.style.cursor = isWait ? 'wait' : ''
}


function markClickedLink(lnk){ //and mark link as "clicked"
 $('a.df-clicked-last').removeClass('df-clicked-last') //rm class from previos click
 lnk.addClass('df-clicked df-clicked-last')
 if( /Watchlist|Recentchanges/.test(mw.config.get('wgCanonicalSpecialPageName')) )
   lnk.closest('li').add(lnk.closest('tr')).addClass('df-clicked')
}

var dfHighlightSheet, dfImprovementSheet
function  addDiffTableCSS(){
 if( dfImprovementSheet ) return

 dfImprovementSheet = mw.util.addCSS('\
td.diff-addedline, td.diff-deletedline {font-size:100%}\
table.diff {border-spacing:1px}\
table.diff td {vertical-align:top}\
table.diff {width:99%}\
body table.diff, td.diff-otitle, td.diff-ntitle {background:#FBFBFB}\
table.diff td.diff-lineno {border-top: 25px solid #FBFBFB}\
tr.df-deleted td {background-color:#FEC}\
table.diff td div {min-height:1em}\
td.df-deletedwords, td.df-addedwords\
 {background:white; border:1px dotted gray; padding:2px}\
td.df-deletedwords span.diffchange {background-color:#FFA}\
td.df-addedwords span.diffchange {background-color:#CFC; color:black; font-weight:normal}\
tr.odd td.diff-addedline {background-color:#BEB}\
span.sig {border:1px dotted gray; border-bottom:none; font-family:cursive; font-size:90%}\
td.df-header {font-weight:bold; font-size:120%; padding-top:15px}\
tr.df-change td {font-size:100%}\
tr.df-added ins.diffchange {color:inherit; font-weight:normal; border:none}\
div.df-toolbar span.df-improve-btn {border:1px inset #EEEEEE}\
.df-btn {padding:2px; border:1px solid gray; margin-right:2px; cursor:pointer}\
table.diff td {cursor:help}\
table.diff td div, table.diff td.diff-multi {margin:0 8px 0 2px; cursor:default}'
+ (window.dfDiffTableCSS || '')) //user CSS

//cursor stuff shows that TD is clickable (sticking out from under DIV inside)

//padding: 0 8px 0 2px

//different approach to make cells clickable
//+ 'table.diff {border-spacing:0} table.diff td {border-top:4px solid #FBFBFB} table.diff td.diff-deletedline {border-right: 6px solid #FBFBFB}'
}






//==============================================================================



function improveTable($tbl){
 if (window.dfMaxImproveSize && $tbl.html().length>dfMaxImproveSize) return
 //improve rows
 //curTitle = $tbl.parent()[0].diffTitle //needed in improveCell() for relative links
 curTitle = getTitleFromURL( $tbl.parent().attr('dfURL') )
 curStripes = false
 addDiffTableCSS()
 $tbl.find('tr').each(improveRow)
 $tbl.click(tableOnclick)
}


function dfAddToolbar($tbl, where){
 //return 
 $('<div class=df-toolbar style="float:right" />')
 .append(
   //btn(changeTable, '¤', 'Enable/disable improvements'),
   btn(diffJSEngine, 'js', 'Javascript diff engine')
 )
 .prependTo(where)
 
 function changeTable(){alert(11)}
 function btn(func, txt, tip){ //creates <span> button
   return $('<span class=df-btn title="' + tip + '">' + txt + '</span>')
          .click( { dTable: $tbl }, func )
 } 
}









function improveRow(){

 var tr = this, tds = $(this).find('td'), td


 if (tds.length <= 2) return // 'diff info' or 'intermediate revisions' or 'Line xx:'
   //  case 'diff-otitle': case 'diff-multi': case 'diff-lineno':
  
 if( tds.length == 3 ){
   if( tds[1].className == 'diff-deletedline' ){
     tr.className += ' df-deleted' //new class, means the line was simply deleted, has pink background
     if( tds[1].innerHTML.length==0 ) tds[1].innerHTML = '<br />'
     expandCell(tds[1]) 
     return
   }else if( tds[2].className == 'diff-addedline' ){
     tr.className += ' df-added'
     improveCell(tds[2])
     htm = tds[2].innerHTML
     if( !htm.length ) tds[2].innerHTML = '<br />'
     if( curStripes ) tr.className += ' odd'
     if( /<span class="sig">/i.test(htm) ) curStripes = !curStripes
     expandCell(tds[2])
     return
   }
 }else if (tds[1].className == 'diff-context'){
   tr.className = 'df-context'
   if (window.dfParseContext) improveCell(tds[1])
   expandCell(tds[1])
   curStripes = false
   return
 }else{ //normal yellow/green rows with 4 cells
   tr.className = 'df-change'
   tds[1].colSpan = tds[3].colSpan = 2
   tr.removeChild(tds[2]); tr.removeChild(tds[0])
 }
 //if( window.dfImproveAdvanced ) improveRowMore(tr)
 return 





 
 
 function expandCell(td, clss){
 /*
  while (td.nextSibling) tr.removeChild(td.nextSibling)
  while (td.previousSibling) tr.removeChild(td.previousSibling)
  td.colSpan = 4
  if( clss ) td.className = clss
  */
  tr.innerHTML = '<td colspan=4 class="' + td.className + (clss?' ' + clss:'') + '">'
   + td.innerHTML + '</td>'
 }


 function splitRowsUp(tdGoesUp){
  tds[0].colSpan = 4
  tds[1].colSpan = 4
  //tr.removeChild(tds[2])
  //tr.removeChild(tds[0])
  var trnew = document.createElement('tr')
  trnew.className = 'df-change'
  trnew.appendChild(tdGoesUp)
  tr.parentNode.insertBefore(trnew, tr)
 }

 

 
} //improveRow()









function improveCell(cell){
 if (window.dfNoWikiParsing) return

 cell = $(cell)
 var htm = cell.html()
 if (htm.length == 0) return  //cell.innerHTML = '&nbsp;'

 cell.data('origHTML', htm)
 if (/^==.*== *$/i.test(cell.text())) cell.addClass('df-header') 
       

 htm = htm.replace(/\u00A0/g, '<b>\u00B7</b>')
 htm = htm.replace(/({\{u(?:ser(?:links)?)?\|)([^}]+)}\}/g, function(str,tmpl,user){
  return tmpl + outputLink2('special:contributions/'+user,'',user) + '}}' })

 //mark signatures
 htm = htm.replace(/(\[\[[^\[]{4,65})?\d\d:\d\d, \d\d? \S{3,9} 20\d\d \(UTC\)/g, '<span class="sig">$&</span>')
 htm = htm.replace(/\{\{unsigned[^\}]\}\}/i, '<span class="sig">$&</span>')

 //[[link]]
 var CatOrFileRegExp = RegExp('^('+mw.config.get('wgFormattedNamespaces')[6]+'|'
  +mw.config.get('wgFormattedNamespaces')[14]+'|category|image|file):|\.(jpg|png|svg|gif)$','i')
 htm = htm.replace(/\[\[([^\]><}{|]+)\|?([^\]><]*)?\]\]/g,
 function(wikicode,page,name){
  if (/http:\/\//i.test(page)) return wikicode //user made a mistake
  if (CatOrFileRegExp.test(page)) name = page+(name?'|'+name:'') //file or category, show in full
  else if (!name) name = page
  if (/^[#\/]/.test(page)) page = curTitle + page //relative link
  else if (/^[a-z]{2,7}:/.test(page)) page = 'Special:Search/'+page //possible interproject link, some are not "local"
  return outputLink2(page, '', name, wikicode)
 })

 // [http://...]
 htm = htm.replace(/\[(https?:\/\/[^ \]><]*)( [^\]]*)?\]/g, //
 function  (str,link,name){
  var output = '<a href=' + link, title, tip, nameWas = name
  if (link.indexOf(mw.config.get('wgServer')+mw.config.get('wgScript')) == 0){ //local link
    tip = tryDecodeURI(link.substring((mw.config.get('wgServer')+mw.config.get('wgScript')).length+1))
    if (!name){
      name = getTitleFromURL(link) || tip
      if (/diff=/.test(link)) name = 'diff: ' + name
      else if (tip.match(/action=history/)) name = 'hist: ' + name
      else if (tip.match(/oldid=/)) name = 'oldid: ' + name
      else name = 'wiki: ' + name
    }
  } else { //ext link
    tip = tryDecodeURI(link.substring(7))
    output += ' class="external text"'
    if (!name) name = tip
  }
  if (!nameWas && (name.length > 70)) name = name.substring(0,60) + '… …'
  return output + ' title="' + tip + '">[' + name + ']</a>'
 })

 cell.html(htm)

function tryDecodeURI(s){ try{s=decodeURIComponent(s)}catch (e){}; return s }

}




function outputLink2(page, params, name, tooltip){
 name = name || page
 page = page.replace(/&amp;/gi,'&').replace(/#.+$/, calcAnchor)
 return '<a href="'+ mw.config.get('wgArticlePath').replace(/\$1/,'')
  + encodeURI(page).replace(/\?/g,'%3F') 
  + (params||'')
  + '" title="' + (tooltip||name).replace(/"/g,'&quot;') + '">' + name + '</a>' //'
}


function calcAnchor(txt){ //try to create href anchor similar to Parser.php::guessSectionNameFromWikiText() 
 txt = $.trim(txt).replace(/#/g,'')
      .replace(/\[\[([^|]+\|)?([^\]]+)\]\]/g, '$2') //[[foo|bar]] -> bar
      .replace(/<.*?>/g, '').replace(/ /g,'_')
       // (we skip step "HTML entities are turned into their proper characters")
 return '#' + encodeURIComponent(txt).replace('%3A', ':').replace(/%([0-9A-F][0-9A-F])/g, '.$1')
 //maybe encodeURI(p1.replace(/\?/g,'%3F').replace(/&/g,'%26'))  ?
}


function getTitleFromURL(url){
 var tt = /title=([^&>"]+)/.exec(url) //"
 if( tt ) return decodeURIComponent(tt[1]).replace(/_/g,' ')
 else return ''
}


function tableOnclick(e){
 var trg = e.target
 if( trg.className ) switch ( trg.className ){
  //case 'diff-lineno': changeBlock(trg.parentNode); return
  //case 'diff-otitle': case 'diff-ntitle': return
  case 'diff-addedline': case 'diff-deletedline': case 'diff-context':
   //if( trg.nodeName != 'TD' || $(trg).parents('table.diff').length == 0 ) break 
   //parse wikicode in already improved row when clicked on white border above row
   var orig = $(trg).data('origHTML')
   if( orig ) $(trg).html(orig).data('origHTML','')
   else improveCell(trg)
   return
 }
}


function changeBlockXXX(row){ //switch improvement level for this part of diff table
 var dTbody = row.parentNode, rowIdx = 1, isImproved
 //find clicked row number
 while (rowIdx < dTbody.rows.length && dTbody.rows[rowIdx] != row) rowIdx++
 if (dTbody.rows[rowIdx] != row) return
 //check if rows are improved or not
 if (row.className == 'df-lineno'){
   isImproved = true
   var origTable = createTableFromHTML(requestedPages[dTbody.parentNode.parentNode.dfURL])
 }else
   dfImprovementSheet.disabled = false
 //improve / de-improve rows
 do{
   if (isImproved) dTbody.replaceChild(origTable.rows[rowIdx].cloneNode(true), row)
   else improveRow(row)
 }while((row=dTbody.rows[++rowIdx]) && row.cells[0].className != 'diff-lineno')

}















// *** JS Diff Engine ***
function diffJSEngine(e){
 var $tbl = e.data.dTable
 var htm = $tbl.data('origHTML')
 
 //call and run JS diff engine
 if( window.WDiffString ) diffJSGo()
 else importScriptAndRun('http://en.wikipedia.org/w/index.php?title=User:Cacycle/diff.js', diffJSGo)

 function diffJSGo(){
  cursorWait(true)
  //var
  var oldVer = '', newVer = '', txt, marker
  $tbl.find('td').each(function(){
    txt = $(this).data('origHTML') || this.innerHTML
    txt = txt.replace(/<.+?>/g,'') + '\n'
    switch( this.className ){
    case 'diff-context':
      marker = '\x03' + txt + '\x04\n'
      oldVer += marker
      newVer += marker
      i += 2 //skip other context cell
      break
    case 'diff-lineno':
      //oldVer += '\n\n\n\n\n\n'
      //newVer += '\n\n\n\n\n\n'
      break  // !!!
    case 'diff-deletedline':
      oldVer += txt
      break
    case 'diff-addedline':
      newVer += txt
      break
    }
  })
  //compare and display
  txt = WDiffString(oldVer, newVer)
  //txt = txt.replace(/\x03.*?\x04/g, '<br><br><hr><br><br>')
  txt = txt.replace(/\x03|\x04/g, '')
  txt = WDiffShortenOutput(txt)
  //txt = txt.replace(/¶/g,'<br>')
  txt = txt.replace(/&amp;/g,'&')
  //txt = txt.replace(/&lt;/g,'<').replace(/&gt;/g, '>')
  $('<div style="padding:2px"><br><br><h3>JS Engine diff</h3><hr style="height:5px" />'
     + txt + '</div>')
   .insertAfter($tbl)[0].scrollIntoView()  
  cursorWait()
 }


}



function importScriptAndRun(url, func) {
 var s = document.createElement('script')
 s.type = 'text/javascript'
 s.src = url + '&action=raw&ctype=text/javascript'
 if( $.client.profile().name == 'msie') 
   s.onreadystatechange = function(){ if( /loaded|complete/.test(this.readyState) ) func() }
 else 
   s.onload = func
 document.getElementsByTagName('head')[0].appendChild(s)
}





})