1. Benoit Chesneau
  2. Pypaste

Commits

benoitc  committed d64b7b3

- started interface for revisions

  • Participants
  • Parent commits 8de2346
  • Branches default

Comments (0)

Files changed (12)

File friendpaste/application.py

View file
 from friendpaste.urls import map, all_views
 from friendpaste.utils import local, local_manager
 from friendpaste.template import TemplateLoader, do_highlight, \
-        do_timesince, ALL_COLORSHEME
+        do_timesince, ALL_COLORSHEME, datetimeformat
 from friendpaste.http import Request
 
 
         env.template_charset = 'utf-8'
         env.filters['highlight']=do_highlight
         env.filters['timesince']=do_timesince
+        env.filters['datetimeformat']=datetimeformat
         env.globals['ALL_COLORSHEME'] = ALL_COLORSHEME 
         self.template_env = env
         

File friendpaste/paste/models.py

View file
 from datetime import datetime
 import binascii
 import base64
-import sha
+
+# compatibility with python 2.4
+try:
+    from hashlib import sha1 as _sha
+except:
+    import sha
+    _sha = sha.new
+
 
 from couchdb.client import ResourceNotFound
 from couchdb.schema import *
 from friendpaste.utils import local
 
 hex = binascii.hexlify
-_sha = sha.new
 
 def hash(text, p):
     """generate a hash from the given text and its parent hashes
 
     This hash combines both the current file contents and its history
-    in a manner that makes it easy to distinguish nodes with the same
-    content in the revision graph.
     """
     s = _sha(p)
-    s.update(text)
+    s.update(text.encode('utf-8'))
     return s.digest()
 
 def short(node):

File friendpaste/template.py

View file
         return timesince(value)
     return wrapped
 
+def datetimeformat():
+    """
+    datetime filter for the template
+    """
+    def wrapped(env, ctx, value):
+        value=datetime.strptime(value,'%Y-%m-%dT%H:%M:%SZ')
+        return value.strftime('%c')
+    return wrapped
+
+
+
 ALL_COLORSHEME = list(get_all_styles())

File static/css/base.css

View file
 	margin: 0;
 	padding: 0;
 	background: #e5f1f4 url(../images/bg.png) repeat-x;
-	/*font-family:Verdana,Corbel,"Bitstream Vera Sans",sans-serif;*/
 	font-family: Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif;
     color: #000;
     height:100%;
 
 ol, ul { list-style-image:none; list-style-position:outside; list-style-type:none; }
 
-#column { margin-top: 0; padding-top: 0; }
+.column { margin-top: 0; padding-top: 0; }
 .container { margin: 0pt auto; width: 950px;}
 
 
 
 /* content */
 .content-wrapper { background: transparent url(../images/home-back.gif) no-repeat 100px 0pt; width: 100%; margin: 0; padding: 0;  }
-#content { padding: 20px 0px 0 0; }
+#content { padding: 25px 0pt; }
 
 /* paste form */
-#snippet-edit { padding-top: 10px; }
-a.e { display: block; width: 60px; float: left; text-decoration: none; }
+#snippet-edit,#snippet,#revisions { padding-top: 15px; }
+
 form, #snippet { width: 90%; margin: 0pt auto; }
 form li { padding-top: 5px; clear: both; overflow:hidden;}
 form li * { vertical-align: middle }
 input[type='submit'], input[type='reset'], input[type='button'] { background: #303030; color: #ccc; border-color: #899834; border-width: 0 1px 1px 0; font-size: 1em;  padding: 0.3em 0.5em 0.3em 0.5em;  font-weight: bold; }
 }
 
-
-
-
-
-
 /* paste detail */ 
-#snippet { padding-top: 15px; }
 #snippet h2 { padding-bottom: 15px; }
 
 #actions { display: block; background: #f7f7f0; width: 99%; color: #666; font-weight: bold; font-size: 0.8em; padding: 0.3em; height: 1.4em; }
 }
 
 table.sourcetable {
-        empty-cells:show;
+    empty-cells:show;
     font-size:12px;
     line-height:130%;
     margin:0pt auto;
     width:100%;
 }
 
+#bottoma { position: relative; width: 100%; clear: both; padding: 15pt 0; }
+#bottom form { width: auto; margin: 0pt; }
+#bottoma ul { list-style: none; position: relative; }
+#bottoma li { display: block; float: left; width: 100px; }
 
+table.sourcetable th { background: #f7f7f0; border-bottom:1px solid #899834; border-right:1px solid #899834; font-size:1em; }
+table.sourcetable th.linenos { vertical-align: top; width: 1.3em;  }
 
-table.sourcetable th {
-    background: #f7f7f0;
-    border-bottom:1px solid #899834;
-    border-right:1px solid #899834;
-    font-size:1em;
-}
-table.sourcetable th.linenos {
-    vertical-align: top;
-    width: 1.3em;  
-}
-
+#content #revisions { position: relative; width: 90%; margin: 0pt auto; }
+#content table.revisionstable { position: relative; width: auto; margin: 0; padding: 0;}
+#revisions form { margin: 0pt; padding: 0pt; }

File static/css/trac.css

View file
+.highlight  { background: #ffffff; }
+.highlight .c { color: #999988; font-style: italic } /* Comment */
+.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.highlight .k { font-weight: bold } /* Keyword */
+.highlight .o { font-weight: bold } /* Operator */
+.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
+.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #aa0000 } /* Generic.Error */
+.highlight .gh { color: #999999 } /* Generic.Heading */
+.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.highlight .go { color: #888888 } /* Generic.Output */
+.highlight .gp { color: #555555 } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #aaaaaa } /* Generic.Subheading */
+.highlight .gt { color: #aa0000 } /* Generic.Traceback */
+.highlight .kc { font-weight: bold } /* Keyword.Constant */
+.highlight .kd { font-weight: bold } /* Keyword.Declaration */
+.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
+.highlight .kr { font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #009999 } /* Literal.Number */
+.highlight .s { color: #bb8844 } /* Literal.String */
+.highlight .na { color: #008080 } /* Name.Attribute */
+.highlight .nb { color: #999999 } /* Name.Builtin */
+.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
+.highlight .no { color: #008080 } /* Name.Constant */
+.highlight .ni { color: #800080 } /* Name.Entity */
+.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
+.highlight .nn { color: #555555 } /* Name.Namespace */
+.highlight .nt { color: #000080 } /* Name.Tag */
+.highlight .nv { color: #008080 } /* Name.Variable */
+.highlight .ow { font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #009999 } /* Literal.Number.Float */
+.highlight .mh { color: #009999 } /* Literal.Number.Hex */
+.highlight .mi { color: #009999 } /* Literal.Number.Integer */
+.highlight .mo { color: #009999 } /* Literal.Number.Oct */
+.highlight .sb { color: #bb8844 } /* Literal.String.Backtick */
+.highlight .sc { color: #bb8844 } /* Literal.String.Char */
+.highlight .sd { color: #bb8844 } /* Literal.String.Doc */
+.highlight .s2 { color: #bb8844 } /* Literal.String.Double */
+.highlight .se { color: #bb8844 } /* Literal.String.Escape */
+.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */
+.highlight .si { color: #bb8844 } /* Literal.String.Interpol */
+.highlight .sx { color: #bb8844 } /* Literal.String.Other */
+.highlight .sr { color: #808000 } /* Literal.String.Regex */
+.highlight .s1 { color: #bb8844 } /* Literal.String.Single */
+.highlight .ss { color: #bb8844 } /* Literal.String.Symbol */
+.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #008080 } /* Name.Variable.Class */
+.highlight .vg { color: #008080 } /* Name.Variable.Global */
+.highlight .vi { color: #008080 } /* Name.Variable.Instance */
+.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */

File static/images/dialog-titlebar-close-hover.png

Added
New image

File static/images/dialog-titlebar-close.png

Added
New image

File static/js/friendpaste.js

View file
 
     init: function() {
         $("#snippet-edit").hide();
+        $("#revisions").hide();
 
         /* set handlers */
         $(".e").click(this.do_edit);
-        $("#cancel").click(this.do_cancel);
+        $(".cancel").click(this.do_cancel);
         $("#show-lineos").click(this.toggleLineNumber);
         $("#wrap-lines").click(this.wrapLines);
         $("#change-theme").change(this.setTheme);
+        $(".show-history").click(this.do_history);
 
         /* init default */
         if (getCookie("nowrap-lines")) { 
 
     do_cancel: function(event) {
         $("#snippet-edit").hide();
+        $("#revisions").hide();
         $("#snippet").show();
     },
     
     setTheme: function(event) {
         setCookie("theme", this.value, 60);
         document.location.reload();
+    },
+
+    do_history: function(event) {
+        $("#snippet").hide();
+        $("#revisions").show();
+
+        return false;
     }
 }

File static/js/jquery.dimensions.js

View file
+/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * $LastChangedDate: 2007-12-20 15:43:48 +0100 (Do, 20 Dez 2007) $
+ * $Rev: 4257 $
+ *
+ * Version: @VERSION
+ *
+ * Requires: jQuery 1.2+
+ */
+
+(function($){
+	
+$.dimensions = {
+	version: '@VERSION'
+};
+
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+$.each( [ 'Height', 'Width' ], function(i, name){
+	
+	// innerHeight and innerWidth
+	$.fn[ 'inner' + name ] = function() {
+		if (!this[0]) return;
+		
+		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
+		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
+		
+		return this.is(':visible') ? this[0]['client' + name] : num( this, name.toLowerCase() ) + num(this, 'padding' + torl) + num(this, 'padding' + borr);
+	};
+	
+	// outerHeight and outerWidth
+	$.fn[ 'outer' + name ] = function(options) {
+		if (!this[0]) return;
+		
+		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
+		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
+		
+		options = $.extend({ margin: false }, options || {});
+		
+		var val = this.is(':visible') ? 
+				this[0]['offset' + name] : 
+				num( this, name.toLowerCase() )
+					+ num(this, 'border' + torl + 'Width') + num(this, 'border' + borr + 'Width')
+					+ num(this, 'padding' + torl) + num(this, 'padding' + borr);
+		
+		return val + (options.margin ? (num(this, 'margin' + torl) + num(this, 'margin' + borr)) : 0);
+	};
+});
+
+// Create scrollLeft and scrollTop methods
+$.each( ['Left', 'Top'], function(i, name) {
+	$.fn[ 'scroll' + name ] = function(val) {
+		if (!this[0]) return;
+		
+		return val != undefined ?
+		
+			// Set the scroll offset
+			this.each(function() {
+				this == window || this == document ?
+					window.scrollTo( 
+						name == 'Left' ? val : $(window)[ 'scrollLeft' ](),
+						name == 'Top'  ? val : $(window)[ 'scrollTop'  ]()
+					) :
+					this[ 'scroll' + name ] = val;
+			}) :
+			
+			// Return the scroll offset
+			this[0] == window || this[0] == document ?
+				self[ (name == 'Left' ? 'pageXOffset' : 'pageYOffset') ] ||
+					$.boxModel && document.documentElement[ 'scroll' + name ] ||
+					document.body[ 'scroll' + name ] :
+				this[0][ 'scroll' + name ];
+	};
+});
+
+$.fn.extend({
+	position: function() {
+		var left = 0, top = 0, elem = this[0], offset, parentOffset, offsetParent, results;
+		
+		if (elem) {
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+			
+			// Get correct offsets
+			offset       = this.offset();
+			parentOffset = offsetParent.offset();
+			
+			// Subtract element margins
+			offset.top  -= num(elem, 'marginTop');
+			offset.left -= num(elem, 'marginLeft');
+			
+			// Add offsetParent borders
+			parentOffset.top  += num(offsetParent, 'borderTopWidth');
+			parentOffset.left += num(offsetParent, 'borderLeftWidth');
+			
+			// Subtract the two offsets
+			results = {
+				top:  offset.top  - parentOffset.top,
+				left: offset.left - parentOffset.left
+			};
+		}
+		
+		return results;
+	},
+	
+	offsetParent: function() {
+		var offsetParent = this[0].offsetParent;
+		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && $.css(offsetParent, 'position') == 'static') )
+			offsetParent = offsetParent.offsetParent;
+		return $(offsetParent);
+	}
+});
+
+function num(el, prop) {
+	return parseInt($.curCSS(el.jquery?el[0]:el,prop,true))||0;
+};
+
+})(jQuery);

File static/js/ui.resizable.js

View file
+(function($) {
+  
+  $.fn.resizable = function(options) {
+    return this.each(function() {
+      var args = Array.prototype.slice.call(arguments, 1);
+      
+      if (typeof options == "string") {
+        var resize = $.data(this, "ui-resizable");
+        resize[options].apply(resize, args);
+
+      } else if(!$(this).is(".ui-resizable"))
+        new $.ui.resizable(this, options);
+        
+    });
+  };
+  
+  $.ui.resizable = function(element, options) {
+    //Initialize needed constants
+    var self = this;
+    
+    this.element = $(element);
+    
+    $.data(element, "ui-resizable", this);
+    this.element.addClass("ui-resizable");
+    
+    //Prepare the passed options
+    this.options = $.extend({
+      preventDefault: true,
+      transparent: false,
+      minWidth: 10,
+      minHeight: 10,
+      aspectRatio: false,
+      disableSelection: true,
+      preserveCursor: true,
+      autohide: false
+    }, options);
+    
+		this.options._aspectRatio = !!(this.options.aspectRatio);
+		
+    $(element).bind("setData.resizable", function(event, key, value){
+      self.options[key] = value;
+    }).bind("getData.resizable", function(event, key){
+      return self.options[key];
+    });
+    
+    var o = this.options;
+    
+    //Default Theme
+    var aBorder = '1px solid #DEDEDE';
+    
+    o.defaultTheme = {
+      'ui-resizable': { display: 'block' },
+      'ui-resizable-handle': { position: 'absolute', background: '#F5F5F5' },
+      'ui-resizable-n': { cursor: 'n-resize', height: '4px', left: '0px', right: '0px', borderTop: aBorder },
+      'ui-resizable-s': { cursor: 's-resize', height: '4px', left: '0px', right: '0px', borderBottom: aBorder },
+      'ui-resizable-e': { cursor: 'e-resize', width: '4px', top: '0px', bottom: '0px', borderRight: aBorder },
+      'ui-resizable-w': { cursor: 'w-resize', width: '4px', top: '0px', bottom: '0px', borderLeft: aBorder },
+      'ui-resizable-se': { cursor: 'se-resize', width: '4px', height: '4px', borderRight: aBorder, borderBottom: aBorder },
+      'ui-resizable-sw': { cursor: 'sw-resize', width: '4px', height: '4px', borderBottom: aBorder, borderLeft: aBorder },
+      'ui-resizable-ne': { cursor: 'ne-resize', width: '4px', height: '4px', borderRight: aBorder, borderTop: aBorder },
+      'ui-resizable-nw': { cursor: 'nw-resize', width: '4px', height: '4px', borderLeft: aBorder, borderTop: aBorder }
+    };
+    
+    //Position the node
+    if(!o.proxy && (this.element.css('position') == 'static' || this.element.css('position') == ''))
+      this.element.css('position', 'relative');
+    
+    var nodeName = element.nodeName;
+    
+    //Wrap the element if it cannot hold child nodes
+    if(nodeName.match(/textarea|input|select|button|img/i)) {
+      
+      //Create a wrapper element and set the wrapper to the new current internal element
+      this.element.wrap('<div class="ui-wrapper"  style="overflow: hidden; position: relative; width: '+this.element.outerWidth()+'px; height: '+this.element.outerHeight()+';"></div>');
+      var oel = this.element; element = element.parentNode; this.element = $(element);
+      
+      //Move margins to the wrapper
+      this.element.css({ marginLeft: oel.css("marginLeft"), marginTop: oel.css("marginTop"),
+        marginRight: oel.css("marginRight"), marginBottom: oel.css("marginBottom")
+      });
+      
+      oel.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
+      
+      //Prevent Safari textarea resize
+      if ($.browser.safari && o.preventDefault) oel.css('resize', 'none');
+      
+      o.proportionallyResize = oel.css('position', 'static');
+			
+			// fix handlers offset
+			this._proportionallyResize();
+    }
+    
+    if(!o.handles) o.handles = !$('.ui-resizable-handle', element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' };
+    if(o.handles.constructor == String) {
+
+      if(o.handles == 'all')
+        o.handles = 'n,e,s,w,se,sw,ne,nw';
+        
+      var n = o.handles.split(","); o.handles = {};
+      
+      o.zIndex = o.zIndex || 1000;
+      
+      var insertions = {
+				handle: 'overflow:hidden; position:absolute;',
+        n: 'top: 0pt; width:100%;',
+        e: 'right: 0pt; height:100%;',
+        s: 'bottom: 0pt; width:100%;',
+        w: 'left: 0pt; height:100%;',
+        se: 'bottom: 0pt; right: 0px;',
+        sw: 'bottom: 0pt; left: 0px;',
+        ne: 'top: 0pt; right: 0px;',
+        nw: 'top: 0pt; left: 0px;'
+      };
+      
+      for(var i = 0; i < n.length; i++) {
+        var d = jQuery.trim(n[i]), t = o.defaultTheme, hname = 'ui-resizable-'+d;
+        
+        var rcss = $.extend(t[hname], t['ui-resizable-handle']), 
+            axis = $(["<div class=\"",hname," ui-resizable-handle\" style=\"",insertions[d], insertions.handle,"\"></div>"].join("")).css(/sw|se|ne|nw/.test(d) ? { zIndex: ++o.zIndex } : {});
+        
+        o.handles[d] = '.ui-resizable-'+d;
+          
+        this.element.append(
+          //Theme detection, if not loaded, load o.defaultTheme
+          axis.css( !$.ui.css(hname) ? rcss : {} )
+        );
+      }
+    }
+    
+    this._renderAxis = function(target) {
+      target = target || this.element;
+      
+      for(var i in o.handles) {
+        if(o.handles[i].constructor == String) 
+          o.handles[i] = $(o.handles[i], element).show();
+        
+        if (o.transparent)
+          o.handles[i].css({opacity:0});
+        
+        //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
+        if (this.element.is('.ui-wrapper') && 
+          nodeName.match(/textarea|input|select|button/i)) {
+            
+          var axis = $(o.handles[i], element), padWrapper = 0;
+          
+          //Checking the correct pad and border
+          padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
+          
+          //The padding type i have to apply...
+          var padPos = [ 'padding', 
+            /ne|nw|n/.test(i) ? 'Top' :
+            /se|sw|s/.test(i) ? 'Bottom' : 
+            /^e$/.test(i) ? 'Right' : 'Left' ].join(""); 
+          
+          if (!o.transparent)
+            target.css(padPos, padWrapper);
+        }
+        if(!$(o.handles[i]).length) continue;
+      }
+    };
+    
+    this._renderAxis(this.element);
+    var handlers = $('.ui-resizable-handle', self.element);
+    
+    if (o.disableSelection)
+      handlers.each(function(i, e) { $.ui.disableSelection(e); });
+    
+    //Matching axis name
+    handlers.mouseover(function() {
+      if (!o.resizing) {
+        if (this.className) 
+          var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
+        //Axis, default = se
+        o.axis = axis && axis[1] ? axis[1] : 'se';
+      }
+    });
+        
+    //If we want to auto hide the elements
+    if (o.autohide) {
+      var tLoaded = $.ui.css('ui-resizable-s') || $.ui.css('ui-resizable-e');
+      if (!tLoaded) handlers.hide();
+      
+      $(self.element).addClass("ui-resizable-autohide").hover(function(){
+        if (!tLoaded) handlers.show();
+        $(this).removeClass("ui-resizable-autohide");
+      }, function(){
+        if (!o.resizing) {
+          if (!tLoaded) handlers.hide();
+          $(this).addClass("ui-resizable-autohide");
+        }
+      });
+    }
+  
+    //Initialize mouse events for interaction
+    this.element.mouseInteraction({
+      executor: this,
+      delay: 0,
+      distance: 0,
+      dragPrevention: ['input','textarea','button','select','option'],
+      start: this.start,
+      stop: this.stop,
+      drag: this.drag,
+      condition: function(e) {
+        if(this.disabled) return false;
+        for(var i in this.options.handles) {
+          if($(this.options.handles[i])[0] == e.target) return true;
+        }
+        return false;
+      }
+    });
+  };
+  
+  $.extend($.ui.resizable.prototype, {
+    plugins: {},
+    ui: function() {
+      return {
+        instance: this,
+        axis: this.options.axis,
+        options: this.options
+      };      
+    },
+    _proportionallyResize: function() {
+			var o = this.options;
+
+			if (!o.proportionallyResize)
+				return;
+			
+			var prel = o.proportionallyResize;
+			
+      var b = [ prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth') ];
+      var p = [ prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft') ];
+      
+      o.borderDif = o.borderDif || $.map(b, function(v, i) {
+        var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
+        return border + padding; 
+      });
+			
+      prel.css({
+        display: 'block', //Needed to fix height autoincrement
+        height: (this.element.height() - o.borderDif[0] - o.borderDif[2]) + "px",
+        width: (this.element.width() - o.borderDif[1] - o.borderDif[3]) + "px"
+      });
+    },
+    _renderProxy: function() {
+      var el = this.element, o = this.options;
+      this.offset = el.offset();
+      
+      if(o.proxy) {
+        this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
+				
+				// fix ie6 offset
+				var ie6offset = ($.browser.msie && $.browser.version < 7 ? 3 : 0);
+				
+        this.helper.addClass(o.proxy).css({
+          width: el.outerWidth(),
+          height: el.outerHeight(),
+          position: 'absolute',
+          left: this.offset.left - ie6offset +'px',
+          top: this.offset.top - ie6offset +'px',
+          zIndex: ++o.zIndex
+        });
+        
+        this.helper.appendTo("body");
+        
+        if (o.disableSelection)
+          $.ui.disableSelection(this.helper.get(0));
+            
+      } else {
+        this.helper = el; 
+      }
+    },
+    propagate: function(n,e) {
+      $.ui.plugin.call(this, n, [e, this.ui()]);
+      this.element.triggerHandler(n == "resize" ? n : "resize"+n, [e, this.ui()], this.options[n]);
+    },
+    destroy: function() {
+      this.element
+        .removeClass("ui-resizable ui-resizable-disabled")
+        .removeMouseInteraction()
+        .removeData("ui-resizable")
+        .unbind(".resizable");
+    },
+    enable: function() {
+      this.element.removeClass("ui-resizable-disabled");
+      this.disabled = false;
+    },
+    disable: function() {
+      this.element.addClass("ui-resizable-disabled");
+      this.disabled = true;
+    },
+    start: function(e) {
+      var o = this.options, iniPos = this.element.position(), el = this.element;
+      o.resizing = true;
+			o.documentScroll = { top: $(document).scrollTop(),	left: $(document).scrollLeft() };
+			
+			// buf fix #1749
+      if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
+				// sOffset decides if document scrollOffset will be added to the top/left of the resizable element
+				var sOffset = $.browser.msie && !o.containment && (/absolute/).test(el.css('position')) && !(/relative/).test(el.parent().css('position'));
+				var dscrollt = sOffset ? o.documentScroll.top : 0, dscrolll = sOffset ? o.documentScroll.left : 0;
+	  		el.css({ position: 'absolute', top: (iniPos.top + dscrollt),	left: (iniPos.left + dscrolll)	});
+	  	}
+      
+      //Opera fixing relative position
+      if (/relative/.test(el.css('position')) && $.browser.opera)
+        el.css({ position: 'relative', top: 'auto', left: 'auto' });
+      
+      this._renderProxy();
+      
+			var curleft = parseInt(this.helper.css('left'),10) || 0, curtop = parseInt(this.helper.css('top'),10) || 0;
+			
+      //Store needed variables
+      $.extend(o, {
+        currentSize: { width: el.outerWidth(), height: el.outerHeight() },
+        currentSizeDiff: { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() },
+        startMousePosition: { left: e.pageX, top: e.pageY },
+        startPosition: { left: curleft, top: curtop },
+        currentPosition: { left: curleft,top: curtop }
+      });
+
+			//Aspect Ratio
+			var iswlt = o.currentSize.width < o.currentSize.height;
+ 			o.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : Math.pow(o.currentSize.width / o.currentSize.height, iswlt ? 1 : -1);
+			o.aspectRatioTarget = iswlt ? "width" : "height";
+			
+      if (o.preserveCursor)
+        $('body').css('cursor', o.axis + '-resize');
+      
+      if (o.containment) {
+        var oc = o.containment,
+           ce = (oc instanceof jQuery) ? oc.get(0) : 
+              (/parent/.test(oc)) ? el.parent().get(0) : null;
+        if (ce) {
+          
+          var scroll = function(e, a) {
+            var scroll = /top/.test(a||"top") ? 'scrollTop' : 'scrollLeft', has = false;
+            if (e[scroll] > 0) return true; e[scroll] = 1;
+            has = e[scroll] > 0 ? true : false; e[scroll] = 0;
+            return has; 
+          };
+          
+          var co = $(ce).offset(), ch = $(ce).innerHeight(), cw = $(ce).innerWidth();
+          o.cdata = { e: ce, l: co.left, t: co.top, w: (scroll(ce, "left") ? ce.scrollWidth : cw ), h: (scroll(ce) ? ce.scrollHeight : ch) };
+        }
+        if (/document/.test(oc) || oc == document) o.cdata = { e: document, l: 0, t: 0, w: $(document).width(), h: $(document).height() };
+      }
+      this.propagate("start", e);   
+      return false;
+      
+    },
+    stop: function(e) {
+      this.options.resizing = false;
+      var o = this.options;
+      
+      if(o.proxy) {
+        var style = { 
+          width: (this.helper.width() - o.currentSizeDiff.width) + "px",
+          height: (this.helper.height() - o.currentSizeDiff.height) + "px",
+          top: ((parseInt(this.element.css('top'),10) || 0) + ((parseInt(this.helper.css('top'),10) - this.offset.top)||0)),
+          left: ((parseInt(this.element.css('left'),10) || 0) + ((parseInt(this.helper.css('left'),10) - this.offset.left)||0))
+        };
+       	this.element.css(style);
+        if (o.proxy) this._proportionallyResize();
+        this.helper.remove();
+      }
+      
+      if (o.preserveCursor)
+        $('body').css('cursor', 'auto');
+      
+      this.propagate("stop", e);  
+      return false;
+      
+    },
+    drag: function(e) {
+      //Increase performance, avoid regex
+      var el = this.helper, o = this.options, props = {}, 
+						self = this, pRatio = o._aspectRatio || e.shiftKey;
+			
+      var change = function(a,b) {
+        var isth = (a=="top"||a=="height"), ishw = (a=="width"||a=="height"),
+							defAxis = (o.axis=="se"||o.axis=="s"||o.axis=="e");
+								 
+        var mod = (e[isth ? 'pageY' : 'pageX'] - o.startMousePosition[isth ? 'top' : 'left']) * (b ? -1 : 1);
+        var val = o[ishw ? 'currentSize' : 'startPosition'][a] - mod - (!o.proxy && defAxis ? o.currentSizeDiff.width : 0);
+				
+				//Preserve ratio
+        if (pRatio) {
+					var v = val * Math.pow(o.aspectRatio, (isth ? -1 : 1) * (o.aspectRatioTarget == 'height' ? 1 : -1)), locked = false;
+					
+					if (isth && v >= o.maxWidth || !isth && v >= o.maxHeight)	locked = true;
+					if (isth && v <= o.minWidth || !isth && v <= o.minHeight)	locked = true;
+						
+					if (ishw && !locked)	el.css(isth ? "width" : "height", v);
+					
+					if (a == "top" && (o.axis == "ne" || o.axis == "nw")) {
+						//el.css('top', o.startPosition['top'] - (el.outerHeight() - o.currentSize.height) );
+						/*TODO*/ return;
+					};
+				}
+				el.css(a, val);
+      };
+      
+			var a = o.axis, tminval = 0, tmaxval;
+			
+      //Change the height
+      if(a=="n"||a=="ne"||a=="nw") change("height");
+      if(a=="s"||a=="se"||a=="sw") change("height", 1);
+      
+      //Measure the new height and correct against min/maxHeight
+			var curheight = parseInt(el.css('height'),10)||0;
+      if(o.minHeight && curheight <= o.minHeight) el.css('height', o.minHeight);  
+      if(o.maxHeight && curheight >= o.maxHeight) el.css('height', o.maxHeight);
+      
+      //Change the top position when picking a handle at north
+      if(a=="n"||a=="ne"||a=="nw") change("top", 1);
+	  
+      //Measure the new top position and correct against min/maxHeight
+			var curtop = parseInt(el.css('top'),10)||0;
+			
+			tminval = (o.startPosition.top + (o.currentSize.height - o.minHeight));
+			tmaxval = (o.startPosition.top + (o.currentSize.height - o.maxHeight));
+      if(o.minHeight && curtop >= tminval) el.css('top', tminval);
+      if(o.maxHeight && curtop <= tmaxval) el.css('top', tmaxval);
+		
+      //Change the width
+      if(a=="e"||a=="se"||a=="ne") change("width", 1);
+      if(a=="sw"||a=="w"||a=="nw") change("width");
+      
+      //Measure the new width and correct against min/maxWidth
+			var curwidth = parseInt(el.css('width'),10)||0;
+      if(o.minWidth && curwidth <= o.minWidth) el.css('width', o.minWidth);  
+      if(o.maxWidth && curwidth >= o.maxWidth) el.css('width', o.maxWidth);
+      
+      //Change the left position when picking a handle at west
+      if(a=="sw"||a=="w"||a=="nw") change("left", 1);
+      
+      //Measure the new left position and correct against min/maxWidth
+			var curleft = parseInt(el.css('left'),10)||0;
+			
+			tminval = (o.startPosition.left + (o.currentSize.width - o.minWidth));
+			tmaxval = (o.startPosition.left + (o.currentSize.width - o.maxWidth));
+      if(o.minWidth && curleft >= tminval) el.css('left', tminval);
+      if(o.maxWidth && curleft <= tmaxval) el.css('left', tmaxval);
+      
+      if (o.containment && o.cdata.e) {
+        if (curleft < 0) {
+          el.css('left', 0);
+          el.css('width', curwidth + curleft);
+        }
+        if (curtop < 0) {
+          el.css('top', 0);
+          el.css('height', curheight + curtop);
+        }
+        if (curwidth + o.currentSizeDiff.width + curleft >= o.cdata.w) 
+					el.css('width', o.cdata.w - o.currentSizeDiff.width - (curleft < 0 ? 0 : curleft));
+        if (curheight + o.currentSizeDiff.height + curtop >= o.cdata.h)
+					el.css('height', o.cdata.h - o.currentSizeDiff.height - (curtop < 0 ? 0 : curtop));
+      }
+			o.currentPosition = { left: curleft, top: curtop };
+      if (!o.proxy) this._proportionallyResize();
+      this.propagate("resize", e);  
+      return false;
+    }
+  });
+
+})(jQuery);

File templates/base.html

View file
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     <head> 
         <title>Friendpaste - {% block title %}Welcome{% endblock %}</title>

File templates/paste/view.html

View file
     <div>
         
     </div>
-    <div id="info">Revision <a href="/{{ snippet.id }}/revs">{{ revisions[0].value['revid'] }}</a> ({{ snippet.updated|timesince }} ago)<br />Link to this snippet : <a href="/{{ snippet.id }}">http://friendpaste.com/{{ snippet.id  }}</a></div>
+    <div id="info">
+        <p>Revision <a href="/{{ snippet.id }}">{{ snippet.revid }}</a> ({{ snippet.updated|timesince }} ago) - <a href="/{{ snippet.id }}/changeset?rev={{ snippet.revid }}">Diff</a>
+        </p>
+        <p>Link to this snippet : <a href="/{{ snippet.id }}">http://friendpaste.com/{{ snippet.id  }}</a></p>
+    </div>
 
         <div id="actions">
-            <a href="/{{ snippet.id }}/edit" class="e">Edit paste</a> 
+            
             <form id="factions" action="/{{ snippet.id }}" method="post">
                 <select name="theme" id="change-theme">
                     {% for t in ALL_COLORSHEME %}
         <div class="highlight">
             {{ snippet.snippet|highlight(snippet.language) }}
         </div>
+        <div id="bottoma">
+            <ul>
+                <li>
+                    <form action="/{{ snippet.id }}/edit" method="get">
+                        <input type="submit" class="e" value="Edit paste" />
+                    </form>
+                </li>
+                <li>
+                    <form action="/{{ snippet.id }}/revisions" method="get">
+                        <input type="submit" class="show-history" value="Older revisions..." />
+                    </form>
+                </li>
+            </ul>
+        </div>
         <p id="dl"><strong>View in other formats:</strong><br /><a href="/{{ snippet.id }}/raw">raw format</a> | <a href="/{{ snippet.id }}/original">download source</a></p>
 
     
         <li>{{ form.snippet(cols=70,rows=10) }}</li>
         <li>{{ form.language.label }}{{ form.language }}</li>
         <li>{{ form.title.label }}{{ form.title }}</li>
-        <li><input type="submit" name="psubmit" value="Edit paste" />&nbsp;<input type="reset" name="preset" value="Reset form" />&nbsp;<input type="button" id="cancel" value="Cancel" /></li>
+        <li><input type="submit" name="psubmit" value="Save changes" />&nbsp;<input type="reset" name="preset" value="Reset form" />&nbsp;<input type="button" class="cancel" value="Cancel" /></li>
     </ol>
 </form>
+
+<div id="revisions">
+    <p>last change {{ snippet.updated|datetimeformat }}</p>
+    <h3>Changes</h3>
+    <table class="revisionstable">
+        <tr>
+            <td class="since">{{ snippet.updated|timesince }}</td>
+            <td class="rev">r<a href="/{{ snippet.id }}">{{ snippet.id }}</a></td>
+            <td class="changeset"><a href="/{{ snippet.id }}/changeset?rev={{ snippet.revid }}">Diff</a></td>
+            <td class="view-rev"><a href="{{ snippet.id }}">View</a></td>
+        </tr>
+    
+
+    {% for rev in revisions %}
+        <tr>
+            <td class="since">{{ rev.value['updated']|timesince }}</td>
+            <td class="rev">r<a href="/{{ snippet.id }}?rev={{ rev.value['revid'] }}">{{ rev.value['revid'] }}</a></td>
+            <td class="changeset"><a href="/{{ snippet.id }}/changeset?rev={{ rev.value['revid'] }}">Diff</a></td>
+            <td class="view-rev"><a href="/{{ snippet.id }}?rev={{ rev.value['revid'] }}">View</a></td>
+        </tr>
+    {% endfor %}
+    </table>
+    <button class="cancel">Back to the snippet</button>
+</div>
+
 <script type="text/javascript">
     $(window).bind("load",function(){
-        Friendpaste.init()      
+            Friendpaste.init()    
     });
     
 </script>