Ticket #7145: 7145-ticket-preview-2-r10560.patch
| File 7145-ticket-preview-2-r10560.patch, 17.7 KB (added by rblank, 15 months ago) |
|---|
-
trac/htdocs/css/ticket.css
diff --git a/trac/htdocs/css/ticket.css b/trac/htdocs/css/ticket.css
a b label[for=comment] { float: right } 136 136 form .field .wikitoolbar { margin-left: -1px } 137 137 form .field div.trac-resizable { width: 100% } 138 138 139 #propertyform { margin-bottom: 2em; } 139 140 #properties { white-space: nowrap; line-height: 160%; padding: .5em } 140 141 #properties table { border-spacing: 0; width: 100%; padding: 0 .5em } 141 142 #properties table th { -
trac/htdocs/js/auto_preview.js
diff --git a/trac/htdocs/js/auto_preview.js b/trac/htdocs/js/auto_preview.js
a b 1 // Automatic preview through XHR1 // Automatic form submission and preview through XHR 2 2 3 3 (function($) { 4 // Enable automatic submission of forms. 5 // 6 // This method can be applied to a single form, where it enables 7 // auto-submission on all the editable elements that it contains. 8 // It can also be applied on a list of elements, in which case it 9 // enables auto-submission only for these elements. 10 // 11 // Arguments: 12 // - `args`: additional form data to be passed with the XHR. 13 // - `update`: the function that is called with the submission reply. It 14 // is called with the request data and the reply. 15 $.fn.autoSubmit = function(args, update) { 16 if (this.length == 0 || auto_preview_timeout <= 0) 17 return this; 18 if (this[0].nodeName == 'FORM') { 19 var form = this; 20 var inputs = this.find("textarea, select, :text, :checkbox, :radio"); 21 } else { 22 var form = this.closest('form'); 23 var inputs = this; 24 } 25 var timeout = auto_preview_timeout * 1000; 26 var timer = null; 27 var updating = false; 28 var queued = false; 29 30 // Return true iff the values have changed 31 function values_changed(new_values) { 32 if (values.length != new_values.length) 33 return true; 34 for (var i in values) { 35 var value = values[i], new_value = new_values[i]; 36 if ((value.name != new_value.name) || (value.value != new_value.value)) 37 return true; 38 } 39 return false; 40 } 41 42 // Request a preview through XHR 43 function request() { 44 if (!updating) { 45 var new_values = form.serializeArray(); 46 if (values_changed(new_values)) { 47 values = new_values; 48 updating = true; 49 50 // Construct request data 51 var data = values.slice(0); 52 for (var key in args) 53 data.push({name: key, value: args[key]}); 54 55 $.ajax({ 56 type: form.attr('method'), url: form.attr('action'), 57 data: data, traditional: true, dataType: "html", 58 success: function(reply) { 59 if (queued) 60 timer = setTimeout(request, timeout); 61 updating = false; 62 queued = false; 63 update(data, reply); 64 }, 65 error: function(req, err, exc) { 66 updating = false; 67 queued = false; 68 }, 69 }); 70 } 71 } 72 } 73 74 // Trigger a request after the given timeout 75 function trigger() { 76 if (!updating) { 77 if (timer) 78 clearTimeout(timer); 79 timer = setTimeout(request, timeout); 80 } else { 81 queued = true; 82 } 83 return true; 84 } 85 86 var values = form.serializeArray(); 87 return inputs.each(function() { 88 $(this).keydown(trigger).keypress(trigger).change(trigger).blur(trigger); 89 }); 90 }; 91 4 92 // Enable automatic previewing to <textarea> elements. 5 93 // 6 94 // Arguments: … … 56 144 57 145 $(this).keydown(trigger).keypress(trigger).blur(request); 58 146 }); 59 } 147 }; 60 148 })(jQuery); -
trac/ticket/templates/ticket.html
diff --git a/trac/ticket/templates/ticket.html b/trac/ticket/templates/ticket.html
a b 19 19 $("div.description").find("h1,h2,h3,h4,h5,h6").addAnchor(_("Link to this section")); 20 20 $(".foldable").enableFolding(false, true); 21 21 <py:when test="ticket.exists"> 22 var args = {realm: "ticket", id: ${ticket.id}, escape_newlines: ${int(preserve_newlines)}}23 $("#comment").autoPreview("${href.wiki_render()}", args, function(textarea, text, rendered) {24 $("#ticketchange div.comment").html(rendered);25 if (rendered)26 $("#ticketchange").show();27 else if ($("#ticketchange ul.changes").length == 0)28 $("#ticketchange").hide();29 });30 $("#trac-comment-editor textarea").autoPreview("${href.wiki_render()}", args,31 function(textarea, text, rendered) {32 var comment = $("#trac-comment-editor").next("div.comment");33 comment.html(rendered);34 if (rendered)35 comment.show();36 else37 comment.hide();38 });39 22 $("#modify").parent().toggleClass("collapsed"); 40 23 $(".trac-topnav a").click(function() { $("#modify").parent().removeClass("collapsed"); }); 41 24 … … 50 33 } 51 34 actions.click(updateActionFields); 52 35 updateActionFields(); 53 </py:when> 54 <py:otherwise> 55 $("#field-summary").focus(); 56 </py:otherwise> 57 <py:if test="preview_mode"> 36 37 $("#propertyform").autoSubmit({preview: '1'}, function(data, reply) { 38 var items = $(reply); 39 $("#ticket").replaceWith(items.filter('#ticket')); 40 var changes = $("#ticketchange-content").html(items.filter('ul.changes, div.comment')); 41 $("#ticketchange").toggle(changes.children().length != 0); 42 }); 43 $("#trac-comment-editor").autoSubmit({preview_comment: '1'}, function(data, reply) { 44 var comment = $("#trac-comment-editor").next("div.comment").html(reply); 45 comment.toggle(comment.children().length != 0); 46 }); 47 <py:if test="preview_mode"> 58 48 $("#changelog").parent().toggleClass("collapsed"); 59 49 $("#attachments").toggleClass("collapsed"); 60 50 $("#trac-add-comment").scrollToTop(); 61 </py:if> 51 </py:if> 52 </py:when> 53 <py:otherwise> 54 $("#propertyform").autoSubmit({preview: '1'}, function(data, reply) { 55 $('#ticket').replaceWith(reply); 56 }); 57 <py:if test="not preview_mode"> 58 $("#field-summary").focus(); 59 </py:if> 60 </py:otherwise> 62 61 }); 63 62 </script> 64 63 </head> … … 77 76 has_edit_comment = 'TICKET_EDIT_COMMENT' in perm(ticket.resource); 78 77 has_property_editor = not version and version != 0 and not cnum_edit 79 78 and (can_append or can_modify or can_edit or can_create)"> 80 <div class="trac-topnav" py:if=" (ticket.exists or preview_mode)and has_property_editor">79 <div class="trac-topnav" py:if="ticket.exists and has_property_editor"> 81 80 <a href="#propertyform" title="Go to the ticket editor">Modify</a> ↓ 82 81 </div> 83 82 <h1 id="trac-ticket-title" py:choose=""> … … 101 100 </py:otherwise> 102 101 </h1> 103 102 104 <xi:include href="ticket_box.html" py:if="ticket.exists or preview_mode"/>105 103 106 104 <py:if test="ticket.exists"> 105 <xi:include href="ticket_box.html" py:with="preview_mode = change_preview.fields"/> 106 107 107 <!--! do not show attachments for old versions of this ticket or for new tickets --> 108 108 <py:if test="not version and version != 0 and ticket.exists"> 109 109 <xi:include href="list_of_attachments.html" … … 127 127 show_editor = can_edit_comment and str(change.cnum) == cnum_edit; 128 128 show_history = str(change.cnum) == cnum_hist; 129 129 max_version = max(change.comment_history); 130 comment_version = (max_version, int(cversion or 0))[show_history]"> 131 <div class="change" id="${'cnum' in change and 'trac-change-%d' % change.cnum or None}"> 130 comment_version = (max_version, int(cversion or 0))[show_history]; 131 show_buttons = not show_editor and comment_version == max_version"> 132 <div class="change${not show_buttons and ' trac-nobuttons' or ''}" 133 id="${'cnum' in change and 'trac-change-%d' % change.cnum or None}"> 132 134 <h3 class="change"> 133 135 <span class="threading" py:if="'cnum' in change" 134 136 py:with="change_replies = replies.get(str(change.cnum), [])"> … … 150 152 </span> 151 153 <i18n:msg params="date, author">Changed ${dateinfo(change.date)} ago by ${authorinfo(change.author)}</i18n:msg> 152 154 </h3> 153 <py:if test=" not show_editor and comment_version == max_version">155 <py:if test="show_buttons"> 154 156 <form py:if="'cnum' in change and can_edit_comment" method="get" action="#comment:${change.cnum}"> 155 157 <div class="inlinebuttons"> 156 158 <input type="hidden" name="cnum_edit" value="${change.cnum}"/> … … 197 199 <!--! End of the section we don't show on initial new tickets --> 198 200 199 201 <form py:if="has_property_editor" method="post" id="propertyform" 200 action="${ticket.exists and href.ticket(ticket.id) + '#trac-add-comment' or href.newticket()}"> 202 action="${href.ticket(ticket.id) + '#trac-add-comment' if ticket.exists 203 else href.newticket() + '#ticket'}"> 201 204 <!--! Add comment --> 202 <div py:if="ticket.exists and can_append" class="field" 203 py:with="show_comment_preview = (change_preview.fields or change_preview.comment) and cnum_edit is None"> 205 <div py:if="ticket.exists and can_append" class="field"> 204 206 <div class="trac-nav"> 205 207 <a href="#content" title="View ticket fields and description">View</a> ↑ 206 208 </div> … … 221 223 may have failed. 222 224 </div> 223 225 <!--! Preview of ticket changes --> 224 <div id="ticketchange" class="ticketdraft" style="${not show_comment_preview and 'display: none' or None}"> 226 <div id="ticketchange" class="ticketdraft" 227 style="${(not (change_preview.fields or change_preview.comment) 228 or cnum_edit is not None) and 'display: none' or None}"> 225 229 <h3 class="change" id="${'cnum' in change_preview and 'comment:%d' % change_preview.cnum or None}"> 226 230 <span class="threading" py:if="'replyto' in change_preview"> 227 231 in reply to: ${commentref('↑ ', change_preview.replyto)} 228 232 </span> 229 233 <i18n:msg params="author">Changed by ${authorinfo(change_preview.author)}</i18n:msg> 230 234 </h3> 231 < xi:include href="ticket_change.html" py:with="change = change_preview"/>235 <div id="ticketchange-content"><xi:include href="ticket_change.html" py:with="change = change_preview"/></div> 232 236 </div> 233 237 </div> 234 238 239 235 240 <div> 236 241 <h2 py:if="ticket.exists" class="foldable">Modify Ticket</h2> 237 242 <div id="modify"> … … 409 414 410 415 </form> 411 416 417 <xi:include href="ticket_box.html" py:if="not ticket.exists" py:with="preview_mode = True"/> 418 412 419 <div id="help" i18n:msg=""> 413 420 <strong>Note:</strong> See 414 421 <a href="${href.wiki('TracTickets')}">TracTickets</a> for help on using -
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: 13 13 xmlns:i18n="http://genshi.edgewall.org/i18n" 14 14 py:with="show_editor = value_of('show_editor', False)" py:strip=""> 15 15 <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())"> 17 17 <strong>${field.label}</strong> 18 18 <py:choose> 19 19 <py:when test="field_name == 'attachment'"><i18n:msg params="name"> … … Arguments: 35 35 </py:choose> 36 36 </li> 37 37 </ul> 38 <form py:if="show_editor" id="trac-comment-editor" method="post" action="#comment:${change.cnum}"> 38 <form py:if="show_editor" id="trac-comment-editor" method="post" 39 action="${href.ticket(ticket.id) + '#comment:%d' % change.cnum}"> 39 40 <div> 40 41 <textarea name="edited_comment" class="wikitext trac-resizable" rows="10" cols="78"> 41 42 ${(edited_comment, change.comment)[edited_comment is None]}</textarea> … … Arguments: 55 56 class="comment searchable ticketdraft" style="${not text and 'display: none' or None}" xml:space="preserve"> 56 57 ${wiki_to_html(context, text, escape_newlines=preserve_newlines)} 57 58 </div> 58 <div py:otherwise="" py:choose="" class="comment searchable" xml:space="preserve"> 59 <py:when test="show_history">${wiki_to_html(context, change.comment_history[int(cversion)].comment, 60 escape_newlines=preserve_newlines)}</py:when> 61 <py:otherwise>${wiki_to_html(context, change.comment, escape_newlines=preserve_newlines)}</py:otherwise> 59 <div py:when="show_history" class="comment searchable" xml:space="preserve"> 60 ${wiki_to_html(context, change.comment_history[int(cversion)].comment, escape_newlines=preserve_newlines)} 61 </div> 62 <div py:when="change.comment" class="comment searchable" xml:space="preserve"> 63 ${wiki_to_html(context, change.comment, escape_newlines=preserve_newlines)} 62 64 </div> 63 65 </py:choose> 64 66 </html> -
new file trac/ticket/templates/ticket_preview.html
diff --git a/trac/ticket/templates/ticket_preview.html b/trac/ticket/templates/ticket_preview.html new file mode 100644
- + 1 <!--! 2 Render a ticket box preview and a ticket change preview, to be sent as a reply 3 to a ticket change auto-preview. 4 --> 5 <html xmlns="http://www.w3.org/1999/xhtml" 6 xmlns:py="http://genshi.edgewall.org/" 7 xmlns:xi="http://www.w3.org/2001/XInclude" 8 xmlns:i18n="http://genshi.edgewall.org/i18n" 9 py:strip=""> 10 <xi:include href="ticket_box.html" py:with="can_append = 'TICKET_APPEND' in perm(ticket.resource)"/> 11 <xi:include href="ticket_change.html" py:with="change = change_preview"/> 12 </html> -
trac/ticket/web_ui.py
diff --git a/trac/ticket/web_ui.py b/trac/ticket/web_ui.py
a b from trac.ticket.api import TicketSystem 36 36 from trac.ticket.model import Milestone, Ticket, group_milestones 37 37 from trac.ticket.notification import TicketNotifyEmail 38 38 from trac.timeline.api import ITimelineEventProvider 39 from trac.util import as_bool, get_reporter_id39 from trac.util import as_bool, as_int, get_reporter_id 40 40 from trac.util.datefmt import format_datetime, from_utimestamp, \ 41 41 to_utimestamp, utc 42 42 from trac.util.text import exception_to_unicode, obfuscate_email_address, \ … … class TicketModule(Component): 441 441 442 442 data['fields'] = fields 443 443 444 if req.get_header('X-Requested-With') == 'XMLHttpRequest': 445 data['preview_mode'] = True 446 return 'ticket_box.html', data, None 447 444 448 add_stylesheet(req, 'common/css/ticket.css') 445 449 add_script(req, 'common/js/folding.js') 446 450 Chrome(self.env).add_wiki_toolbars(req) 451 Chrome(self.env).add_auto_preview(req) 447 452 return 'ticket.html', data, None 448 453 449 454 def _process_ticket_request(self, req): 450 455 id = int(req.args.get('id')) 451 version = req.args.get('version', None) 452 if version is not None: 453 try: 454 version = int(version) 455 except ValueError: 456 version = None 456 version = as_int(req.args.get('version'), None) 457 xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' 458 459 if xhr and 'preview_comment' in req.args: 460 context = web_context(req, 'ticket', id, version) 461 escape_newlines = self.must_preserve_newlines 462 rendered = format_to_html(self.env, context, 463 req.args.get('edited_comment', ''), 464 escape_newlines=escape_newlines) 465 req.send(rendered.encode('utf-8')) 457 466 458 467 req.perm('ticket', id, version).require('TICKET_VIEW') 459 468 ticket = Ticket(self.env, id, version=version) … … class TicketModule(Component): 572 581 self._insert_ticket_data(req, ticket, data, 573 582 get_reporter_id(req, 'author'), field_changes) 574 583 584 if xhr: 585 data['preview_mode'] = bool(data['change_preview']['fields']) 586 return 'ticket_preview.html', data, None 587 575 588 mime = Mimeview(self.env) 576 589 format = req.args.get('format') 577 590 if format: -
trac/wiki/web_api.py
diff --git a/trac/wiki/web_api.py b/trac/wiki/web_api.py
a b 13 13 14 14 from trac.core import * 15 15 from trac.resource import Resource 16 from trac.util import as_int 16 17 from trac.web.api import IRequestHandler 17 18 from trac.web.chrome import web_context 18 19 from trac.wiki.formatter import format_to … … class WikiRenderer(Component): 36 37 req.perm.require('TRAC_ADMIN') 37 38 realm = req.args.get('realm', 'wiki') 38 39 id = req.args.get('id') 39 version = req.args.get('version') 40 if version is not None: 41 try: 42 version = int(version) 43 except ValueError: 44 version = None 40 version = as_int(req.args.get('version'), None) 45 41 text = req.args.get('text', '') 46 42 flavor = req.args.get('flavor') 47 43 options = {}
