84b11de68417 initial-docs

Merge with default.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sat, 03 Jul 2010 14:28:27 -0400
parents 773e2317f086 (current diff) fb401cae8830 (diff)
children 1f220b967ed8
branches/tags initial-docs
files

Changes

--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fabfile.py	Sat Jul 03 14:28:27 2010 -0400
@@ -0,0 +1,15 @@
+from __future__ import with_statement
+
+from fabric.api import *
+
+def demo():
+    env.hosts += ['stevelosh.com:22779']
+    env.data_repo = '/var/www/review.stevelosh.com/data'
+    env.tool_repo = '/var/www/review.stevelosh.com/hg-review'
+    env.supervisord_program = 'gunicorn-review-hg-review'
+
+def deploy():
+    run('hg -R %s pull' % env.data_repo)
+    run('hg -R %s pull --update' % env.tool_repo)
+    sudo('supervisorctl restart %s' % env.supervisord_program)
+
--- a/review/static/comments.js	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/static/comments.js	Sat Jul 03 14:28:27 2010 -0400
@@ -1,65 +1,108 @@
-function RenderLineCommentForm(line, currNum) {
-    var comment_form = '\
-            <tr class="comment-form">\
-                <td colspan="3">\
-                    <form id="id_comment-line-form_' + currNum + '" method="POST" action="">\
-                        <span class="lastlinenumber disabled">' + currNum + '</span>\
-                        <div class="field">\
-                            <label class="infield" for="id_comment-line-form_' + currNum + '_body">Comment</label>\
-                            <textarea id="id_comment-line-form_' + currNum + '_body" \
-                                      name="new-comment-body"></textarea>\
-                        </div>\
-                        \
-                        <div class="field cuddly">\
-                            <input type="checkbox" class="checkbox" name="comment-markdown" id="id_comment-line-form_' + currNum + '_markdown" checked="checked" />\
-                            <label for="id_comment-line-form_' + currNum + '_markdown">Use Markdown to format this comment.</label>\
-                        </div>\
-                        \
-                        <a class="submit button"><span>Post Comment</span></a>\
-                        <a class="cancel-line button"><span>Cancel</span></a>\
-                        \
-                        <input type="hidden" name="filename" value="<<<FILENAME>>>" />\
-                        <input class="lines" type="hidden" name="lines" value="<<<LINENUMBER>>>" />\
-                    </form>\
-                </td>\
-            </tr>';
-    comment_form = comment_form.replace('<<<FILENAME>>>', line.closest(".file").find(".filename h3 a .name").html());
-    comment_form = comment_form.replace('<<<LINENUMBER>>>', currNum);
-    return comment_form;
+_.templateSettings = {
+  start       : '{{',
+  end         : '}}',
+  interpolate : /\{\{(.+?)\}\}/g
+};
+
+var comment_form = _.template(
+    '<tr class="comment-form">' +
+        '<td colspan="3">' +
+            '<form id="id_comment-line-form_{{ currNum }}" method="POST" action="">' +
+                '<span class="lastlinenumber disabled">{{ currNum }}</span>' +
+                '<div class="field">' +
+                    '<label class="infield" for="id_comment-line-form_{{ currNum }}_body">Comment</label>' +
+                    '<textarea id="id_comment-line-form_{{ currNum }}_body" class="body"'  +
+                              'name="new-comment-body">{{ body }}</textarea>' +
+                '</div>' +
+
+                '<div class="field cuddly">' +
+                    '<input type="checkbox" class="checkbox markdown-select" name="comment-markdown" id="id_comment-line-form_{{ currNum }}_markdown" {{ markdown_checked }} />' +
+                    '<label for="id_comment-line-form_{{ currNum }}_markdown">Use Markdown to format this comment.</label>' +
+                '</div>' +
+
+                '<a class="submit button"><span>Post Comment</span></a>' +
+                '<a class="cancel-line button"><span>Cancel</span></a>' +
+
+                '<input type="hidden" name="filename" value="{{ filename }}" />' +
+                '<input type="hidden" name="current" value="{{ identifier }}" class="current" />' +
+                '<input class="lines" type="hidden" name="lines" value="{{ currNum }}" />' +
+            '</form>' +
+        '</td>' +
+    '</tr>'
+);
+
+function RenderLineCommentForm(line, currNum, body, markdown, identifier) {
+    var filename = line.closest(".file").find(".filename h3 a .name").html();
+    markdown_checked = markdown ? 'checked="checked"' : '';
+    return comment_form({ filename: filename, currNum: currNum, body: body,
+                          markdown_checked: markdown_checked, identifier: identifier });
 }
 
 $(function() {
-    
     $(".activate a").click(function(event) {
         $(event.target).closest(".activate").hide();
         $(event.target).closest("div").children("form").fadeIn("fast");
         return false;
     });
-
+    $(".activate-edit a").click(function(event) {
+        $(event.target).closest(".activate-edit").hide();
+        $(event.target).closest(".comment").find(".message").hide();
+        $(event.target).closest("div").children("form").fadeIn("fast");
+        return false;
+    });
     $("a.cancel").click(function(event) {
         $(event.target).closest(".togglebox").children(".activate").show();
         $(event.target).parents("form").hide();
         return false;
     });
-
+    $("a.cancel-edit").click(function(event) {
+        $(event.target).closest(".toggleinline").children(".activate-edit").show();
+        $(event.target).closest(".comment").find(".message").show();
+        $(event.target).parents("form").hide();
+        return false;
+    });
     $("a.cancel-line").live('click', function(event) {
         $(event.target).closest(".diff").find(".chosen").removeClass("chosen");
+        $(event.target).closest("tr.comment-form").hide();
+        $(event.target).closest(".diff").find("tr.comment").fadeIn('fast');
         $(event.target).closest("tr.comment-form").remove();
         return false;
     });
+    $("a.edit-line").click(function(event) {
+        var diff = $(this).closest(".diff");
+        var comment = $(this).closest("tr.comment");
+
+        diff.find(".chosen").removeClass("chosen");
+        diff.find(".comment-form").remove();
+        comment.hide();
+
+        _.each($(event.target).closest(".comment").find(".commentlines").html().split(","),
+               function(i) { diff.find(".line-" + i).addClass("chosen") });
+
+        var lines_chosen = diff.find(".chosen");
+        var last_line = lines_chosen.last();
+        var last_line_number = parseInt(lines_chosen.find(".linenumber").last().html());
+        var body = comment.find(".raw").html();
+        var markdown = comment.find(".message").hasClass("markdown");
+        var identifier = comment.find(".identifier").html();
+
+        var comment_form = RenderLineCommentForm(last_line, last_line_number, body, markdown, identifier);
+        last_line.after(comment_form);
+        diff.find("label").inFieldLabels();
+
+        return false;
+    });
 
     $("tr.comment").hover(function(event) {
         var diff = $(event.target).closest(".diff");
-        var lines = $(event.target).find(".commentlines").html().split(",");
-        for (i=0; i < lines.length; i++) {
-            diff.find(".line-" + lines[i]).addClass("viewing");
-        }
+
+        _.each($(event.target).find(".commentlines").html().split(","),
+               function (i) { diff.find(".line-" + i).addClass("viewing"); });
     }, function(event) {
         $(".viewing").removeClass("viewing");
     });
 
     var lastSelected = null;
-
     $(".commentable").click(function(event) {
         var currNum = parseInt($(this).find(".linenumber").html());
         var diff = $(this).closest(".diff");
@@ -67,63 +110,59 @@
         if ($(this).hasClass("chosen")) {
             $(this).removeClass("chosen");
 
-            var newLines = "";
-            jQuery.each(diff.find(".chosen .linenumber"), function() {
-                newLines += $(this).html() + ",";
-            });
+            var newLines = _.reduce(diff.find(".chosen .linenumber"), "",
+                                    function(memo, lne) { return memo + $(lne).html() + ','; });
             diff.find(".comment-form form .lines").val(newLines);
 
             lastSelected = null;
-            return false;
+        } else {
+            if (event.shiftKey && lastSelected) {
+                if (lastSelected && jQuery.contains(diff.get(0), lastSelected.get(0))) {
+                    var lastNum = parseInt(lastSelected.find(".linenumber").html());
+                    _.each(_.range(currNum, lastNum, lastNum > currNum ? 1 : -1),
+                           function(i) { diff.find(".line-" + i).addClass("chosen"); });
+                }
+            } else {
+                $(this).addClass("chosen");
+            }
         }
 
-        if (event.shiftKey && lastSelected) {
-            if (lastSelected && jQuery.contains(diff.get(0), lastSelected.get(0))) {
-                var lastNum = parseInt(lastSelected.find(".linenumber").html());
-                if (lastNum > currNum) {
-                    for (i = currNum; i < lastNum; i++) {
-                        diff.find(".line-" + i).addClass("chosen");
-                    }
-                } else {
-                    for (i = currNum; i > lastNum; i--) {
-                        diff.find(".line-" + i).addClass("chosen");
-                    }
-                }
-            }
-        } else {
-            $(this).addClass("chosen");
-        }
-        lastSelected = $(this);
-
         var lines_chosen = diff.find(".chosen");
-        var last_line = lines_chosen.last();
+        var last_line_number = parseInt(lines_chosen.find(".linenumber").last().html());
         var existing_forms = diff.find(".comment-form");
 
         if (existing_forms.length) {
-            var existing_form = existing_forms.last();
-            var existing_form_line_number = parseInt(existing_form.find(".lastlinenumber").html());
-            
-            if (existing_form_line_number < currNum) {
+            if (_.isNaN(last_line_number)) {
                 existing_forms.remove();
+                diff.find("tr.comment").fadeIn('fast');
+            } else {
+                var existing_form = existing_forms.last();
+                var existing_form_line_number = parseInt(existing_form.find(".lastlinenumber").html());
+                var existing_body = existing_form.find(".body").val();
+                var existing_markdown = existing_form.find('.markdown-select').is(':checked');
+                var existing_current = existing_form.find('.current').val();
+
+                if (existing_form_line_number != last_line_number) {
+                    existing_forms.remove();
 
-                var comment_form = RenderLineCommentForm($(this), currNum);
-                $(this).after(comment_form);
-                diff.find("label").inFieldLabels();
-            }
+                    var comment_form = RenderLineCommentForm($(this), last_line_number, existing_body,
+                                                             existing_markdown, existing_current);
+                    lines_chosen.last().after(comment_form);
+                    diff.find("label").inFieldLabels();
+                }
 
-            var newLines = "";
-            jQuery.each(diff.find(".chosen .linenumber"), function() {
-                newLines += $(this).html() + ",";
-            });
-            diff.find(".comment-form form .lines").val(newLines);
+                var newLines = _.reduce(diff.find(".chosen .linenumber"), "",
+                                        function(memo, lne) { return memo + $(lne).html() + ','; });
+                diff.find(".comment-form form .lines").val(newLines);
+            }
         } else {
-            var comment_form = RenderLineCommentForm($(this), currNum);
+            var comment_form = RenderLineCommentForm($(this), last_line_number, '', true, '');
             $(this).after(comment_form);
             diff.find("label").inFieldLabels();
         }
 
         return false;
     });
-    
+
     $("label.infield").inFieldLabels();
 });
--- a/review/static/style.css	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/static/style.css	Sat Jul 03 14:28:27 2010 -0400
@@ -306,7 +306,7 @@
 #changeset .content a.submit:hover span {
   background-color: #eaeaea;
 }
-#changeset .content a.cancel, #changeset .content a.cancel-line {
+#changeset .content a.cancel, #changeset .content a.cancel-line, #changeset .content a.cancel-edit {
   cursor: pointer;
   font: bold 12px "Helvetica Neue", HelveticaNeue, Arial, Helvetica, sans-serif;
   color: #1e1e1e;
@@ -326,12 +326,12 @@
   border-left: 1px solid #a6a6a6;
   border-bottom: 1px solid #959595;
 }
-#changeset .content a.cancel:focus, #changeset .content a.cancel-line:focus {
+#changeset .content a.cancel:focus, #changeset .content a.cancel-line:focus, #changeset .content a.cancel-edit:focus {
   box-shadow: 0px 0px 4px rgba(100, 100, 100, 0.9);
   -moz-box-shadow: 0px 0px 4px rgba(100, 100, 100, 0.9);
   -webkit-box-shadow: 0px 0px 4px rgba(100, 100, 100, 0.9);
 }
-#changeset .content a.cancel span, #changeset .content a.cancel-line span {
+#changeset .content a.cancel span, #changeset .content a.cancel-line span, #changeset .content a.cancel-edit span {
   display: inline-block;
   padding: 0 6px;
   text-shadow: 0px 1px 1px #e2e2e2;
@@ -339,14 +339,14 @@
   -moz-border-radius: 3px;
   border-radius: 3px;
 }
-#changeset .content a.cancel:hover, #changeset .content a.cancel-line:hover {
+#changeset .content a.cancel:hover, #changeset .content a.cancel-line:hover, #changeset .content a.cancel-edit:hover {
   border-top: 1px solid #d3d3d3;
   border-right: 1px solid #bbbbbb;
   border-left: 1px solid #bbbbbb;
   border-bottom: 1px solid #a4a4a4;
   background-color: #eaeaea;
 }
-#changeset .content a.cancel:hover span, #changeset .content a.cancel-line:hover span {
+#changeset .content a.cancel:hover span, #changeset .content a.cancel-line:hover span, #changeset .content a.cancel-edit:hover span {
   background-color: #eaeaea;
 }
 #changeset .content .navigation .middle {
@@ -407,6 +407,9 @@
 #changeset .content .review-level-comments {
   margin-top: 30px;
 }
+#changeset .content .edit-review-comment {
+  margin-top: 3px;
+}
 #changeset .content .activate a {
   cursor: pointer;
   font: bold 12px "Helvetica Neue", HelveticaNeue, Arial, Helvetica, sans-serif;
@@ -450,6 +453,10 @@
 #changeset .content .activate a:hover span {
   background-color: #eaeaea;
 }
+#changeset .content .edit-line {
+  display: inline-block;
+  margin-top: 3px;
+}
 #changeset .content .togglebox form {
   float: left;
   border: 1px solid #ccc;
--- a/review/static/style.less	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/static/style.less	Sat Jul 03 14:28:27 2010 -0400
@@ -329,7 +329,7 @@
         &:hover { .button-hover(@c-metal, #000, 12px, 1.45); }
         &:hover span { .button-hover-span(@c-metal, #000, 12px, 1.45); }
     }
-    a.cancel, a.cancel-line {
+    a.cancel, a.cancel-line, a.cancel-edit {
         .button(@c-metal, #000, 12px, 1.45);
         span { .button-span(@c-metal, #000, 12px, 1.45); }
         &:hover { .button-hover(@c-metal, #000, 12px, 1.45); }
@@ -396,12 +396,19 @@
     .review-level-comments {
         margin-top: 30px;
     }
+    .edit-review-comment {
+        margin-top: 3px;
+    }
     .activate a {
         .button(@c-metal, #000, 12px, 1.45);
         span { .button-span(@c-metal, #000, 12px, 1.45); }
         &:hover { .button-hover(@c-metal, #000, 12px, 1.45); }
         &:hover span { .button-hover-span(@c-metal, #000, 12px, 1.45); }
     }
+    .edit-line {
+        display: inline-block;
+        margin-top: 3px;
+    }
     .togglebox {
         form {
             float: left;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/review/static/underscore.min.js	Sat Jul 03 14:28:27 2010 -0400
@@ -0,0 +1,17 @@
+(function(){var n=this,A=n._,r=typeof StopIteration!=="undefined"?StopIteration:"__break__",j=Array.prototype,l=Object.prototype,o=j.slice,B=j.unshift,C=l.toString,p=l.hasOwnProperty,s=j.forEach,t=j.map,u=j.reduce,v=j.reduceRight,w=j.filter,x=j.every,y=j.some,m=j.indexOf,z=j.lastIndexOf;l=Array.isArray;var D=Object.keys,b=function(a){return new k(a)};if(typeof exports!=="undefined")exports._=b;n._=b;b.VERSION="1.0.4";var i=b.forEach=function(a,c,d){try{if(s&&a.forEach===s)a.forEach(c,d);else if(b.isNumber(a.length))for(var e=
+0,f=a.length;e<f;e++)c.call(d,a[e],e,a);else for(e in a)p.call(a,e)&&c.call(d,a[e],e,a)}catch(g){if(g!=r)throw g;}return a};b.map=function(a,c,d){if(t&&a.map===t)return a.map(c,d);var e=[];i(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(u&&a.reduce===u)return a.reduce(b.bind(d,e),c);i(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=function(a,c,d,e){if(v&&a.reduceRight===v)return a.reduceRight(b.bind(d,e),c);a=b.clone(b.toArray(a)).reverse();return b.reduce(a,
+c,d,e)};b.detect=function(a,c,d){var e;i(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.filter=function(a,c,d){if(w&&a.filter===w)return a.filter(c,d);var e=[];i(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];i(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.every=function(a,c,d){c=c||b.identity;if(x&&a.every===x)return a.every(c,d);var e=true;i(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.some=
+function(a,c,d){c=c||b.identity;if(y&&a.some===y)return a.some(c,d);var e=false;i(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(m&&a.indexOf===m)return a.indexOf(c)!=-1;var d=false;i(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,d)})};b.pluck=function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,
+a);var e={computed:-Infinity};i(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};i(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(e,f,g){return{value:e,criteria:c.call(d,e,f,g)}}).sort(function(e,f){var g=e.criteria,h=f.criteria;return g<h?-1:g>h?
+1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return o.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=function(a,c,d){return c&&!d?o.call(a,0,c):a[0]};b.rest=function(a,c,d){return o.call(a,b.isUndefined(c)||d?1:c)};b.last=function(a){return a[a.length-1]};
+b.compact=function(a){return b.filter(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.filter(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c===true?b.last(d)!=e:!b.include(d,e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.filter(b.uniq(a),function(d){return b.every(c,
+function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e<c;e++)d[e]=b.pluck(a,String(e));return d};b.indexOf=function(a,c){if(m&&a.indexOf===m)return a.indexOf(c);for(var d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(z&&a.lastIndexOf===z)return a.lastIndexOf(c);for(var d=a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=
+1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;;f+=d){if((d>0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||{},d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);i(c,function(d){a[d]=b.bind(a[d],a)});return a};b.memoize=function(a,c){var d={};c=c||b.identity;return function(){var e=c.apply(this,arguments);return e in
+d?d[e]:(d[e]=a.apply(this,arguments))}};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=D||function(a){if(b.isArray(a))return b.range(0,
+a.length);var c=[];for(var d in a)p.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){i(b.rest(arguments),function(c){for(var d in c)a[d]=c[d]});return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;
+if(a==c)return true;if(!a&&c||a&&!c)return false;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return false;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!(f in c)||!b.isEqual(a[f],
+c[f]))return false;return true};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(p.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=l||function(a){return!!(a&&a.concat&&a.unshift&&!a.callee)};b.isArguments=function(a){return a&&a.callee};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return a===
++a||C.call(a)==="[object Number]"};b.isBoolean=function(a){return a===true||a===false};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){n._=A;return this};b.identity=function(a){return a};b.times=function(a,c,d){for(var e=
+0;e<a;e++)c.call(d,e)};b.breakLoop=function(){throw r;};b.mixin=function(a){i(b.functions(a),function(c){E(c,b[c]=a[c])})};var F=0;b.uniqueId=function(a){var c=F++;return a?a+c:c};b.templateSettings={start:"<%",end:"%>",interpolate:/<%=(.+?)%>/g};b.template=function(a,c){var d=b.templateSettings,e=new RegExp("'(?=[^"+d.end.substr(0,1)+"]*"+d.end.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")+")","g");d=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g,
+" ").replace(e,"\t").split("'").join("\\'").split("\t").join("'").replace(d.interpolate,"',$1,'").split(d.start).join("');").split(d.end).join("p.push('")+"');}return p.join('');");return c?d(c):d};b.each=b.forEach;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.select=b.filter;b.all=b.every;b.any=b.some;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a){this._wrapped=a},q=function(a,c){return c?b(a).chain():a},E=function(a,c){k.prototype[a]=function(){var d=b.toArray(arguments);
+B.call(d,this._wrapped);return q(c.apply(b,d),this._chain)}};b.mixin(b);i(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=j[a];k.prototype[a]=function(){c.apply(this._wrapped,arguments);return q(this._wrapped,this._chain)}});i(["concat","join","slice"],function(a){var c=j[a];k.prototype[a]=function(){return q(c.apply(this._wrapped,arguments),this._chain)}});k.prototype.chain=function(){this._chain=true;return this};k.prototype.value=function(){return this._wrapped}})();
\ No newline at end of file
--- a/review/templates/base.html	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/templates/base.html	Sat Jul 03 14:28:27 2010 -0400
@@ -11,6 +11,7 @@
 
         <script type="text/javascript" src="/static/jquery-1.4.2.min.js"></script>
         <script type="text/javascript" src="/static/jquery.infieldlabel.min.js"></script>
+        <script type="text/javascript" src="/static/underscore.min.js"></script>
         <script type="text/javascript" src="/static/colorbox/jquery.colorbox.js"></script>
         <script type="text/javascript" src="/static/ui.js"></script>
         <script type="text/javascript" src="/static/comments.js"></script>
--- a/review/templates/pieces/comment.html	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/templates/pieces/comment.html	Sat Jul 03 14:28:27 2010 -0400
@@ -45,4 +45,7 @@
             <div class="message">{{ comment.message }}</div>
         </div>
     </div>
+    {% if not read_only %}
+        {% include "pieces/forms/comment-change.html" %}
+    {% endif %}
 </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/review/templates/pieces/forms/comment-change.html	Sat Jul 03 14:28:27 2010 -0400
@@ -0,0 +1,30 @@
+<div class="edit-review-comment toggleinline group">
+    <span class="activate-edit"><a href="#">Edit</a></span>
+    <form class="disabled" id="comment-review-form" method="POST" action="">
+        <div class="field">
+            <label class="infield"
+                   for="id_comment-{{ comment.identifier }}-review-form_body"
+            >Comment</label>
+
+            <textarea autocomplete="off"
+                      id="id_comment-{{ comment.identifier }}-review-form_body"
+                      cols="60" rows="6" name="new-comment-body"
+            >{{ comment.message }}</textarea>
+        </div>
+        <div class="field cuddly">
+            <input type="checkbox" class="checkbox"
+                   name="comment-markdown"
+                   id="id_comment-{{ comment.identifier }}-review-form_markdown"
+                   {% if comment.style == "markdown" %}checked="checked"{% endif %}
+            />
+            <label for="id_comment-{{ comment.identifier }}-review-form_markdown">
+                Use Markdown to format this comment.
+            </label>
+
+        </div>
+        <input type="hidden" name="filename" value="{{ filename }}" />
+        <input type="hidden" name="current" value="{{ comment.identifier }}"/>
+        <a class="submit button" href="#"><span>Edit Comment</span></a>
+        <a class="cancel-edit button" href="#"><span>Cancel</span></a>
+    </form>
+</div>
--- a/review/templates/pieces/linecomment.html	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/templates/pieces/linecomment.html	Sat Jul 03 14:28:27 2010 -0400
@@ -5,6 +5,7 @@
 <tr class="comment">
     <td class="comment group" colspan="3" id="comment-{{ comment.identifier }}">
         <div class="comment-content">
+            <span class="identifier disabled">{{ comment.identifier }}</span>
             <span class="commentlines disabled">{{ ','.join(utils['map'](utils['str'], comment.lines)) }}</span>
             <a href="#comment-{{ comment.identifier }}" rel="comments" class="expand" id="comment-expand-{{ comment.identifier }}">&rarr;</a>
             <script type="text/javascript">
@@ -26,8 +27,14 @@
 
                 {% if comment.style == 'markdown' %}
                     <div class="message markdown">{{ rendered|safe }}</div>
+                    <div class="raw disabled">{{ comment.message }}</div>
                 {% else %}
-                    <div class="message plain">{{ comment.message }}</div>
+                    <div class="message raw plain">{{ comment.message }}</div>
+                {% endif %}
+
+
+                {% if not read_only %}
+                    <a class="edit-line" href="#">Edit</a>
                 {% endif %}
             </div>
         </div>
--- a/review/web.py	Thu Jul 01 19:32:49 2010 -0400
+++ b/review/web.py	Sat Jul 03 14:28:27 2010 -0400
@@ -38,6 +38,7 @@
 
 LOG_PAGE_LEN = 15
 
+
 def _item_gravatar(item, size=30):
     return 'http://www.gravatar.com/avatar/%s?s=%d' % (md5(email(item.author)).hexdigest(), size)
 
@@ -135,8 +136,12 @@
     style = 'markdown' if request.form.get('comment-markdown') else ''
     
     if body:
-        rcset = g.datastore[revhash]
-        rcset.add_comment(body, filename, lines, style=style)
+        current = request.form.get('current')
+        if current:
+            g.datastore.edit_comment(current, body, filename, lines, style)
+        else:
+            rcset = g.datastore[revhash]
+            rcset.add_comment(body, filename, lines, style)
     
     return redirect("%s/changeset/%s/" % (app.site_root, revhash))