Edgewall Software

ChristianBoos: ticket_edit_mode-r4932-ticket-preview.diff

File ticket_edit_mode-r4932-ticket-preview.diff, 19.9 kB (added by cboos, 18 months ago)

The work-in-progress patch I used to produce the above prototypes

  • trac/ticket/web_ui.py

     
    264264    def _process_newticket_request(self, req): 
    265265        context = Context(self.env, req)('ticket') 
    266266        req.perm.require('TICKET_CREATE') 
     267        preview = 'preview' in req.args 
    267268 
    268269        if req.method == 'POST' and 'field_owner' in req.args and \ 
    269270               'TICKET_MODIFY' not in req.perm: 
    270271            del req.args['field_owner'] 
    271272 
    272         if req.method == 'POST' and 'preview' not in req.args: 
     273        if req.method == 'POST' and not preview: 
    273274            self._do_create(context) # ...redirected 
    274275 
    275276        # Preview a new ticket 
     
    279280        self._populate(req, ticket) 
    280281        ticket.values['reporter'] = get_reporter_id(req, 'reporter') 
    281282 
    282         data = {} 
    283         data['ticket'] = ticket 
    284         data['context'] = context 
     283        data = {'ticket': ticket, 
     284                'context': context, 
     285                'action': preview and 'view' or 'edit'} 
    285286 
    286287        field_names = [field['name'] for field in ticket.fields 
    287288                       if not field.get('custom')] 
     
    322323 
    323324    def _process_ticket_request(self, req): 
    324325        req.perm.require('TICKET_VIEW') 
    325         action = req.args.get('action', ('history' in req.args and 'history' or 
    326                                          'view')) 
     326        action = 'history' in req.args and 'history' or 'view' 
     327        action = req.args.get('action', action) 
     328        # FIXME: currently action can also be a transtion: leave, accept, ... 
     329        edit = 'edit' in req.args 
     330        preview = 'preview' in req.args 
    327331        id = int(req.args.get('id')) 
    328332         
    329333        context = Context(self.env, req)('ticket', id) 
    330          
    331334        ticket = context.resource 
    332335         
    333         data = {} 
    334         data['ticket'] = ticket 
    335         data['context'] = context 
    336          
     336        data = {'ticket': ticket, 'context': context, 
     337                'edit': edit, 'preview': preview} 
     338 
    337339        if action in ('history', 'diff'): 
    338340            field = req.args.get('field') 
    339341            if field: 
     
    347349            elif action == 'diff': 
    348350                return self._render_diff(context, data, text_fields) 
    349351        elif req.method == 'POST': 
    350             if 'preview' not in req.args: 
     352            if not preview and not edit: 
    351353                self._do_save(context) 
    352354            else: 
    353355                # Use user supplied values 
  • trac/ticket/templates/ticket.html

     
    3939      ${prevnext_nav('Ticket', 'Back to Query')} 
    4040    </div> 
    4141 
    42     <div id="content" class="ticket"> 
    43       <!-- Ticket number, summary, etc heading --> 
     42    <div id="content" class="ticket" py:with="latest_version = not version and version != 0 "> 
     43 
     44      <!-- Ticket number, summary, etc. heading --> 
    4445      <h1> 
    45      
    46         <py:choose>  
     46        <py:choose> 
    4747          <py:when test="ticket.id"> 
    4848            <a py:strip="not version and version != 0" href="${href.ticket(ticket.id)}"> 
    4949              Ticket #${ticket.id} 
     
    5454          </py:otherwise> 
    5555        </py:choose> 
    5656 
    57         <span class="status">(<py:choose><py:when test="ticket.status">${ticket.status}</py:when><py:otherwise>new</py:otherwise></py:choose> <py:if 
     57        <span py:if="ticket.id" class="status"> 
     58          (<py:choose><py:when test="ticket.status">${ticket.status}</py:when><py:otherwise>new</py:otherwise></py:choose> <py:if 
    5859            test="ticket.type">${ticket.type}</py:if><py:if 
    59             test="ticket.resolution">: ${ticket.resolution}</py:if>)</span> 
     60            test="ticket.resolution">: ${ticket.resolution}</py:if>) 
     61        </span> 
    6062        <py:choose test=""> 
    6163          <py:when test="type(version) is Undefined" /> 
    6264          <py:when test="version is None" /> 
     
    6971        </py:choose> 
    7072      </h1> 
    7173 
    72       <!-- Ticket (pre)view --> 
    73       <div id="ticket" py:attrs="(('preview' in req.args) or (not ticket.id)) and {'class':'ticketdraft'} or {}"> 
    74         <div class="date"> 
    75           <p py:if="opened">Opened ${dateinfo(opened)} ago</p> 
    76           <p py:if="lastmod">Last modified ${dateinfo(lastmod)} ago</p> 
    77           <p py:if="not opened"><i>(future ticket)</i></p> 
    78         </div> 
    79         <!-- use a placeholder if it's a new ticket --> 
    80         <h2 class="summary searchable"> 
    81           <py:choose> 
    82             <py:when test="ticket.summary"> 
    83               ${ticket.summary} 
    84             </py:when> 
    85             <py:otherwise> 
    86               <i>(ticket summary)</i> 
    87             </py:otherwise> 
    88           </py:choose> 
    89         </h2> 
     74      <form py:if="latest_version and ('TICKET_CHGPROP' in perm or ('TICKET_CREATE' in perm and not ticket.id))" 
     75            py:strip="not edit and not preview" action="#preview" method="post"> 
    9076 
    91         <table class="properties" 
    92                py:with="fields = [f for f in fields if not f.skip]"> 
    93           <tr> 
    94             <th id="h_reporter">Reported by:</th> 
    95             <td headers="h_reporter" class="searchable">${authorinfo(ticket.reporter)}</td> 
    96             <th id="h_owner">Assigned to:</th> 
    97             <td headers="h_owner">${authorinfo(ticket.owner)} 
    98               <py:if test="ticket.status == 'assigned'">(accepted)</py:if> 
    99             </td> 
    100           </tr> 
    101           <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')" 
    102             py:with="fullrow = len(row) == 1"> 
    103             <py:for each="idx, field in enumerate(row)"> 
    104               <th py:if="idx == 0 or not fullrow"> 
    105                 <py:if test="field">${field.label or field.name}:</py:if> 
    106               </th> 
    107               <td py:if="idx == 0 or not fullrow" 
     77        <!-- Ticket (pre)view --> 
     78        <div id="ticket" py:attrs="preview and {'class':'ticketdraft'}"> 
     79 
     80          <!--! created/last modified --> 
     81          <div class="date"> 
     82            <p py:if="opened">Opened ${dateinfo(opened)} ago</p> 
     83            <p py:if="lastmod">Last modified ${dateinfo(lastmod)} ago</p> 
     84            <p py:if="not opened"><i>(not yet created)</i></p> 
     85          </div> 
     86 
     87          <!--! summary --> 
     88          <h2 class="summary searchable" py:choose=""> 
     89            <py:when test="ticket.summary">${ticket.summary}<br /></py:when> 
     90            <py:otherwise>Summary: </py:otherwise> 
     91            <input py:attrs="edit and {'type': 'text', 'size': '70'} or {'type': 'hidden'}" 
     92              id="field-summary" name="field_summary" value="${ticket.summary}" /> 
     93          </h2> 
     94 
     95          <table class="properties" 
     96            py:with="fields = [f for f in fields if not f.skip]"> 
     97            <tr> 
     98              <th id="h_reporter">Reported by:</th> 
     99              <td headers="h_reporter" class="searchable">${authorinfo(ticket.reporter)}</td> 
     100              <th id="h_owner">Assigned to:</th> 
     101              <td headers="h_owner">${authorinfo(ticket.owner)} 
     102                <py:if test="ticket.status == 'assigned'">(accepted)</py:if> 
     103              </td> 
     104            </tr> 
     105            <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')" 
     106              py:with="fullrow = len(row) == 1"> 
     107              <py:for each="idx, field in enumerate(row)"> 
     108                <th py:if="idx == 0 or not fullrow"> 
     109                  <py:if test="field">${field.label or field.name}:</py:if> 
     110                </th> 
     111                <td py:if="idx == 0 or not fullrow" 
    108112                  class="${field.name in ('cc', 'keywords') and 'searchable' or None}" 
    109113                  colspan="${fullrow and 3 or None}"> 
    110                 <py:if test="field">${ticket[field.name]}</py:if> 
    111               </td> 
    112             </py:for> 
    113           </tr> 
    114         </table> 
    115         <form py:if="ticket.description" method="get" action="#comment" 
    116               class="printableform"> 
    117           <div class="description"> 
    118             <h3 id="comment:description"> 
    119               <!-- check ticket.id; you can't reply to a ticket that does not yet exist. --> 
    120               <span py:if="'TICKET_APPEND' in perm and ticket.id" class="inlinebuttons"> 
    121                 <input type="hidden" name="replyto" value="description" /> 
    122                 <input type="submit" name="reply" value="Reply" title="Reply, quoting this description" /> 
    123               </span> 
    124               Description 
    125               <span py:if="description_change" class="lastmod" 
    126                 title="$description_change.date"> 
    127                 (last modified by ${authorinfo(description_change.author)}) 
    128                 (<a href="${href.ticket(ticket.id, action='diff', version=description_change.cnum)}">diff</a>) 
    129               </span> 
    130             </h3> 
    131             <div class="searchable"> 
    132               ${wiki_to_html(context, ticket.description)} 
     114                  <py:if test="field"> 
     115                    <input py:if="preview" type="hidden" id="field-${field.name}" 
     116                      name="field_${field.name}" value="${ticket[field.name]}" /> 
     117                    <py:if test="ticket.id">${ticket[field.name]} <br /></py:if> 
     118                    <py:choose test="edit and field.type"> 
     119                      <py:when test="False" /> 
     120                      <select py:when="'select'" id="field-${field.name}" name="field_${field.name}"> 
     121                        <option py:if="field.optional"></option> 
     122                        <option py:for="option in field.options" 
     123                          selected="${ticket[field.name] == option or None}" 
     124                          py:content="option"></option> 
     125                      </select> 
     126                      <textarea py:when="'textarea'" id="field-${field.name}" name="field_${field.name}" 
     127                        cols="${field.width}" rows="${field.height}" 
     128                        py:content="ticket[field.name]"></textarea> 
     129                      <span py:when="'checkbox'"> 
     130                        <input type="checkbox" id="field-${field.name}" name="field_${field.name}" 
     131                          checked="${ticket[field.name] == '1' and 'checked' or None}" value="1" /> 
     132                        <input type="hidden" name="field_checkbox_${field.name}" value="1" /> 
     133                      </span> 
     134                      <label py:when="'radio'" 
     135                        py:for="idx, option in enumerate(field.options)"> 
     136                        <input type="radio" name="field_${field.name}" value="${option}" 
     137                          checked="${ticket[field.name] == option or None}" /> 
     138                        ${option} 
     139                      </label> 
     140                      <input py:otherwise="" type="text" id="field-${field.name}" 
     141                        name="field_${field.name}" value="${ticket[field.name]}" /> 
     142                    </py:choose> 
     143                  </py:if> 
     144                </td> 
     145              </py:for> 
     146            </tr> 
     147          </table> 
     148          <form py:strip="edit or preview" method="get" action="#comment" 
     149            class="printableform"> 
     150            <div class="description"> 
     151              <h3 id="comment:description"> 
     152                <!-- check ticket.id; you can't reply to a ticket that does not yet exist. --> 
     153                <span py:if="'TICKET_APPEND' in perm and ticket.id and not edit" class="inlinebuttons"> 
     154                  <input type="hidden" name="replyto" value="description" /> 
     155                  <input type="submit" name="reply" value="Reply" title="Reply, quoting this description" /> 
     156                </span> 
     157                Description 
     158                <py:choose test=""> 
     159                  <py:when test="edit"> 
     160                    (you may use <a tabindex="42" class="lastmod" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here): 
     161                  </py:when> 
     162                  <span py:otherwise="" py:if="description_change" class="lastmod" 
     163                    title="$description_change.date"> 
     164                    (last modified by ${authorinfo(description_change.author)}) 
     165                    (<a href="${href.ticket(ticket.id, action='diff', version=description_change.cnum)}">diff</a>) 
     166                  </span> 
     167                </py:choose> 
     168              </h3> 
     169              <div class="searchable" py:choose=""> 
     170                <py:if test="ticket.description">${wiki_to_html(context, ticket.description)}</py:if> 
     171                <py:choose> 
     172                  <textarea py:when="edit" 
     173                    id="field-description" name="field_description" class="wikitext" 
     174                    rows="10" cols="68" py:content="ticket.description" /> 
     175                  <input py:otherwise="" type="hidden" id="field-description" name="field_description" 
     176                    value="ticket.description" /> 
     177                </py:choose> 
     178              </div> 
    133179            </div> 
    134           </div> 
    135         </form> 
    136       </div> 
    137       <!-- End of ticket (pre)view --> 
     180          </form> 
     181        </div> 
     182        <!-- End of ticket (pre)view --> 
    138183 
    139184      <!-- do not show attachments for old versions of this ticket or for new 
    140185        tickets --> 
     
    215260        </div> 
    216261      </py:if> 
    217262 
     263      <form py:if="latest_version and ('TICKET_APPEND' in perm)" 
     264            py:strip="edit" action="#preview" method="post"> 
     265 
    218266      <!-- TODO: edit form; handle ticket create perms. --> 
    219       <form py:if="not version and version != 0 and ('TICKET_APPEND' in perm or 'TICKET_CHGPROP' in perm or ('TICKET_CREATE' in perm and not ticket.id))" 
    220             action="#preview" method="post"> 
     267      <!--! --> 
    221268        <h3 py:if="ticket.id"><a name="edit" onfocus="$('#comment').get(0).focus()"> 
    222             Add/Change #${ticket.id} (${ticket.summary})</a></h3> 
     269            Comment on #${ticket.id} (${ticket.summary})</a></h3> 
    223270        <div py:if="authname == 'anonymous'" class="field"> 
    224271          <fieldset> 
    225272            <table> 
     
    245292          </fieldset> 
    246293        </div> 
    247294 
    248         <fieldset id="properties" py:if="'TICKET_CHGPROP' in req.perm or ('TICKET_CREATE' in req.perm and not ticket.id)" 
    249                   py:with="fields = [f for f in fields if not f.skip]"> 
    250           <legend>Change Properties</legend> 
    251           <table> 
    252             <tr> 
    253               <th><label for="field-summary">Summary:</label></th> 
    254               <td class="fullrow" colspan="3"> 
    255                 <input type="text" id="field-summary" name="field_summary" value="${ticket.summary}" 
    256                   size="70" /> 
    257               </td> 
    258             </tr> 
    259             <py:if test="'TICKET_ADMIN' in perm"> 
    260               <tr> 
    261                 <th><label for="field-reporter">Reporter:</label></th> 
    262                 <td class="fullrow" colspan="3"> 
    263                   <input type="text" id="field-reporter" name="field_reporter" 
    264                     value="${ticket.reporter}" size="70" /> 
    265                 </td> 
    266               </tr> 
    267             </py:if> 
    268             <py:if test="'TICKET_ADMIN' in perm or not ticket.id"> 
    269               <tr> 
    270                 <th><label for="field-description">Description:</label></th> 
    271                 <td class="fullrow" colspan="3"> 
    272                   <textarea id="field-description" name="field_description" class="wikitext" 
    273                     rows="10" cols="68" py:content="ticket.description"></textarea> 
    274                 </td> 
    275               </tr> 
    276             </py:if> 
    277             <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')" 
    278                 py:with="fullrow = len(row) == 1"> 
    279               <py:for each="idx, field in enumerate(row)"> 
    280                 <th class="col${idx + 1}" py:if="idx == 0 or not fullrow"> 
    281                   <label for="field-${field.name}" py:if="field" 
    282                          py:strip="field.type == 'radio'">${field.label or field.name}:</label> 
    283                 </th> 
    284                 <td class="col${idx + 1}" py:if="idx == 0 or not fullrow" 
    285                     py:attrs="{'colspan': fullrow and 3 or None}"> 
    286                   <py:choose test="field.type" py:if="field"> 
    287                     <select py:when="'select'" id="field-${field.name}" name="field_${field.name}"> 
    288                       <option py:if="field.optional"></option> 
    289                       <option py:for="option in field.options" 
    290                               selected="${ticket[field.name] == option or None}" 
    291                               py:content="option"></option> 
    292                     </select> 
    293                     <textarea py:when="'textarea'" id="field-${field.name}" name="field_${field.name}" 
    294                               cols="${field.width}" rows="${field.height}" 
    295                               py:content="ticket[field.name]"></textarea> 
    296                     <span py:when="'checkbox'"> 
    297                       <input type="checkbox" id="field-${field.name}" name="field_${field.name}" 
    298                              checked="${ticket[field.name] == '1' and 'checked' or None}" value="1" /> 
    299                       <input type="hidden" name="field_checkbox_${field.name}" value="1" /> 
    300                     </span> 
    301                     <label py:when="'radio'" 
    302                            py:for="idx, option in enumerate(field.options)"> 
    303                       <input type="radio" name="field_${field.name}" value="${option}" 
    304                              checked="${ticket[field.name] == option or None}" /> 
    305                       ${option} 
    306                     </label> 
    307                     <input py:otherwise="" type="text" id="field-${field.name}" 
    308                            name="field_${field.name}" value="${ticket[field.name]}" /> 
    309                   </py:choose> 
    310                 </td> 
    311               </py:for> 
    312             </tr> 
    313           </table> 
    314  
    315         </fieldset> 
    316  
    317295        <py:choose test="not actions or actions == ['leave']"> 
    318296 
    319297          <input py:when="True" type="hidden" name="action" value="leave" /> 
     
    361339          <input type="hidden" name="ts" value="${timestamp}" /> 
    362340          <input type="hidden" name="replyto" value="${replyto}" /> 
    363341          <input type="hidden" name="cnum" value="${cnum}" /> 
    364           <input type="submit" name="preview" value="Preview" accesskey="r" />&nbsp; 
     342          <py:choose test=""> 
     343            <input py:when="edit" type="submit" name="preview" value="Preview" accesskey="r" /> 
     344            <input py:otherwise="" type="submit" name="edit" value="Edit" accesskey="e" /> 
     345          </py:choose> 
    365346          <input type="submit" py:attrs="ticket.id and {'value':'Submit changes'} or {'value':'Create ticket'}" /> 
    366347        </div> 
    367  
    368348      </form> 
     349      </form> 
    369350 
    370351      <div id="help"> 
    371352        <strong>Note:</strong> See 
    372353        <a href="${href.wiki('TracTickets')}">TracTickets</a> for help on using 
    373354        tickets. 
    374355      </div> 
     356 
     357<!--! 
     358 
     359        <fieldset id="properties" py:if="'TICKET_CHGPROP' in req.perm or ('TICKET_CREATE' in req.perm and not ticket.id)" 
     360                  py:with="fields = [f for f in fields if not f.skip]"> 
     361          <legend>Change Properties</legend> 
     362          <table> 
     363            <py:if test="'TICKET_ADMIN' in perm"> 
     364              <tr> 
     365                <th><label for="field-reporter">Reporter:</label></th> 
     366                <td class="fullrow" colspan="3"> 
     367                  <input type="text" id="field-reporter" name="field_reporter" 
     368                    value="${ticket.reporter}" size="70" /> 
     369                </td> 
     370              </tr> 
     371            </py:if> 
     372            <tr py:for="row in group(fields, 2, lambda f: f.type != 'textarea')" 
     373                py:with="fullrow = len(row) == 1"> 
     374              <py:for each="idx, field in enumerate(row)"> 
     375                <th class="col${idx + 1}" py:if="idx == 0 or not fullrow"> 
     376                  <label for="field-${field.name}" py:if="field" 
     377                         py:strip="field.type == 'radio'">${field.label or field.name}:</label> 
     378                </th> 
     379                <td class="col${idx + 1}" py:if="idx == 0 or not fullrow" 
     380                    py:attrs="{'colspan': fullrow and 3 or None}"> 
     381                </td> 
     382              </py:for> 
     383            </tr> 
     384          </table> 
     385 
     386        </fieldset> 
     387--> 
     388 
    375389    </div> 
    376390  </body> 
    377391</html>