Edgewall Software

Ticket #7145: 7145-ticket-change-preview-r10500.patch

File 7145-ticket-change-preview-r10500.patch, 9.7 KB (added by rblank, 16 months ago)

Improved ticket change preview.

  • trac/htdocs/js/auto_preview.js

    diff --git a/trac/htdocs/js/auto_preview.js b/trac/htdocs/js/auto_preview.js
    a b  
    11// Automatic preview through XHR 
    22 
    33(function($) { 
     4  // Enable automatic previewing of form submissions. 
     5  // 
     6  // Arguments: 
     7  //  - `args`: additional form data to be passed with the XHR. 
     8  //  - `update`: the function that is called with the preview results. It 
     9  //              is called with the request data and the reply. 
     10  $.fn.autoFormPreview = function(args, update) { 
     11    if (auto_preview_timeout <= 0) 
     12      return this; 
     13    var form = this.closest('form'); 
     14    var data = {}; 
     15    for (var key in args) 
     16      data[key] = args[key]; 
     17    var timer = null; 
     18    var timeout = auto_preview_timeout * 1000; 
     19    var updating = false; 
     20    var queued = false; 
     21     
     22    // Return true iff the values have changed 
     23    function values_changed(new_values) { 
     24      for (var i in values) { 
     25        var value = values[i], new_value = new_values[i]; 
     26        if ((value.name != new_value.name) || (value.value != new_value.value)) 
     27          return true; 
     28      } 
     29      return false; 
     30    } 
     31     
     32    // Request a preview through XHR 
     33    function request() { 
     34      if (!updating) { 
     35        var new_values = form.serializeArray(); 
     36        if (values_changed(new_values)) { 
     37          values = new_values; 
     38          updating = true; 
     39          var data = {}; 
     40          for (var i in values) { 
     41            var value = values[i]; 
     42            data[value.name] = value.value.replace(/\n/g, '\r\n'); 
     43          } 
     44          for (var key in args) 
     45            data[key] = args[key]; 
     46          $.ajax({ 
     47            type: "POST", url: form.attr('action'), data: data, 
     48            dataType: "html", 
     49            success: function(reply) { 
     50              if (queued) 
     51                timer = setTimeout(request, timeout); 
     52              updating = false; 
     53              queued = false; 
     54              update(data, reply); 
     55            }, 
     56            error: function(req, err, exc) { 
     57              updating = false; 
     58              queued = false; 
     59            }, 
     60          }); 
     61        } 
     62      } 
     63    } 
     64     
     65    // Trigger a request after the given timeout 
     66    function trigger() { 
     67      if (!updating) { 
     68        if (timer) 
     69          clearTimeout(timer); 
     70        timer = setTimeout(request, timeout); 
     71      } else { 
     72        queued = true; 
     73      } 
     74      return true; 
     75    } 
     76 
     77    var values = form.serializeArray(); 
     78    return this.each(function() { 
     79      $(this).keydown(trigger).keypress(trigger).change(trigger).blur(request); 
     80    }); 
     81  }; 
     82 
    483  // Enable automatic previewing to <textarea> elements. 
    584  // 
    685  // Arguments: 
     
    56135       
    57136      $(this).keydown(trigger).keypress(trigger).blur(request); 
    58137    }); 
    59   } 
     138  }; 
    60139})(jQuery); 
  • trac/ticket/templates/ticket.html

    diff --git a/trac/ticket/templates/ticket.html b/trac/ticket/templates/ticket.html
    a b  
    1818        $("div.description").find("h1,h2,h3,h4,h5,h6").addAnchor(_("Link to this section")); 
    1919        $(".foldable").enableFolding(false, true); 
    2020      <py:when test="ticket.exists"> 
    21         var args = {realm: "ticket", id: ${ticket.id}, escape_newlines: ${int(preserve_newlines)}} 
    22         $("#comment").autoPreview("${href.wiki_render()}", args, function(textarea, text, rendered) { 
    23             $("#ticketchange div.comment").html(rendered); 
    24             if (rendered) 
    25               $("#ticketchange").show(); 
    26             else if ($("#ticketchange ul.changes").length == 0) 
    27               $("#ticketchange").hide(); 
    28         }); 
    29         $("#trac-comment-editor textarea").autoPreview("${href.wiki_render()}", args, 
    30                                                        function(textarea, text, rendered) { 
    31           var comment = $("#trac-comment-editor").next("div.comment"); 
    32           comment.html(rendered); 
    33           if (rendered) 
    34             comment.show(); 
    35           else 
    36             comment.hide(); 
    37         }); 
    3821        $("#modify").parent().toggleClass("collapsed"); 
    3922        $(".trac-topnav a").click(function() { $("#modify").parent().removeClass("collapsed"); }); 
    4023 
     
    4932        } 
    5033        actions.click(updateActionFields); 
    5134        updateActionFields(); 
     35         
     36        $("#propertyform").find("textarea, select, :text, :checkbox, :radio") 
     37          .autoFormPreview({}, function(data, rendered) { 
     38            $("#ticketchange-content").html(rendered); 
     39            if (rendered) 
     40              $("#ticketchange").show(); 
     41            else if ($("#ticketchange ul.changes").length == 0) 
     42              $("#ticketchange").hide(); 
     43          }); 
     44        var args = {realm: "ticket", id: ${ticket.id}, escape_newlines: ${int(preserve_newlines)}}; 
     45        $("#trac-comment-editor textarea").autoPreview("${href.wiki_render()}", args, 
     46                                                       function(textarea, text, rendered) { 
     47          var comment = $("#trac-comment-editor").next("div.comment"); 
     48          comment.html(rendered); 
     49          if (rendered) 
     50            comment.show(); 
     51          else 
     52            comment.hide(); 
     53        }); 
    5254      </py:when> 
    5355      <py:otherwise> 
    5456        $("#field-summary").focus(); 
     
    190192      <form py:if="has_property_editor" method="post" id="propertyform" 
    191193            action="${ticket.exists and href.ticket(ticket.id) + '#trac-add-comment' or href.newticket()}"> 
    192194        <!--! Add comment --> 
    193         <div py:if="ticket.exists and can_append" class="field" 
    194              py:with="show_comment_preview = (change_preview.fields or change_preview.comment) and cnum_edit is None"> 
     195        <div py:if="ticket.exists and can_append" class="field"> 
    195196          <div class="trac-nav"> 
    196197            <a href="#content" title="View ticket fields and description">View</a> &uarr; 
    197198          </div> 
     
    211212            Warnings are shown at the <a href="#warning">top of the page</a>. The ticket validation 
    212213            may have failed. 
    213214          </div> 
    214           <!--! Preview of ticket changes --> 
    215           <div id="ticketchange" class="ticketdraft" style="${not show_comment_preview and 'display: none' or None}"> 
    216             <h3 class="change" id="${'cnum' in change_preview and 'comment:%d' % change_preview.cnum or None}"> 
    217               <span class="threading" py:if="'replyto' in change_preview"> 
    218                 in reply to: ${commentref('&uarr;&nbsp;', change_preview.replyto)} 
    219               </span> 
    220               <i18n:msg params="author">Changed by ${authorinfo(change_preview.author)}</i18n:msg> 
    221             </h3> 
    222             <xi:include href="ticket_change.html" py:with="change = change_preview"/> 
    223           </div> 
    224215        </div> 
    225216 
    226217        <div> 
     
    377368          </fieldset> 
    378369        </div> 
    379370 
     371        <!--! Preview of ticket changes --> 
     372        <div py:if="ticket.exists and can_append" id="ticketchange" class="ticketdraft" 
     373             style="${(not (change_preview.fields or change_preview.comment) 
     374                       or cnum_edit is not None) and 'display: none' or None}"> 
     375          <h3 class="change" id="${'cnum' in change_preview and 'comment:%d' % change_preview.cnum or None}"> 
     376            <span class="threading" py:if="'replyto' in change_preview"> 
     377              in reply to: ${commentref('&uarr;&nbsp;', change_preview.replyto)} 
     378            </span> 
     379            <i18n:msg params="author">Changed by ${authorinfo(change_preview.author)}</i18n:msg> 
     380          </h3> 
     381          <div id="ticketchange-content"><xi:include href="ticket_change.html" py:with="change = change_preview"/></div> 
     382        </div> 
     383 
    380384        <!--! Attachment on creation checkbox --> 
    381385        <p py:if="not ticket.exists and 'ATTACHMENT_CREATE' in perm(ticket.resource.child('attachment'))"> 
    382386          <label> 
  • trac/ticket/templates/ticket_change.html

    diff --git a/trac/ticket/templates/ticket_change.html b/trac/ticket/templates/ticket_change.html
    a b Arguments: 
    1313      xmlns:i18n="http://genshi.edgewall.org/i18n" 
    1414      py:with="show_editor = value_of('show_editor', False)" py:strip=""> 
    1515  <ul py:if="change.fields" class="changes"> 
    16     <li py:for="field_name, field in change.fields.items()"> 
     16    <li py:for="field_name, field in sorted(change.fields.iteritems(), key=lambda item: item[1].label.lower())"> 
    1717      <strong>${field.label}</strong> 
    1818      <py:choose> 
    1919        <py:when test="field_name == 'attachment'"><i18n:msg params="name"> 
  • trac/ticket/web_ui.py

    diff --git a/trac/ticket/web_ui.py b/trac/ticket/web_ui.py
    a b class TicketModule(Component): 
    454454                version = int(version) 
    455455            except ValueError: 
    456456                version = None 
     457        xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' 
    457458 
    458459        req.perm('ticket', id, version).require('TICKET_VIEW') 
    459460        ticket = Ticket(self.env, id, version=version) 
    class TicketModule(Component): 
    539540            # validates and there were no problems with the workflow side of 
    540541            # things. 
    541542            valid = self._validate_ticket(req, ticket, not valid) and valid 
     543            if xhr: 
     544                req.args['preview'] = True 
    542545            if 'preview' not in req.args: 
    543546                if valid: 
    544547                    # redirected if successful 
    class TicketModule(Component): 
    572575        self._insert_ticket_data(req, ticket, data, 
    573576                                 get_reporter_id(req, 'author'), field_changes) 
    574577 
     578        if xhr: 
     579            # TODO: Differentiate between comment append and comment edit 
     580            data['change'] = data['change_preview'] 
     581            return 'ticket_change.html', data, None 
     582 
    575583        mime = Mimeview(self.env) 
    576584        format = req.args.get('format') 
    577585        if format: