Changeset 540 for branches/mokhet


Ignore:
Timestamp:
08/04/06 18:10:34 (13 years ago)
Author:
mokhet
Message:

commented references to HTMLArea.freeLater, and comment the content of HTMLAre.free. But keeping them in code to prevent plugins to crash while testing the update.
introduce HTMLArea.getEditor() to obtain an editor reference from the textarea.id, the textarea.name or the textarea HTMLElement reference
remove original event system to find all the needed updates

File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/mokhet/htmlarea.js

    r539 r540  
    7373// browser identification 
    7474HTMLArea.agt       = navigator.userAgent.toLowerCase(); 
    75 HTMLArea.is_ie     = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); 
     75//HTMLArea.is_ie           = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); 
     76// it is better to use a conditional comment to detect IE instead of relying on the useragent which can not be trusted 
     77HTMLArea.is_ie = false; 
     78/*@cc_on HTMLArea.is_ie = true; @*/ 
    7679HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1); 
    7780HTMLArea.is_mac    = (HTMLArea.agt.indexOf("mac") != -1); 
     
    199202      panels[i].div = panels[i].container; // legacy 
    200203      panels[i].container.className = 'panels ' + i; 
    201       HTMLArea.freeLater(panels[i], 'container'); 
    202       HTMLArea.freeLater(panels[i], 'div'); 
     204//      HTMLArea.freeLater(panels[i], 'container'); 
     205//      HTMLArea.freeLater(panels[i], 'div'); 
    203206    } 
    204207    // finally store the variable 
    205208    this._panels = panels; 
    206209 
    207     HTMLArea.freeLater(this, '_textArea'); 
     210//    HTMLArea.freeLater(this, '_textArea'); 
    208211  } 
    209212} 
     
    871874  toolbar.unselectable = "1"; 
    872875 
    873   HTMLArea.freeLater(this, '_toolBar'); 
    874   HTMLArea.freeLater(this, '_toolbar'); 
     876//  HTMLArea.freeLater(this, '_toolBar'); 
     877//  HTMLArea.freeLater(this, '_toolbar'); 
    875878   
    876879  var tb_row = null; 
     
    10611064      }; 
    10621065       
    1063       HTMLArea.freeLater(obj); 
     1066//      HTMLArea.freeLater(obj); 
    10641067       
    10651068      tb_objects[txt] = obj; 
     
    11201123        }; 
    11211124       
    1122         HTMLArea.freeLater(obj); 
     1125//        HTMLArea.freeLater(obj); 
    11231126       
    11241127        tb_objects[txt] = obj; 
     
    11491152        context : btn[4] || null // enabled in a certain context? 
    11501153      }; 
    1151       HTMLArea.freeLater(el); 
    1152       HTMLArea.freeLater(obj); 
     1154//      HTMLArea.freeLater(el); 
     1155//      HTMLArea.freeLater(obj); 
    11531156 
    11541157      tb_objects[txt] = obj; 
     
    12701273  { 
    12711274    doc._htmlareaImgCache = {}; 
    1272     HTMLArea.freeLater(doc._htmlareaImgCache); 
     1275//    HTMLArea.freeLater(doc._htmlareaImgCache); 
    12731276  } 
    12741277 
     
    13401343  statusbar.className = "statusBar"; 
    13411344  this._statusBar = statusbar; 
    1342   HTMLArea.freeLater(this, '_statusBar'); 
     1345//  HTMLArea.freeLater(this, '_statusBar'); 
    13431346   
    13441347  // statusbar.appendChild(document.createTextNode(HTMLArea._lc("Path") + ": ")); 
     
    13481351  div.innerHTML = HTMLArea._lc("Path") + ": "; 
    13491352  this._statusBarTree = div; 
    1350   HTMLArea.freeLater(this, '_statusBarTree'); 
     1353//  HTMLArea.freeLater(this, '_statusBarTree'); 
    13511354  this._statusBar.appendChild(div); 
    13521355 
     
    13551358  div.style.display = "none"; 
    13561359  this._statusBarTextMode = div; 
    1357   HTMLArea.freeLater(this, '_statusBarTextMode'); 
     1360//  HTMLArea.freeLater(this, '_statusBarTextMode'); 
    13581361  this._statusBar.appendChild(div); 
    13591362 
     
    14731476 
    14741477  }; 
    1475   HTMLArea.freeLater(this._framework); 
     1478//  HTMLArea.freeLater(this._framework); 
    14761479   
    14771480  var fw = this._framework; 
     
    15201523  var htmlarea = this._framework.table; 
    15211524  this._htmlArea = htmlarea; 
    1522   HTMLArea.freeLater(this, '_htmlArea'); 
     1525//  HTMLArea.freeLater(this, '_htmlArea'); 
    15231526  htmlarea.className = "htmlarea"; 
    15241527 
     
    15321535  this._iframe = iframe; 
    15331536  this._iframe.className = 'xinha_iframe'; 
    1534   HTMLArea.freeLater(this, '_iframe'); 
     1537//  HTMLArea.freeLater(this, '_iframe'); 
    15351538   
    15361539    // creates & appends the status bar 
     
    20792082  } 
    20802083   
    2081   HTMLArea.freeLater(this, '_doc'); 
     2084//  HTMLArea.freeLater(this, '_doc'); 
    20822085   
    20832086  doc.open(); 
     
    28912894    if ( this.config.statusBar && !noStatus ) 
    28922895    { 
    2893       this.statusBarDispose(); 
     2896      this.statusBarDispose(true); 
    28942897      for ( var i = ancestors.length; --i >= 0; ) 
    28952898      { 
     
    31563159}; 
    31573160 
    3158 /** 
    3159  * Dispose the elements in the statusbar 
    3160  * @private 
    3161  */ 
    3162 HTMLArea.prototype.statusBarDispose = function() 
    3163 { 
    3164   for ( var i = 0, m = this._statusElements.length; i < m; i++ ) 
    3165   { 
    3166     var a = this._statusElements[i]; 
    3167     HTMLArea.Events.remove(a, 'click', HTMLArea.onclick_status_updateToolbar); 
    3168     HTMLArea.Events.remove(a, 'contextmenu', HTMLArea.oncontextmenu_status); 
    3169   } 
    3170   this._statusElements = []; 
    3171   this._statusBarTree.innerHTML = HTMLArea._lc("Path") + ": "; // clear 
    3172 }; 
    31733161/** Returns a node after which we can insert other nodes, in the current 
    31743162 * selection.  The selection is removed.  It splits a text node, if needed. 
     
    52465234} 
    52475235 
    5248 // event handling 
    5249  
    5250 /** Event Flushing 
    5251  *  To try and work around memory leaks in the rather broken 
    5252  *  garbage collector in IE, HTMLArea.flushEvents can be called 
    5253  *  onunload, it will remove any event listeners (that were added 
    5254  *  through _addEvent(s)) and clear any DOM-0 events. 
    5255  */ 
    5256 HTMLArea._eventFlushers = []; 
    5257 HTMLArea.flushEvents = function() 
    5258 { 
    5259   var x = 0; 
    5260   // @todo : check if Array.prototype.pop exists for every supported browsers 
    5261   var e = HTMLArea._eventFlushers.pop(); 
    5262   while ( e ) 
    5263   { 
    5264     try 
    5265     { 
    5266       if ( e.length == 3 ) 
    5267       { 
    5268         HTMLArea._removeEvent(e[0], e[1], e[2]); 
    5269         x++; 
    5270       } 
    5271       else if ( e.length == 2 ) 
    5272       { 
    5273         e[0]['on' + e[1]] = null; 
    5274         e[0]._xinha_dom0Events[e[1]] = null; 
    5275         x++; 
    5276       } 
    5277     } 
    5278     catch(ex) 
    5279     { 
    5280       // Do Nothing 
    5281     } 
    5282     e = HTMLArea._eventFlushers.pop(); 
    5283   } 
    5284    
    5285   /*  
    5286     // This code is very agressive, and incredibly slow in IE, so I've disabled it. 
    5287      
    5288     if(document.all) 
    5289     { 
    5290       for(var i = 0; i < document.all.length; i++) 
    5291       { 
    5292         for(var j in document.all[i]) 
    5293         { 
    5294           if(/^on/.test(j) && typeof document.all[i][j] == 'function') 
    5295           { 
    5296             document.all[i][j] = null; 
    5297             x++; 
    5298           } 
    5299         } 
    5300       } 
    5301     } 
    5302   */ 
    5303    
    5304   // alert('Flushed ' + x + ' events.'); 
    5305 }; 
    5306  
    5307 if ( document.addEventListener ) 
    5308 { 
    5309   HTMLArea._addEvent = function(el, evname, func) 
    5310   { 
    5311     el.addEventListener(evname, func, true); 
    5312     HTMLArea._eventFlushers.push([el, evname, func]); 
    5313   }; 
    5314   HTMLArea._removeEvent = function(el, evname, func) 
    5315   { 
    5316     el.removeEventListener(evname, func, true); 
    5317   }; 
    5318   HTMLArea._stopEvent = function(ev) 
    5319   { 
    5320     ev.preventDefault(); 
    5321     ev.stopPropagation(); 
    5322   }; 
    5323 } 
    5324 else if ( document.attachEvent ) 
    5325 { 
    5326   HTMLArea._addEvent = function(el, evname, func) 
    5327   { 
    5328     el.attachEvent("on" + evname, func); 
    5329     HTMLArea._eventFlushers.push([el, evname, func]); 
    5330   }; 
    5331   HTMLArea._removeEvent = function(el, evname, func) 
    5332   { 
    5333     el.detachEvent("on" + evname, func); 
    5334   }; 
    5335   HTMLArea._stopEvent = function(ev) 
    5336   { 
    5337     try 
    5338     { 
    5339       ev.cancelBubble = true; 
    5340       ev.returnValue = false; 
    5341     } 
    5342     catch (ex) 
    5343     { 
    5344       // Perhaps we could try here to stop the window.event 
    5345       // window.event.cancelBubble = true; 
    5346       // window.event.returnValue = false; 
    5347     } 
    5348   }; 
    5349 } 
    5350 else 
    5351 { 
    5352   HTMLArea._addEvent = function(el, evname, func) 
    5353   { 
    5354     alert('_addEvent is not supported'); 
    5355   }; 
    5356   HTMLArea._removeEvent = function(el, evname, func) 
    5357   { 
    5358     alert('_removeEvent is not supported'); 
    5359   }; 
    5360   HTMLArea._stopEvent = function(ev) 
    5361   { 
    5362     alert('_stopEvent is not supported'); 
    5363   }; 
    5364 } 
    5365  
    5366 HTMLArea._addEvents = function(el, evs, func) 
    5367 { 
    5368   for ( var i = evs.length; --i >= 0; ) 
    5369   { 
    5370     HTMLArea._addEvent(el, evs[i], func); 
    5371   } 
    5372 }; 
    5373  
    5374 HTMLArea._removeEvents = function(el, evs, func) 
    5375 { 
    5376   for ( var i = evs.length; --i >= 0; ) 
    5377   { 
    5378     HTMLArea._removeEvent(el, evs[i], func); 
    5379   } 
    5380 }; 
    5381  
    5382 /** 
    5383  * Adds a standard "DOM-0" event listener to an element. 
    5384  * The DOM-0 events are those applied directly as attributes to 
    5385  * an element - eg element.onclick = stuff; 
    5386  * 
    5387  * By using this function instead of simply overwriting any existing 
    5388  * DOM-0 event by the same name on the element it will trigger as well 
    5389  * as the existing ones.  Handlers are triggered one after the other 
    5390  * in the order they are added. 
    5391  * 
    5392  * Remember to return true/false from your handler, this will determine 
    5393  * whether subsequent handlers will be triggered (ie that the event will 
    5394  * continue or be canceled). 
    5395  * 
    5396  */ 
    5397  
    5398 HTMLArea.addDom0Event = function(el, ev, fn) 
    5399 { 
    5400   HTMLArea._prepareForDom0Events(el, ev); 
    5401   el._xinha_dom0Events[ev].unshift(fn); 
    5402 }; 
    5403  
    5404  
    5405 /** 
    5406  * See addDom0Event, the difference is that handlers registered using 
    5407  * prependDom0Event will be triggered before existing DOM-0 events of the 
    5408  * same name on the same element. 
    5409  */ 
    5410  
    5411 HTMLArea.prependDom0Event = function(el, ev, fn) 
    5412 { 
    5413   HTMLArea._prepareForDom0Events(el, ev); 
    5414   el._xinha_dom0Events[ev].push(fn); 
    5415 }; 
    5416  
    5417 /** 
    5418  * Prepares an element to receive more than one DOM-0 event handler 
    5419  * when handlers are added via addDom0Event and prependDom0Event. 
    5420  */ 
    5421 HTMLArea._prepareForDom0Events = function(el, ev) 
    5422 { 
    5423   // Create a structure to hold our lists of event handlers 
    5424   if ( typeof el._xinha_dom0Events == 'undefined' ) 
    5425   { 
    5426     el._xinha_dom0Events = {}; 
    5427     HTMLArea.freeLater(el, '_xinha_dom0Events'); 
    5428   } 
    5429  
    5430   // Create a list of handlers for this event type 
    5431   if ( typeof el._xinha_dom0Events[ev] == 'undefined' ) 
    5432   { 
    5433     el._xinha_dom0Events[ev] = [ ]; 
    5434     if ( typeof el['on'+ev] == 'function' ) 
    5435     { 
    5436       el._xinha_dom0Events[ev].push(el['on'+ev]); 
    5437     } 
    5438  
    5439     // Make the actual event handler, which runs through 
    5440     // each of the handlers in the list and executes them 
    5441     // in the correct context. 
    5442     el['on'+ev] = function(event) 
    5443     { 
    5444       var a = el._xinha_dom0Events[ev]; 
    5445       // call previous submit methods if they were there. 
    5446       var allOK = true; 
    5447       for ( var i = a.length; --i >= 0; ) 
    5448       { 
    5449         // We want the handler to be a member of the form, not the array, so that "this" will work correctly 
    5450         el._xinha_tempEventHandler = a[i]; 
    5451         if ( el._xinha_tempEventHandler(event) === false ) 
    5452         { 
    5453           el._xinha_tempEventHandler = null; 
    5454           allOK = false; 
    5455           break; 
    5456         } 
    5457         el._xinha_tempEventHandler = null; 
    5458       } 
    5459       return allOK; 
    5460     }; 
    5461  
    5462     HTMLArea._eventFlushers.push([el, ev]); 
    5463   } 
    5464 }; 
     5236/* 
     5237--------------------------------------------------------------------------- 
     5238  NOTIFIERS 
     5239--------------------------------------------------------------------------- 
     5240*/ 
    54655241 
    54665242HTMLArea.prototype.notifyOn = function(ev, fn) 
     
    54695245  { 
    54705246    this._notifyListeners[ev] = []; 
    5471     HTMLArea.freeLater(this, '_notifyListeners'); 
     5247//    HTMLArea.freeLater(this, '_notifyListeners'); 
    54725248  } 
    54735249  this._notifyListeners[ev].push(fn); 
     
    54855261}; 
    54865262 
     5263/* 
     5264--------------------------------------------------------------------------- 
     5265  CLASSNAME MANIPULATION 
     5266--------------------------------------------------------------------------- 
     5267*/ 
    54875268HTMLArea._removeClass = function(el, className) 
    54885269{ 
     
    55265307  return false; 
    55275308}; 
     5309 
     5310/* 
     5311--------------------------------------------------------------------------- 
     5312 
     5313--------------------------------------------------------------------------- 
     5314*/ 
    55285315 
    55295316HTMLArea._blockTags = " body form textarea fieldset ul ol dl li div " + 
     
    66176404HTMLArea.freeLater = function(obj,prop) 
    66186405{ 
    6619   HTMLArea.toFree.push({o:obj,p:prop}); 
     6406//  HTMLArea.toFree.push({o:obj,p:prop}); 
    66206407}; 
    66216408 
     
    66286415HTMLArea.free = function(O, P) 
    66296416{ 
     6417/* 
    66306418  if ( O && !P ) 
    66316419  { 
     
    66526440    } catch(x) {} 
    66536441  } 
    6654 }; 
    6655  
    6656 /** IE's Garbage Collector is broken very badly.  We will do our best to  
    6657  *   do it's job for it, but we can't be perfect. 
    6658  */ 
    6659  
    6660 HTMLArea.collectGarbageForIE = function()  
    6661  
    6662   HTMLArea.flushEvents();    
    6663   for ( var x = 0; x < HTMLArea.toFree.length; x++ ) 
    6664   { 
    6665     if ( !HTMLArea.toFree[x].o ) 
    6666     { 
    6667       alert("What is " + x + ' ' + HTMLArea.toFree[x].o); 
    6668     } 
    6669     HTMLArea.free(HTMLArea.toFree[x].o, HTMLArea.toFree[x].p); 
    6670     HTMLArea.toFree[x].o = null; 
    6671   } 
     6442*/ 
    66726443}; 
    66736444 
     
    70076778    var L, i, m; 
    70086779 
    7009     // remove our DOM0 handlers 
     6780    // remove remaining DOM0 handlers 
    70106781    for ( i = 0, m = HTMLArea.Events.DOM0Handlers.length; i < m; i++ ) 
    70116782    { 
     
    70296800      } 
    70306801    } 
    7031  
    7032     for ( i = __htmlareas.length; i--; ) 
    7033     { 
    7034       // this should be in every Xinha instance disposer method instead of here 
    7035       __htmlareas[i].statusBarDispose(); 
    7036       // we should instead do this 
    7037       //__htmlareas[i].dispose(); 
    7038     } 
    7039  
    7040     // garbage IE 
    7041     HTMLArea.collectGarbageForIE(); 
    70426802  } 
    70436803 
     
    72867046 
    72877047 
     7048/* 
     7049--------------------------------------------------------------------------- 
     7050  HELPERS METHODS 
     7051--------------------------------------------------------------------------- 
     7052*/ 
     7053 
     7054/** 
     7055 * Get the Xinha reference from the id, the name or the HTMLElement reference of the textarea 
     7056 * @param {string|HTMLElement} ref The id, the name or the HTMLElement reference of the textarea 
     7057 * @return {object} Return the Xinha reference or null if none could be found 
     7058 * @public 
     7059 */ 
     7060HTMLArea.getEditor = function(ref) 
     7061{ 
     7062  for ( var i = __htmlareas.length; i--; ) 
     7063  { 
     7064    var editor = __htmlareas[i]; 
     7065    if ( editor && ( editor._textArea.id == ref || editor._textArea.name == ref || editor._textArea == ref ) ) 
     7066    { 
     7067      return editor; 
     7068    } 
     7069  } 
     7070  return null; 
     7071}; 
     7072 
     7073/* 
     7074--------------------------------------------------------------------------- 
     7075  DISPOSERS 
     7076--------------------------------------------------------------------------- 
     7077*/ 
     7078 
     7079/** 
     7080 * Specific disposer, similar to _onGenerate. 
     7081 * But instead of testing if the function exist before calling it,  
     7082 * we create an empty one and then the user can surcharge it 
     7083 * @public 
     7084 */ 
     7085HTMLArea.prototype._onDispose = function(){}; 
     7086 
     7087/** 
     7088 * Dispose the editor UI and bring back the textarea 
     7089 * @public 
     7090 */ 
     7091HTMLArea.prototype.dispose = function() 
     7092{ 
     7093  var i, parentNode, Element; 
     7094 
     7095  // specific disposer set by the user 
     7096  this._onDispose(); 
     7097   
     7098  // call the plugins disposer 
     7099  for ( i in this.plugins ) 
     7100  { 
     7101    var plugin = this.plugins[i].instance; 
     7102    if ( plugin && typeof plugin.dispose == "function" ) 
     7103    { 
     7104      plugin.dispose(); 
     7105    } 
     7106  } 
     7107   
     7108  // remove the events 
     7109  HTMLArea.Events.remove(this._doc, 'mousedown', this.activateEditor); 
     7110  HTMLArea.Events.remove(this._doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], HTMLArea.keymousedrag_doc); 
     7111  HTMLArea.Events.remove(window, 'resize', this.sizeEditor); 
     7112  if ( this._textArea.form ) 
     7113  { 
     7114    HTMLArea.Events.remove(this._textArea.form, 'submit', HTMLArea.onsubmit_form); 
     7115    HTMLArea.Events.remove(this._textArea.form, 'reset', HTMLArea.onreset_form); 
     7116  } 
     7117  HTMLArea.Events.remove(window, 'unload', HTMLArea.onunload_backforward); 
     7118  HTMLArea.Events.remove(this._iframe, 'load', HTMLArea.onload_iframe); 
     7119 
     7120  // dispose the statusbar 
     7121  this.statusBarDispose(false); 
     7122   
     7123  // remove the panels elements 
     7124  for ( i in this._panels ) 
     7125  { 
     7126    // since they are TD cells, we must show them or they will leak in IE 
     7127    // but i cant remember where i have found that, so until i find more information, this part is commented out 
     7128/* 
     7129    if ( HTMLArea.is_ie ) 
     7130    { 
     7131      this._panels[i].container.style.display = ''; 
     7132    } 
     7133*/ 
     7134    this._panels[i].container = null; 
     7135    this._panels[i].div = null; 
     7136    this._panels[i] = null; 
     7137  } 
     7138 
     7139  // remove the iframe 
     7140  this._iframe.parentNode.removeChild(this._iframe); 
     7141  this._iframe = null; 
     7142 
     7143  // reinsert the textarea in it's original parent 
     7144  HTMLArea.removeFromParent(this._textArea); 
     7145  this._framework.table.parentNode.insertBefore(this._textArea, this._framework.table); 
     7146 
     7147  // remove the framework 
     7148/* 
     7149  for ( i in this._framework ) 
     7150  { 
     7151    Element = this._framework[i]; 
     7152    parentNode = Element ? Element.parentNode : null; 
     7153    if ( Element && parentNode ) 
     7154    { 
     7155      parentNode.removeChild(Element); 
     7156    } 
     7157    this._framework[i] = null; 
     7158  } 
     7159*/ 
     7160  this._framework.table.parentNode.removeChild(this._framework.table); 
     7161  this._framework = null; 
     7162 
     7163  // remove the reference to the document 
     7164  this._mdoc = null; 
     7165 
     7166  // remove the reference to the HTMLElement textarea 
     7167  // if the original size failed, we fall off to 100px 
     7168  try { this._textArea.style.width = this._initial_ta_size.w; } catch(x) { this._textArea.style.width = '100px'; } 
     7169  try { this._textArea.style.height = this._initial_ta_size.h; } catch(x) { this._textArea.style.height = '100px'; } 
     7170  this._textArea.style.display = ''; 
     7171 
     7172  this._textArea = null; 
     7173 
     7174  // remove reference in the global array 
     7175  __htmlareas[this.__htmlarea_id_num] = null; 
     7176}; 
     7177 
     7178/** 
     7179 * Dispose the elements in the statusbar 
     7180 * @param {boolean} showPath true if "Path: " must be show, false if the content must be emptied 
     7181 * @private 
     7182 */ 
     7183HTMLArea.prototype.statusBarDispose = function(showPath) 
     7184{ 
     7185  for ( var i = this._statusElements.length; i--; ) 
     7186  { 
     7187    var a = this._statusElements[i]; 
     7188    HTMLArea.Events.remove(a, 'click', HTMLArea.onclick_status_updateToolbar); 
     7189    HTMLArea.Events.remove(a, 'contextmenu', HTMLArea.oncontextmenu_status); 
     7190  } 
     7191  this._statusElements = []; 
     7192  this._statusBarTree.innerHTML = showPath ? HTMLArea._lc("Path") + ": " : ''; // clear 
     7193}; 
     7194 
     7195/** 
     7196 * Generic disposer called onunload. 
     7197 * @private 
     7198 */ 
     7199HTMLArea.dispose = function() 
     7200{ 
     7201  // Remove every Xinha instances 
     7202  for ( var i = __htmlareas.length; i--; ) 
     7203  { 
     7204    if ( __htmlareas[i] ) 
     7205    { 
     7206      __htmlareas[i].dispose(); 
     7207    } 
     7208  } 
     7209  // call the event flusher 
     7210  HTMLArea.Events.flusher(); 
     7211}; 
    72887212 
    72897213/* 
     
    72937217*/ 
    72947218HTMLArea.init(); 
    7295 HTMLArea.Events.add(window,'unload',HTMLArea.Events.flusher); 
     7219HTMLArea.Events.add(window,'unload',HTMLArea.dispose); 
Note: See TracChangeset for help on using the changeset viewer.