view website/issues/html/issue.item.html @ 8527:d4a43d9da8ef

chore(build): build(deps): bump anchore/scan-action from 7.3.1 to 7.3.2 pull #82
author John Rouillard <rouilj@ieee.org>
date Mon, 23 Feb 2026 20:16:55 -0500
parents 4ac0bbb3e440
children
line wrap: on
line source

<tal:block metal:use-macro="templates/page/macros/icing">
<title metal:fill-slot="head_title">
<tal:block condition="context/id" i18n:translate=""
 >Issue <tal:x tal:content="context/id" i18n:name="id"
 />: <tal:x tal:content="context/title" i18n:name="title"
 /> - <tal:x tal:content="config/TRACKER_NAME" i18n:name="tracker"
/></tal:block>
<tal:block condition="not:context/id" i18n:translate=""
 >New Issue - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
/></tal:block>
</title>
<tal:block metal:fill-slot="body_title">
 <span tal:condition="python: not (context.id or context.is_edit_ok())"
  tal:omit-tag="python:1" i18n:translate="">New Issue</span>
 <span tal:condition="python: not context.id and context.is_edit_ok()"
  tal:omit-tag="python:1" i18n:translate="">New Issue Editing</span>
 <span tal:condition="python: context.id and not context.is_edit_ok()"
  tal:omit-tag="python:1" i18n:translate="">Issue <tal:x
  replace="context/id" i18n:name="id" /></span>
 <span tal:condition="python: context.id and context.is_edit_ok()"
  tal:omit-tag="python:1" i18n:translate="">Issue<tal:x
  replace="context/id" i18n:name="id" /> Editing</span>
</tal:block>

<td class="content" metal:fill-slot="content">

<p tal:condition="python:not (context.is_view_ok()
 or request.user.hasRole('Anonymous'))" i18n:translate="">
 You are not allowed to view this page.</p>

<p tal:condition="python:not context.is_view_ok()
 and request.user.hasRole('Anonymous')" i18n:translate="">
 Please login with your username and password.</p>

<div tal:condition="context/is_view_ok">

<form method="POST" name="itemSynopsis"
      onSubmit="return submit_once()" enctype="multipart/form-data"
      tal:attributes="action context/designator">

<fieldset><legend>classification</legend>
<table class="form">
<tr>
 <th class="required"><label for="title" i18n:translate="">Title:</label></th>
 <td colspan="3" tal:condition="context/title/is_edit_ok"
     tal:content="structure python:context.title.field(id='title', size=60)">title</td>
 <td colspan="3" tal:condition="not:context/title/is_edit_ok">
  <span tal:content="context/title/plain"/>
  <input type="hidden" name="title" tal:attributes="value context/title">
 </td>
</tr>

<tr>
 <th class="required" i18n:translate="">
   <span tal:replace="structure python:db.issue_type.classhelp('id,name,description',label='Type')" />:
 </th>
 <td tal:content="structure context/type/menu">type</td>
 <th i18n:translate="">
   <span tal:replace="structure python:db.severity.classhelp('id,name,description',label='Severity')" />:
 </th>
 <td tal:content="structure context/severity/menu">components</td>
</tr>

<tr>
 <th i18n:translate="">
   <span tal:replace="structure python:db.component.classhelp('id,name,description',label='Components')" />:
 </th>
 <td tal:content="structure context/components/menu">components</td>
 <th i18n:translate="">
   <span tal:replace="structure python:db.version.classhelp('id,name,description',label='Versions')" />:
 </th>
 <td tal:content="structure context/versions/menu">versions</td>
</tr>
</table>
</fieldset>

<fieldset><legend>process</legend>
<table class="form">
<tr tal:condition="context/id">
 <th i18n:translate="">
   <span tal:replace="structure python:db.status.classhelp('id,name,description',label='Status')" />:
 </th>
 <td tal:content="structure context/status/menu">status</td>
 <th><label for="resolution" i18n:translate="">Resolution:</label></th>
 <td tal:content="structure python:context.resolution.menu(html_kwargs={'id':'resolution'})">resolution</td>
</tr>

<tr tal:condition="context/id">
 <th> <label for="dependencies" i18n:translate="">Dependencies</label>
  <span tal:condition="context/dependencies/is_edit_ok"
        tal:replace="structure python:db.issue.classhelp('id,title', filter='status=0,1', property='dependencies')" />
 </th>
 <td>
  <span tal:replace="structure python:context.dependencies.field(id='dependencies',showid=1,size=20)" />
  <span tal:condition="context/dependencies" tal:repeat="d python:context.dependencies.sorted('creation')">
   <br/>View: <a tal:attributes="href string:issue${d/id}" tal:content="d/id"></a>
  </span>
 </td>
 <th>
  <tal:block><label for="superseder" i18n:translate="">Superseder</label></tal:block>:
  <span tal:condition="context/superseder/is_edit_ok"
        tal:replace="structure python:db.issue.classhelp('id,title', filter='status=0,1', property='superseder')" />
 </th>
 <td>
  <span tal:replace="structure python:context.superseder.field(id='superseder', showid=1, size=20)" />
 <span tal:condition="context/superseder">
<!--   <br><span i18n:translate="">View</span>:
     <a tal:repeat="sup context/superseder"
        tal:content="python:sup['id'] + ', '*(not repeat['sup'].end)"
        tal:attributes="href string:issue${sup/id}; title sup/title;"></a> -->
   <br><span i18n:translate="">View</span>:
     <a tal:content="context/superseder/id"
        tal:attributes="href string:issue${context/superseder/id}; title context/superseder/title;"></a> 
  </span> 
 </td>
 </tr>
 <tr>
 <th><label for="assignee" i18n:translate="">Assigned To</label>:</th>
 <td tal:condition="context/status/is_edit_ok">
  <select id="assignee" name="assignee">
   <option value="-1">nobody</option>
   <tal:block tal:repeat="userdata python:db._db.user.filter_sql('select id,_username from _user where _roles like \'%Developer%\' order by _username')">
    <option tal:attributes="value python:userdata[0]; 
                            selected python:str(userdata[0]) == context.assignee._value"
            tal:content="python:userdata[1]"></option>
   </tal:block>
  </select>
 </td>
 <td tal:condition="not:context/assignee/is_edit_ok">
  <tal:x tal:content="context/assignee/plain" />
 </td>
 <th><label for="nosy" i18n:translate="">Nosy List</label>:
  <span tal:condition="context/nosy/is_edit_ok"
        tal:replace="structure python:db.user.classhelp('username,realname,address', property='nosy')" />
 </th>
 <td>
  <span tal:replace="structure python:context.nosy.field(id='nosy')" />
 </td>
</tr>
<tr>
 <th>
   <span tal:replace="structure python:db.priority.classhelp('id,name,description',label='Priority')" />:
 </th>
 <td tal:content="structure context/priority/menu">priority</td>
 <th><label for="keywords" i18n:translate="">Keywords</label>:</th>
 <td tal:content="structure python:context['keywords'].menu(height=5,html_kwargs={'id': 'keywords'})">keywords</td>


</tr>
<tr tal:condition="context/is_edit_ok">
 <th><label for="@note" i18n:translate="">Comment</label>:</th>
 <td colspan="3">
  <textarea tal:content="request/form/@note/value | default"
            id="@note" name="@note" wrap="hard" rows="10" cols="72"></textarea>
 </td>
</tr>

<tr tal:condition="context/is_edit_ok">
 <th><label for="file-1@content" i18n:translate="">File</label>:</th>
 <td>
   <input type="hidden" name="@link@files" value="file-1">
   <input type="file" id="file-1@content" name="file-1@content" size="40">
 </td>
  <th><label for="file-1@description" i18n:translate="">File Description</label>:</th>
 <td colspan=3><input type="text" class="fileDesc" id="file-1@description" name="file-1@description" size="40"></td>
</tr>
<tr tal:condition="context/is_edit_ok">
  <td colspan=4>
   <textarea readonly id="DropZone" aria-labeledby="DropZone">
     paste images or drag and drop files here....
   </textarea>
  </td>
</tr>
</table>
</fieldset>
<table class="form">
<tr tal:condition="context/is_edit_ok">
 <td>
  &nbsp;
  <input type="hidden" name="@template" value="item">
  <input type="hidden" name="@required" value="title">
 </td>
 <td colspan=3>
  <span tal:replace="structure context/submit">submit button</span>
  <a tal:condition="context/id" tal:attributes="href context/copy_url"
   i18n:translate="">Make a copy</a>
 </td>
</tr>
</table>
</form>

<!-- drag and drop code -->
              <script tal:attributes="nonce request/client/client_nonce">
                /* multiple file drops cause issues with redefined
                   file-X@content issues. input multiple assumes
                   it can start numbering from 1 for each of the
                   multiple files. However numbering here does the
                   same leading to duplicate file-2@content.

                   layout needs some work, alignnment of new file
                   input's isn't great.

                   Need a way to delete or reset file inputs so file
                   assigned to them isn't uploaded. Clicking on button
                   in chrome and then canceling unsets the file. But this
                   sequence does nothing in firefox.

                   Pasting always uses image.<type> can't name file.
                   Need to query user during paste for name/description.
                */

                let newInput=null;
                let NextInputNum = 100; /* file input 1 is hardcoded in form.
                     It is a multiple file input control. To
                     prevent collision, we start dynamic file controls at
                     file-100@content. 100 is larger than we expect
                     the number of files uploaded using file input 1.*/

                let target = document.getElementById('DropZone');
                target.style.display = "block";
                let body = document.body;
                let fileInput = document.getElementById('file-1@content');
		let fileDesc = document.getElementById('file-1@description');

		function make_clear_fileInput_closure(input) {
		  return function(ev) { input.value = "";
		  ev.preventDefault();}
		  }


                function make_new_clear_button() {
		  newClearInput=document.createElement('button');
		  newClearInput.textContent = "X";
		  newClearInput.setAttribute("aria-label",
		    "Clear next file input.");
		  newClearInput.setAttribute("title",
		    "Clear next file input.");
		  newClearInput.classList.add("clearButton");
		  return newClearInput;
		}

                function make_new_file_input() {
                  newInput=document.createElement('input');
                  newInput.type="file";
                  newInput.id="file-" + NextInputNum +"@content";
                  newInput.name=newInput.id;
                  return newInput;
		}

                function add_file_input () {
                 // Only allow one change listener on newest input.
                 fileInput.removeEventListener('change',
                          add_file_input,
                          false);
		  newClearInput = fileInput.insertAdjacentElement(
		                                   'beforebegin',
		                                   make_new_clear_button());

		  // add change handler to file clear button
                  newClearInput.addEventListener('click',
			make_clear_fileInput_closure(fileInput),
			false);

		  /* Insert break so next input is on new line */
		  br = fileInput.insertAdjacentElement('afterend',
					document.createElement('br'));

                  /* create new file input to get next dragged file */
                  /* <input type="file" name="file-2@content"> for 2,
                     3, 4, ... */
                  fileInput = br.insertAdjacentElement('afterend',
                                                    make_new_file_input());

                  // add change hander to newest file input
                  fileInput.addEventListener('change',
                        add_file_input, // create new input for more files
                        false);

                  /* link file-N to list of files on issue.
                     also link to msg-1 */
                  addLink=document.createElement('input');
                  addLink.type="hidden";
                  addLink.id="@link@file=file-" + NextInputNum;
                  addLink.name="@link@files"
                  addLink.value="file-" + NextInputNum;
                  fileInput.insertAdjacentElement('afterend', addLink);

                  addLink=document.createElement('input');
                  addLink.type="hidden";
                  addLink.id="msg-1@link@files=file-" + NextInputNum;
                  addLink.name="msg-1@link@files"
                  addLink.value="file-" + NextInputNum
                  fileInput.insertAdjacentElement('afterend', addLink);

		  /* break line before description field to prevent
		   wrapping multiple descriptions onto one line when
		   zoomed out or large display.*/
		  br = fileDesc.insertAdjacentElement('afterend',
				       document.createElement('br'));
                  fileDesc=document.createElement('input');
                  fileDesc.type="text";
                  fileDesc.id="file-" + NextInputNum + "@description";
                  fileDesc.name=fileDesc.id
                  fileDesc.size = 40
		  fileDesc.classList.add("fileDesc");
                  fileDesc=br.insertAdjacentElement('afterend', fileDesc);

                  NextInputNum = NextInputNum+1;
                }

                function MarkDropZone(e, active) {
                    active == true ? e.style.backgroundColor = "goldenrod" :
                    e.style.backgroundColor = "";
                }
                fileInput.addEventListener('change',
                         add_file_input, // create new input for more files
                         false);

                target.addEventListener('dragover', (e) => {
                    e.preventDefault();
                    body.classList.add('dragging');
                });

                target.addEventListener('dragenter', (e) => {
                    e.preventDefault();
                    MarkDropZone(target, true);
                });


                target.addEventListener('dragleave', (e) => {
                    e.preventDefault();
                    MarkDropZone(target, false);
                });

                target.addEventListener('dragleave', () => {
                    body.classList.remove('dragging');
                });

                target.addEventListener('drop', (e) => {
                    body.classList.remove('dragging');
                    MarkDropZone(target, false);

		    // Only allow single file drop unless
		    // fileInput name is @file that can support
		    // multiple file drop and file drop is multiple.
		    if (( fileInput.name != "@file" ||
			  ! fileInput.hasAttribute('multiple')) &&
			  e.dataTransfer.files.length != 1 ) {
			alert("File input can only accept one file.")
			e.preventDefault();
			return
		    }
                    // set file input files to the dragged files
                    fileInput.files = e.dataTransfer.files;

                    add_file_input(); // create new input for more files
                    // run last otherwise firefox empties e.dataTransfer
                    e.preventDefault();
                });

                target.addEventListener('mouseover', (e) => {
                    e.preventDefault();
                    MarkDropZone(target, true);
                });

                target.addEventListener('mouseout', (e) => {
                    e.preventDefault();
                    MarkDropZone(target, false);
                });

                target.addEventListener('paste', (event) => {
                    // https://mobiarch.wordpress.com/2013/09/25/upload-image-by-copy-and-paste/
                    // add paste event listener to the page

                    // https://stackoverflow.com/questions/50427513/
                    // html-paste-clipboard-image-to-file-input
                    if ( event.clipboardData.files.length == 0) {
                       // if not file data alert
                       alert("No image found for pasting");
                       event.preventDefault();
                       return;
                    }
                    fileInput.files = event.clipboardData.files;

                    /* Possible enhancement if file check fails.
                       iterate over all items 0 ...:
                           event.clipboardData.items.length
                       look at all items[i].kind for 'string' and
                       items[i].type looking for a text/plain item. If
                       found,
                         event.clipboardData.items[1].getAsString(
                           callback_fcn(s))

                       where callback function that creates a new
                       dataTransfer object with a file and insert the
                       content s and assigns it to the input.

                       https://gist.github.com/guest271314/7eac2c21911f5e40f489\33ac78e518bd
                    */
                    add_file_input(); // create new input for more files
                    // do not paste contents to dropzone
                    event.preventDefault();
                }, false);
              </script>
              <style tal:attributes="nonce request/client/client_nonce">
                #FileArea button.clearButton ~ input[type=file] {display:inline-block;}
                #DropZone {     /* don't display dropzone by default.
                                   Displayed as block by javascript. */
                             display:none;
	                     width: 100%;
                                /* override textarea inset */
                             border-style: dashed;
                             padding: 3ex 0; /* larger dropzone */
                             /* add space below inputs */
                             margin-block-start: 1em;
                                /* lighter color */
                             background: rgba(255,255,255,0.4);
                          }
		input[id$=\@content], input.fileDesc {margin-block-end: 0.5em}
              </style>

<p tal:condition="context/id" i18n:translate="">
 Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b>
 by <b><tal:x replace="context/creator" i18n:name="creator" /></b>,
 last changed <b><tal:x replace="python:context.activity.pretty('%Y-%m-%d %H:%M')" i18n:name="activity" /></b>
 by <b><tal:x replace="context/actor" i18n:name="actor" /></b>.
</p>

<table class="files" tal:condition="context/files">
 <tr><th colspan="5" class="header" i18n:translate="">Files</th></tr>
 <tr>
  <th i18n:translate="">File name</th>
  <th i18n:translate="">Uploaded</th>
  <th i18n:translate="">Description</th>
  <th i18n:translate="">Edit</th>
  <th i18n:translate="">Remove</th>
 </tr>
 <tr tal:repeat="file python:context.files.sorted('creation')">
  <td>
   <a tal:attributes="href file/download_url"
      tal:content="file/name">dld link</a>
  </td>
  <td>
   <span tal:content="file/creator">creator's name</span>,
   <span tal:content="python:file.creation.pretty('%Y-%m-%d %H:%M')">creation date</span>
  </td>
  <td tal:content="file/description" />
  <td><a tal:condition="file/is_edit_ok"
          tal:attributes="href string:file${file/id}">edit</a>
  </td>
  <td>
   <form style="padding:0" method="POST" tal:condition="file/is_edit_ok"
         tal:attributes="action string:issue${context/id}">
    <input type="hidden" name="@remove@files" tal:attributes="value file/id">
    <input type="hidden" name="@action" value="edit">
    <input type="submit" value="remove" i18n:attributes="value">
   </form>
  </td>
 </tr>
</table>

<table class="messages" tal:condition="context/messages">
 <tr><th colspan="4" class="header" i18n:translate="">Messages</th></tr>
 <tal:block tal:repeat="msg context/messages">
  <tr>
   <th><a tal:attributes="href string:msg${msg/id}"
    i18n:translate="">msg<tal:x replace="msg/id" i18n:name="id" /></a></th>
   <th i18n:translate="">Author: <tal:x replace="python:msg.author.realname.plain()"
       i18n:name="author" /> (<tal:x replace="msg/author"/>)</th>
   <th i18n:translate="">Date: <tal:x replace="python:msg.date.pretty('%Y-%m-%d %H:%M')"
       i18n:name="date" /></th>
   <th>
    <form style="padding:0" method="POST" tal:condition="msg/is_edit_ok"
          tal:attributes="action string:issue${context/id}">
     <input type="hidden" name="@remove@messages" tal:attributes="value msg/id">
     <input type="hidden" name="@action" value="edit">
     <input type="submit" value="remove" i18n:attributes="value">
    </form>
   </th>
  </tr>
  <tr>
   <td colspan="4" class="content">
    <pre tal:condition="python:msg.content.is_view_ok()"
         tal:content="structure python:utils.localReplace(msg.content.hyperlinked())">content</pre>
   </td>
  </tr>
 </tal:block>
</table>

<tal:block tal:condition="context/id" tal:replace="structure context/history" />

</div>

</td>

</tal:block>
<!-- SHA: ad841842c0da5f9d1a7f69a1e0c847a549b75bf2 -->

Roundup Issue Tracker: http://roundup-tracker.org/