Jump to content

User:Js/diffs.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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)
}





})