Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/templates/tracker/admin-components-page.ezt b/templates/tracker/admin-components-page.ezt
new file mode 100644
index 0000000..0c9d2d1
--- /dev/null
+++ b/templates/tracker/admin-components-page.ezt
@@ -0,0 +1,203 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="adminComponents.do" id="adminComponents" method="POST">
+ <input type="hidden" name="token" value="form_token]">
+
+ <h4>Issue components</h4>
+ [if-any perms.EditProject]
+ <span style="margin:0 .7em">Show:
+ <select id="rowfilter">
+ <option label="All components" value="all">
+ <option label="Active components" value="active" selected=true>
+ <option label="Top-level components" value="toplevel">
+ <option label="Components I administer" value="myadmin">
+ <option label="Components I am CC'd on" value="mycc">
+ <option label="Deprecated components" value="deprecated">
+ </select>
+ </span>
+ <span style="margin:0 .7em">Select:
+ <a id="selectall" href="#">All</a>
+ <a id="selectnone" href="#">None</a>
+ </span>
+ [end]
+
+ <div class="list-foot"></div>
+ [if-any perms.EditProject]
+ <form action="adminComponents.do" method="POST">
+ <a href="/p/[projectname]/components/create" class="buttonify primary">Create component</a>
+ <input type="hidden" name="delete_components">
+ <input type="hidden" name="token" value="[form_token]">
+ <input type="submit" class="secondary" name="deletebtn" value="Delete Component(s)" disabled>
+ </form>
+ <div id="deletebtnsfeedback" class="fielderror" style="margin-left:1em">
+ [if-any failed_perm]
+ You do not have permission to delete the components:
+ [failed_perm]<br/>
+ [end]
+ [if-any failed_subcomp]
+ Can not delete the following components because they have subcomponents:
+ [failed_subcomp]<br/>
+ [end]
+ [if-any failed_templ]
+ Can not delete the following components because they are listed in templates:
+ [failed_templ]<br/>
+ [end]
+ </div>
+ [end]
+
+ <div class="section">
+ <table cellspacing="0" cellpadding="2" border="0" class="comptable results striped vt active" id="resultstable" width="100%">
+ <tbody>
+ <tr>
+ [if-any perms.EditProject]<th></th>[end]
+ <th>ID</th>
+ <th>Name</th>
+ <th>Administrators</th>
+ <th>Auto Cc</th>
+ <th>Add Labels</th>
+ <th>Description</th>
+ </tr>
+ [if-any component_defs][else]
+ <tr>
+ <td colspan="5">
+ <div style="padding: 3em; text-align: center">
+ This project has not defined any components.
+ </div>
+ </td>
+ </tr>
+ [end]
+ [for component_defs]
+ [define detail_url]/p/[projectname]/components/detail?component=[format "url"][component_defs.path][end][end]
+ <tr data-url="[detail_url]" class="comprow [component_defs.classes]">
+ [if-any perms.EditProject]
+ <td class="cb rowwidgets">
+ <input type="checkbox" data-path="[component_defs.path]" class="checkRangeSelect">
+ </td>
+ [end]
+ <td>
+ [component_defs.component_id]
+ </td>
+ <td class="id">
+ <a style="white-space:nowrap" href="[detail_url]">[component_defs.path]</a>
+ </td>
+ <td>
+ [for component_defs.admins]
+ [include "../framework/user-link.ezt" component_defs.admins][if-index component_defs.admins last][else],[end]
+ [end]
+ </td>
+ <td>
+ [for component_defs.cc]
+ [include "../framework/user-link.ezt" component_defs.cc][if-index component_defs.cc last][else],[end]
+ [end]
+ </td>
+ <td>
+ [for component_defs.labels]
+ [component_defs.labels][if-index component_defs.labels last][else],[end]
+ [end]
+ </td>
+ <td>
+ [component_defs.docstring_short]
+ </td>
+ </tr>
+ [end]
+ </tbody>
+ </table>
+ </div>[# section]
+
+ <div class="list-foot"></div>
+ [if-any perms.EditProject]
+ <form action="adminComponents.do" method="POST">
+ <a href="/p/[projectname]/components/create" class="buttonify primary">Create component</a>
+ <input type="hidden" name="delete_components">
+ <input type="hidden" name="token" value="[form_token]">
+ <input type="submit" class="secondary" name="deletebtn" value="Delete Component(s)" disabled>
+ </form>
+ [end]
+
+</form>
+
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ if ($("selectall")) {
+ $("selectall").addEventListener("click", function() {
+ _selectAllIssues();
+ setDisabled(false);
+ });
+ }
+ if ($("selectnone")) {
+ $("selectnone").addEventListener("click", function() {
+ _selectNoneIssues();
+ setDisabled(true);
+ });
+ }
+
+ var checkboxNodes = document.getElementsByClassName("checkRangeSelect");
+ var checkboxes = Array();
+ for (var i = 0; i < checkboxNodes.length; ++i) {
+ var checkbox = checkboxNodes.item(i);
+ checkboxes.push(checkbox);
+ checkbox.addEventListener("click", function (event) {
+ _checkRangeSelect(event, event.target);
+ _highlightRow(event.target);
+ updateEnabled();
+ });
+ }
+
+ function updateEnabled() {
+ var anySelected = checkboxes.some(function(checkbox) {
+ return checkbox.checked;
+ });
+ setDisabled(!anySelected);
+ }
+
+ var deleteButtons = document.getElementsByName("deletebtn");
+ function setDisabled(disabled) {
+ for (var i = 0; i < deleteButtons.length; ++i) {
+ deleteButtons.item(i).disabled = disabled;
+ }
+ }
+
+ for (var i = 0; i < deleteButtons.length; ++i) {
+ deleteButtons.item(i).addEventListener("click", function(event) {
+ var componentsToDelete = [];
+ for (var i = 0; i< checkboxes.length; ++i) {
+ var checkbox = checkboxes[[]i];
+ if (checkbox.checked)
+ componentsToDelete.push(checkbox.getAttribute("data-path"));
+ }
+ var fields = document.getElementsByName("delete_components");
+ for (var i = 0; i< fields.length; ++i) {
+ fields.item(i).value = componentsToDelete.join();
+ }
+ if (!confirm("Are you sure you want to delete the selected components ?\nThis operation cannot be undone."))
+ event.preventDefault();
+ });
+ }
+
+ function _handleResultsClick(event) {
+ var target = event.target;
+ if (target.tagName == "A" || target.type == "checkbox" || target.className == "cb")
+ return;
+ while (target && target.tagName != "TR") target = target.parentNode;
+ _go(target.attributes[[]"data-url"].value,
+ (event.metaKey || event.ctrlKey || event.button == 1));
+ };
+ _addClickListener($("resultstable"), _handleResultsClick);
+
+
+ function _handleRowFilterChange(event) {
+ $("resultstable").classList.remove('all', 'active', 'toplevel', 'myadmin', 'mycc', 'deprecated');
+ $("resultstable").classList.add(event.target.value);
+ };
+ $("rowfilter").addEventListener("change", _handleRowFilterChange);
+});
+</script>
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/admin-labels-page.ezt b/templates/tracker/admin-labels-page.ezt
new file mode 100644
index 0000000..e8cb7ae
--- /dev/null
+++ b/templates/tracker/admin-labels-page.ezt
@@ -0,0 +1,138 @@
+[define category_css]css/ph_list.css[end]
+[include "../framework/header.ezt" "showtabs"]
+[include "../framework/js-placeholders.ezt"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="adminLabels.do" id="adminLabels" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+
+ <h4>Predefined issue labels</h4>
+ <div class="section">
+ [if-any perms.EditProject]
+ <table class="vt">
+ <tr><td>
+ <textarea name="predefinedlabels" rows="12" cols="75" style="tab-size:18">[labels_text]</textarea>
+ [if-any errors.label_defs]
+ <div class="fielderror">[errors.label_defs]</div>
+ [end]
+ <div>
+ Each issue may have <b>at most one</b> label with each of these prefixes:<br>
+ <input type="text" size="75" name="excl_prefixes"
+ value="[for config.excl_prefixes][config.excl_prefixes][if-index config.excl_prefixes last][else], [end][end]">
+ </div>
+ </td>
+ <td style="padding-left:.7em">
+ <div class="tip">
+ <b>Instructions:</b><br> List one label per line in desired sort-order.<br><br>
+ Optionally, use an equals-sign to document the meaning of each label.
+ </div>
+ </td>
+ </tr>
+ </table>
+ [else]
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped" width="100%">
+ <tr>
+ <th style="min-width:14em">Label</th>
+ <th width="100%">Meaning</th>
+ </tr>
+ [for config.issue_labels]
+ <tr>
+ <td style="white-space:nowrap; padding-right:2em; color:#363">[config.issue_labels.name]</td>
+ <td>[config.issue_labels.docstring]</td>
+ </tr>
+ [end]
+ </table>
+ [end]
+ </div>
+
+ [if-any perms.EditProject]
+ <input type="submit" id="savechanges" name="btn" value="Save changes" class="submit">
+ [end]
+
+ <br>
+ <br>
+
+ <h4>Custom fields</h4>
+ <div class="section">
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped vt" id="resultstable" width="100%">
+ <tbody>
+ <tr>
+ <th>ID</th>
+ <th>Name</th>
+ <th>Type</th>
+ <th>Required</th>
+ <th>Multivalued</th>
+ <th>Applicable to</th>
+ <th>Description</th>
+ </tr>
+ [if-any field_defs][else]
+ <tr>
+ <td colspan="40">
+ <div style="padding: 3em; text-align: center">
+ This project has not defined any custom fields.
+ </div>
+ </td>
+ </tr>
+ [end]
+ [for field_defs]
+ [define detail_url]/p/[projectname]/fields/detail?field=[field_defs.field_name][end]
+ [is field_defs.type_name "INT_TYPE"][define pretty_type_name]Integer[end][end]
+ [is field_defs.type_name "ENUM_TYPE"][define pretty_type_name]Enum[end][end]
+ [is field_defs.type_name "USER_TYPE"][define pretty_type_name]User[end][end]
+ [is field_defs.type_name "STR_TYPE"][define pretty_type_name]String[end][end]
+ [is field_defs.type_name "DATE_TYPE"][define pretty_type_name]Date[end][end]
+ [is field_defs.type_name "URL_TYPE"][define pretty_type_name]Url[end][end]
+ [is field_defs.type_name "APPROVAL_TYPE"][define pretty_type_name]Approval[end][end]
+ <tr data-url="[detail_url]">
+ <td>
+ [field_defs.field_def.field_id]
+ </td>
+ <td class="id" style="white-space:nowrap">
+ <a href="[detail_url]">[field_defs.field_name]</a></td>
+ <td style="white-space:nowrap">
+ [pretty_type_name]
+ </td>
+ <td style="white-space:nowrap">
+ [if-any field_defs.is_required_bool]Required[else]Optional[end]
+ </td>
+ <td style="white-space:nowrap">
+ [if-any field_defs.is_multivalued_bool]Multiple[else]Single[end]
+ </td>
+ <td style="white-space:nowrap">
+ [if-any field_defs.applicable_type][field_defs.applicable_type][else]Any issue[end]
+ </td>
+ <td>
+ [field_defs.docstring_short]
+ </td>
+ </tr>
+ [end]
+ </tbody>
+ </table>
+ <div class="list-foot"></div>
+ [if-any perms.EditProject]
+ <p><a href="/p/[projectname]/fields/create" class="buttonify primary">Add field</a></p>
+ [end]
+ </div>
+
+</form>
+
+[end]
+
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function _handleResultsClick(event) {
+ var target = event.target;
+ if (target.tagName == "A")
+ return;
+ while (target && target.tagName != "TR") target = target.parentNode;
+ _go(target.attributes[[]"data-url"].value,
+ (event.metaKey || event.ctrlKey || event.button == 1));
+ };
+ _addClickListener($("resultstable"), _handleResultsClick);
+});
+</script>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/admin-rules-page.ezt b/templates/tracker/admin-rules-page.ezt
new file mode 100644
index 0000000..2ffc3fa
--- /dev/null
+++ b/templates/tracker/admin-rules-page.ezt
@@ -0,0 +1,17 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="adminRules.do" id="adminRules" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+
+ [include "../framework/filter-rule-admin-part.ezt" "with_tracking_actions"]
+
+ <input type="submit" id="savechanges" name="btn" value="Save changes" class="submit">
+</form>
+
+[end]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/admin-statuses-page.ezt b/templates/tracker/admin-statuses-page.ezt
new file mode 100644
index 0000000..2d2d936
--- /dev/null
+++ b/templates/tracker/admin-statuses-page.ezt
@@ -0,0 +1,82 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="adminStatuses.do" id="adminStatuses" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+
+ [if-any perms.EditProject]
+ <table class="vt">
+ <tr><td>
+ <h4>Open Issue Status Values</h4>
+ <div class="section">
+ <textarea name="predefinedopen" rows="6" cols="75" style="tab-size:18">[open_text]</textarea>
+ [if-any errors.open_statuses]
+ <div class="fielderror">[errors.open_statuses]</div>
+ [end]
+ </div>
+ <h4>Closed Issue Status Values</h4>
+ <div class="section">
+ <textarea name="predefinedclosed" rows="6" cols="75" style="tab-size:18">[closed_text]</textarea><br><br>
+ [if-any errors.closed_statuses]
+ <div class="fielderror">[errors.closed_statuses]</div>
+ [end]
+
+ If an issue's status is being set to one of these values, offer to merge issues:<br>
+ <input type="text" size="75" name="statuses_offer_merge"
+ value="[for config.statuses_offer_merge][config.statuses_offer_merge][if-index config.statuses_offer_merge last][else], [end][end]">
+ </div>
+ </td>
+ <td style="padding-left:.7em">
+ <div class="tip">
+ <b>Instructions:</b><br> List one status value per line in desired sort-order.<br><br>
+ Optionally, use an equals-sign to document the meaning of each status value.
+ </div>
+ </td>
+ </tr>
+ </table>
+ [else]
+ <h4>Open Issue Status Values</h4>
+ <div class="section">
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped" width="100%">
+ <tr>
+ <th style="min-width:14em">Status</th>
+ <th width="100%">Meaning</th>
+ </tr>
+ [for config.open_statuses]
+ <tr>
+ <td style="white-space:nowrap; padding-right:2em;">[config.open_statuses.name]</td>
+ <td>[config.open_statuses.docstring]</td>
+ </tr>
+ [end]
+ </table>
+ </div>
+
+ <h4>Closed Issue Status Values</h4>
+ <div class="section">
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped" width="100%">
+ <tr>
+ <th style="min-width:14em">Status</th>
+ <th width="100%">Meaning</th>
+ </tr>
+ [for config.closed_statuses]
+ <tr>
+ <td style="white-space:nowrap; padding-right:2em;">[config.closed_statuses.name]</td>
+ <td>[config.closed_statuses.docstring]</td>
+ </tr>
+ [end]
+ </table>
+ </div>
+ [end]
+
+
+ [if-any perms.EditProject]
+ <input type="submit" id="savechanges" name="btn" value="Save changes" class="submit">
+ [end]
+</form>
+
+[end]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/admin-templates-page.ezt b/templates/tracker/admin-templates-page.ezt
new file mode 100644
index 0000000..2ef36f3
--- /dev/null
+++ b/templates/tracker/admin-templates-page.ezt
@@ -0,0 +1,76 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+[if-any perms.EditProject]
+ <h4>Default templates</h4>
+ <div class="section" style="padding-top:0">
+ <form action="adminTemplates.do" id="adminTemplates" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+
+ <div style="margin: 2em 0 1em 0">
+ Default template for project members:
+ <select name="default_template_for_developers" id="default_template_for_developers">
+ [for config.templates]
+ <option value="[config.templates.name]" [is config.templates.template_id config.default_template_for_developers]selected[end]>[config.templates.name]</option>
+ [end]
+ </select>
+ <br><br>
+
+ Default template for non-members:
+ <select name="default_template_for_users" id="default_template_for_users">
+ [for config.templates]
+ [define offer_template_in_users_menu]No[end]
+ [is config.templates.template_id config.default_template_for_users][define offer_template_in_users_menu]Yes[end][end]
+ [if-any config.templates.members_only][else][define offer_template_in_users_menu]Yes[end][end]
+ [is offer_template_in_users_menu "Yes"]
+ <option value="[config.templates.name]" [is config.templates.template_id config.default_template_for_users]selected[end]>[config.templates.name]</option>
+ [end]
+ [end]
+ </select>
+ </div>
+
+ <input type="submit" id="savechanges" name="btn" value="Save changes" class="submit" style="margin-left:0">
+ </form>
+ </div>
+[end]
+
+<h4>Issue templates</h4>
+<div class="section">
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped vt" id="resultstable" width="100%">
+ <tbody>
+ <tr>
+ <th>Name</th>
+ </tr>
+ [if-any config.templates][else]
+ <tr>
+ <td colspan="40">
+ <div style="padding: 3em; text-align: center">
+ This project has not defined any issue templates.
+ </div>
+ </td>
+ </tr>
+ [end]
+ [for config.templates]
+ [if-any config.templates.can_view perms.EditProject]
+ [define detail_url]/p/[projectname]/templates/detail?template=[format "url"][config.templates.name][end][end]
+ <tr data-url="detail_url">
+ <td style="white-space:nowrap" class="id">
+ <a href="[detail_url]">[config.templates.name]</a></td>
+ </td>
+ </tr>
+ [end]
+ [end]
+ </tbody>
+ </table>
+
+ [if-any perms.EditProject]
+ <p><a href="/p/[projectname]/templates/create" class="buttonify primary">Add template</a></p>
+ [end]
+</div>
+
+[end][# end if not read_only]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/admin-views-page.ezt b/templates/tracker/admin-views-page.ezt
new file mode 100644
index 0000000..5ec5d15
--- /dev/null
+++ b/templates/tracker/admin-views-page.ezt
@@ -0,0 +1,70 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="adminViews.do" id="adminViews" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+
+ [include "../framework/artifact-list-admin-part.ezt" "with_grid"]
+
+<h4 id="queries">Saved queries</h4>
+<div class="section">
+
+ <div class="closed">
+ <div>Saved queries help project visitors easily view relevant issue lists.
+ <a class="ifClosed toggleHidden" href="#"
+ style="font-size:90%; margin-left:.5em">Learn more</a>
+ </div>
+
+ <div id="filterhelp" class="ifOpened help">
+ Project owners can set up saved queries to make it easier for team members to
+ quickly run common queries. More importantly, project owners can use saved
+ queries to focus the team's attention on the issue lists that are most important
+ for the project's success. The project's saved queries are shown in the middle
+ section of the search dropdown menu that is next to the issue search box.
+ </div>
+ <br>
+
+ [if-any perms.EditProject]
+ [include "../framework/saved-queries-admin-part.ezt" "project"]
+ [else]
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped">
+ <tr>
+ <th align="left">Saved query name</th>
+ <th align="left">Search in</th>
+ <th align="left">Query</th>
+ </tr>
+ [for canned_queries]
+ <tr>
+ <td>[canned_queries.name]</td>
+ <td>
+ [define can][canned_queries.base_query_id][end]
+ [is can "1"]All issues[end]
+ [is can "2"]Open issues[end]
+ [is can "3"]Open and owned by me[end]
+ [is can "4"]Open and reported by me[end]
+ [is can "5"]Open and starred by me[end]
+ [is can "6"]New issues[end]
+ [is can "7"]Issues to verify[end]
+ [is can "8"]Open with comment by me[end]
+ </td>
+ <td>
+ [canned_queries.query]
+ </td>
+ </tr>
+ [end]
+ </table>
+ [end]
+ </div>
+</div>
+
+ [if-any perms.EditProject]
+ <input type="submit" id="savechanges" name="btn" value="Save changes" class="submit">
+ [end]
+</form>
+
+[end]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/approval-change-notification-email.ezt b/templates/tracker/approval-change-notification-email.ezt
new file mode 100644
index 0000000..c6a81ea
--- /dev/null
+++ b/templates/tracker/approval-change-notification-email.ezt
@@ -0,0 +1,23 @@
+[if-any comment.amendments][#
+ ]Updates:
+[#][for comment.amendments] [comment.amendments.field_name]: [format "raw"][comment.amendments.newvalue][end]
+[#][end][#
+ ][end]
+Comment #[comment.sequence] on issue [issue_local_id][#
+ ] by [comment.creator.display_name]: [format "raw"][summary][#
+][end]
+[approval_url]
+
+[if-any comment.content][#
+ ][for comment.text_runs][include "render-plain-text.ezt" comment.text_runs][end][#
+][else](No comment was entered for this change.)[#
+][end]
+[if-any comment.attachments][#
+ ]Attachments:
+[#][for comment.attachments][#
+ ] [comment.attachments.filename]: [domain_url][comment.attachments.downloadurl]&inline=1[end]
+[end]
+
+You are receiving this message because you are listed as the TL/PM
+on this issue or you are/were listed as an approver for this
+issue's approval.
diff --git a/templates/tracker/component-create-page.ezt b/templates/tracker/component-create-page.ezt
new file mode 100644
index 0000000..b818435
--- /dev/null
+++ b/templates/tracker/component-create-page.ezt
@@ -0,0 +1,129 @@
+[define title]Add a Component[end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<a href="/p/[projectname]/adminComponents">‹ Back to component list</a><br><br>
+
+
+<h4>Add a component</h4>
+
+<form action="create.do" method="POST">
+<input type="hidden" name="token" value="[form_token]">
+
+<table cellspacing="8" class="rowmajor vt">
+
+ <tr>
+ <th width="1%">Parent:</th>
+ <td>
+ <select name="parent_path" id="parent_path">
+ <option value="">Top level</option>
+ [for component_defs]
+ <option value="[component_defs.path]" [if-any component_defs.selected]selected=true[end]>[component_defs.path]</option>
+ [end]
+ </select>
+ </td>
+ <td rowspan="10">
+ <div class="tip">
+ <p>Components should describe the structure of the software being
+ built so that issues can be related to the correct parts.</p>
+
+ <p>Please use labels instead for releases,
+ milestones, task forces, types of issues, etc.</p>
+
+ <p>Deprecated components won't be shown in autocomplete.</p>
+ </div>
+ </td>
+ </tr>
+
+ <tr>
+ <th width="1%">Name:</th>
+ <td>
+ <input id="leaf_name" name="leaf_name" size="30" value="[initial_leaf_name]"
+ class="acob">
+ <span id="leafnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.leaf_name][errors.leaf_name][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ <textarea name="docstring" rows="4" cols="75">[initial_docstring]</textarea>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Admins:</th>
+ <td>
+ <textarea id="member_admins" name="admins" rows="3" cols="75">[for initial_admins][initial_admins], [end]</textarea>
+ <span id="memberadminsfeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.member_admins][errors.member_admins][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Auto Cc:</th>
+ <td>
+ <textarea id="member_cc" name="cc" rows="3" cols="75">[for initial_cc][initial_cc], [end]</textarea>
+ <span id="memberccfeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.member_cc][errors.member_cc][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Add Labels:</th>
+ <td>
+ <textarea id="labels" name="labels" rows="3" cols="75">[for initial_labels][initial_labels], [end]</textarea>
+ <span id="labelsfeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.labels][errors.labels][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Deprecated:</th>
+ <td>
+ <input type="checkbox" id="deprecated" name="deprecated">
+ </td>
+ </tr>
+
+ <tr>
+ <td></td>
+ <td>
+ <input id="submit_btn" type="submit" name="submit" value="Create component">
+ </td>
+ </tr>
+
+</table>
+</form>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ document.getElementById('submit_btn').disabled = 'disabled';
+ document.getElementById('leaf_name').focus();
+
+ function checkSubmit() {
+ _checkLeafName(
+ '[projectname]',
+ document.getElementById('parent_path').value,
+ '', CS_env.token);
+ }
+ setInterval(checkSubmit, 700);
+
+ var acobElements = document.getElementsByClassName("acob");
+ for (var i = 0; i < acobElements.length; ++i) {
+ var el = acobElements[[]i];
+ el.addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+});
+</script>
+
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/component-detail-page.ezt b/templates/tracker/component-detail-page.ezt
new file mode 100644
index 0000000..01757d1
--- /dev/null
+++ b/templates/tracker/component-detail-page.ezt
@@ -0,0 +1,169 @@
+[# Use raw format because the title variable will be escaped when used.]
+[define title]Component [format "raw"][component_def.path][end][end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<a href="/p/[projectname]/adminComponents">‹ Back to component list</a><br><br>
+
+
+<h4>Component</h4>
+[if-any creator]
+ Created by <a href="[creator.profile_url]">[creator.display_name]</a> [created]<br/>
+[end]
+[if-any modifier]
+ Last modified by <a href="[modifier.profile_url]">[modifier.display_name]</a> [modified]<br/>
+[end]
+
+<br/>
+<form action="detail.do" method="POST">
+<input type="hidden" name="token" value="[form_token]">
+<input type="hidden" name="component" value="[component_def.path]">
+<table cellspacing="8" class="rowmajor vt">
+ <tr>
+ <th width="1%">Name:</th>
+ <td>
+ [if-any allow_edit]
+ [if-any component_def.parent_path][component_def.parent_path]>[end]
+ <input id="leaf_name" name="leaf_name" value="[initial_leaf_name]" size="30" class="acob">
+ <span id="leafnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.leaf_name][errors.leaf_name][end]
+ </span>
+ [else]
+ [component_def.path]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea name="docstring" rows="4" cols="75">[initial_docstring]</textarea>
+ [else]
+ [component_def.docstring]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Admins:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea id="member_admins" name="admins" rows="3" cols="75">[for initial_admins][initial_admins], [end]</textarea>
+ <span id="memberadminsfeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.member_admins][errors.member_admins][end]
+ </span>
+ [else]
+ [for component_def.admins]
+ <div>[include "../framework/user-link.ezt" component_def.admins]</div>
+ [end]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Auto Cc:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea id="member_cc" name="cc" rows="3" cols="75">[for initial_cc][initial_cc], [end]</textarea>
+ <span id="memberccfeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.member_cc][errors.member_cc][end]
+ </span>
+ [else]
+ [for component_def.cc]
+ <div>[include "../framework/user-link.ezt" component_def.cc]</div>
+ [end]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Add Labels:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea id="labels" name="labels" rows="3" cols="75">[for initial_labels][initial_labels], [end]</textarea>
+ <span id="labelsfeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.labels][errors.labels][end]
+ </span>
+ [else]
+ [for component_def.labels]
+ <div>[component_def.labels]</div>
+ [end]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Deprecated:</th>
+ <td>
+ <input type="checkbox" id="deprecated" name="deprecated" [if-any initial_deprecated]checked="checked"[end]
+ [if-any allow_edit][else]disabled[end]>
+ </td>
+ </tr>
+
+ <tr>
+ <td></td>
+ <td>
+ [if-any allow_edit]
+ <div>
+ <span style="float:left;">
+ <input type="submit" name="submit" id="submit_btn" value="Submit changes">
+ <input type="submit" class="secondary" name="deletecomponent" value="Delete component"
+ [if-any allow_delete][else]disabled[end]
+ id="deletecomponent">
+ </span>
+ <span style="float:right;">
+ <a href="/p/[projectname]/components/create?component=[component_def.path]">Create new subcomponent</a>
+ </span>
+ <div style="clear:both;"></div>
+ </div>
+ [if-any allow_delete][else]
+ <br/><br/>
+ <b>Note:</b>
+ [if-any subcomponents]
+ <br/>
+ Can not delete this component because it has the following subcomponents:<br/>
+ [for subcomponents]<div style="margin-left:1em">[subcomponents.path]</div>[end]
+ [end]
+ [if-any templates]
+ <br/>
+ Can not delete this component because it is listed in the following templates:<br/>
+ [for templates]<div style="margin-left:1em">[templates.name]</div>[end]
+ [end]
+ [end]
+ [end]
+ </td>
+ </tr>
+
+</table>
+</form>
+
+[if-any allow_edit]
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function checkSubmit() {
+ _checkLeafName('[format "js"][projectname][end]', '[format "js"][component_def.parent_path][end]', '[format "js"][component_def.leaf_name][end]', CS_env.token);
+ }
+ setInterval(checkSubmit, 700);
+
+ if ($("deletecomponent")) {
+ $("deletecomponent").addEventListener("click", function(event) {
+ if (!confirm("Are you sure you want to delete [component_def.path]?\nThis operation cannot be undone."))
+ event.preventDefault();
+ });
+ }
+
+ var acobElements = document.getElementsByClassName("acob");
+ for (var i = 0; i < acobElements.length; ++i) {
+ var el = acobElements[[]i];
+ el.addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+});
+</script>
+[end]
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/field-create-page.ezt b/templates/tracker/field-create-page.ezt
new file mode 100644
index 0000000..9b0bc11
--- /dev/null
+++ b/templates/tracker/field-create-page.ezt
@@ -0,0 +1,402 @@
+[define title]Add a Field[end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<a href="/p/[projectname]/adminLabels">‹ Back to field list</a><br><br>
+
+
+<h4>Add a custom field</h4>
+
+<form action="create.do" method="POST">
+<input type="hidden" name="token" value="[form_token]">
+
+<table cellspacing="8" class="rowmajor vt">
+ <tr>
+ <th width="1%">Name:</th>
+ <td>
+ <input id="fieldname" name="name" size="30" value="[initial_field_name]" class="acob">
+ <span id="fieldnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.field_name][errors.field_name][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ <textarea name="docstring" rows="4" cols="75">[initial_field_docstring]</textarea>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Type:</th>
+ <td>
+ <select id="field_type" name="field_type">
+ <option value="enum_type" [is initial_type "enum_type"]selected="selected"[end]>Enum</option>
+ <option value="int_type" [is initial_type "int_type"]selected="selected"[end]>Integer</option>
+ <option value="str_type" [is initial_type "str_type"]selected="selected"[end]>String</option>
+ <option value="user_type" [is initial_type "user_type"]selected="selected"[end]>User</option>
+ <option value="date_type" [is initial_type "date_type"]selected="selected"[end]>Date</option>
+ <option value="url_type" [is initial_type "url_type"]selected="selected"[end]>URL</option>
+ <option value="approval_type" [is initial_type "approval_type"]selected="selected"[end]>Approval</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr class="js-make_phase_subfield">
+ <th>Issue Gate field:</th>
+ <td>
+ <input id="phase_input" type="checkbox" name="is_phase_field" class="acob"
+ [if-any initial_is_phase_field]checked="checked"[end]>
+ <label for="phase_input">This field can only belong to issue gates.</label>
+ </td>
+ </tr>
+
+ [if-any approval_names]
+ <tr class="js-make_approval_subfield">
+ <th>Parent Approval:</th>
+ <td>
+ <select id="parent_input" name="parent_approval_name">
+ <option value="" [is initial_parent_approval_name ""]selected[end]>Not an approval's subfield</option>
+ [for approval_names]
+ <option value="[approval_names]"
+ [is initial_parent_approval_name approval_names]selected[end]
+ >[approval_names]</option>
+ [end]
+ </select>
+ </td>
+ </tr>
+ [end]
+
+ [# TODO(jojwang): monorail:3241, evaluate how to use applicable/importance for approval subfields]
+ <tr id="applicable_row">
+ <th>Applicable:</th>
+ <td>When issue type is:
+ <select id="applicable_type" name="applicable_type">
+ <option value="" [is initial_applicable_type ""]selected="selected"[end]>Anything</option>
+ <option disabled="disabled">----</option>
+ [for well_known_issue_types]
+ <option value="[well_known_issue_types]" [is initial_applicable_type well_known_issue_types]selected="selected"[end]>[well_known_issue_types]</option>
+ [end]
+ </select>
+ [# TODO(jrobbins): AND with free-form applicability predicate.]
+ </td>
+ </tr>
+
+ <tr id="importance_row">
+ <th>Importance:</th>
+ <td>
+ <select id="importance" name="importance">
+ <option value="required" [is initial_importance "required"]selected[end]>Required when applicable</option>
+ <option value="normal" [is initial_importance "normal"]selected[end]>Offered when applicable</option>
+ <option value="niche" [is initial_importance "niche"]selected[end]>Under "Show all fields" when applicable</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr id="multi_row">
+ <th>Multivalued:</th>
+ <td>
+ <input type="checkbox" name="is_multivalued" class="acob"
+ [if-any initial_is_multivalued]checked="checked"[end]>
+ </td>
+ </tr>
+
+ <tr id="choices_row" style="display:none">
+ <th>Choices:</th>
+ <td>
+ <textarea id="choices" name="choices" rows="10" cols="75" style="tab-size:12"
+ >[initial_choices]</textarea>
+ </td>
+ </tr>
+
+ <tr id="int_row" style="display:none">
+ <th>Validation:</th>
+ <td>
+ Min value: <input type="number" name="min_value" style="text-align:right; width: 4em">
+ Max value: <input type="number" name="max_value" style="text-align:right; width: 4em"><br>
+ <span class="fielderror" style="margin-left: 1em">
+ [if-any errors.min_value][errors.min_value][end]</span><br>
+ </td>
+ </tr>
+
+ <tr id="str_row" style="display:none">
+ <th>Validation:</th>
+ <td>
+ Regex: <input type="text" name="regex" size="30"><br>
+ </td>
+ </tr>
+
+ <tr id="user_row" style="display:none">
+ <th>Validation:</th>
+ <td>
+ <input type="checkbox" name="needs_member" id="needs_member" class="acob"
+ [if-any initial_needs_member]checked[end]>
+ <label for="needs_member">User must be a project member</label><br>
+ <span id="needs_perm_span" style="margin-left:1em">
+ Required permission:
+ <input type="text" name="needs_perm" id="needs_perm" size="20"
+ value="[initial_needs_perm]" class="acob">
+ </span><br>
+ </td>
+ </tr>
+ <tr id="user_row2" style="display:none">
+ <th>Permissions:</th>
+ <td>
+ The users named in this field is granted this permission on this issue:<br>
+ [# TODO(jrobbins): one-click way to specify View vs. EditIssue vs. any custom perm.]
+ <input type="text" name="grants_perm" id="grants_perm" class="acob"
+ size="20" value="[initial_grants_perm]" autocomplete="off">
+ </td>
+ </tr>
+ <tr id="user_row3" style="display:none">
+ <th>Notification:</th>
+ <td>
+ The users named in this field will be notified via email whenever:<br>
+ <select name="notify_on">
+ <option value="never" [is initial_notify_on "0"]selected="selected"[end]
+ >No notifications</option>
+ <option value="any_comment" [is initial_notify_on "1"]selected="selected"[end]
+ >Any change or comment is added</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr id="date_row" style="display:none">
+ <th>Action:</th>
+ <td>
+ When this date arrives:
+ <select name="date_action">
+ <option value="no_action" [is initial_date_action "no_action"]selected="selected"[end]
+ >No action</option>
+ [# TODO(jrobbins): owner-only option.]
+ <option value="ping_participants" [is initial_date_action "ping_participants"]selected="selected"[end]
+ >Post a "ping" comment and notify all issue participants</option>
+ </select>
+ </td>
+ </tr>
+
+ <tr id="approval_row" style="display:none">
+ <th>Approvers:</th>
+ <td>
+ <input id="member_approvers" name="approver_names" size="75" value="[initial_approvers]"
+ autocomplete="off">
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.approvers][errors.approvers][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr id="approval_row2" style="display:none">
+ <th>Survey:</th>
+ <td>
+ Any information feature owners need to provide for the approval team should be requested here.
+ <textarea name="survey" rows="4" cols="75">[initial_survey]</textarea>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Admins:</th>
+ <td>
+ <input id="member_admins" name="admin_names" size="75" value="[initial_admins]"
+ autocomplete="off" class="acob">
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.field_admins][errors.field_admins][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr id="field_restriction">
+ <th>Restriction
+ <i id="editors_tooltip" class="material-icons inline-icon" style="font-size:14px; vertical-align: text-bottom"
+ title="Project owners and field admins can always edit the values of a custom field.">
+ info_outline</i> :
+ </th>
+ <td style="display:flex; align-items:center">
+ <input id="editors_checkbox" type="checkbox" name="is_restricted_field" class="acob"
+ [if-any initial_is_restricted_field]checked="checked"[end]>
+ Restrict users that can edit values of this custom field.
+ </td>
+ </tr>
+ <tr id="editors_input" style="display:none">
+ <th>Editors:</th>
+ <td>
+ <input id="member_editors" name="editor_names" size="75" value="[initial_editors]"
+ autocomplete="off" class="acob" disabled>
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.field_editors][errors.field_editors][end]
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <td></td>
+ <td>
+ <input id="submit_btn" type="submit" name="submit" value="Create field">
+ </td>
+ </tr>
+
+</table>
+</form>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var submit = document.getElementById('submit_btn');
+ submit.disabled = 'disabled';
+ var fieldname = document.getElementById('fieldname');
+ var oldName = '';
+ fieldname.focus();
+
+ var fieldNameRE = /^[[]a-z]([[]-_]?[[]a-z0-9])*$/i;
+
+ function checkFieldName() {
+ name = fieldname.value;
+ if (name != oldName) {
+ oldName = name;
+ feedback = document.getElementById('fieldnamefeedback');
+ submit.disabled = 'disabled';
+ if (name == '') {
+ feedback.textContent = 'Please choose a field name';
+ } else if (!fieldNameRE.test(name)) {
+ feedback.textContent = 'Invalid field name';
+ } else if (name.length > 30) {
+ feedback.textContent = 'Field name is too long';
+ } else {
+ _checkFieldNameOnServer('[projectname]', name, CS_env.token);
+ }
+ }
+ }
+
+ setInterval(checkFieldName, 700);
+
+ function updateForm(new_type) {
+ let choices_row = document.getElementById('choices_row');
+ choices_row.style.display = (new_type == 'enum_type') ? '' : 'none';
+
+ // Approval fields cannot be subfields of approvals.
+ let approval_subfield_display = (new_type == 'approval_type') ? 'none' : '';
+ let approval_subfield_rows = document.getElementsByClassName('js-make_approval_subfield');
+ Array.prototype.forEach.call(approval_subfield_rows, row => {
+ row.style.display = approval_subfield_display;
+ });
+
+ // Enum and Approval fields cannot be gate subfields.
+ let gate_subfield_display = (new_type == 'enum_type' || new_type == 'approval_type') ? 'none': '';
+ let phase_subfield_rows = document.getElementsByClassName('js-make_phase_subfield');
+ Array.prototype.forEach.call(phase_subfield_rows, row => {
+ row.style.display = gate_subfield_display;
+ });
+
+ // Prevent users from making a field a Gate and Approval subfield.
+ if ($('parent_input')) {
+ let phase_input = $('phase_input');
+ let parent_input = $('parent_input');
+ parent_input.addEventListener('change', () => {
+ if (parent_input.value === '') {
+ phase_input.disabled = false;
+ } else {
+ phase_input.disabled = true;
+ }
+ });
+ phase_input.addEventListener('change', () => {
+ if (phase_input.checked) {
+ parent_input.disabled = true;
+ } else {
+ parent_input.disabled = false;
+ }
+ });
+ };
+
+ let int_row = document.getElementById('int_row');
+ int_row.style.display = (new_type == 'int_type') ? '' : 'none';
+
+ let str_row = document.getElementById('str_row');
+ str_row.style.display = (new_type == 'str_type') ? '' : 'none';
+
+ let user_row_display = (new_type == 'user_type') ? '' : 'none';
+ document.getElementById('user_row').style.display = user_row_display;
+ document.getElementById('user_row2').style.display = user_row_display;
+ document.getElementById('user_row3').style.display = user_row_display;
+
+ let date_row_display = (new_type == 'date_type') ? '' : 'none';
+ document.getElementById('date_row').style.display = date_row_display;
+
+ let approval_row_display = (new_type == 'approval_type') ? '' : 'none';
+ let approval_row_hide = (new_type == 'approval_type') ? 'none' : '';
+ let new_type_is_approval = (new_type == 'approval_type');
+ document.getElementById(
+ 'multi_row').style.display = approval_row_hide;
+ document.getElementById(
+ 'importance_row').style.display = approval_row_hide;
+ document.getElementById(
+ 'applicable_row').style.display = approval_row_hide;
+ document.getElementById(
+ 'field_restriction').style.display = approval_row_hide;
+ if (new_type_is_approval) {
+ document.getElementById('editors_input').style.display = 'none';
+ } else {
+ if (document.getElementById('editors_checkbox').checked) {
+ document.getElementById('editors_input').style.display = '';
+ } else {
+ document.getElementById('editors_input').style.display = 'none';
+ }
+ }
+ document.getElementById(
+ 'editors_checkbox').disabled = new_type_is_approval;
+ document.getElementById(
+ 'member_editors').disabled = new_type_is_approval || !document.getElementById('editors_checkbox').checked;
+ document.getElementById('approval_row').style.display = approval_row_display;
+ document.getElementById('approval_row2').style.display = approval_row_display;
+ }
+
+ let type_select = document.getElementById('field_type');
+ updateForm(type_select.value);
+ type_select.addEventListener("change", function() {
+ updateForm(type_select.value);
+ });
+
+ let needs_perm_span = document.getElementById('needs_perm_span');
+ let needs_perm = document.getElementById('needs_perm');
+ function enableNeedsPerm(enable) {
+ needs_perm_span.style.color = enable ? 'inherit' : '#999';
+ needs_perm.disabled = enable ? '' : 'disabled';
+ if (!enable) needs_perm.value = '';
+ }
+ enableNeedsPerm(false);
+
+ //Enable editors input only when restricting the field.
+ document.getElementById('editors_checkbox').onchange = function() {
+ let member_editors = document.getElementById('member_editors');
+ let editors_input = document.getElementById('editors_input');
+ if (this.checked) {
+ editors_input.style.display = '';
+ } else {
+ editors_input.style.display = 'none';
+ }
+ member_editors.disabled = !this.checked;
+ };
+
+ let needs_member = document.getElementById("needs_member");
+ if (needs_member)
+ needs_member.addEventListener("change", function() {
+ enableNeedsPerm(needs_member.checked);
+ });
+
+ let acobElements = document.getElementsByClassName("acob");
+ for (let i = 0; i < acobElements.length; ++i) {
+ let el = acobElements[[]i];
+ el.addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+
+ $('member_approvers').addEventListener("focus", function(event) {
+ _acof(event);
+ });
+
+});
+</script>
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/field-detail-page.ezt b/templates/tracker/field-detail-page.ezt
new file mode 100644
index 0000000..75c09fa
--- /dev/null
+++ b/templates/tracker/field-detail-page.ezt
@@ -0,0 +1,421 @@
+[define title]Field [field_def.field_name][end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<a href="/p/[projectname]/adminLabels">‹ Back to field list</a><br><br>
+
+
+<h4>Custom field</h4>
+
+<form action="detail.do" method="POST">
+<input type="hidden" name="token" value="[form_token]">
+<input type="hidden" name="field" value="[field_def.field_name]">
+
+<table cellspacing="8" class="rowmajor vt">
+ <tr>
+ <th width="1%">Name:</th>
+ <td>
+ [if-any uneditable_name]
+ <input type="hidden" name="name" value="[field_def.field_name]">
+ [field_def.field_name]
+ [else][if-any allow_edit]
+ <input name="name" value="[field_def.field_name]" size="30" class="acob">
+ [else]
+ [field_def.field_name]
+ [end][end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea name="docstring" rows="4" cols="75">[field_def.docstring]</textarea>
+ [else]
+ [field_def.docstring]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Type:</th>
+ [# TODO(jrobbins): make field types editable someday.]
+ <td>[field_def.type_name]</td>
+ </tr>
+
+ [is field_def.type_name "APPROVAL_TYPE"]
+ <tr>
+ <th>Approvers:</th>
+ <td>
+ [if-any allow_edit]
+ <input id="member_approvers" name="approver_names" size="75" value="[initial_approvers]"
+ autocomplete="off">
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.approvers][errors.approvers][end]
+ </span>
+ [else]
+ [for field_def.approvers]
+ <div>[include "../framework/user-link.ezt" field_def.approvers]</div>
+ [end]
+ [end]
+ </td>
+ </tr>
+ <tr>
+ <th>Survey:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea name="survey" rows="4" cols="75">[field_def.survey]</textarea>
+ [else]
+ <table cellspacing="4" cellpadding="0" style="padding: 2px; border:2px solid #eee">
+ [for field_def.survey_questions]
+ <tr><td>[field_def.survey_questions]</td></tr>
+ [end]
+ </table>
+ [end]
+ </td>
+ </tr>
+
+ [if-any approval_subfields]
+ <tr>
+ <th>Subfields:</th>
+ <td>
+ [for approval_subfields]
+ <div><a href="/p/[projectname]/fields/detail?field=[approval_subfields.field_name]">
+ [approval_subfields.field_name]
+ </a></div>
+ [end]
+ </td>
+ </tr>
+ [end]
+ [else]
+
+ <tr>
+ <th>Issue Gate field:</th>
+ <td>
+ [if-any field_def.is_phase_field]Yes[else]No[end]
+ </td>
+ </tr>
+
+ [is field_def.field_name "Type"][else]
+ <tr>
+ <th>Applicable:</th>
+ <td>When issue type is:
+ [if-any allow_edit]
+ [define oddball_applicability]Yes[end]
+ <select id="applicable_type" name="applicable_type">
+ <option value=""
+ [is initial_applicable_type ""]
+ selected="selected"
+ [define oddball_applicability]No[end]
+ [end]
+ >Anything</option>
+ <option disabled="disabled">----</option>
+ [for well_known_issue_types]
+ <option value="[well_known_issue_types]"
+ [is initial_applicable_type well_known_issue_types]
+ selected="selected"
+ [define oddball_applicability]No[end]
+ [end]
+ >[well_known_issue_types]</option>
+ [end]
+ [# If an oddball type was used, keep it.]
+ [is oddball_applicability "Yes"]
+ <option value="[initial_applicable_type]" selected="selected"
+ >[initial_applicable_type]</option>
+ [end]
+ </select>
+ [else]
+ [initial_applicable_type]
+ [end]
+ [# TODO(jrobbins): editable applicable_predicate.]
+ </td>
+ </tr>
+ [end]
+
+ <tr>
+ <th>Importance:</th>
+ <td>
+ [if-any allow_edit]
+ <select id="importance" name="importance">
+ <option value="required" [is field_def.importance "required"]selected[end]>Required when applicable</option>
+ <option value="normal" [is field_def.importance "normal"]selected[end]>Offered when applicable</option>
+ <option value="niche" [is field_def.importance "niche"]selected[end]>Under "Show all fields" when applicable</option>
+ </select>
+ [else]
+ [is field_def.importance "required"]Required when applicable[end]
+ [is field_def.importance "normal"]Offered when applicable[end]
+ [is field_def.importance "niche"]Under "Show all fields" when applicable[end]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Multivalued:</th>
+ <td>
+ [if-any allow_edit]
+ <input type="checkbox" name="is_multivalued" class="acob"
+ [if-any field_def.is_multivalued_bool]checked="checked"[end]>
+ [else]
+ [if-any field_def.is_multivalued_bool]Yes[else]No[end]
+ [end]
+ </td>
+ </tr>
+ [end]
+
+ [# TODO(jrobbins): dynamically display validation info as field type is edited.]
+ [is field_def.type_name "ENUM_TYPE"]
+ <tr>
+ <th>Choices:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea name="choices" rows="10" cols="75" style="tab-size:18" [if-any allow_edit][else]disabled="disabled"[end]
+ >[initial_choices]</textarea>
+ [else]
+ <table cellspacing="4" cellpadding="0" style="padding: 2px; border:2px solid #eee">
+ [for field_def.choices]
+ <tr>
+ <td>[field_def.choices.name]</td>
+ <td>[if-any field_def.choices.docstring]= [end][field_def.choices.docstring]</td>
+ </tr>
+ [end]
+ </table>
+ [end]
+ </td>
+ </tr>
+ [end]
+
+ [is field_def.type_name "INT_TYPE"]
+ <tr id="int_row">
+ <th>Validation:</th>
+ <td>
+ Min value:
+ <input type="number" name="min_value" style="text-align:right; width: 4em"
+ value="[field_def.min_value]" class="acob"
+ [if-any allow_edit][else]disabled="disabled"[end]>
+
+ Max value:
+ <input type="number" name="max_value" style="text-align:right; width: 4em"
+ value="[field_def.max_value]" class="acob"
+ [if-any allow_edit][else]disabled="disabled"[end]>
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.min_value][errors.min_value][end]</span><br>
+ </td>
+ </tr>
+ [end]
+
+ [is field_def.type_name "STR_TYPE"]
+ <tr id="str_row">
+ <th>Validation:</th>
+ <td>
+ Regex: <input type="text" name="regex" size="30" value="[field_def.regex]" class="acob"><br>
+ <span class="fielderror" style="margin-left:1em"
+ >[if-any errors.regex][errors.regex][end]</span>
+ </td>
+ </tr>
+ [end]
+
+ [is field_def.type_name "USER_TYPE"]
+ <tr id="user_row">
+ <th>Validation:</th>
+ <td>
+ <input type="checkbox" name="needs_member" id="needs_member" class="acob"
+ [if-any allow_edit][else]disabled="disabled"[end]
+ [if-any field_def.needs_member_bool]checked="checked"[end]>
+ <label for="needs_member">User must be a project member</label><br>
+ <span id="needs_perm_span" style="margin-left:1em">Required permission:
+ <input type="text" name="needs_perm" id="needs_perm" size="20"
+ value="[field_def.needs_perm]" autocomplete="off" class="acob"
+ [if-any allow_edit][else]disabled="disabled"[end]></span><br>
+ </td>
+ </tr>
+ <tr id="user_row2">
+ <th>Permissions:</th>
+ <td>
+ The users named in this field is granted this permission on this issue:<br>
+ [# TODO(jrobbins): one-click way to specify View vs. EditIssue vs. any custom perm.]
+ <input type="text" name="grants_perm" id="grants_perm" class="acob"
+ size="20" value="[field_def.grants_perm]" autocomplete="off"
+ [if-any allow_edit][else]disabled[end]>
+ </td>
+ </tr>
+ <tr id="user_row3">
+ <th>Notification:</th>
+ <td>
+ The users named in this field will be notified via email whenever:<br>
+ <select name="notify_on" [if-any allow_edit][else]disabled[end]
+ class="acrob">
+ <option value="never" [is field_def.notify_on "0"]selected="selected"[end]
+ >No notifications</option>
+ <option value="any_comment" [is field_def.notify_on "1"]selected="selected"[end]
+ >Any change or comment is added</option>
+ </select>
+ </td>
+ </tr>
+ [end]
+
+ [is field_def.type_name "DATE_TYPE"]
+ <tr id="date_row">
+ <th>Action:</th>
+ <td>
+ [if-any allow_edit]
+ <select name="date_action">
+ <option value="no_action" [is field_def.date_action_str "no_action"]selected="selected"[end]
+ >No action</option>
+ [# TODO(jrobbins): owner-only option.]
+ <option value="ping_participants" [is field_def.date_action_str "ping_participants"]selected="selected"[end]
+ >Post a comment and notify all issue participants</option>
+ </select>
+ [else]
+ [is field_def.date_action_str "no_action"]No action[end]
+ [# TODO(jrobbins): owner-only option.]
+ [is field_def.date_action_str "ping_participants"]Post a comment and notify all issue participants[end]
+ [end]
+ </td>
+ </tr>
+ [end]
+
+ [if-any field_def.is_approval_subfield]
+ <tr>
+ <th>Parent Approval:</th>
+ <td>
+ <a href="/p/[projectname]/fields/detail?field=[field_def.parent_approval_name]">
+ [field_def.parent_approval_name]
+ </a>
+ </td>
+ </tr>
+ [end]
+
+ <th>Admins:</th>
+ <td>
+ [if-any allow_edit]
+ <input id="member_admins" name="admin_names" size="75" value="[initial_admins]"
+ autocomplete="off" class="acob">
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.field_admins][errors.field_admins][end]
+ </span>
+ [else]
+ [for field_def.admins]
+ <div>[include "../framework/user-link.ezt" field_def.admins]</div>
+ [end]
+ [end]
+ </td>
+ </tr>
+
+ [is field_def.type_name "APPROVAL_TYPE"][else]
+
+ <tr id="editors_restriction">
+ <th>Restriction
+ <i id="editors_tooltip" class="material-icons inline-icon" style="font-size:14px; vertical-align: text-bottom"
+ title="Project owners and field admins can always edit the values of a custom field.">
+ info_outline</i> :
+ </th>
+ <td style="display:flex; align-items:center">
+ [if-any allow_edit]
+ <input id="editors_checkbox" type="checkbox" name="is_restricted_field" class="acob"
+ [if-any field_def.is_restricted_field]checked="checked"[end]>
+ Restrict users that can edit values of this custom field.
+ [else]
+ [if-any field_def.is_restricted_field]Yes[else]No[end]
+ [end]
+ </td>
+ </tr>
+ <tr id="editors_input"
+ [if-any field_def.is_restricted_field][else]style="display:none"[end]>
+ <th>Editors:</th>
+ <td>
+ [if-any allow_edit]
+ <input id="member_editors" name="editor_names" size="75" value="[initial_editors]"
+ autocomplete="off" class="acob"
+ [if-any field_def.is_restricted_field][else]disabled[end]>
+ <span class="fielderror" style="margin-left:1em">
+ [if-any errors.field_editors][errors.field_editors][end]
+ </span>
+ [else]
+ [for field_def.editors]
+ <div>[include "../framework/user-link.ezt" field_def.editors]</div>
+ [end]
+ [end]
+ </td>
+ </tr>
+
+ [end]
+
+ <tr>
+ <td></td>
+ <td>
+ [if-any allow_edit]
+ <input type="submit" name="submit" value="Save changes">
+ <input type="submit" class="secondary" name="deletefield" value="Delete Field"
+ id="deletefield">
+ [end]
+ </td>
+ </tr>
+
+</table>
+</form>
+
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var needs_perm_span = document.getElementById('needs_perm_span');
+ var needs_perm = document.getElementById('needs_perm');
+ var needs_member = document.getElementById('needs_member');
+ function enableNeedsPerm(enable) {
+ needs_perm_span.style.color = enable ? 'inherit' : '#999';
+ needs_perm.disabled = enable ? '' : 'disabled';
+ if (!enable) needs_perm.value = '';
+ }
+ [if-any allow_edit]
+ if (needs_perm)
+ enableNeedsPerm(needs_member.checked);
+ [end]
+
+ if ($("deletefield")) {
+ $("deletefield").addEventListener("click", function(event) {
+ var msg = ("Are you sure you want to delete [field_def.field_name]?\n" +
+ "This operation cannot be undone. " +
+ "[if-any approval_subfields]\nAll subfields will also be deleted.[end]" +
+ "[is field_def.type_name "ENUM_TYPE"]\nEnum values will be retained on issues as labels.[end]");
+ if (!confirm(msg))
+ event.preventDefault();
+ });
+ }
+
+ [is field_def.type_name "APPROVAL_TYPE"][else]
+ //Enable editors input only when restricting the field.
+ document.getElementById('editors_checkbox').onchange = function() {
+ var member_editors = document.getElementById('member_editors');
+ var editors_input = document.getElementById('editors_input');
+ if (this.checked) {
+ editors_input.style.display = '';
+ } else {
+ editors_input.style.display = 'none';
+ }
+ member_editors.disabled = !this.checked;
+ };
+ [end]
+
+ var acobElements = document.getElementsByClassName("acob");
+ for (var i = 0; i < acobElements.length; ++i) {
+ var el = acobElements[[]i];
+ el.addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+
+ [is field_def.type_name "APPROVAL_TYPE"]
+ $('member_approvers').addEventListener("focus", function(event) {
+ _acof(event);
+ });
+ [end]
+
+ if ($("needs_member")) {
+ $("needs_member").addEventListener("change", function(event) {
+ enableNeedsPerm($("needs_member").checked);
+ });
+ }
+});
+</script>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/field-value-multi-date.ezt b/templates/tracker/field-value-multi-date.ezt
new file mode 100644
index 0000000..2e5e657
--- /dev/null
+++ b/templates/tracker/field-value-multi-date.ezt
@@ -0,0 +1,41 @@
+[if-any fields.values]
+ [for fields.values]
+ <input type="date" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ [if-index fields.values first]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ [if-index fields.values first][else]
+ <u class="removeMultiFieldValueWidget">X</u>
+ [end]
+ [if-index fields.values last]
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="date"
+ data-validate-1="[fields.field_def.min_value]" data-validate-2="[fields.field_def.max_value]"
+ data-phase-name="[arg2]"
+ >Add a value</u>
+ [end]
+ [end]
+[else]
+ <input type="date" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value=""
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="date"
+ data-validate-1="[fields.field_def.min_value]" data-validate-2="[fields.field_def.max_value]"
+ data-phase-name="[arg2]">Add a value</u>
+[end]
+
+[for fields.derived_values]
+ <input type="date" disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic; text-align:right; width:12em" class="multivalued"
+ aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-multi-enum.ezt b/templates/tracker/field-value-multi-enum.ezt
new file mode 100644
index 0000000..004b5ac
--- /dev/null
+++ b/templates/tracker/field-value-multi-enum.ezt
@@ -0,0 +1,66 @@
+[for fields.field_def.choices]
+ [define checked]No[end]
+ [define derived]No[end]
+ [for fields.values]
+ [is fields.values.val fields.field_def.choices.name]
+ [define checked]Yes[end]
+ [end]
+ [end]
+ [for fields.derived_values]
+ [is fields.derived_values.val fields.field_def.choices.name]
+ [define checked]Yes[end]
+ [define derived]Yes[end]
+ [end]
+ [end]
+
+ <label id="[fields.field_id]_[fields.field_def.choices.name]_label" class="enum_checkbox"
+ title="[is derived "Yes"]derived: [end][fields.field_def.choices.name][if-any fields.field_def.choices.docstring]: [fields.field_def.choices.docstring][end]"
+ [is derived "Yes"]style="font-style:italic"[end]>
+ <input type="checkbox" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]"
+ value="[fields.field_def.choices.name]"
+ id="[arg0]_custom_[fields.field_id]_[fields.field_def.choices.idx]"
+ [is checked "Yes"]checked="checked"[end] [is derived "Yes"]disabled="disabled"[end]
+ aria-labelledby="[fields.field_id]_label [fields.field_id]_[fields.field_def.choices.name]_label">
+ [fields.field_def.choices.name]
+ </label>
+
+[end]
+
+
+[# Also include any oddball values as plain text with an _X_ icon.]
+[for fields.values]
+ [define already_shown]No[end]
+ [for fields.field_def.choices]
+ [is fields.field_def.choices.name fields.values.val]
+ [define already_shown]Yes[end]
+ [end]
+ [end]
+ [is already_shown "No"]
+ <span class="enum_checkbox"
+ title="This is not a defined choice for this field"
+ id="span_[arg0]_oddball_[fields.values.idx]">
+ <a id="[arg0]_oddball_[fields.values.idx]" class="remove_oddball x_icon"></a>[fields.values.val]
+ [# Below hidden input contains the value of the field for tracker_helpers._ParseIssueRequestFields ]
+ <input type="text" class="labelinput" id="input_[arg0]_oddball_[fields.values.idx]" size="20" name="label"
+ value="[fields.field_name]-[fields.values.val]" hidden>
+ </span>
+ [end]
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var oddballAnchors = document.getElementsByClassName("remove_oddball");
+ for (var i = 0; i < oddballAnchors.length; ++i) {
+ var oddballAnchor = oddballAnchors[[]i];
+
+ oddballAnchor.addEventListener("click", function(event) {
+ var oddballSpan = $("span_" + this.id);
+ oddballSpan.style.display = "none";
+ var oddballInput = $("input_" + this.id);
+ oddballInput.value = "";
+ event.preventDefault();
+ });
+ }
+});
+</script>
+
diff --git a/templates/tracker/field-value-multi-int.ezt b/templates/tracker/field-value-multi-int.ezt
new file mode 100644
index 0000000..f17c2cf
--- /dev/null
+++ b/templates/tracker/field-value-multi-int.ezt
@@ -0,0 +1,41 @@
+[if-any fields.values]
+ [for fields.values]
+ <input type="number" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ [if-index fields.values first]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ [if-index fields.values first][else]
+ <u class="removeMultiFieldValueWidget">X</u>
+ [end]
+ [if-index fields.values last]
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="int"
+ data-validate-1="[fields.field_def.min_value]" data-validate-2="[fields.field_def.max_value]"
+ data-phase-name="[arg2]"
+ >Add a value</u>
+ [end]
+ [end]
+[else]
+ <input type="number" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value=""
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="int"
+ data-validate-1="[fields.field_def.min_value]" data-validate-2="[fields.field_def.max_value]"
+ data-phase-name="[arg2]">Add a value</u>
+[end]
+
+[for fields.derived_values]
+ <input type="number" disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic; text-align:right; width:12em" class="multivalued"
+ aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-multi-str.ezt b/templates/tracker/field-value-multi-str.ezt
new file mode 100644
index 0000000..62c72d7
--- /dev/null
+++ b/templates/tracker/field-value-multi-str.ezt
@@ -0,0 +1,33 @@
+[if-any fields.values]
+ [for fields.values]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [# TODO(jrobbins): string validation]
+ [if-index fields.values first]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [end]
+ style="width: 12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ [if-index fields.values first][else]
+ <u class="removeMultiFieldValueWidget">X</u>
+ [end]
+ [if-index fields.values last]
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="str" data-phase-name="[arg2]">Add a value</u>
+ [end]
+ [end]
+[else]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value=""
+ [# TODO(jrobbins): string validation]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width: 12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="str" data-phase-name="[arg2]">Add a value</u>
+[end]
+
+[for fields.derived_values]
+ <input disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic" style="width: 12em" class="multivalued"
+ aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-multi-url.ezt b/templates/tracker/field-value-multi-url.ezt
new file mode 100644
index 0000000..de8a3e1
--- /dev/null
+++ b/templates/tracker/field-value-multi-url.ezt
@@ -0,0 +1,29 @@
+[if-any fields.values]
+ [for fields.values]
+ <input type="text" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [if-index fields.values first]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [end]
+ style="width: 12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ [if-index fields.values first][else]
+ <u class="removeMultiFieldValueWidget">X</u>
+ [end]
+ [if-index fields.values last]
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="url" data-phase-name="[arg2]">Add a value</u>
+ [end]
+ [end]
+[else]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value=""
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width: 12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="url" data-phase-name="[arg2]">Add a value</u>
+[end]
+
+[for fields.derived_values]
+ <input disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic" style="width: 12em" class="multivalued" aria-labelledby="[fields.field_id]_label">
+[end]
\ No newline at end of file
diff --git a/templates/tracker/field-value-multi-user.ezt b/templates/tracker/field-value-multi-user.ezt
new file mode 100644
index 0000000..678cd79
--- /dev/null
+++ b/templates/tracker/field-value-multi-user.ezt
@@ -0,0 +1,32 @@
+[if-any fields.values]
+ [for fields.values]
+ <input type="text" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [# TODO(jrobbins): include fields.min_value and fields.max_value attrs]
+ [if-index fields.values first]
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [end]
+ style="width:12em" class="multivalued userautocomplete customfield" autocomplete="off"
+ data-ac-type="owner" aria-labelledby="[fields.field_id]_label">
+ [if-index fields.values first][else]
+ <u class="removeMultiFieldValueWidget">X</u>
+ [end]
+ [if-index fields.values last]
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="user" data-phase-name="[arg2]">Add a value</u>
+ [end]
+ [end]
+[else]
+ <input type="text" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value=""
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width:12em" class="multivalued userautocomplete customfield" autocomplete="off"
+ data-ac-type="owner" aria-labelledby="[fields.field_id]_label">
+ <u class="addMultiFieldValueWidget" data-field-id="[fields.field_id]" data-field-type="user" data-phase-name="[arg2]">Add a value</u>
+[end]
+
+[for fields.derived_values]
+ <input type="text" disabled="disabled" value="[fields.derived_values.val]"
+ style="width:12em" class="multivalued" aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-single-date.ezt b/templates/tracker/field-value-single-date.ezt
new file mode 100644
index 0000000..d9f344a
--- /dev/null
+++ b/templates/tracker/field-value-single-date.ezt
@@ -0,0 +1,43 @@
+[# Even though this field definition says it is single-valued, the issue might have
+ multiple values if the field definition was previously multi-valued. In such a situation
+ values other than the first value are shown read-only and must be explicitly removed
+ before the comment can be submitted. ]
+
+[# If the field has no explicit values, then show an empty form element.]
+[if-any fields.values][else]
+ <input type="date" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" id="[arg0]_custom_[fields.field_id]" value=""
+ [is arg0 "hidden"][else]
+ [if-any arg1] required="required"[end]
+ [end]
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+[end]
+
+
+[for fields.values]
+ [if-index fields.values first]
+ <input type="date" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ [else]
+ <span>
+ <input type="date" disabled="disabled" value="[fields.values.val]"
+ style="text-align:right; width: 12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ <u class="removeMultiFieldValueWidget">X</u>
+ </span>
+ [end]
+[end]
+
+[for fields.derived_values]
+ <input type="date" disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic; text-align:right; width:12em" class="multivalued"
+ aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-single-enum.ezt b/templates/tracker/field-value-single-enum.ezt
new file mode 100644
index 0000000..eb575a6
--- /dev/null
+++ b/templates/tracker/field-value-single-enum.ezt
@@ -0,0 +1,81 @@
+[if-any fields.values fields.derived_values]
+
+ [# TODO(jrobbins): a better UX for undesired values would be to replace the current
+ --/value slect widget with a plain-text display of the value followed by an _X_
+ link to delete it. There would be a hidden field with the value. Validation would
+ fail in JS and on the server if each such _X_ had not already been clicked.]
+
+ [# There could be more than one if this field used to be multi-valued.]
+ [for fields.values]
+ <select name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" id="[arg0]_custom_[fields.field_id]"
+ class="custom_field_value_menu" aria-labelledby="[fields.field_id]_label">
+ [define show_no_value_choice]No[end]
+ [# Non-required fields can have any value removed.]
+ [if-any fields.field_def.is_required_bool][else]
+ [define show_no_value_choice]Yes[end]
+ [end]
+ [# Formerly multi-valued fields need -- to narrow down to being singled valued.]
+ [if-index fields.values first][else]
+ [define show_no_value_choice]Yes[end]
+ [end]
+ [is show_no_value_choice "Yes"]
+ <option value="--"
+ [is fields.values.val ""]selected="selected"[end]
+ title="No value">--</option>
+ [end]
+
+ [define value_is_shown]No[end]
+ [for fields.field_def.choices]
+ [define show_choice]No[end]
+ [# Always show the current value]
+ [is fields.values.val fields.field_def.choices.name]
+ [define value_is_shown]Yes[end]
+ [define show_choice]Yes[end]
+ [end]
+ [# Formerly multi-valued fields extra values can ONLY be removed.]
+ [if-index fields.values first]
+ [define show_choice]Yes[end]
+ [end]
+ [is show_choice "Yes"]
+ <option value="[fields.field_def.choices.name]"
+ [is fields.values.val fields.field_def.choices.name]selected="selected"[end]>
+ [fields.field_def.choices.name]
+ [if-any fields.field_def.choices.docstring]= [fields.field_def.choices.docstring][end]
+ </option>
+ [end]
+ [end]
+
+ [is value_is_shown "No"]
+ [# This is an oddball label, force the user to explicitly remove it.]
+ <option value="[fields.values.val]" selected="selected"
+ title="This value is not a defined choice for this field">
+ [fields.values.val]
+ </option>
+ [end]
+ </select><br>
+ [end]
+
+ [for fields.derived_values]
+ <div title="Derived: [fields.derived_values.docstring]" class="rolloverzone">
+ <i>[fields.derived_values.val]</i>
+ </div>
+ [end]
+
+[else][# No current values, just give all choices.]
+
+ <select name="custom_[fields.field_id][is arg2 ""][else]_arg2[end]" id="[arg0]_custom_[fields.field_id]"
+ class="custom_field_value_menu" aria-labelledby="[fields.field_id]_label">
+ [if-any fields.field_def.is_required_bool]
+ <option value="" disabled="disabled" selected="selected">Select value…</option>
+ [else]
+ <option value="--" selected="selected" title="No value">--</option>
+ [end]
+ [for fields.field_def.choices]
+ <option value="[fields.field_def.choices.name]">
+ [fields.field_def.choices.name]
+ [if-any fields.field_def.choices.docstring]= [fields.field_def.choices.docstring][end]
+ </option>
+ [end]
+ </select><br>
+
+[end]
diff --git a/templates/tracker/field-value-single-int.ezt b/templates/tracker/field-value-single-int.ezt
new file mode 100644
index 0000000..944ba8b
--- /dev/null
+++ b/templates/tracker/field-value-single-int.ezt
@@ -0,0 +1,43 @@
+[# Even though this field definition says it is single-valued, the issue might have
+ multiple values if the field definition was previously multi-valued. In such a situation
+ values other than the first value are shown read-only and must be explicitly removed
+ before the comment can be submitted. ]
+
+[# If the field has no explicit values, then show an empty form element.]
+[if-any fields.values][else]
+ <input type="number" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" id="[arg0]_custom_[fields.field_id]" value=""
+ [is arg0 "hidden"][else]
+ [if-any arg1] required="required"[end]
+ [end]
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+[end]
+
+
+[for fields.values]
+ [if-index fields.values first]
+ <input type="number" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [if-any fields.field_def.min_value]min="[fields.field_def.min_value]"[end]
+ [if-any fields.field_def.max_value]max="[fields.field_def.max_value]"[end]
+ style="text-align:right; width:12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ [else]
+ <span>
+ <input type="number" disabled="disabled" value="[fields.values.val]"
+ style="text-align:right; width: 12em" class="multivalued customfield"
+ aria-labelledby="[fields.field_id]_label">
+ <u class="removeMultiFieldValueWidget">X</u>
+ </span>
+ [end]
+[end]
+
+[for fields.derived_values]
+ <input type="number" disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic; text-align:right; width:12em" class="multivalued"
+ aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-single-str.ezt b/templates/tracker/field-value-single-str.ezt
new file mode 100644
index 0000000..60ff63c
--- /dev/null
+++ b/templates/tracker/field-value-single-str.ezt
@@ -0,0 +1,41 @@
+[# Even though this field definition says it is single-valued, the issue might have
+ multiple values if the field definition was previously multi-valued. In such a situation
+ values other than the first value are shown read-only and must be explicitly removed
+ before the comment can be submitted. ]
+
+[# If the field has no explicit values, then show an empty form element.]
+[if-any fields.values][else]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" id="[arg0]_custom_[fields.field_id]" value=""
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [# TODO(jrobbins): validation]
+ class="multivalued customfield" style="width: 12em"
+ aria-labelledby="[fields.field_id]_label">
+[end]
+
+
+[for fields.values]
+ [if-index fields.values first]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ class="multivalued customfield"
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ [# TODO(jrobbins): validation]
+ style="width: 12em"
+ aria-labelledby="[fields.field_id]_label"><br>
+ [else]
+ <span>
+ <input disabled="disabled" value="[fields.values.val]"
+ class="multivalued" style="width: 12em" aria-labelledby="[fields.field_id]_label">
+ <a href="#" class="removeMultiFieldValueWidget">X</a>
+ </span>
+ [end]
+[end]
+
+[for fields.derived_values]
+ <input disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic" class="multivalued" style="width: 12em"
+ aria-labelledby="[fields.field_id]_label"><br>
+[end]
diff --git a/templates/tracker/field-value-single-url.ezt b/templates/tracker/field-value-single-url.ezt
new file mode 100644
index 0000000..27bd9fe
--- /dev/null
+++ b/templates/tracker/field-value-single-url.ezt
@@ -0,0 +1,31 @@
+[# Even though this field definition says it is single-valued, the issue might have
+ multiple values if the field definition was previously multi-valued. In such a situation
+ values other than the first value are shown read-only and must be explicitly removed
+ before the comment can be submitted. ]
+
+[# If the field has no explicit values, then show an empty form element.]
+[if-any fields.values][else]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" id="[arg0]_custom_[fields.field_id]" value=""
+ class="multivalued customfield"
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width: 12em" aria-labelledby="[fields.field_id]_label">
+[end]
+
+[for fields.values]
+ [if-index fields.values first]
+ <input name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ class="multivalued customfield"
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width: 12em" aria-labelledby="[fields.field_id]_label"><br>
+ [else]
+ <span>
+ <input disabled="disabled" value="[fields.values.val]"
+ class="multivalued" style="width: 12em" aria-labelledby="[fields.field_id]_label">
+ <a href="#" class="removeMultiFieldValueWidget">X</a>
+ </span>
+ [end]
+[end]
\ No newline at end of file
diff --git a/templates/tracker/field-value-single-user.ezt b/templates/tracker/field-value-single-user.ezt
new file mode 100644
index 0000000..5a09d00
--- /dev/null
+++ b/templates/tracker/field-value-single-user.ezt
@@ -0,0 +1,39 @@
+[# Even though this field definition says it is single-valued, the issue might have
+ multiple values if the field definition was previously multi-valued. In such a situation
+ values other than the first value are shown read-only and must be explicitly removed
+ before the comment can be submitted. ]
+
+[# If the field has no explicit values, then show an empty form element.]
+[if-any fields.values][else]
+ <input type="text" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" id="[arg0]_custom_[fields.field_id]" value=""
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width:12em" class="multivalued userautocomplete customfield" autocomplete="off"
+ data-ac-type="owner" aria-labelledby="[fields.field_id]_label">
+[end]
+
+
+[for fields.values]
+ [if-index fields.values first]
+ <input type="text" name="custom_[fields.field_id][is arg2 ""][else]_[arg2][end]" value="[fields.values.val]"
+ [is arg0 "hidden"][else]
+ [if-any arg1]required="required"[end]
+ [end]
+ style="width:12em" class="multivalued userautocomplete customfield" autocomplete="off"
+ data-ac-type="owner" aria-labelledby="[fields.field_id]_label">
+ [else]
+ <span>
+ <input type="text" disabled="disabled" value="[fields.values.val]"
+ style="width:12em" class="multivalued userautocomplete customfield" autocomplete="off"
+ data-ac-type="owner" aria-labelledby="[fields.field_id]_label">
+ <a href="#" class="removeMultiFieldValueWidget">X</a>
+ </span>
+ [end]
+[end]
+
+[for fields.derived_values]
+ <input type="text" disabled="disabled" value="[fields.derived_values.val]"
+ style="font-style:italic; width:12em" class="multivalued"
+ data-ac-type="owner" aria-labelledby="[fields.field_id]_label">
+[end]
diff --git a/templates/tracker/field-value-widgets-js.ezt b/templates/tracker/field-value-widgets-js.ezt
new file mode 100644
index 0000000..127d85c
--- /dev/null
+++ b/templates/tracker/field-value-widgets-js.ezt
@@ -0,0 +1,35 @@
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var removeMFVElements = document.getElementsByClassName("removeMultiFieldValueWidget");
+ for (var i = 0; i < removeMFVElements.length; ++i) {
+ var el = removeMFVElements[[]i];
+ el.addEventListener("click", function(event) {
+ _removeMultiFieldValueWidget(event.target);
+ });
+ }
+
+ var addMFVElements = document.getElementsByClassName("addMultiFieldValueWidget");
+ for (var i = 0; i < addMFVElements.length; ++i) {
+ var el = addMFVElements[[]i];
+ el.addEventListener("click", function(event) {
+ var target = event.target;
+ var fieldID = target.getAttribute("data-field-id");
+ var fieldType = target.getAttribute("data-field-type");
+ var fieldValidate1 = target.getAttribute("data-validate-1");
+ var fieldValidate2 = target.getAttribute("data-validate-2");
+ var fieldPhaseName = target.getAttribute("data-phase-name");
+ _addMultiFieldValueWidget(
+ event.target, fieldID, fieldType, fieldValidate1, fieldValidate2, fieldPhaseName);
+ });
+ }
+
+ var customFieldElements = document.getElementsByClassName("customfield");
+ for (var i = 0; i < customFieldElements.length; ++i) {
+ var el = customFieldElements[[]i];
+ el.addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+});
+</script>
diff --git a/templates/tracker/field-value-widgets.ezt b/templates/tracker/field-value-widgets.ezt
new file mode 100644
index 0000000..3593fa2
--- /dev/null
+++ b/templates/tracker/field-value-widgets.ezt
@@ -0,0 +1,56 @@
+[# Display widgets for editing one custom field.
+ The variable "fields" must already refer to a FieldValueView object.
+ arg0: True if the field is multi-valued.
+ arg1: Prefix for IDs
+ arg2: True if the field should be required
+ arg3: Parent phase name suffix if any.
+]
+[is fields.field_def.type_name "ENUM_TYPE"]
+ [if-any arg0]
+ [include "field-value-multi-enum.ezt" arg1 arg2 arg3]
+ [else]
+ [include "field-value-single-enum.ezt" arg1 arg2 arg3]
+ [end]
+[end]
+
+[is fields.field_def.type_name "INT_TYPE"]
+ [if-any arg0]
+ [include "field-value-multi-int.ezt" arg1 arg2 arg3]
+ [else]
+ [include "field-value-single-int.ezt" arg1 arg2 arg3]
+ [end]
+[end]
+
+[is fields.field_def.type_name "STR_TYPE"]
+ [if-any arg0]
+ [include "field-value-multi-str.ezt" arg1 arg2 arg3]
+ [else]
+ [include "field-value-single-str.ezt" arg1 arg2 arg3]
+ [end]
+[end]
+
+[is fields.field_def.type_name "USER_TYPE"]
+ [if-any arg0]
+ [include "field-value-multi-user.ezt" arg1 arg2 arg3]
+ [else]
+ [include "field-value-single-user.ezt" arg1 arg2 arg3]
+ [end]
+[end]
+
+[is fields.field_def.type_name "DATE_TYPE"]
+ [if-any arg0]
+ [include "field-value-multi-date.ezt" arg1 arg2 arg3]
+ [else]
+ [include "field-value-single-date.ezt" arg1 arg2 arg3]
+ [end]
+[end]
+
+[is fields.field_def.type_name "URL_TYPE"]
+ [if-any arg0]
+ [include "field-value-multi-url.ezt" arg1 arg2 arg3]
+ [else]
+ [include "field-value-single-url.ezt" arg1 arg2 arg3]
+ [end]
+[end]
+
+[# TODO(jrobbins): more field types. ]
diff --git a/templates/tracker/issue-advsearch-page.ezt b/templates/tracker/issue-advsearch-page.ezt
new file mode 100644
index 0000000..6fc89fb
--- /dev/null
+++ b/templates/tracker/issue-advsearch-page.ezt
@@ -0,0 +1,82 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+<form action="advsearch.do" method="POST" style="margin:6px;margin-top:12px;" autocomplete="false">
+
+[# Note: No need for UI element permission checking here. ]
+
+<table cellspacing="0" cellpadding="4" border="0" class="advquery">
+ <tr class="focus"><td width="25%"><b> Find issues</b></td>
+ <td>Search within</td>
+ <td>
+ <select name="can" style="width:100%">
+ [include "issue-can-widget.ezt" "advsearch"]
+ </select>
+ </td>
+ <td width="25%" align="center" rowspan="3">
+ <input type="submit" name="btn" value="Search" style="font-size:120%">
+ </td>
+ </tr>
+ <tr class="focus"><td width="25%"></td>
+ <td>with <b>all</b> of the words</td><td><input type="text" size="25" name="words" value=""></td>
+ </tr>
+ <tr class="focus"><td></td>
+ <td><b>without</b> the words</td><td><input type="text" size="25" name="without" value=""></td>
+ </tr>
+ <tr><td> </td><td></td><td></td><td></td></tr>
+ [# TODO(jrobbins): allow commas ]
+ <tr><td><b>Restrict search to</b></td><td>Labels</td><td><input type="text" name="labels" id="labelsearch" size="25" value="" placeholder="All the labels" autocomplete="off"></td><td class="eg">e.g., FrontEnd Priority:High</td></tr>
+ <tr><td rowspan="5"><br>
+ <table cellspacing="0" cellpadding="0" border="0"><tr><td>
+ <div class="tip">
+ <b>Tip:</b> Search results can be<br>refined by clicking on
+ the<br>result table headings.<br> <a href="searchtips">More
+ Search Tips</a>
+ </div>
+ </td></tr></table>
+ </td>
+ [# TODO(jrobbins): allow commas ]
+ <td>Statuses</td><td><input type="text" name="statuses" id="statussearch" size="25" value="" placeholder="Any status" autocomplete="off"></td><td class="eg">e.g., Started</td></tr>
+ <tr><td>Components</td><td><input type="text" size="25" name="components" id="componentsearch" value="" placeholder="Any component" autocomplete="off"></td><td class="eg"></td></tr>
+ <tr><td>Reporters</td><td><input type="text" size="25" name="reporters" id="memberreportersearch" value="" placeholder="Any reporter" autocomplete="off"></td><td class="eg"></td></tr>
+ [# TODO(jrobbins): allow commas ]
+ <tr><td>Owners</td><td><input type="text" size="25" name="owners" id="ownersearch" value="" placeholder="Any owner" autocomplete="off"></td><td class="eg">e.g., user@example.com</td></tr>
+ <tr><td>Cc</td><td><input type="text" size="25" name="cc" id="memberccsearch" value="" placeholder="Any cc" autocomplete="off"></td><td class="eg"></td></tr>
+ <tr><td></td><td>Comment by</td><td><input type="text" size="25" name="commentby" id="membercommentbysearch" value="" placeholder="Any commenter"></td><td class="eg"></td></tr>
+ [# TODO(jrobbins): implement search by star counts
+ <tr><td></td><td>Starred by</td>
+ <td>
+ <select name="starcount" style="width:100%">
+ <option value="-1" selected="selected">Any number of users</option>
+ <option value="0">Exactly zero users</option>
+ <option value="1">1 or more users</option>
+ <option value="2">2 or more users</option>
+ <option value="3">3 or more users</option>
+ <option value="4">4 or more users</option>
+ <option value="5">5 or more users</option>
+ </select></td>
+ <td class="eg"></td>
+ </tr>
+ ]
+ [# TODO(jrobbins) search by dates? ]
+ <tr><td></td><td> </td><td></td><td class="eg"></td></tr>
+</table>
+</form>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var _idsToAddDefaultListeners = [[]
+ "labelsearch", "statussearch", "componentsearch", "memberreportersearch",
+ "ownersearch", "memberccsearch", "membercommentbysearch"];
+ for (var i = 0; i < _idsToAddDefaultListeners.length; i++) {
+ var id = _idsToAddDefaultListeners[[]i];
+ if ($(id)) {
+ $(id).addEventListener("focus", function(event) {
+ _acof(event);
+ });
+ }
+ }
+});
+</script>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/issue-attachment-text.ezt b/templates/tracker/issue-attachment-text.ezt
new file mode 100644
index 0000000..98e9cdf
--- /dev/null
+++ b/templates/tracker/issue-attachment-text.ezt
@@ -0,0 +1,43 @@
+[define category_css]css/ph_detail.css[end]
+[define page_css]css/d_sb.css[end]
+[# Use raw format because filename will be escaped when title variable is used.]
+[define title][format "raw"][filename][end] ([filesize])[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<link type="text/css" rel="stylesheet"
+ href="[version_base]/static/css/prettify.css">
+
+<h3 style="margin-bottom: 0">Issue <a href="detail?id=[local_id][#TODO(jrobbins): comment number]">[local_id]</a> attachment: [filename] <small>([filesize])</small>
+</h3>
+
+
+
+<div class="fc">
+ [if-any too_large]
+ <p><em>This file is too large to display.</em></p>
+
+ [else][if-any is_binary]
+
+ <p><em>
+ This file is not plain text (only UTF-8 and Latin-1 text encodings are currently supported).
+ </em></p>
+ [else]
+
+ [include "../framework/file-content-part.ezt"]
+ [include "../framework/file-content-js.ezt"]
+
+ [end][end]
+
+</div>
+
+
+[if-any should_prettify]
+<script src="[version_base]/static/js/prettify.js" nonce="[nonce]"></script>
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ prettyPrint();
+});
+</script>
+[end]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/issue-blocking-change-notification-email.ezt b/templates/tracker/issue-blocking-change-notification-email.ezt
new file mode 100644
index 0000000..9e45c69
--- /dev/null
+++ b/templates/tracker/issue-blocking-change-notification-email.ezt
@@ -0,0 +1,7 @@
+Issue [issue.local_id]: [format "raw"][summary][end]
+[detail_url]
+
+[if-any is_blocking]This issue is now blocking issue [downstream_issue_ref].
+See [downstream_issue_url]
+[else]This issue is no longer blocking issue [downstream_issue_ref].
+See [downstream_issue_url][end]
diff --git a/templates/tracker/issue-bulk-change-notification-email.ezt b/templates/tracker/issue-bulk-change-notification-email.ezt
new file mode 100644
index 0000000..8a2d996
--- /dev/null
+++ b/templates/tracker/issue-bulk-change-notification-email.ezt
@@ -0,0 +1,16 @@
+[if-any any_link_only][else][if-any amendments]Updates:
+[amendments]
+[end]
+Comment[if-any commenter] by [commenter.display_name][end]:
+[if-any comment_text][format "raw"][comment_text][end][else](No comment was entered for this change.)[end]
+[end]
+Affected issues:
+[for issues] Issue [issues.local_id]: [if-any issues.link_only][else][format "raw"][issues.summary][end][end]
+ [format "raw"]http://[hostport][issues.detail_relative_url][end]
+
+[end]
+--
+You received this message because you are listed in the owner
+or CC fields of these issues, or because you starred them.
+You may adjust your issue notification preferences at:
+http://[hostport]/hosting/settings
diff --git a/templates/tracker/issue-bulk-edit-page.ezt b/templates/tracker/issue-bulk-edit-page.ezt
new file mode 100644
index 0000000..d57c0ae
--- /dev/null
+++ b/templates/tracker/issue-bulk-edit-page.ezt
@@ -0,0 +1,483 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+[# Note: base permission for this page is EditIssue]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+
+<div style="margin-top: 0; padding: 3px;" class="closed">
+ <form action="bulkedit.do" method="POST" style="margin: 0; padding: 0" enctype="multipart/form-data"
+ id="bulk_form">
+
+ <input type="hidden" name="can" value=[can] >
+ <input type="hidden" name="start" value=[start] >
+ <input type="hidden" name="num" value=[num] >
+ <input type="hidden" name="q" value="[query]">
+ <input type="hidden" id="sort" name="sort" value="[sortspec]">
+ <input type="hidden" name="groupby" value="[groupby]">
+ <input type="hidden" name="colspec" value="[colspec]">
+ <input type="hidden" name="x" value="[grid_x_attr]">
+ <input type="hidden" name="y" value="[grid_y_attr]">
+ <input type="hidden" name="mode" value="[if-any grid_mode]grid[end]">
+ <input type="hidden" name="cells" value="[grid_cell_mode]">
+
+ <input type="hidden" name="ids"
+ value="[for issues][issues.local_id][if-index issues last][else], [end][end]">
+ <input type="hidden" name="token" value="[form_token]">
+ <table cellpadding="0" cellspacing="0" border="0">
+ <tr><td>
+
+ <table cellspacing="0" cellpadding="3" border="0" class="rowmajor vt">
+ <tr><th>Issues:</th>
+ <td colspan="2">
+ [for issues]
+ <a href="detail?id=[issues.local_id]" title="[issues.summary]"
+ [if-any issues.closed]class=closed_ref[end]
+ >[if-any issues.closed] [end][issues.local_id][if-any issues.closed] [end]</a>[if-index issues last][else], [end]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Comment:</th>
+ <td colspan="2">
+ <textarea cols="75" rows="6" name="comment" id="comment" class="issue_text">[initial_comment]</textarea>
+ [if-any errors.comment]
+ <div class="fielderror">[errors.comment]</div>
+ [end]
+ </td>
+ </tr>
+
+ <tr><th width="10%"><label for="statusenter">Status:</label></th><td colspan="2">
+ <select id="statusenter" name="status">
+ <option style="display: none" value="[initial_status]"></option>
+ </select>
+ <span id="merge_area" style="margin-left:2em;">
+ Merge into issue:
+ <input type="text" id="merge_into" name="merge_into" style="width: 5em"
+ value="[is initial_merge_into "0"][else][initial_merge_into][end]">
+ </span>
+ [if-any errors.merge_into_id]
+ <div class="fielderror">[errors.merge_into_id]</div>
+ [end]
+ </td>
+ </tr>
+ <tr><th width="10%">Owner:</th><td colspan="2">
+ [include "issue-bulk-operator-part.ezt" "ownerenter" ""]
+ <input id="ownerenter" type="text" autocomplete="off" style="width: 12em"
+ name="owner" value="[initial_owner]">
+ [if-any errors.owner]
+ <div class="fielderror">[errors.owner]</div>
+ [end]
+ </td>
+ </tr>
+ <tr><th>Cc:</th><td colspan="2">
+ [include "issue-bulk-operator-part.ezt" "memberenter" "multi"]
+ <input type="text" multiple id="memberenter" autocomplete="off" style="width: 30em"
+ name="cc" value="[initial_cc]">
+ [if-any errors.cc]
+ <div class="fielderror">[errors.cc]</div>
+ [end]
+ </td>
+ </tr>
+
+ <tr><th>Components:</th><td colspan="2">
+ [include "issue-bulk-operator-part.ezt" "componententer" "multi"]
+ <input type="text" id="componententer" style="width:30em"
+ name="components" value="[initial_components]">
+ [if-any errors.components]
+ <div class="fielderror">[errors.components]</div>
+ [end]
+ </td></tr>
+
+ <tbody class="collapse">
+ [# Show some field editing elements immediately, others can be revealed.]
+ [define any_fields_to_reveal]No[end]
+ [for fields]
+ [if-any fields.applicable][if-any fields.is_editable]
+ [# TODO(jrobbins): determine applicability dynamically and update fields in JS]
+ <tr [if-any fields.display][else]class="ifExpand"[define any_fields_to_reveal]Yes[end][end]>
+ <th>[fields.field_name]:</th>
+ <td colspan="2">
+ [define widget_id]custom_[fields.field_id][end]
+ [define multi][if-any fields.field_def.is_multivalued_bool]multi[end][end]
+ [include "issue-bulk-operator-part.ezt" widget_id multi]
+ [include "field-value-widgets.ezt" False "" fields.field_def.is_required_bool ""]
+ <div class="fielderror" style="display:none" id="error_custom_[fields.field_id]"></div>
+ </td>
+ <tr>
+ [end][end]
+ [end]
+ [is any_fields_to_reveal "Yes"]
+ <tr class="ifCollapse">
+ <td colspan="2"><a href="#" class="toggleCollapse">Show all fields</a><t/td>
+ </tr>
+ [end]
+ </tbody>
+
+ [for issue_phase_names]
+ [for fields]
+ [is fields.phase_name issue_phase_names][if-any fields.is_editable]
+ [# TODO(jojwang): monorail:5154, bulk-editing single phase values not supported]
+ [if-any fields.field_def.is_multivalued_bool]
+ <tr><th>[issue_phase_names].[fields.field_name]:</th>
+ <td colspan="2">
+ [define widget_id]custom_[fields.field_id]_[issue_phase_names][end]
+ [include "issue-bulk-operator-part.ezt" widget_id "multi"]
+ [include "field-value-widgets.ezt" False "" fields.field_def.is_required_bool issue_phase_names]
+ <div class="fielderror" style="display:none" id="error_custom_[issue_phase_names]_[fields.field_id]"></div>
+ </td>
+ </tr>
+ [end]
+ [end][end]
+ [end]
+ [end]
+
+ <tr><th>Labels:</th>
+ <td colspan="2" class="labelediting">
+ <div id="enterrow1">
+ <input type="text" class="labelinput" id="label0" size="20" autocomplete="off"
+ name="label" value="[label0]">
+ <input type="text" class="labelinput" id="label1" size="20" autocomplete="off"
+ name="label" value="[label1]">
+ <input type="text" class="labelinput" id="label2" size="20" autocomplete="off"
+ data-show-id="enterrow2" data-hide-id="addrow1"
+ name="label" value="[label2]"> <span id="addrow1" class="fakelink" data-instead="enterrow2">Add a row</span>
+ </div>
+ <div id="enterrow2" style="display:none">
+ <input type="text" class="labelinput" id="label3" size="20" autocomplete="off"
+ name="label" value="[label3]">
+ <input type="text" class="labelinput" id="label4" size="20" autocomplete="off"
+ name="label" value="[label4]">
+ <input type="text" class="labelinput" id="label5" size="20" autocomplete="off"
+ data-show-id="enterrow3" data-hide-id="addrow2"
+ name="label" value="[label5]"> <span id="addrow2" class="fakelink" data-instead="enterrow3">Add a row</span>
+ </div>
+ <div id="enterrow3" style="display:none">
+ <input type="text" class="labelinput" id="label6" size="20" autocomplete="off"
+ name="label" value="[label6]">
+ <input type="text" class="labelinput" id="label7" size="20" autocomplete="off"
+ name="label" value="[label7]">
+ <input type="text" class="labelinput" id="label8" size="20" autocomplete="off"
+ data-show-id="enterrow4" data-hide-id="addrow3"
+ name="label" value="[label8]"> <span id="addrow3" class="fakelink" data-instead="enterrow4">Add a row</span>
+ </div>
+ <div id="enterrow4" style="display:none">
+ <input type="text" class="labelinput" id="label9" size="20" autocomplete="off"
+ name="label" value="[label9]">
+ <input type="text" class="labelinput" id="label10" size="20" autocomplete="off"
+ name="label" value="[label10]">
+ <input type="text" class="labelinput" id="label11" size="20" autocomplete="off"
+ data-show-id="enterrow5" data-hide-id="addrow4"
+ name="label" value="[label11]"> <span id="addrow4" class="fakelink" data-instead="enterrow5">Add a row</span>
+ </div>
+ <div id="enterrow5" style="display:none">
+ <input type="text" class="labelinput" id="label12" size="20" autocomplete="off"
+ name="label" value="[label12]">
+ <input type="text" class="labelinput" id="label13" size="20" autocomplete="off"
+ name="label" value="[label13]">
+ <input type="text" class="labelinput" id="label14" size="20" autocomplete="off"
+ data-show-id="enterrow6" data-hide-id="addrow5"
+ name="label" value="[label14]"> <span id="addrow5" class="fakelink" data-instead="enterrow6">Add a row</span>
+ </div>
+ <div id="enterrow6" style="display:none">
+ <input type="text" class="labelinput" id="label15" size="20" autocomplete="off"
+ name="label" value="[label15]">
+ <input type="text" class="labelinput" id="label16" size="20" autocomplete="off"
+ name="label" value="[label16]">
+ <input type="text" class="labelinput" id="label17" size="20" autocomplete="off"
+ data-show-id="enterrow7" data-hide-id="addrow6"
+ name="label" value="[label17]"> <span id="addrow6" class="fakelink" data-instead="enterrow7">Add a row</span>
+ </div>
+ <div id="enterrow7" style="display:none">
+ <input type="text" class="labelinput" id="label18" size="20" autocomplete="off"
+ name="label" value="[label18]">
+ <input type="text" class="labelinput" id="label19" size="20" autocomplete="off"
+ name="label" value="[label19]">
+ <input type="text" class="labelinput" id="label20" size="20" autocomplete="off"
+ data-show-id="enterrow8" data-hide-id="addrow7"
+ name="label" value="[label20]"> <span id="addrow7" class="fakelink" data-instead="enterrow8">Add a row</span>
+ </div>
+ <div id="enterrow8" style="display:none">
+ <input type="text" class="labelinput" id="label21" size="20" autocomplete="off"
+ name="label" value="[label21]">
+ <input type="text" class="labelinput" id="label22" size="20" autocomplete="off"
+ name="label" value="[label22]">
+ <input type="text" class="labelinput" id="label23" size="20" autocomplete="off"
+ name="label" value="[label23]">
+ </div>
+ </td>
+ </tr>
+
+ <tr><th>Blocked on:</th><td colspan="2">
+ [include "issue-bulk-operator-part.ezt" "blockedonenter" "multi"]
+ <input type="text" multiple id="blockedonenter" style="width: 30em"
+ name="blocked_on" value="[initial_blocked_on]">
+ [if-any errors.blocked_on]
+ <div class="fielderror">[errors.blocked_on]</div>
+ [end]
+ </td>
+ </tr>
+
+ <tr><th>Blocking:</th><td colspan="2">
+ [include "issue-bulk-operator-part.ezt" "blockingenter" "multi"]
+ <input type="text" multiple id="blockingenter" style="width: 30em"
+ name="blocking" value="[initial_blocking]">
+ [if-any errors.blocking]
+ <div class="fielderror">[errors.blocking]</div>
+ [end]
+ </td>
+ </tr>
+
+ [if-any page_perms.DeleteIssue]
+ <tr><th width="10%">Move to project:</th><td colspan="2">
+ <input id="move_toenter" type="text" autocomplete="off" style="width: 12em"
+ name="move_to">
+ [if-any errors.move_to]
+ <div class="fielderror">[errors.move_to]</div>
+ [end]
+ </td>
+ </tr>
+ [end]
+
+ <tr>
+ <td colspan="3"><span id="confirmarea" class="novel" style="padding-top:5px; margin:0">
+ <span id="confirmmsg"></span>
+ [# TODO(jrobbins): <a href="TODO" target="_new">Learn more</a>]
+ </span>
+ </td>
+ </tr>
+ </table>
+
+
+
+[# TODO(jrobbins): <a class="ifClosed toggleHidden" href="#">More options</a>]
+[# <a class="ifOpened" href="#" class="toggleHidden" style="background:#ccc; padding: 4px;">Hide options</a>]
+[# <div class="ifOpened" style="background:#ccc; padding: 8px"><a href="#autmatically-generated">Bookmarkable link to these values</a></div>]
+[# <br><br>]
+
+
+
+
+ <div style="padding:6px">
+ <input type="submit" id="submit_btn" name="btn" value="Update [num_issues] Issue[is num_issues "1"][else]s[end]">
+ <input type="button" id="discard" name="nobtn" value="Discard">
+
+ <input type="checkbox" checked="checked" name="send_email" id="send_email" style="margin-left:1em">
+ <label for="send_email" title="Send issue change notifications to interested users">Send email</label>
+
+ </div>
+
+
+
+[if-any show_progress]
+ <div>Note: Updating [num_issues] issues will take approximately [num_seconds] seconds.</div>
+ <div id="progress">
+ </div>
+[end]
+
+ </td>
+ <td>
+ <div class="tip">
+ <b>Usage:</b> This form allows you to update several issues at one
+ time.<br><br>
+ The same comment will be applied to all issues.<br><br>
+
+ If specified, the status or owner you enter will be applied to all
+ issues.<br><br>
+
+ You may append or remove values in multi-valued fields by choosing the += or -= operators.
+ To remove labels, preceed the label with a leading dash. (You may also use a leading dash
+ to remove individual items when using the += operator.)
+ </div>
+ </td>
+ </tr>
+ </table>
+
+
+ </form>
+</div>
+
+<mr-bulk-approval-update
+ projectName="[projectname]"
+ localIdsStr="[local_ids_str]"
+></mr-bulk-approval-update>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ document.getElementById('comment').select();
+ _lfidprefix = 'label';
+ setTimeout(_forceProperTableWidth, 100);
+
+ _exposeExistingLabelFields();
+
+ [if-any errors.custom_fields]
+ var field_error;
+ [for errors.custom_fields]
+ field_error = document.getElementById('error_custom_' + [errors.custom_fields.field_id]);
+ field_error.textContent = "[errors.custom_fields.message]";
+ field_error.style.display = "";
+ [end]
+ [end]
+
+ checksubmit();
+ setInterval(checksubmit, 700); [# catch changes that were not keystrokes, e.g., paste menu item.]
+
+
+
+function checksubmit() {
+ var submit = document.getElementById('submit_btn');
+ var cg = document.getElementById('cg');
+ if (cg != undefined) { submit.disabled='disabled'; }
+
+ submit.disabled='disabled';
+ var restrict_to_known = [if-any restrict_to_known]true[else]false[end];
+ var confirmmsg = document.getElementById('confirmmsg');
+ var statusenter = $('statusenter');
+ var merge_area = $('merge_area');
+ var statuses_offer_merge = [[] [for statuses_offer_merge]"[statuses_offer_merge]"[if-index statuses_offer_merge last][else],[end][end] ];
+ if (restrict_to_known && confirmmsg && confirmmsg.textContent.length > 0) {
+ return;
+ }
+ if (cg == undefined || cg.value.length > 1) {
+ submit.disabled='';
+ }
+
+ if (statusenter) {
+ var offer_merge = 'none';
+ for (var i = 0; i < statuses_offer_merge.length; i++) {
+ if (statusenter.value == statuses_offer_merge[[]i]) offer_merge = '';
+ }
+ merge_area.style.display = offer_merge;
+ }
+}
+
+
+function disableFormElement(el) {
+ el.readOnly = 'yes';
+ el.style.background = '#eee';
+ [# TODO(jrobbins): disable auto-complete ]
+}
+
+
+function bulkOnSubmit() {
+ var inputs = document.getElementsByTagName('input');
+ for (var i = 0; i < inputs.length; i++) {
+ disableFormElement(inputs[[]i]);
+ }
+ disableFormElement(document.getElementById('comment'));
+ [if-any show_progress]
+ var progress = document.getElementById('progress');
+ progress.textContent = 'Processing...';
+ [end]
+}
+
+
+function _checkAutoClear(inputEl, selectID) {
+ var val = inputEl.value;
+ var sel = document.getElementById(selectID);
+ if (val.match(/^--+$/)) {
+ sel.value = 'clear';
+ inputEl.value = '';
+ } else if (val) {
+ sel.value = 'set';
+ }
+}
+
+
+$("bulk_form").addEventListener("submit", bulkOnSubmit);
+
+if ($("statusenter")) {
+ _loadStatusSelect("[projectname]", "statusenter", "[initial_status]", isBulkEdit=true);
+ $("statusenter").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ $("statusenter").addEventListener("keyup", function(event) {
+ _checkAutoClear(event.target, "op_statusenter");
+ return _confirmNovelStatus(event.target);
+ });
+}
+if ($("ownerenter")) {
+ $("ownerenter").addEventListener("focus", function(event) {
+ _acof(event);
+ });
+ $("ownerenter").addEventListener("keyup", function(event) {
+ _checkAutoClear(event.target, "op_ownerenter");
+ return true;
+ });
+}
+if ($("memberenter")) {
+ $("memberenter").addEventListener("focus", function(event) {
+ _acof(event);
+ });
+}
+if ($("componententer")) {
+ $("componententer").addEventListener("focus", function(event) {
+ _acof(event);
+ });
+}
+
+if ($("move_toenter")) {
+ $("move_toenter").addEventListener("focus", function(event) {
+ _acof(event);
+ });
+}
+
+if ($("submit_btn")) {
+ $("submit_btn").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ $("submit_btn").addEventListener("mousedown", function(event) {
+ _acrob(null);
+ });
+ $("submit_btn").addEventListener("click", function(event) {
+ _trimCommas();
+ });
+}
+if ($("discard")) {
+ $("discard").addEventListener("click", function(event) {
+ _confirmDiscardEntry(this);
+ event.preventDefault();
+ });
+}
+
+var labelInputs = document.getElementsByClassName("labelinput");
+for (var i = 0; i < labelInputs.length; ++i) {
+ var labelInput = labelInputs[[]i];
+ labelInput.addEventListener("keyup", function (event) {
+ if (event.target.getAttribute("data-show-id") &&
+ event.target.getAttribute("data-hide-id") &&
+ event.target.value) {
+ _showID(event.target.getAttribute("data-show-id"));
+ _hideID(event.target.getAttribute("data-hide-id"));
+ }
+ return _vallab(event.target);
+ });
+ labelInput.addEventListener("blur", function (event) {
+ return _vallab(event.target);
+ });
+ labelInput.addEventListener("focus", function (event) {
+ return _acof(event);
+ });
+}
+
+var addRowLinks = document.getElementsByClassName("fakelink");
+for (var i = 0; i < addRowLinks.length; ++i) {
+ var rowLink = addRowLinks[[]i];
+ rowLink.addEventListener("click", function (event) {
+ _acrob(null);
+ var insteadID = event.target.getAttribute("data-instead");
+ if (insteadID)
+ _showInstead(insteadID, this);
+ });
+}
+
+});
+</script>
+
+[end]
+
+[include "field-value-widgets-js.ezt"]
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/issue-bulk-operator-part.ezt b/templates/tracker/issue-bulk-operator-part.ezt
new file mode 100644
index 0000000..8b1f37a
--- /dev/null
+++ b/templates/tracker/issue-bulk-operator-part.ezt
@@ -0,0 +1,29 @@
+[# Display a <select> widget with options to set/append/remove/clear the field.
+ Args:
+ arg0: element ID of widget to disable if Clear is selected. The form name and ID
+ of the <select> will be "op_" + arg0.
+ arg1: "multi" for multi-valued fields so that "Append" and "Remove" are offered.
+ ]
+<select name="op_[arg0]" id="op_[arg0]" style="width:9em" tabindex="-1">
+ [is arg1 "multi"]
+ <option value="append" selected="selected">Append +=</option>
+ <option value="remove">Remove -=</option>
+ [# TODO(jrobbins): <option value="setexact">Set exactly :=</option>]
+ [else]
+ <option value="set" selected="selected">Set =</option>
+ <option value="clear">Clear</option>
+ [end]
+</select>
+
+[is arg1 "multi"][else]
+<script type="text/javascript" nonce="[nonce]">
+
+runOnLoad(function() {
+ if ($("op_[arg0]")) {
+ $("op_[arg0]").addEventListener("change", function(event) {
+ TKR_ignoreWidgetIfOpIsClear(event.target, '[arg0]');
+ });
+ }
+});
+</script>
+[end]
diff --git a/templates/tracker/issue-can-widget.ezt b/templates/tracker/issue-can-widget.ezt
new file mode 100644
index 0000000..b0f8958
--- /dev/null
+++ b/templates/tracker/issue-can-widget.ezt
@@ -0,0 +1,82 @@
+[# This is used in the issue search form and issue advanced search page. We want to show the same options in both contexts.]
+[define selected]False[end]
+<option disabled="disabled">Search within:</option>
+<option value="1" [is can "1"]selected=selected [define selected]True[end] [end]
+ title="All issues in the project"> All issues</option>
+<option value="2" [is can "2"]selected=selected [define selected]True[end] [end]
+ title="All issues except ones with a closed status"> Open issues</option>
+
+[if-any logged_in_user]
+ [define username][logged_in_user.email][end]
+ [is arg0 "admin"][define username]logged-in-user[end][end]
+ <option value="3" [is can "3"]selected=selected [define selected]True[end] [end]
+ title="[[]Open issues] owner=[username]"> Open and owned by me</option>
+ <option value="4" [is can "4"]selected=selected [define selected]True[end] [end]
+ title="[[]Open issues] reporter=[username]"> Open and reported by me</option>
+ <option value="5" [is can "5"]selected=selected [define selected]True[end] [end]
+ title="[[]Open issues] starredby:[username]"> Open and starred by me</option>
+ <option value="8" [is can "8"]selected=selected [define selected]True[end] [end]
+ title="[[]Open issues] commentby:[username]"> Open with comment by me</option>
+[end]
+
+[# TODO(jrobbins): deprecate these and tell projects to define canned queries instead.]
+<option value="6" [is can "6"]selected=selected [define selected]True[end] [end]
+ title="[[]Open issues] status=New"> New issues</option>
+<option value="7" [is can "7"]selected=selected [define selected]True[end] [end]
+ title="[[]All issues] status=fixed,done"> Issues to verify</option>
+
+[is arg0 "admin"][else]
+ [define first]Yes[end]
+ [for canned_queries]
+ [is first "Yes"]
+ [define first]No[end]
+ <option disabled="disabled">----</option>
+ [end]
+ [# TODO(jrobbins): canned query visibility conditions, e.g., members only. ]
+ <option value="[canned_queries.query_id]"
+ [is can canned_queries.query_id]
+ selected=selected
+ [define selected]True[end]
+ [end]
+ title="[canned_queries.docstring]"
+ > [canned_queries.name]</option>
+ [end]
+ [if-any perms.EditProject][if-any is_cross_project][else]
+ [is first "Yes"]
+ [define first]No[end]
+ <option disabled="disabled">----</option>
+ [end]
+ <option value="manageprojectqueries"
+ > Manage project queries...</option>
+ [end][end]
+
+ [if-any logged_in_user]
+ [define first]Yes[end]
+ [for saved_queries]
+ [is first "Yes"]
+ [define first]No[end]
+ <option disabled="disabled">----</option>
+ [end]
+ <option value="[saved_queries.query_id]"
+ [is can saved_queries.query_id]
+ selected=selected
+ [define selected]True[end]
+ [end]
+ title="[saved_queries.docstring]"
+ > [saved_queries.name]</option>
+ [end]
+ [is first "Yes"]
+ [define first]No[end]
+ <option disabled="disabled">----</option>
+ [end]
+ <option value="managemyqueries"
+ > Manage my saved queries...</option>
+ [end][# end if logged in]
+
+[end][# end not "admin"]
+
+[is selected "False"]
+ <option value="[can]" selected=selected
+ title="Custom Query"
+ > Custom Query</option>
+[end]
diff --git a/templates/tracker/issue-change-notification-email-link-only.ezt b/templates/tracker/issue-change-notification-email-link-only.ezt
new file mode 100644
index 0000000..e0adf5c
--- /dev/null
+++ b/templates/tracker/issue-change-notification-email-link-only.ezt
@@ -0,0 +1,2 @@
+The following issue was [if-any was_created]created[else]updated[end]:
+[detail_url]
diff --git a/templates/tracker/issue-change-notification-email.ezt b/templates/tracker/issue-change-notification-email.ezt
new file mode 100644
index 0000000..a8b3a93
--- /dev/null
+++ b/templates/tracker/issue-change-notification-email.ezt
@@ -0,0 +1,37 @@
+[is comment.sequence "0"][#
+ ]Status: [is issue.status.name ""]----[else][issue.status.name][end]
+[#]Owner: [is issue.owner.username ""]----[else][issue.owner.display_name][end][#
+ ][if-any issue.cc]
+[# ]CC: [for issue.cc][issue.cc.display_name][if-index issue.cc last] [else], [end][end][#
+ ][end][#
+ ][if-any issue.labels]
+[# ]Labels:[for issue.labels] [issue.labels.name][end][#
+ ][end][#
+ ][if-any issue.components]
+[# ]Components:[for issue.components] [issue.components.path][end][#
+ ][end][#
+ ][if-any issue.blocked_on]
+[# ]BlockedOn:[for issue.blocked_on] [if-any issue.blocked_on.visible][issue.blocked_on.display_name][end][end][#
+ ][end][#
+ ][for issue.fields][if-any issue.fields.display][if-any issue.fields.values]
+[# ][issue.fields.field_name]:[for issue.fields.values] [issue.fields.values.val][end][end][#
+ ][end][end]
+[else][if-any comment.amendments][#
+ ]Updates:
+[#][for comment.amendments] [comment.amendments.field_name]: [format "raw"][comment.amendments.newvalue][end]
+[#][end][#
+ ][end][end]
+[is comment.sequence "0"]New issue [issue.local_id][#
+ ][else]Comment #[comment.sequence] on issue [issue.local_id][end][#
+ ] by [comment.creator.display_name]: [format "raw"][summary][#
+][end]
+[detail_url]
+
+[if-any comment.content][#
+ ][for comment.text_runs][include "render-plain-text.ezt" comment.text_runs][end][#
+][else](No comment was entered for this change.)[#
+][end]
+[if-any comment.attachments]
+Attachments:
+[for comment.attachments] [comment.attachments.filename] [comment.attachments.filesizestr]
+[end][end]
diff --git a/templates/tracker/issue-chart-body.ezt b/templates/tracker/issue-chart-body.ezt
new file mode 100644
index 0000000..34ae0f7
--- /dev/null
+++ b/templates/tracker/issue-chart-body.ezt
@@ -0,0 +1,17 @@
+<mr-chart
+ projectName="[projectname]"
+ hotlistId="[if-any hotlist_id][hotlist_id][end]"
+></mr-chart>
+
+<div>
+ <div class="help" style="padding: 1em;">
+ <h2 style="font-size: 1.2em; margin: 0 0 0.5em;">Supported query parameters:</h2>
+ <span style="font-family: monospace;">
+ cc, component, hotlist, label, owner, reporter, status
+ </span>
+ <br /><br />
+ <a href="https://bugs.chromium.org/p/monorail/issues/entry?labels=Feature-Charts">
+ Please file feedback here.
+ </a>
+ </div>
+</div>
diff --git a/templates/tracker/issue-chart-controls-top.ezt b/templates/tracker/issue-chart-controls-top.ezt
new file mode 100644
index 0000000..082affb
--- /dev/null
+++ b/templates/tracker/issue-chart-controls-top.ezt
@@ -0,0 +1,9 @@
+<!-- TODO: make this cleaner by replacing it with web component. -->
+<div class="list">
+ <div class="button_set">
+ <a class="choice_chip" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]">List</a>
+ <a class="choice_chip" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]&mode=grid">Grid</a>
+ <a class="choice_chip active_choice" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]&mode=chart">Chart</a>
+ </div>
+</div>
+
diff --git a/templates/tracker/issue-entry-page.ezt b/templates/tracker/issue-entry-page.ezt
new file mode 100644
index 0000000..b9889d6
--- /dev/null
+++ b/templates/tracker/issue-entry-page.ezt
@@ -0,0 +1,556 @@
+[define title]New Issue[end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+[# Note: base permission for this page is CreateIssue]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<div id="color_control" style="margin-top: 0; padding: 3px;" class="closed [if-any code_font]codefont[end]">
+ <form action="entry.do" method="POST" style="margin: 0; padding: 0" enctype="multipart/form-data" id="create_issue_form">
+ <input type="hidden" name="token" value="[form_token]">
+ <input type="hidden" name="template_name" value="[template_name]">
+ <input type="hidden" name="star" id="star_input" value="1">
+ <table cellpadding="0" cellspacing="0" border="0" role="presentation">
+ <tr><td>
+
+ <table cellspacing="0" cellpadding="3" border="0" class="rowmajor vt" role="presentation">
+ [if-any offer_templates]
+ <tr><th><label for="template_name">Template:</label></th>
+ <td colspan="2">
+ <select name="template_name" id="template_name" data-project-name="[projectname]" ignore-dirty>
+ [for config.template_names]
+ <option role="option" value="[format "url"][config.template_names][end]" [is config.template_names template_name]selected=selected[end]>[config.template_names]</option>
+ [end]
+ </select>
+ <span id="mr-code-font-toggle-slot"></span>
+ </td>
+ </tr>
+ [else]
+ <tr>
+ <td colspan="3">
+ <span id="mr-code-font-toggle-slot"></span>
+ </td>
+ </tr>
+ [end]
+
+ <tr><th><label for="summary">Summary:</label></th>
+ <td colspan="2" class="inplace">
+ <input type="text" id="summary" name="summary" value="[initial_summary]" required data-clear-summary-on-click="[clear_summary_on_click]"
+ [if-any any_errors][else]autofocus[end]>
+ [if-any errors.summary]
+ <div class="fielderror">[errors.summary]</div>
+ [end]
+
+ [if-any any_errors][else]
+ <script type="text/javascript" nonce="[nonce]">
+ document.getElementById('summary').select();
+ </script>
+ [end]
+ </td>
+ </tr>
+
+ <tr><th rowspan="3"><label for="comment">Description:</label></th>
+ <td colspan="2">
+ <textarea style="width:100%" cols="80" rows="15" name="comment" id="comment" class="issue_text" required>[initial_description]
+</textarea> [# We want 1 final newline but 0 trailing spaces in the textarea]
+ [if-any errors.comment]
+ <div class="fielderror">[errors.comment]</div>
+ [end]
+ </td>
+ </tr>
+
+ <tr><td colspan="2">
+ [include "../features/cues-conduct.ezt"]
+ <div id="attachmentareadeventry"></div>
+ </td></tr>
+
+ <tr>
+ <td style="width: 12em">
+ [if-any allow_attachments]
+ <span id="attachprompt"><img width="16" height="16" src="/static/images/paperclip.png" border="0" alt="A paperclip">
+ <a href="#" id="attachafile">Attach a file</a></span>
+ <div id="attachmaxsize" style="margin-left:1.2em; display:none">Max. attachments: [max_attach_size]</div>
+ [if-any errors.attachments]
+ <div class="fielderror">[errors.attachments]</div>
+ [end]
+ [else]
+ <div style="color:#666">Issue attachment storage quota exceeded.</div>
+ [end]
+ </td>
+ <td id="star_cell" style="vertical-align: initial">
+ [# Note: if the user is permitted to enter an issue, they are permitted to star it.]
+ <a class="star" id="star" style="color:cornflowerblue;">★</a>
+ Notify me of issue changes, if enabled in <a id="settings" target="new" href="/hosting/settings">settings</a>
+ </td>
+ </tr>
+
+ <tr [if-any page_perms.EditIssue page_perms.EditIssueStatus][else]style="display:none;"[end]><th width="10%"><label for="statusenter">Status:</label></th>
+ <td colspan="2" class="inplace">
+ <select id="statusenter" name="status">
+ <option style="display: none" value="[initial_status]"></option>
+ </select>
+ </label>
+ </td>
+ </tr>
+ <tr [if-any page_perms.EditIssue page_perms.EditIssueOwner][else]style="display:none;"[end]><th width="10%"><label for="ownerenter">Owner:</label></th>
+ <td colspan="2">
+ <input type="text" id="ownerenter" autocomplete="off"
+ style="width:16em"
+ name="owner" value="[initial_owner]" aria-autocomplete="list" role="combobox">
+ <span class="availability_[owner_avail_state]" id="owner_avail_state"
+ style="padding-left:1em; [if-any owner_avail_message_short][else]display:none[end]">
+ █
+ <span id="owner_availability">[owner_avail_message_short]</span>
+ </span>
+ </div>
+ [if-any errors.owner]
+ <div class="fielderror">[errors.owner]</div>
+ [end]
+ </td>
+ </tr>
+
+ <tr [if-any page_perms.EditIssue page_perms.EditIssueCc][else]style="display:none;"[end]><th><label for="memberenter">Cc:</label></th>
+ <td colspan="2" class="inplace">
+ <input type="text" multiple id="memberenter" autocomplete="off" name="cc" value="[initial_cc]" aria-autocomplete="list" role="combobox">
+ [if-any errors.cc]
+ <div class="fielderror">[errors.cc]</div>
+ [end]
+ </td>
+ </tr>
+
+ [# TODO(jrobbins): page_perms.EditIssueComponent]
+ <tr [if-any page_perms.EditIssue][else]style="display:none;"[end]><th><label for="components">Components:</label></th>
+ <td colspan="2" class="inplace">
+ <input type="text" id="components" autocomplete="off" name="components" value="[initial_components]" aria-autocomplete="list" role="combobox">
+ [if-any errors.components]
+ <div class="fielderror">[errors.components]</div>
+ [end]
+ </td>
+ </tr>
+
+ [if-any uneditable_fields]
+ <tr id="res_fd_banner"><th></th>
+ <td colspan="2" class="inplace" style="text-align:left; border-radius:25px">
+ <span style="background:var(--chops-orange-50); padding:5px; margin-top:10px; padding-left:10px; padding-right:10px; border-radius:25px">
+ <span style="padding-right:7px">
+ Info: Disabled inputs occur when you are not allowed to edit that restricted field.
+ </span>
+ <i id="res_fd_message" class="material-icons inline-icon" style="font-weight:bold; font-size:14px; vertical-align: text-bottom; cursor: pointer">
+ close</i>
+ </span>
+ </td>
+ </tr>
+ [end]
+
+ <tbody [if-any page_perms.EditIssue][else]style="display:none;"[end] class="collapse">
+ [define any_fields_to_reveal]No[end]
+ [for fields]
+ [if-any fields.applicable][if-any fields.field_def.is_approval_subfield][else][if-any fields.field_def.is_phase_field][else]
+ [# TODO(jrobbins): determine applicability dynamically and update fields in JS]
+ <tr [if-any fields.display][else]class="ifExpand"[define any_fields_to_reveal]Yes[end][end]>
+ <th id="[fields.field_id]_label">[fields.field_name]:</th>
+ <td colspan="2">
+ [if-any fields.is_editable]
+ [include "field-value-widgets.ezt" fields.field_def.is_multivalued_bool "" fields.field_def.is_required_bool ""]
+ <div class="fielderror" style="display:none" id="error_custom_[fields.field_id]"></div>
+ [else]
+ <input disabled value = "
+ [for fields.values]
+ [fields.values.val]
+ [end]
+ " style="text-align:right; width:12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ [end]
+ </td>
+ <tr>
+ [end][end][end]
+ [end]
+ [is any_fields_to_reveal "Yes"]
+ <tr class="ifCollapse">
+ <td colspan="2"><a href="#" class="toggleCollapse">Show all fields</a><t/td>
+ </tr>
+ [end]
+ </tbody>
+
+ <tr [if-any page_perms.EditIssue][else]style="display:none;"[end]><th>Labels:</th>[# aria-labels added in label-fields.ezt]
+ <td colspan="2" class="labelediting">
+ [include "label-fields.ezt" "just-two" ""]
+ </td>
+ </tr>
+
+ <tbody class="collapse">
+ [if-any page_perms.EditIssue]
+ <tr class="ifCollapse">
+ <td><a href="#" class="toggleCollapse">More options</a></td>
+ </tr>
+ [end]
+
+ <tr [if-any page_perms.EditIssue][else]style="display:none;"[end] class="ifExpand"><th style="white-space:nowrap"><label for="blocked_on">Blocked on:</label></th>
+ <td class="inplace" colspan="2">
+ <input type="text" name="blocked_on" id="blocked_on" value="[initial_blocked_on]">
+ [if-any errors.blocked_on]
+ <div class="fielderror">[errors.blocked_on]</div>
+ [end]
+ </td>
+ </tr>
+ <tr [if-any page_perms.EditIssue][else]style="display:none;"[end] class="ifExpand"><th><label for="blocking">Blocking:</label></th>
+ <td class="inplace" colspan="2">
+ <input type="text" name="blocking" id="blocking" value="[initial_blocking]" />
+ [if-any errors.blocking]
+ <div class="fielderror">[errors.blocking]</div>
+ [end]
+ </td>
+ </tr>
+
+ <tr [if-any page_perms.EditIssue][else]style="display:none;"[end] class="ifExpand"><th><label for="hotlistsenter">Hotlists:</label></th>
+ <td class="inplace" colspan="2">
+ <input type="text" name="hotlists" autocomplete="off" id="hotlistsenter" value="[initial_hotlists]" />
+ [if-any errors.hotlists]
+ <div class="fielderror">[errors.hotlists]</div>
+ [end]
+ </td>
+ </tr>
+ </tbody>
+
+ [if-any approvals]
+ <tr>
+ <th>Launch Gates:</th>
+ <td colspan="7">
+ [include "launch-gates-widget.ezt"]
+ </td>
+ </tr>
+ [end]
+
+ [for fields][if-any fields.applicable][if-any fields.field_def.is_approval_subfield]
+ <tr is="subfield-row">
+ <th>[fields.field_def.parent_approval_name] [fields.field_name]:</th>
+ <td colspan="2">
+ [if-any fields.is_editable]
+ [include "field-value-widgets.ezt" False "tmpl" False ""]
+ <div class="fielderror" style="display:none" id="error_custom_[fields.field_id]"></div>
+ [else]
+ <input disabled value = "
+ [for fields.values]
+ [fields.values.val]
+ [end]
+ " style="text-align:right; width:12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ [end]
+ </td>
+ </tr>
+ [end][end][end]
+
+ [for issue_phase_names]
+ [for fields]
+ [is fields.phase_name issue_phase_names]
+ <tr>
+ <th>[issue_phase_names].[fields.field_name]:</th>
+ <td colspan="2">
+ [if-any fields.is_editable]
+ [include "field-value-widgets.ezt" False "tmpl" False issue_phase_names]
+ <div class="fielderror" style="display:none" id="error_custom_[issue_phase_names]_[fields.field_id]"></div>
+ [else]
+ <input disabled value = "
+ [for fields.values]
+ [fields.values.val]
+ [end]
+ " style="text-align:right; width:12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ [end]
+ </td>
+ </th>
+ </tr>
+ [end][end][end]
+
+ [include "../framework/label-validation-row.ezt"]
+ [include "../framework/component-validation-row.ezt"]
+ </table>
+
+ <div style="padding:6px">
+ <input type="submit" id="submit_btn" name="btn" value="Submit issue">
+ <input type="button" id="discard" name="nobtn" value="Discard">
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+
+[include "../features/filterrules-preview.ezt"]
+
+<div style="margin-top:5em; margin-left: 8px;">
+ Problems submitting issues?
+ <a href="#" id="new-issue-feedback-link">
+ Send feedback
+ </a>
+</div>
+
+<div id="helparea"></div>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ window.getTSMonClient().recordIssueEntryTiming();
+
+ if ($('launch-gates-table')) {
+ $('launch-gates-table').classList.remove('hidden');
+ }
+
+ if ($("template_name")) {
+ $("template_name").addEventListener("change", function(event) {
+ _switchTemplate(event.target.getAttribute("data-project-name"),
+ event.target.value)
+ });
+ }
+
+ if ($("summary")) {
+ var clearSummaryOnClick = $("summary").getAttribute("data-clear-summary-on-click");
+ if (clearSummaryOnClick) {
+ $("summary").addEventListener("keydown", function(event) {
+ _clearOnFirstEvent('[format "js"][initial_summary][end]');
+ });
+ }
+ $("summary").addEventListener("click", function(event) {
+ if (clearSummaryOnClick) {
+ _clearOnFirstEvent('[format "js"][initial_summary][end]');
+ }
+ checksubmit();
+ });
+ $("summary").addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ $("summary").addEventListener("keyup", function(event) {
+ checksubmit();
+ return true;
+ });
+ }
+
+ if ($("settings")) {
+ $("settings").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ }
+ if ($("statusenter")) {
+ _loadStatusSelect("[projectname]", "statusenter", "[initial_status]");
+ $("statusenter").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ }
+ if($("res_fd_message")) {
+ $("res_fd_message").onclick = function(){
+ $("res_fd_banner").classList.add("hidden");
+ };
+ };
+
+ if ($("submit_btn")) {
+ $("submit_btn").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ $("submit_btn").addEventListener("click", function(event) {
+ _acrob(null);
+ _trimCommas();
+ userMadeChanges = false;
+ });
+ }
+ if ($("discard")) {
+ $("discard").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ $("discard").addEventListener("click", function(event) {
+ _acrob(null);
+ _confirmDiscardEntry(event.target);
+ event.preventDefault();
+ });
+ }
+ if ($("new-issue-feedback-link")) {
+ $("new-issue-feedback-link").addEventListener("click", function(event) {
+ userfeedback.api.startFeedback({
+ 'productId': '5208992', // Required.
+ 'productVersion': '[app_version]' // Optional.
+ });
+ })
+ }
+
+ window.allowSubmit = true;
+ $("create_issue_form").addEventListener("submit", function() {
+ if (allowSubmit) {
+ allowSubmit = false;
+ $("submit_btn").value = "Creating issue...";
+ $("submit_btn").disabled = "disabled";
+ }
+ else {
+ event.preventDefault();
+ }
+ });
+
+ var _blockIdsToListeners = [[]"blocked_on", "blocking", "hotlistsenter"];
+ for (var i = 0; i < _blockIdsToListeners.length; i++) {
+ var id = _blockIdsToListeners[[]i];
+ if ($(id)) {
+ $(id).addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+ }
+
+ var _idsToAddDefaultListeners = [[]"ownerenter", "memberenter", "components"];
+ for (var i = 0; i < _idsToAddDefaultListeners.length; i++) {
+ var id = _idsToAddDefaultListeners[[]i];
+ if ($(id)) {
+ $(id).addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+ }
+
+ var _elementsToAddPresubmit = document.querySelectorAll(
+ "#create_issue_form input, #create_issue_form select");
+ var debounced_presubmit = debounce(TKR_presubmit, 500);
+ for (var i = 0; i < _elementsToAddPresubmit.length; i++) {
+ var el = _elementsToAddPresubmit[[]i];
+ el.addEventListener("keyup", debounced_presubmit);
+ el.addEventListener("change", debounced_presubmit);
+ }
+ debounced_presubmit();
+
+ if ($("attachafile")) {
+ $("attachafile").addEventListener("click", function(event) {
+ _addAttachmentFields("attachmentareadeventry");
+ event.preventDefault();
+ });
+ }
+
+ document.addEventListener('keydown', function(event) {
+ if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
+ event.preventDefault();
+ $('submit_btn').click();
+ }
+ })
+
+ window.onsubmit = function() {
+ TKR_initialFormValues = TKR_currentFormValues();
+ };
+
+ window.onbeforeunload = function() {
+ if (TKR_isDirty()) {
+ // This message is ignored in recent versions of Chrome and Firefox.
+ return "You have unsaved changes. Leave this page and discard them?";
+ }
+ };
+
+ _lfidprefix = 'labelenter';
+ [if-any any_errors]
+ function _clearOnFirstEvent(){}
+ [end]
+
+ [if-any page_perms.EditIssue page_perms.EditIssueStatus page_perms.EditIssueOwner page_perms.EditIssueCc]
+ setTimeout(_forceProperTableWidth, 100);
+ [end]
+
+ [if-any page_perms.EditIssue]
+ _exposeExistingLabelFields();
+ [end]
+
+ var field_error;
+ [if-any errors.custom_fields]
+ [for errors.custom_fields]
+ field_error = document.getElementById('error_custom_' + [errors.custom_fields.field_id]);
+ field_error.textContent = "[errors.custom_fields.message]";
+ field_error.style.display = "";
+ [end]
+ [end]
+
+
+
+function checksubmit() {
+ var restrict_to_known = [if-any restrict_to_known]true[else]false[end];
+ var confirmmsg = document.getElementById('confirmmsg');
+ var cg = document.getElementById('cg');
+ var label_blocksubmitmsg = document.getElementById('blocksubmitmsg');
+ var component_blocksubmitmsg = document.getElementById('component_blocksubmitmsg');
+
+ // Check for templates that require components.
+ var component_required = [if-any component_required]true[else]false[end];
+ var components = document.getElementById('components');
+ if (components && component_required && components.value == "") {
+ component_blocksubmitmsg.textContent = "You must specify a component for this template.";
+ } else {
+ component_blocksubmitmsg.textContent = "";
+ }
+
+ var submit = document.getElementById('submit_btn');
+ var summary = document.getElementById('summary');
+ if ((restrict_to_known && confirmmsg && confirmmsg.textContent) ||
+ (label_blocksubmitmsg && label_blocksubmitmsg.textContent) ||
+ (component_blocksubmitmsg && component_blocksubmitmsg.textContent) ||
+ (cg && cg.value == "") ||
+ (!allowSubmit) ||
+ (!summary.value [if-any must_edit_summary]|| summary.value == '[format "js"][template_summary][end]'[end])) {
+ submit.disabled='disabled';
+ } else {
+ submit.disabled='';
+ }
+}
+checksubmit();
+setInterval(checksubmit, 700); [# catch changes that were not keystrokes, e.g., paste menu item.]
+
+$("star").addEventListener("click", function (event) {
+ _TKR_toggleStarLocal($("star"), "star_input");
+});
+
+ const mrCodeFontToggle = document.createElement('mr-pref-toggle');
+ mrCodeFontToggle.style = 'float:right; margin: 3px;';
+ [if-any code_font]
+ mrCodeFontToggle.initialValue = true;
+ [end]
+ [if-any logged_in_user]
+ mrCodeFontToggle.userDisplayName = "[logged_in_user.email]";
+ [end]
+ mrCodeFontToggle.label = "Code";
+ mrCodeFontToggle.title = "Code font";
+ mrCodeFontToggle.prefName = "code_font";
+ $('mr-code-font-toggle-slot').appendChild(mrCodeFontToggle);
+ mrCodeFontToggle.fetchPrefs();
+ mrCodeFontToggle.addEventListener('font-toggle', function(e) {
+ const checked = e.detail.checked;
+ const ancestor = $('color_control');
+ if (ancestor) {
+ if (checked) {
+ ancestor.classList.add('codefont');
+ } else {
+ ancestor.classList.remove('codefont');
+ }
+ }
+ });
+
+
+});
+</script>
+
+<script type="text/javascript" defer src="/static/third_party/js/keys.js?version=[app_version]" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="/static/third_party/js/skipper.js?version=[app_version]" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="https://support.google.com/inapp/api.js" nonce="[nonce]"></script>
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ _setupKibblesOnEntryPage('[project_home_url]/issues/list');
+});
+</script>
+
+[end]
+
+[include "field-value-widgets-js.ezt"]
+[include "../framework/footer.ezt"]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ if (typeof(ClientLogger) === "function") {
+ const l = new ClientLogger("issues");
+ l.logStart("new-issue", "user-time");
+ document.forms.create_issue_form.addEventListener('submit', function() {
+ l.logStart("new-issue", "server-time");
+ });
+ }
+});
+</script>
diff --git a/templates/tracker/issue-export-page.ezt b/templates/tracker/issue-export-page.ezt
new file mode 100644
index 0000000..c7892d7
--- /dev/null
+++ b/templates/tracker/issue-export-page.ezt
@@ -0,0 +1,39 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<h3>Issue export</h3>
+
+<form action="export/json" method="GET">
+ [# We use xhr_token here because we are doing a GET on a JSON servlet.]
+ <input type="hidden" name="token" value="[xhr_token]">
+ <table cellpadding="3" class="rowmajor vt">
+ <tr>
+ <th>Format</th>
+ <td style="width:90%">JSON</td>
+ </tr>
+ <tr>
+ <select id="can" name="can">
+ [include "issue-can-widget.ezt" "search"]
+ </select>
+ <label for="searchq"> for </label>
+ <span id="qq"><input type="text" size="[q_field_size]" id="searchq" name="q"
+ value="[query]" autocomplete="off"></span>
+ </tr>
+ <tr>
+ <th>Start</th>
+ <td><input type="number" size="7" name="start" value="[initial_start]"></td>
+ </tr>
+ <tr>
+ <th>Num</th>
+ <td><input type="number" size="4" name="num" value="[initial_num]"></td>
+ </tr>
+ <tr>
+ <th></th>
+ <td><input type="submit" name="btn" value="Submit"></td>
+ </tr>
+ </table>
+</form>
+
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/issue-grid-body.ezt b/templates/tracker/issue-grid-body.ezt
new file mode 100644
index 0000000..94f08ea
--- /dev/null
+++ b/templates/tracker/issue-grid-body.ezt
@@ -0,0 +1,75 @@
+[if-any results]
+
+ [is grid_x_attr "--"][else]
+ <tr>
+ [is grid_y_attr "--"][else]<th> </th>[end]
+ [for grid_x_headings]
+ <th>[grid_x_headings]</th>
+ [end]
+ </tr>
+ [end]
+
+ [for grid_data]
+ <tr class="grid">
+ [is grid_y_attr "--"][else]<th>[grid_data.grid_y_heading]</th>[end]
+
+ [for grid_data.cells_in_row]
+ <td class="vt hoverTarget [is grid_cell_mode "tiles"][else]idcount[end]">
+ [for grid_data.cells_in_row.tiles]
+ [is grid_cell_mode "tiles"]
+ [include "issue-grid-tile.ezt" grid_data.cells_in_row.tiles.starred grid_data.cells_in_row.tiles.local_id grid_data.cells_in_row.tiles.status grid_data.cells_in_row.tiles.summary grid_data.cells_in_row.tiles.issue_url grid_data.cells_in_row.tiles.data_idx]
+ [end]
+ [is grid_cell_mode "ids"]
+ <a title="[grid_data.cells_in_row.tiles.summary]"
+ href=[grid_data.cells_in_row.tiles.issue_url] class="computehref" data-idx="[grid_data.cells_in_row.tiles.data_idx]">[if-any is_hotlist][grid_data.cells_in_row.tiles.issue_ref][else][grid_data.cells_in_row.tiles.local_id][end]</a>
+ [end]
+ [end]
+ [is grid_cell_mode "counts"]
+ [is grid_data.cells_in_row.count "0"]
+ [else]
+ [is grid_data.cells_in_row.count "1"]
+ <a href=[for grid_data.cells_in_row.tiles][grid_data.cells_in_row.tiles.issue_url][end]
+ >[grid_data.cells_in_row.count] item</a>
+ [else]
+ <a href="[if-any is_hotlist][else]list[end]?can=[can]&q=[grid_data.cells_in_row.drill_down][query]">[grid_data.cells_in_row.count] items</a>
+ [end]
+ [end]
+
+ [end]
+ </td>
+ [end]
+ </tr>
+ [end]
+
+[else]
+
+ <tr>
+ <td colspan="40" class="id" style="cursor:default">
+ <div style="padding: 3em; text-align: center">
+ [if-any is_hotlist]
+ This hotlist currently has no issues.<br>
+ [if-any owner_permissions editor_permissions]
+ Select 'Add issues...' in the above 'Actions...' dropdown menu to add some.
+ [end]
+ [else]
+ [if-any project_has_any_issues]
+ Your search did not generate any results. <br>
+ [is can "1"]
+ You may want to remove some terms from your query.<br>
+ [else]
+ You may want to try your search over <a href="list?can=1&q=[query]&x=[grid_x_attr]&y=[grid_y_attr]&mode=grid">all issues</a>.<br>
+ [end]
+ [else]
+ This project currently has no issues.<br>
+ [if-any page_perms.CreateIssue]
+ [if-any read_only][else]
+ You may want to enter a <a class="id" href="entry">new issue</a>.
+ [end]
+ [end]
+ [end]
+ [end]
+ </div>
+ </td>
+ </tr>
+
+[end]
diff --git a/templates/tracker/issue-grid-controls-top.ezt b/templates/tracker/issue-grid-controls-top.ezt
new file mode 100644
index 0000000..5d63e67
--- /dev/null
+++ b/templates/tracker/issue-grid-controls-top.ezt
@@ -0,0 +1,59 @@
+<div class="list">
+
+<div class="button_set">
+ <a class="choice_chip" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]">List</a><span
+ class="choice_chip active_choice">Grid</span>
+ <a class="choice_chip" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]&mode=chart">Chart</a>
+</div>
+
+[if-any pagination]
+ [if-any pagination.visible]
+ <div class="pagination">
+ [is pagination.total_count "1"]
+ [pagination.total_count] issue shown
+ [else]
+ [if-any grid_limited][grid_shown] issues of [end]
+ [pagination.total_count] issues shown
+ [end]
+ </div>
+ [end]
+[end]
+
+ <form id="colspecform" action="[if-any is_hotlist][else]list[end]" method="GET" style="display:inline">
+ <input type="hidden" name="can" value="[can]">
+ <input type="hidden" name="q" value="[query]">
+ <input type="hidden" name="colspec" id="colspec" value="[colspec]">
+ <input type="hidden" name="sort" value="[sortspec]">
+ <input type="hidden" name="groupby" value="[groupby]">
+ <input type="hidden" name="mode" value="grid">
+<span>Rows:</span>
+<select name="y" class="drop-down-bub">
+ <option value="--" [if-any grid_y_attr][else]selected=selected[end]>None</option>
+ [for grid_axis_choices]
+ <option value="[grid_axis_choices]"
+ [is grid_axis_choices grid_y_attr]selected=selected[end]
+ >[grid_axis_choices]</option>
+ [end]
+</select>
+
+<span style="margin-left:.7em">Cols:</span>
+<select name="x" class="drop-down-bub">
+ <option value="--" [if-any grid_x_attr][else]selected=selected[end]>None</option>
+ [for grid_axis_choices]
+ <option value="[grid_axis_choices]"
+ [is grid_axis_choices grid_x_attr]selected=selected[end]
+ >[grid_axis_choices]</option>
+ [end]
+</select>
+
+<span style="margin-left:.7em">Cells:</span>
+<select name="cells" class="drop-down-bub">
+ <option value="tiles" [is grid_cell_mode "tiles"]selected=selected[end]>Tiles</option>
+ <option value="ids" [is grid_cell_mode "ids"]selected=selected[end]>IDs</option>
+ <option value="counts" [is grid_cell_mode "counts"]selected=selected[end]>Counts</option>
+</select>
+
+<input type="submit" name="nobtn" style="font-size:90%; margin-left:.5em" value="Update">
+
+</form>
+</div>
diff --git a/templates/tracker/issue-grid-tile.ezt b/templates/tracker/issue-grid-tile.ezt
new file mode 100644
index 0000000..43e6a04
--- /dev/null
+++ b/templates/tracker/issue-grid-tile.ezt
@@ -0,0 +1,27 @@
+<div class="gridtile">
+ <table cellspacing="0" cellpadding="0">
+ <tr>
+ <td class="id">
+ [if-any read_only][else]
+ [if-any page_perms.SetStar]
+ <a class="star"
+ style="color:[if-any arg0]cornflowerblue[else]gray[end]; text-decoration:none;"
+ title="[if-any arg0]Un-s[else]S[end]tar this issue"
+ data-project-name="[projectname]" data-local-id="[arg1]">
+ [if-any arg0]★[else]☆[end]
+ </a>
+ [end]
+ [end]
+ <a href=[arg4] class="computehref" data-idx=[arg5]>[arg1]</a>
+ </td>
+ <td class="status">[arg2]</td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div>
+ <a href=[arg4] class="computehref" data-idx=[arg5]>[arg3]</a>
+ </div>
+ </td>
+ </tr>
+ </table>
+</div>
diff --git a/templates/tracker/issue-hidden-fields.ezt b/templates/tracker/issue-hidden-fields.ezt
new file mode 100644
index 0000000..eaeeedf
--- /dev/null
+++ b/templates/tracker/issue-hidden-fields.ezt
@@ -0,0 +1,14 @@
+[# This template part renders important hidden fields for issue update forms.
+]
+
+<input type="hidden" name="_charset_" value="">
+<input type="hidden" name="token" value="[form_token]">
+<input type="hidden" name="id" value="[issue.local_id]">
+<input type="hidden" name="can" value="[can]">
+<input type="hidden" name="q" value="[query]">
+<input type="hidden" name="colspec" value="[colspec]">
+<input type="hidden" name="sort" value="[sortspec]">
+<input type="hidden" name="groupby" value="[groupby]">
+<input type="hidden" name="start" value="[start]">
+<input type="hidden" name="num" value="[num]">
+<input type="hidden" name="pagegen" value="[pagegen]">
diff --git a/templates/tracker/issue-hovercard.ezt b/templates/tracker/issue-hovercard.ezt
new file mode 100644
index 0000000..b70341b
--- /dev/null
+++ b/templates/tracker/issue-hovercard.ezt
@@ -0,0 +1,6 @@
+[# Show a small dialog box allows the user to quickly view one issue.]
+
+<div id="infobubble">
+ <div id="peekarea" style="width:72em; padding:0"
+ ><div class="loading">Loading...</div></div>
+</div>
diff --git a/templates/tracker/issue-import-page.ezt b/templates/tracker/issue-import-page.ezt
new file mode 100644
index 0000000..524486d
--- /dev/null
+++ b/templates/tracker/issue-import-page.ezt
@@ -0,0 +1,44 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<h3>Issue export</h3>
+
+[if-any import_errors]
+ [# This is actually used to show both errors and progress messages
+ after a successful import.]
+ <div class="error" style="margin-bottom:1em">
+ Import event log:
+ <ul>
+ [for import_errors]
+ <li>[import_errors]</li>
+ [end]
+ </ul>
+ </div>
+[end]
+
+
+<form action="import.do" enctype="multipart/form-data" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+ <table cellpadding="3" class="rowmajor vt">
+ <tr>
+ <th>Format</th>
+ <td style="width:90%">JSON</td>
+ </tr>
+ <tr>
+ <th>File</th>
+ <td><input type="file" name="jsonfile"></td>
+ </tr>
+ <tr>
+ <th>Pre-check only</th>
+ <td><input type="checkbox" name="pre_check_only"></td>
+ </tr>
+ <tr>
+ <th></th>
+ <td><input type="submit" name="btn" value="Submit"></td>
+ </tr>
+ </table>
+</form>
+
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/issue-list-controls-bottom.ezt b/templates/tracker/issue-list-controls-bottom.ezt
new file mode 100644
index 0000000..b1905a1
--- /dev/null
+++ b/templates/tracker/issue-list-controls-bottom.ezt
@@ -0,0 +1,7 @@
+<div class="list-foot">
+[if-any logged_in_user]
+ <a href="[csv_link]&token=[form_token]" style="float:right; margin-left: 1em">CSV</a>
+[end]
+
+[include "../framework/artifact-list-pagination-part.ezt"]
+</div>
diff --git a/templates/tracker/issue-list-controls-top.ezt b/templates/tracker/issue-list-controls-top.ezt
new file mode 100644
index 0000000..d308570
--- /dev/null
+++ b/templates/tracker/issue-list-controls-top.ezt
@@ -0,0 +1,108 @@
+<div class="list">
+ <div class="button_set">
+ <span class="active_choice choice_chip">List</span>
+ <a class="choice_chip" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]&mode=grid">Grid</a>
+ <a class="choice_chip" href="[if-any is_hotlist][else]list[end]?can=[can]&q=[query]&colspec=[format "url"][colspec][end]&groupby=[format "url"][groupby][end]&sort=[format "url"][sortspec][end]&x=[grid_x_attr]&y=[grid_y_attr]&cells=[grid_cell_mode]&mode=chart">Chart</a>
+ </div>
+
+ [include "../framework/artifact-list-pagination-part.ezt"]
+ [include "update-issues-hotlists-dialog.ezt"]
+
+ [if-any page_perms.EditIssue]
+ [if-any is_cross_project][else]
+ <span style="margin:0 .7em">Select:
+ <a id="selectall" href="#">All</a>
+ <a id="selectnone" href="#">None</a>
+ </span>
+ [end]
+ <select id="moreactions" class="drop-down-bub">
+ <option value="moreactions" disabled="disabled" selected="selected">Actions...</option>
+ <option value="colspec">Change columns...</option>
+ [if-any is_cross_project][else][# TODO(jrobbins): cross-project bulk edit]
+ <option value="bulk">Bulk edit...</option>
+ [end]
+ [if-any is_cross_project][else][# TODO(jrobbins): cross-project spam flagging]
+ <option value="flagspam">Flag as spam...</option>
+ <option value="unflagspam">Un-flag as spam...</option>
+ [end]
+ <option value="addtohotlist">Add to hotlist...</option>
+ </select>
+ <span id='bulk-action-loading' class='loading' style='visibility:hidden'>Processing</span>
+ [end]
+
+ [if-any hotlist_id][if-any logged_in_user]
+ <span style="margin:0 .7em">Select:
+ <a id="selectall" href="#">All</a>
+ <a id="selectnone" href="#">None</a>
+ </span>
+ <select id="moreactions" class="drop-down-bub">
+ <option value="moreactions" disabled="disabled" [if-any add_issues_selected][else]selected="selected"[end]>Actions...</option>
+ [if-any owner_permissions editor_permissions]
+ <option value="addissues" [if-any add_issues_selected]selected="selected"[end]>Add issues...</option>
+ <option value="removeissues">Remove issues...</options>
+ <option value="colspec">Change columns...</option>
+ [end]
+ <option value="addtohotlist">Add to hotlist...</option>
+ </select>
+ [end][end]
+
+
+ <form id="colspecform" action=[if-any hotlist_id]"[hotlist.name]"[else]"list"[end] method="GET" autocomplete="off"
+ style="display:inline; margin-left:1em">
+ <input type="hidden" name="can" value="[can]">
+ <input type="hidden" name="q" value="[query]">
+ <input type="hidden" name="sort" value="[sortspec]">
+ <input type="hidden" id="groupbyspec" name="groupby" value="[groupby]">
+ <span id="columnspec" style="display:none; font-size:90%">
+ <span>Columns:</span>
+ <span id="colspec_field"><input type="text" size="60" name="colspec"
+ value="[colspec]"></span>
+ <input type="submit" name="nobtn" value="Update">
+ [# TODO(jrobbins): <a href="TODO">Learn more</a> ]
+ </span>
+ </form>
+</div>
+
+[if-any is_hotlist]
+<form id='bulkremoveissues' method="POST" action="/u/[viewed_user_id]/hotlists/[hotlist.name].do">
+<input type="hidden" name="token" value="[edit_hotlist_token]">
+ <input type="hidden" id="current_col_spec" name="current_col_spec" value="[col_spec]">
+ <input type="hidden" id="bulk_remove_local_ids" name="remove_local_ids">
+ <input type ="hidden" id="bulk_remove_value" name = "remove" value="false">
+ <span id="addissuesspec" style="display:none; font-size:90%">
+ <span>Issues:</span>
+ <span id="issues_field"><input type="text" size="60" name="add_local_ids"
+ value="[add_local_ids]" placeholder="[placeholder]"></span>
+ <input type="submit" name="nobtn" value="Add Issues">
+ </span>
+ [if-any errors.issues]
+ <div class="fielderror">[errors.issues]</div>
+ [end]
+ <div class="fielderror">
+ <span id="add_local_idsfeedback">
+ [if-any errors.add_local_ids][errors.add_local_ids][end]
+ </span>
+ </div>
+</form>
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ if ($("selectall")) {
+ $("selectall").addEventListener("click", function() { _selectAllIssues(); });
+ }
+ if ($("selectnone")) {
+ $("selectnone").addEventListener("click", function() { _selectNoneIssues(); });
+ }
+ if ($("moreactions")) {
+ $("moreactions").addEventListener("change", function(event) {
+ _handleListActions(event.target);
+ });
+ if ($("moreactions").value == 'addissues') {
+ _showID('addissuesspec');
+ }
+ }
+ window.__hotlists_dialog.onResponse = onAddIssuesResponse;
+ window.__hotlists_dialog.onFailure = onAddIssuesFailure;
+});
+</script>
diff --git a/templates/tracker/issue-list-csv.ezt b/templates/tracker/issue-list-csv.ezt
new file mode 100644
index 0000000..62de9c6
--- /dev/null
+++ b/templates/tracker/issue-list-csv.ezt
@@ -0,0 +1,16 @@
+[# Prefix response body with over 1024 bytes of static content to avoid content sniffing.]
+"-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
+This file contains the same information as the issue list web page, but in CSV format.
+You can adjust the columns of the CSV file by adjusting the columns shown on the web page
+before clicking the CSV link.
+"-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"
+
+
+[for panels][# There will always be exactly one panel.][for panels.ordered_columns]"[panels.ordered_columns.name]"[if-index panels.ordered_columns last][else],[end][end][end]
+[for table_data][for table_data.cells][is table_data.cells.type "ID"]"[table_data.local_id]",[else]"[format "raw"][if-any table_data.cells.values][for table_data.cells.values][is table_data.cells.type "issues"][table_data.cells.values.item.id][else][table_data.cells.values.item][end][if-index table_data.cells.values last][else], [end][end][end][end]"[if-index table_data.cells last][else],[end][end][end]
+[end]
+
+[if-any next_csv_link]
+This file is truncated to [item_count] out of [pagination.total_count] total results.
+See [next_csv_link] for the next set of results.
+[end]
diff --git a/templates/tracker/issue-list-headings.ezt b/templates/tracker/issue-list-headings.ezt
new file mode 100644
index 0000000..6ec4e41
--- /dev/null
+++ b/templates/tracker/issue-list-headings.ezt
@@ -0,0 +1,31 @@
+[# arg0 is the ordered_columns argument that gives the name and index of each column.]
+
+<thead id="resultstablehead">
+<tr id="headingrow"><th style="border-left: 0"> </th>
+ [for panels.ordered_columns]
+ [is panels.ordered_columns.name "Summary"]
+ <th class="col_[panels.ordered_columns.col_index]" nowrap="nowrap" id="summaryheading"
+ data-col-index="[panels.ordered_columns.col_index]" width="100%"
+ ><a href="#" style="text-decoration: none">Summary + Labels <span class="indicator">▼</span></a></th>
+ [else]
+ [is panels.ordered_columns.name "ID"]
+ <th class="col_[panels.ordered_columns.col_index]" nowrap="nowrap"
+ data-col-index="[panels.ordered_columns.col_index]"
+ ><a href="#" style="text-decoration: none">[panels.ordered_columns.name] <span class="indicator">▼</span></a></th>
+ [else]
+ <th class="col_[panels.ordered_columns.col_index]"
+ data-col-index="[panels.ordered_columns.col_index]"
+ ><a href="#" style="text-decoration: none">[panels.ordered_columns.name] <span class="indicator">▼</span></a></th>
+ [end]
+ [end]
+ [end]
+ [if-any is_hotlist]
+ <th data-col-index="dot" style="width:3ex"><a href="#columnprefs"
+ class="dotdotdot" aria-label="Column list">...</a></th>
+ [else]
+ <th style="padding: 0;">
+ <ezt-show-columns-connector colspec="[colspec]" phasespec="[phasespec]"></ezt-show-columns-connector>
+ </th>
+ [end]
+</tr>
+</thead>
diff --git a/templates/tracker/issue-list-js.ezt b/templates/tracker/issue-list-js.ezt
new file mode 100644
index 0000000..1eb1aad
--- /dev/null
+++ b/templates/tracker/issue-list-js.ezt
@@ -0,0 +1,110 @@
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+
+ [# Pass the list of column names from HTML to JS ]
+ window._allColumnNames = [
+ [for column_values]'[column_values.column_name]'[if-index column_values last][else], [end][end]
+ ];
+
+ [# Update the issue link hrefs on-load and whenever the column-spec changes.]
+ _ctxCan = [can];
+ _ctxQuery = "[format "js"][query][end]";
+ _ctxSortspec = "[format "js"][sortspec][end]";
+ _ctxGroupBy = "[format "js"][groupby][end]";
+ _ctxDefaultColspec = "[format "js"][default_colspec][end]";
+ _ctxStart = [start];
+ _ctxNum = [num];
+ _ctxResultsPerPage = [default_results_per_page];
+ _ctxHotlistID = "[hotlist_id]";
+ _ctxArgs = _formatContextQueryArgs();
+
+ function _goIssue(issueIndex, newWindow) {
+ var url = _makeIssueLink(issueRefs[[]issueIndex]);
+ _go(url, newWindow);
+ }
+ // Added to enable calling from TKR_openArtifactAtCursor
+ window._goIssue = _goIssue;
+
+ window.issueRefs = [[]
+ [for table_data]
+ {project_name: "[format "js"][table_data.project_name][end]",
+ id: [table_data.local_id]}[if-index table_data last][else],[end][end]
+ ];
+
+ function _handleResultsClick(event) {
+ var target = event.target;
+ if (event.button >= 3)
+ return;
+ if (target.classList.contains("label"))
+ return;
+ if (target.classList.contains("rowwidgets") || target.parentNode.classList.contains("rowwidgets"))
+ return;
+ while (target && target.tagName != "TR") target = target.parentNode;
+ if ('[is_hotlist]') {
+ if (!target.attributes[[]"issue-context-url"]) return;
+ _go(target.attributes[[]"issue-context-url"].value, (event.metaKey || event.ctrlKey || event.button == 1));
+ }
+ else {
+ if (!target.attributes[[]"data-idx"]) return;
+ _goIssue(target.attributes[[]"data-idx"].value,
+ (event.metaKey || event.ctrlKey || event.button == 1));
+ }
+ };
+ [if-any table_data]
+ _addClickListener($("resultstable"), _handleResultsClick);
+ [end]
+
+ var issueCheckboxes = document.getElementsByClassName("checkRangeSelect");
+ for (var i = 0; i < issueCheckboxes.length; ++i) {
+ var el = issueCheckboxes[[]i];
+ el.addEventListener("click", function (event) {
+ _checkRangeSelect(event, event.target);
+ _highlightRow(event.target);
+ });
+ }
+
+ function _handleHeaderClick(event) {
+ var target = event.target;
+ while (target && target.tagName != "TH") target = target.parentNode;
+ var colIndex = target.getAttribute("data-col-index");
+ if (colIndex) {
+ _showBelow("pop_" + colIndex, target);
+ }
+ event.preventDefault();
+ }
+ var resultsTableHead = $("resultstablehead");
+ if (resultsTableHead) {
+ resultsTableHead.addEventListener("click", _handleHeaderClick);
+ }
+
+ if (typeof(ClientLogger) == "function") {
+ let cl = new ClientLogger("issues");
+ if (cl.started("issue-search")) {
+ cl.logPause("issue-search", "computer-time");
+ cl.logResume("issue-search", "user-time");
+
+ // Now we want to listen for clicks on any issue search result.
+ let logResultClick = function() {
+ cl.logPause("issue-search", "user-time");
+ cl.logResume("issue-search", "computer-time");
+ }
+
+ let links = document.querySelectorAll("#resultstable tbody .id a");
+ for (let i = 0; i < links.length; i++) {
+ links[[]i].addEventListener("click", logResultClick);
+ }
+ }
+ }
+});
+</script>
+
+<script type="text/javascript" defer src="/static/third_party/js/keys.js?version=[app_version]" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="/static/third_party/js/skipper.js?version=[app_version]" nonce="[nonce]"></script>
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ _setupKibblesOnListPage(
+ [is arg0 "issuelist"]'[project_home_url]/issues/list'[else]'[currentPageURLEncoded]'[end],
+ '[project_home_url]/issues/entry',
+ '[projectname]', [is arg0 "issuelist"]1[else]5[end], 0);
+});
+</script>
diff --git a/templates/tracker/issue-list-menus.ezt b/templates/tracker/issue-list-menus.ezt
new file mode 100644
index 0000000..d4ab7a5
--- /dev/null
+++ b/templates/tracker/issue-list-menus.ezt
@@ -0,0 +1,170 @@
+[# Table header popup menus ]
+
+[for column_values]
+ [is column_values.column_name "id"]
+ <div id="pop_[column_values.col_index]" class="popup">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr id="pop_up_[column_values.col_index]"><td>Sort Up</td></tr>
+ <tr id="pop_down_[column_values.col_index]"><td>Sort Down</td></tr>
+ <tr id="pop_hide_[column_values.col_index]"><td>Hide Column</td></tr>
+ </table>
+ </div>
+ [else]
+ [is column_values.column_name "summary"]
+ <div id="pop_[column_values.col_index]" class="popup">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr id="pop_up_[column_values.col_index]"><td>Sort Up</td></tr>
+ <tr id="pop_down_[column_values.col_index]"><td>Sort Down</td></tr>
+ [if-any is_hotlist][else]
+ [if-any column_values.filter_values]
+ <tr id="pop_show_only_[column_values.col_index]"><td>Show only
+ <span class="indicator">►</span></td></tr>
+ [end][end]
+ <tr id="pop_hide_[column_values.col_index]"><td>Hide Column</td></tr>
+ </table>
+ </div>
+ [else]
+ <div id="pop_[column_values.col_index]" class="popup">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr id="pop_up_[column_values.col_index]"><td>Sort Up</td></tr>
+ <tr id="pop_down_[column_values.col_index]"><td>Sort Down</td></tr>
+ [if-any is_hotlist][else]
+ [if-any column_values.filter_values]
+ <tr id="pop_show_only_[column_values.col_index]"><td>Show only
+ <span class="indicator">►</span></td></tr>
+ [end][end]
+ <tr id="pop_hide_[column_values.col_index]"><td>Hide Column</td></tr>
+ <tr id="pop_groupby_[column_values.col_index]"><td>Group Rows</td></tr>
+ </table>
+ </div>
+ [end]
+ [end]
+[end]
+
+[# Table header popup submenus for autofiltering of values ]
+
+[for column_values]
+ <div id="filter_[column_values.col_index]" class="popup subpopup">
+ <table cellspacing="0" cellpadding="0" border="0">
+ [for column_values.filter_values]
+ <tr data-filter-column="[is column_values.column_name "Summary"]label[else][column_values.column_name][end]"
+ data-filter-value="[column_values.filter_values]">
+ <td>[column_values.filter_values]</td></tr>
+ [end]
+ </table>
+ </div>
+[end]
+
+[# Popup menu showing the list of available columns allowing show/hide ]
+
+<div id="pop_dot" class="popup">
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr><th>Show columns:</th></tr>
+ [for panels.ordered_columns]
+ <tr data-toggle-column-index="[panels.ordered_columns.col_index]"><td> <span
+ class="col_[panels.ordered_columns.col_index]">♦</span> [panels.ordered_columns.name]</td></tr>
+ [end]
+ [for unshown_columns]
+ <tr data-add-column-name="[unshown_columns]"
+ ><td> [unshown_columns]</td></tr>
+ [end]
+ <tr id="pop_dot_edit"
+ ><td> Edit column spec...</td></tr>
+ </table>
+</div>
+
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function registerPopHandlers(colIndex, colName) {
+ var sortUpEl = $("pop_up_" + colIndex);
+ if (sortUpEl) {
+ sortUpEl.addEventListener("click", function () {
+ _closeAllPopups(sortUpEl);
+ _sortUp(colName);
+ });
+ sortUpEl.addEventListener("mouseover", function () {
+ _closeSubmenus();
+ });
+ }
+
+ var sortDownEl = $("pop_down_" + colIndex);
+ if (sortDownEl) {
+ sortDownEl.addEventListener("click", function () {
+ _closeAllPopups(sortDownEl);
+ _sortDown(colName);
+ });
+ sortDownEl.addEventListener("mouseover", function () {
+ _closeSubmenus();
+ });
+ }
+
+ var hideEl = $("pop_hide_" + colIndex);
+ if (hideEl) {
+ hideEl.addEventListener("click", function () {
+ _closeAllPopups(hideEl);
+ _toggleColumnUpdate(colIndex);
+ });
+ hideEl.addEventListener("mouseover", function () {
+ _closeSubmenus();
+ });
+ }
+
+ var showOnlyEl = $("pop_show_only_" + colIndex);
+ if (showOnlyEl) {
+ showOnlyEl.addEventListener("mouseover", function () {
+ _showRight("filter_" + colIndex, showOnlyEl);
+ });
+ }
+
+ var groupByEl = $("pop_groupby_" + colIndex);
+ if (groupByEl) {
+ groupByEl.addEventListener("click", function () {
+ _closeAllPopups(groupByEl);
+ _addGroupBy(colIndex);
+ });
+ groupByEl.addEventListener("mouseover", function () {
+ _closeSubmenus();
+ });
+ }
+ }
+
+ [for column_values]
+ registerPopHandlers([column_values.col_index], "[column_values.column_name]");
+ [end]
+
+ function handleFilterValueClick(event) {
+ var target = event.target;
+ if (target.tagName != "TR") target = target.parentNode;
+ _closeAllPopups(target);
+ var filterColumn = target.getAttribute("data-filter-column");
+ var filterValue = target.getAttribute("data-filter-value");
+ _filterTo(filterColumn, filterValue);
+ }
+
+ [for column_values]
+ $("filter_" + [column_values.col_index]).addEventListener(
+ "click", handleFilterValueClick);
+ [end]
+
+ function handleDotDotDotClick(event) {
+ var target = event.target;
+ if (target.tagName != "TR") target = target.parentNode;
+ _closeAllPopups(target);
+ var colIndex = target.getAttribute("data-toggle-column-index");
+ if (colIndex != null)
+ _toggleColumnUpdate(colIndex);
+ var colName = target.getAttribute("data-add-column-name");
+ if (colName != null)
+ _addcol(colName);
+ }
+
+ $("pop_dot").addEventListener("click", handleDotDotDotClick);
+
+ $("pop_dot_edit").addEventListener("click", function() {
+ var target = $("pop_dot_edit");
+ _closeAllPopups(target);
+ $("columnspec").style.display = "";
+ });
+});
+</script>
diff --git a/templates/tracker/issue-original-page.ezt b/templates/tracker/issue-original-page.ezt
new file mode 100644
index 0000000..580b739
--- /dev/null
+++ b/templates/tracker/issue-original-page.ezt
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>Issue [projectname]:[local_id] comment #[seq]</title>
+ <meta name="ROBOTS" content="NOINDEX">
+ <meta name="referrer" content="no-referrer">
+ <link type="text/css" rel="stylesheet" href="[version_base]/static/css/ph_core.css">
+ </head>
+ <body>
+ <h3>Original email for issue [projectname]:[local_id] comment #[seq]</h3>
+ [if-any is_binary]
+ <i>The message could not be displayed.</i>
+ [else]
+ <pre>[message_body]</pre>
+ [end]
+ </body>
+</html>
diff --git a/templates/tracker/issue-reindex-page.ezt b/templates/tracker/issue-reindex-page.ezt
new file mode 100644
index 0000000..0d80dee
--- /dev/null
+++ b/templates/tracker/issue-reindex-page.ezt
@@ -0,0 +1,45 @@
+[define title]Reindex Issues[end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="reindex.do" method="POST" id="form">
+ <input type="hidden" name="token" value="[form_token]">
+ <table>
+ <tr>
+ <td>Start:</td>
+ <td><input type="input" name="start" value="[start]"></td>
+ </tr>
+ <tr>
+ <td>Num:</td>
+ <td><input type="input" name="num" value="[num]"></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <input type="submit" id="submit_btn" name="btn" value="Re-index"></td>
+ </tr>
+ <tr>
+ <td><label for="autosubmit">Autosubmit:</label></td>
+ <td><input type="checkbox" name="auto_submit" id="autosubmit"
+ [is auto_submit "True"]checked="checked"[end] ></td>
+ </tr>
+ </table>
+</form>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function autosubmit() {
+ if (document.getElementById('autosubmit').checked) {
+ document.getElementById('form').submit();
+ }
+ }
+ if (document.getElementById('autosubmit').checked) {
+ setTimeout(autosubmit, 5000);
+ }
+});
+</script>
+
+[end]
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/issue-search-tips.ezt b/templates/tracker/issue-search-tips.ezt
new file mode 100644
index 0000000..50d4d8c
--- /dev/null
+++ b/templates/tracker/issue-search-tips.ezt
@@ -0,0 +1,398 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+[# Note: No UI element permission checking needed on this page. ]
+
+<div id="searchtips">
+
+<h3>Basic issue search</h3>
+
+<p>In most cases you can find the issues that you want to work with
+very easily by using the issue list headers or by entering a few
+simple keywords into the main search field.</p>
+
+<p>Whenever you visit the "<a href="list">issue list</a>" in your
+project, you are presented with a table of all open issues, or the default
+query set up by the project owners. If you
+see too many results, you can quickly filter your results by clicking
+on the table headers and choosing a specific value from the "Show
+only:" submenu.</p>
+
+[# TODO screenshot ]
+
+<p>The main search field consists of two parts:</p>
+
+<ul>
+ <li>A drop-down selection of search scopes, e.g, "All issues" or just "Open issues".</li>
+ <li>A search text field where you can enter search terms.</li>
+</ul>
+
+[# TODO screenshot ]
+
+<p>In the text field, you may enter simple search terms, or add any of
+the search operators described below.</p>
+
+<p>You can also use the search text field to jump directly to any
+issue by entering its issue number. If you wish to search for issues
+that contain a number, rather than jumping to that issue, enclose the
+number in quotation marks.</p>
+
+<p>Behind the scenes, the search scope is simply an additional set of
+search terms that is automatically combined with the user's search
+terms to make a complete query. To see what search terms will be
+used for each scope, hover your mouse over the scope item.</p>
+
+
+<h3>Advanced issue search</h3>
+
+<p>The <a href="advsearch">Advanced Search</a> page helps you
+compose a complex query. The advanced search form breaks the search
+down into several popular criteria and allows you to specify each one
+easily. The search criteria boil down to the same thing as the search
+operators described below, but you don't need to remember the operator
+names.</p>
+
+
+
+<h3>Full-text search</h3>
+
+<p>As with Google web search, you can search for issues by simply
+entering a few words. However, you may get a few more results than
+you expected. When you need to search more precisely, you can use
+search operators for more power.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value=""out of memory"">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>Full-text search terms can include quoted phrases, and words or
+phrases can be negated by using a leading minus sign. Please note
+that negated full-text terms are likely to give large result sets,
+so it is best to use structured search operators when possible.</p>
+
+
+<h3>Search operators</h3>
+
+<p>Normal search terms will match words found in any field of an
+issue. You can narrow the search to a specific field by using the
+name of the field. The built-in field operators are <tt>summary</tt>,
+<tt>description</tt>, <tt>comment</tt>, <tt>status</tt>, <tt>reporter</tt>,
+<tt>owner</tt>, <tt>cc</tt>, <tt>component</tt>, <tt>commentby</tt>,
+<tt>hotlist</tt>, <tt>ID</tt>, <tt>project</tt>,
+and <tt>label</tt>.</p>
+
+<p>Field names can be compared to a list of values using:</p>
+<ul>
+ <li>a colon (:) for word matching,</li>
+ <li>an equals sign (=) for full string matching,</li>
+ <li>a not equals sign (!=) or leading minus sign to negate, or</li>
+ <li>inequality operators (<, >, <=, >=) for numeric comparison.</li>
+</ul>
+
+<p>You can limit your search to just open issues by using
+is:open, or to just closed issues by using a minus sign to negate it:
+<tt>-is:open</tt>.</p>
+[# TODO(jrobbins): dateopened:]
+
+<p>For example, here's how to search for issues with the word
+"calculation" in the summary field.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="summary:calculation">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>When searching for issues owned by a specific user, you can use their
+email address, or part of it. When referring to yourself, you can also
+ use the special term <tt>me</tt>. For example, this restricts the search to
+issues that are owned by you.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="owner:user@chromium.org">
+ <input type="submit" name="btn" value="Search">
+</form>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="owner:me">
+ <input type="submit" name="btn" [if-any logged_in_user][else]disabled=disabled[end] value="Search">
+ [if-any logged_in_user][else]
+ <span style="white-space:nowrap"><a href="[login_url]"
+ >Sign in</a> to try this example</span>
+ [end]</p>
+</form>
+
+<p>Rather than have a large number of predefined fields, our issue
+tracker stores many issue details as labels.</p>
+
+<p>For example, if you labeled security-related issues with the label
+<tt>Security</tt>, here's how to search for them.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="label:security">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<p>In addition to simple one-word labels, you can use two part labels
+that specify an attribute and a value, like <tt>Priority-High</tt>,
+<tt>Priority-Medium</tt>, and <tt>Priority-Low</tt>. You can search for
+these with the <tt>label</tt> operator, or you can use the first part of the
+label name like an operator.</p>
+
+<p>For example, if you labeled high priority issues with
+<tt>Priority-High</tt>, here's one way to search for them.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="label:Priority-High">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>And, here is a more compact way to do the same search.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="Priority:High">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>For the <tt>components</tt> operator, the default search will find
+issues in that component and all of its subcomponents.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="component:UI">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>And of course, you can combine any of these field operators.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q"
+ value="status!=New owner:me component:UI">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>You can search for issues in the current project that are also on a user's
+hotlist.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q"
+ value="hostlist=username@domain:hotlistname">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<h3>Empty (or non-empty) field search</h3>
+
+<p>For each built-in field operator, you can use the <tt>has</tt>
+operator to search for issues with empty or non-empty fields. The
+<tt>has</tt> operator can be used with status, owner, cc, component,
+attachments, blocking, blockedon, mergedinto, any key-value label prefix, or
+any custom field name.</p>
+
+<p>For example, here's how to search for issues that have one or more
+components.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="has:component">
+ <input type="submit" name="btn" value="Search">
+</form>
+
+<p>Or, you can use the <tt>-has</tt> operator for negation, to search for
+issues with empty fields.</p>
+
+<p>For example, here's how to search for issues that are not associated with
+any component.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="-has:component">
+ <input type="submit" name="btn" value="Search">
+</form>
+
+
+<h3>Multiple values in search terms</h3>
+
+<p>You can search for two values for one field, or two labels
+with the same prefix by using.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="Priority:High,Medium">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<p>You can combine two separate queries into one using the <tt>OR</tt> operator.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="Priority:High OR -has:owner">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<p>You can create more complex <tt>OR</tt> queries using parentheses nesting to
+distribute search terms across <tt>OR</tt> clauses. A search query may contain as
+many sets of parentheses and levels of parentheses nesting as needed.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="Pri:0,1 (status:Untriaged OR -has:owner)">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<h3>Exact value search</h3>
+
+<p>You can search for issues that exactly match the given term by using
+the search operator <tt>=</tt>.</p>
+
+<p>For example, searching for <tt>Milestone=2009</tt> only matches issues with the
+label <tt>Milestone-2009</tt>, while searching for <tt>Milestone:2009</tt> matches
+issues with the labels <tt>Milestone-2009</tt>, <tt>Milestone-2009-Q1</tt>, <tt>Milestone-2009-Q3</tt>,
+etc.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="Milestone=2009">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>Similarly, using exact matching on components will get you only those issues
+that are in that component, not including any of its descendants.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="component=UI">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<h3>Star search</h3>
+
+<p>Any logged in user can mark any issue with a star. The star
+indicates interest in the issue.</p>
+
+<p>For example, to quickly see all the issues in this project that you
+have starred, you could use the following:</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="is:starred">
+ <input type="submit" name="btn" [if-any logged_in_user][else]disabled="disabled"[end] value="Search">
+ [if-any logged_in_user][else]
+ <span style="white-space:nowrap"><a href="[login_url]"
+ >Sign in</a> to try this example</span>
+ [end]</p>
+</form>
+
+<p>And, to see the issues that more than ten users have starred, use the following:</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="stars>10">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<h3>Jump to issue and numeric search</h3>
+
+<p>You can jump directly to a specific issue by entering its ID in the
+search field.</p>
+
+<p>For example, to jump to issue 1, just search for 1. If there is no
+existing issue with that ID, the system will search for issues that
+contain that number anywhere in the issue.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="1">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>If you just want to search for issues that contain the number 1, without
+jumping to issue 1, enclose the number in quotation marks.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value=""1"">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>Searching for a list of specific issue IDs is one way to
+communicate a set of issues to someone that you are working with. Be
+sure to set the search scope to "All issues" if the issues might be
+closed.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="ID=1,2,3,4">
+ <input type="hidden" name="can" value="1">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<h3>Attachment search</h3>
+
+<p>Users can attach files to any issues, either when issues are created or as
+part of issue comments.</p>
+
+<p>To quickly see all the issues that have attachments, use the following:</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="has:attachments">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>Or, you can search for a specific filename of the attachment.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="attachment:screenshot">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>You can also search for the file extension of the attachment.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="attachment:png">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<p>You can also search for issues with a certain number of attachments.</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="attachments>10">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+<h3>Date range search</h3>
+
+<p>You can perform searches based on date ranges.</p>
+
+<p>This search syntax is divided into two parts, the action and the date,
+[[]action]:[[]date]</p>
+
+<p>Built-in date operators include <tt>opened</tt>,
+<tt>modified</tt>, and <tt>closed</tt>. Each can be paired with an
+inequality operator <tt><</tt> or <tt>></tt>. The date must to be
+specified as YYYY-MM-DD, YYYY/MM/DD or today-N.</p>
+
+<p>For example, if you want to search for issues opened after 2009/4/1, you
+could do the following:</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="opened>2009/4/1">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>Or, if you want to search for issues modified 20 days before today's date,
+you could do the following:</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="modified<today-20">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+<p>You can search for issues that had specific fields modified
+recently by using ownermodified:, statusmodified:, componentmodified:.
+For example:</p>
+
+<form action="list" method="GET">
+ <p><input type="text" size="45" name="q" value="ownermodified>today-20">
+ <input type="submit" name="btn" value="Search"></p>
+</form>
+
+
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/label-fields.ezt b/templates/tracker/label-fields.ezt
new file mode 100644
index 0000000..7909c3e
--- /dev/null
+++ b/templates/tracker/label-fields.ezt
@@ -0,0 +1,128 @@
+[# Make a 3x8 grid of label entry form fields with autocomplete on each one.
+
+ Args:
+ arg0: if "just-two" is passed, only show the first two rows
+ and give the user links to click to expose more rows.
+ arg1: the ID prefix for the row divs.
+]
+
+<div id="[arg1]LF_row1" class="nowrap">
+ <input aria-label="label 1" type="text" class="labelinput" id="[arg1]label0" size="20" autocomplete="off"
+ name="label" value="[label0]">
+ <input aria-label="label 2" type="text" class="labelinput" id="[arg1]label1" size="20" autocomplete="off"
+ name="label" value="[label1]">
+ <input aria-label="label 3" type="text" class="labelinput" id="[arg1]label2" size="20" autocomplete="off"
+ name="label" value="[label2]">
+</div>
+
+<div id="[arg1]LF_row2" class="nowrap">
+ <input aria-label="label 4" type="text" class="labelinput" id="[arg1]label3" size="20" autocomplete="off"
+ name="label" value="[label3]">
+ <input aria-label="label 5" type="text" class="labelinput" id="[arg1]label4" size="20" autocomplete="off"
+ name="label" value="[label4]">
+ <input aria-label="label 6" type="text" class="labelinput" id="[arg1]label5" size="20" autocomplete="off"
+ [is arg0 "just-two"]data-show-id="LF_row3" data-hide-id="addrow2"[end]
+ name="label" value="[label5]">
+ [is arg0 "just-two"]<span id="addrow2" class="fakelink" data-instead="LF_row3">Add a row</span>[end]
+</div>
+
+<div id="[arg1]LF_row3" [is arg0 "just-two"]style="display:none"[end] class="nowrap">
+ <input aria-label="label 7" type="text" class="labelinput" id="[arg1]label6" size="20" autocomplete="off"
+ name="label" value="[label6]">
+ <input aria-label="label 8" type="text" class="labelinput" id="[arg1]label7" size="20" autocomplete="off"
+ name="label" value="[label7]">
+ <input aria-label="label 9" type="text" class="labelinput" id="[arg1]label8" size="20" autocomplete="off"
+ [is arg0 "just-two"]data-show-id="LF_row4" data-hide-id="addrow3"[end]
+ name="label" value="[label8]">
+ [is arg0 "just-two"]<span id="addrow3" class="fakelink" data-instead="LF_row4">Add a row</span>[end]
+</div>
+
+<div id="[arg1]LF_row4" [is arg0 "just-two"]style="display:none"[end] class="nowrap">
+ <input aria-label="label 10" type="text" class="labelinput" id="[arg1]label9" size="20" autocomplete="off"
+ name="label" value="[label9]">
+ <input aria-label="label 11" type="text" class="labelinput" id="[arg1]label10" size="20" autocomplete="off"
+ name="label" value="[label10]">
+ <input aria-label="label 12" type="text" class="labelinput" id="[arg1]label11" size="20" autocomplete="off"
+ [is arg0 "just-two"]data-show-id="LF_row5" data-hide-id="addrow4"[end]
+ name="label" value="[label11]">
+ [is arg0 "just-two"]<span id="addrow4" class="fakelink" data-instead="LF_row5">Add a row</span>[end]
+</div>
+
+<div id="[arg1]LF_row5" [is arg0 "just-two"]style="display:none"[end] class="nowrap">
+ <input aria-label="label 13" type="text" class="labelinput" id="[arg1]label12" size="20" autocomplete="off"
+ name="label" value="[label12]">
+ <input aria-label="label 14" type="text" class="labelinput" id="[arg1]label13" size="20" autocomplete="off"
+ name="label" value="[label13]">
+ <input aria-label="label 15" type="text" class="labelinput" id="[arg1]label14" size="20" autocomplete="off"
+ [is arg0 "just-two"]data-show-id="LF_row6" data-hide-id="addrow5"[end]
+ name="label" value="[label14]">
+ [is arg0 "just-two"]<span id="addrow5" class="fakelink" data-instead="LF_row6">Add a row</span>[end]
+</div>
+
+<div id="[arg1]LF_row6" [is arg0 "just-two"]style="display:none"[end] class="nowrap">
+ <input aria-label="label 16" type="text" class="labelinput" id="[arg1]label15" size="20" autocomplete="off"
+ name="label" value="[label15]">
+ <input aria-label="label 17" type="text" class="labelinput" id="[arg1]label16" size="20" autocomplete="off"
+ name="label" value="[label16]">
+ <input aria-label="label 18" type="text" class="labelinput" id="[arg1]label17" size="20" autocomplete="off"
+ [is arg0 "just-two"]data-show-id="LF_row7" data-hide-id="addrow6"[end]
+ name="label" value="[label17]">
+ [is arg0 "just-two"]<span id="addrow6" class="fakelink" data-instead="LF_row7">Add a row</span>[end]
+</div>
+
+<div id="[arg1]LF_row7" [is arg0 "just-two"]style="display:none"[end] class="nowrap">
+ <input aria-label="label 19" type="text" class="labelinput" id="[arg1]label18" size="20" autocomplete="off"
+ name="label" value="[label18]">
+ <input aria-label="label 20" type="text" class="labelinput" id="[arg1]label19" size="20" autocomplete="off"
+ name="label" value="[label19]">
+ <input aria-label="label 21" type="text" class="labelinput" id="[arg1]label20" size="20" autocomplete="off"
+ [is arg0 "just-two"]data-show-id="LF_row8" data-hide-id="addrow7"[end]
+ name="label" value="[label20]">
+ [is arg0 "just-two"]<span id="addrow7" class="fakelink" data-instead="LF_row8">Add a row</span>[end]
+</div>
+
+<div id="[arg1]LF_row8" [is arg0 "just-two"]style="display:none"[end] class="nowrap">
+ <input aria-label="label 22" type="text" class="labelinput" id="[arg1]label21" size="20" autocomplete="off"
+ name="label" value="[label21]">
+ <input aria-label="label 23" type="text" class="labelinput" id="[arg1]label22" size="20" autocomplete="off"
+ name="label" value="[label22]">
+ <input aria-label="label 24" type="text" class="labelinput" id="[arg1]label23" size="20" autocomplete="off"
+ name="label" value="[label23]">
+</div>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var labelInputs = document.getElementsByClassName("labelinput");
+ for (var i = 0; i < labelInputs.length; ++i) {
+ var labelInput = labelInputs[[]i];
+ if (labelInput.getAttribute("id").startsWith("hidden")) continue;
+ labelInput.addEventListener("keyup", function (event) {
+ if (event.target.getAttribute("data-show-id") &&
+ event.target.getAttribute("data-hide-id") &&
+ event.target.value) {
+ _showID(event.target.getAttribute("data-show-id"));
+ _hideID(event.target.getAttribute("data-hide-id"));
+ }
+ return _vallab(event.target);
+ });
+ labelInput.addEventListener("blur", function (event) {
+ _acrob(null);
+ return _vallab(event.target);
+ });
+ labelInput.addEventListener("focus", function (event) {
+ return _acof(event);
+ });
+ }
+
+ var addRowLinks = document.getElementsByClassName("fakelink");
+ for (var i = 0; i < addRowLinks.length; ++i) {
+ var rowLink = addRowLinks[[]i];
+ rowLink.addEventListener("click", function (event) {
+ _acrob(null);
+ var insteadID = event.target.getAttribute("data-instead");
+ if (insteadID)
+ _showInstead(insteadID, this);
+ });
+ }
+});
+</script>
diff --git a/templates/tracker/launch-gates-widget.ezt b/templates/tracker/launch-gates-widget.ezt
new file mode 100644
index 0000000..225a3d2
--- /dev/null
+++ b/templates/tracker/launch-gates-widget.ezt
@@ -0,0 +1,50 @@
+<table id="launch-gates-table" class="hidden">
+ <tr>
+ <th>Approval</th>
+ <th style="color:grey">gate-less</th>
+ <th><input name="phase_0" placeholder="Gate Name" size="7" [if-any allow_edit][else]disabled[end]></th>
+ <th><input name="phase_1" size="7" [if-any allow_edit][else]disabled[end]></th>
+ <th><input name="phase_2" size="7" [if-any allow_edit][else]disabled[end]></th>
+ <th><input name="phase_3" size="7" [if-any allow_edit][else]disabled[end]></th>
+ <th><input name="phase_4" size="7" [if-any allow_edit][else]disabled[end]></th>
+ <th><input name="phase_5" size="7" [if-any allow_edit][else]disabled[end]></th>
+ <th style="color:grey">omit</th>
+ </tr>
+ [for approvals]
+ <tr>
+ <td nowrap><b>[approvals.field_name]</b>
+ <br>
+ <span><input id="[approvals.field_id]_required" name="approval_[approvals.field_id]_required" type="checkbox" [if-any allow_edit][else]disabled[end]>
+ <label for="[approvals.field_id]_required">Require review</label></span>
+ </td>
+ <td><input id="[approvals.field_id]" name="approval_[approvals.field_id]" value="no_phase" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input id="[approvals.field_id]_phase_0" name="approval_[approvals.field_id]" value="phase_0" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input id="[approvals.field_id]_phase_1" name="approval_[approvals.field_id]" value="phase_1" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input id="[approvals.field_id]_phase_2" name="approval_[approvals.field_id]" value="phase_2" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input id="[approvals.field_id]_phase_3" name="approval_[approvals.field_id]" value="phase_3" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input id="[approvals.field_id]_phase_4" name="approval_[approvals.field_id]" value="phase_4" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input id="[approvals.field_id]_phase_5" name="approval_[approvals.field_id]" value="phase_5" type="radio" [if-any allow_edit][else]disabled[end]></td>
+ <td><input name="approval_[approvals.field_id]" value="omit" type="radio" checked="checked" [if-any allow_edit][else]disabled[end]></td>
+ </tr>
+ [end]
+</table>
+
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ let phaseNum = 0;
+ [for initial_phases]
+ document.getElementsByName(`phase_${phaseNum++}`)[0].value = '[format "js"][initial_phases.name][end]';
+ [end]
+
+ [for prechecked_approvals]
+ document.getElementById("[prechecked_approvals]").checked = "checked"
+ [end]
+
+ [for required_approval_ids]
+ document.getElementById("[required_approval_ids]_required").checked = "checked"
+ [end]
+
+});
+
+</script>
\ No newline at end of file
diff --git a/templates/tracker/render-plain-text.ezt b/templates/tracker/render-plain-text.ezt
new file mode 100644
index 0000000..e79bb2a
--- /dev/null
+++ b/templates/tracker/render-plain-text.ezt
@@ -0,0 +1,8 @@
+[# Safely display some text that includes some markup, completely removing the markup.
+
+ arg0 is a list of element EZT objects that have a tad and content and maybe some
+ other attributes.
+
+ We do not use extra whitespace in this template because it generates text into a
+ context where whitespace is significant.
+][arg0.content]
\ No newline at end of file
diff --git a/templates/tracker/render-rich-text.ezt b/templates/tracker/render-rich-text.ezt
new file mode 100644
index 0000000..89ab07d
--- /dev/null
+++ b/templates/tracker/render-rich-text.ezt
@@ -0,0 +1,10 @@
+[# Safely display some text that includes some markup. Only the tags
+ that we explicitly allowlist are allowed, everything else gets
+ escaped.
+
+ description.text_runs is a list of element EZT objects that have a
+ tag and content and maybe some other attributes.
+
+ We do not use extra whitespace in this template because it
+ generates text into a context where whitespace is significant.
+][is arg0.tag ""][arg0.content][end][is arg0.tag "a"]<a href="[arg0.href]" title="[arg0.title]" class="[arg0.css_class]" rel="nofollow">[arg0.content]</a>[end][is arg0.tag "b"]<b>[arg0.content]</b>[end]
\ No newline at end of file
diff --git a/templates/tracker/spam-moderation-queue.ezt b/templates/tracker/spam-moderation-queue.ezt
new file mode 100644
index 0000000..e43e477
--- /dev/null
+++ b/templates/tracker/spam-moderation-queue.ezt
@@ -0,0 +1,121 @@
+[define title]Spam Moderation Queue[end]
+[define category_css]css/ph_list.css[end]
+[define page_css]css/ph_detail.css[end][# needed for infopeek]
+
+[if-any projectname]
+ [include "../framework/header.ezt" "showtabs"]
+[else]
+ [include "../framework/header.ezt" "hidetabs"]
+[end]
+[include "../framework/js-placeholders.ezt" "showtabs"]
+
+<h2>Spam Moderation Queue: Automatic Classifier Close Calls</h2>
+[include "../framework/artifact-list-pagination-part.ezt"]
+
+<button type="submit" vaue="mark_spam" disabled="true">Mark as Spam</button>
+<button type="submit" value="mark_ham" disabled="true">Mark as Ham</button>
+
+<span style="margin:0 .7em">Select:
+ <a id="selectall" href="#">All</a>
+ <a id="selectnone" href="#">None</a>
+</span>
+
+<table id='resultstable'>
+<tr>
+ <td>
+ </td>
+ <td>ID</td>
+ <td>Author</td>
+ <td>Summary</td>
+ <td>Snippet</td>
+ <td>Opened at</td>
+ <td>Spam?</td>
+ <td>Verdict reason</td>
+ <td>Confidence</td>
+ <td>Verdict at</td>
+ <td>Flag count</td>
+</tr>
+[for issue_queue]
+<tr>
+ <td><input type='checkbox' name='issue_local_id' value='[issue_queue.issue.local_id]'/></td>
+ <td><a href='/p/[projectname]/issues/detail?id=[issue_queue.issue.local_id]'>[issue_queue.issue.local_id]</a></td>
+ <td><a href='/u/[issue_queue.reporter.email]'>[issue_queue.reporter.email]</a></td>
+ <td><a href='/p/[projectname]/issues/detail?id=[issue_queue.issue.local_id]'>[issue_queue.summary]</a></td>
+ <td>
+ [issue_queue.comment_text]
+ </td>
+ <td>[issue_queue.issue.opened_timestamp]</td>
+ <td>[issue_queue.issue.is_spam]</td>
+
+ <td>[issue_queue.reason]</td>
+ <td>[issue_queue.classifier_confidence]</td>
+ <td>[issue_queue.verdict_time]</td>
+ <td>[issue_queue.flag_count]</td>
+</tr>
+[end]
+</table>
+
+[include "../framework/artifact-list-pagination-part.ezt"]
+<button type="submit" vaue="mark_spam" disabled="true">Mark as Spam</button>
+<button type="submit" value="mark_ham" disabled="true">Mark as Ham</button>
+
+</form>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ if ($("selectall")) {
+ $("selectall").addEventListener("click", function() {
+ _selectAllIssues();
+ setDisabled(false);
+ });
+ }
+ if ($("selectnone")) {
+ $("selectnone").addEventListener("click", function() {
+ _selectNoneIssues();
+ setDisabled(true);
+ });
+ }
+
+ const checkboxes = Array.from(
+ document.querySelectorAll('input[type=checkbox]'));
+ checkboxes.forEach(checkbox => {
+ checkbox.addEventListener('change', updateEnabled);
+ });
+
+ const buttons = Array.from(
+ document.querySelectorAll('button[type=submit]'));
+ buttons.forEach(button => {
+ button.addEventListener('click', function(event) {
+ const markSpam = (button.value === 'mark_spam');
+ const issueRefs = [];
+ checkboxes.forEach(checkbox => {
+ if (checkbox.checked) {
+ issueRefs.push({
+ projectName: window.CS_env.projectName,
+ localId: checkbox.value,
+ });
+ const rowElement = checkbox.parentElement.parentElement;
+ rowElement.parentElement.removeChild(rowElement);
+ }
+ });
+ window.prpcClient.call('monorail.Issues', 'FlagIssues', {
+ issueRefs: issueRefs,
+ flag: markSpam,
+ });
+ });
+ });
+
+ function updateEnabled() {
+ const anySelected = checkboxes.some(checkbox => checkbox.checked);
+ setDisabled(!anySelected);
+ }
+
+ function setDisabled(disabled) {
+ buttons.forEach(button => {
+ button.disabled = disabled;
+ });
+ }
+});
+</script>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/template-detail-page.ezt b/templates/tracker/template-detail-page.ezt
new file mode 100644
index 0000000..b596e4c
--- /dev/null
+++ b/templates/tracker/template-detail-page.ezt
@@ -0,0 +1,303 @@
+[define title]Issue Template [template_name][end]
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<a href="/p/[projectname]/adminTemplates">‹ Back to template list</a><br><br>
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<h4>Issue Template</h4>
+
+[if-any new_template_form]
+ <form action="create.do" method="POST">
+[else]
+ <form action="detail.do" method="POST">
+[end]
+<input type="hidden" name="token" value="[form_token]">
+<input type="hidden" name="template" value="[template_name]">
+
+
+<table cellspacing="0" cellpadding="3" class="rowmajor vt">
+ <tr>
+ <th>Members only:</th>
+ <td>
+ <input type="checkbox"[if-any allow_edit][else]disabled[end] name="members_only" [if-any initial_members_only]checked[end]>
+ <label for="members_only_checkbox">Only offer this template to project members</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Name:</th>
+ <td>
+ [if-any new_template_form]
+ <input type="text" name="name" value="[template_name]">
+ <span id="fieldnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.name][errors.name][end]
+ </span>
+ [else]
+ [template_name]
+ <input type="hidden" name="name" value="[template_name]">
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Summary:</th>
+ <td>
+ [if-any allow_edit]
+ <input type="text" name="summary" size="60" class=acob" value="[initial_summary]"><br>
+ [else]
+ [initial_summary]<br>
+ [end]
+ <input type="checkbox" [if-any allow_edit][else]disabled[end] name="summary_must_be_edited" [if-any initial_must_edit_summary]checked[end]>
+ <label for="summary_must_be_edited_checkbox">Users must edit issue summary before submitting</label>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Description:</th>
+ <td>
+ [if-any allow_edit]
+ <textarea name="content" rows="12" cols="75">[initial_content]</textarea>
+ [# Note: wrap="hard" has no effect on content_editor because we copy to a hidden field before submission.]
+ [else]
+ [initial_content]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Status:</th>
+ <td>
+ [if-any allow_edit]
+ <select id="status" name="status">
+ <option style="display: none" value="[initial_status]"></option>
+ </select>
+ [else]
+ [initial_status]
+ [end]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Owner:</th>
+ <td>
+ [if-any allow_edit]
+ <input id="owner_editor" type="text" name="owner" size="25" class="acob" value="[initial_owner]"
+ autocomplete="off">
+ <span id="fieldnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.owner][errors.owner][end]
+ </span>
+ [else]
+ [initial_owner]<br>
+ [end]
+ <span>
+ <input type="checkbox" [if-any allow_edit][else]disabled[end] name="owner_defaults_to_member" style="margin-left:2em" [if-any initial_owner_defaults_to_member]checked[end]>
+ <label for="owner_defaults_to_member_checkbox">Default to member who is entering the issue</label>
+ </span>
+ </td>
+ </tr>
+
+ <tr>
+ <th>Components:</th>
+ <td>
+ [if-any allow_edit]
+ <input id="components" type="text" name="components" size="75" class="acob"
+ autocomplete="off" value="[initial_components]">
+ <span id="fieldnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.components][errors.components][end]
+ </span>
+ [else]
+ [initial_components]
+ [end]
+ <br/>
+ <span>
+ <input type="checkbox" [if-any allow_edit][else]disabled[end] name="component_required" [if-any initial_component_required]checked[end]>
+ <label for="component_required_checkbox">Require at least one component</label>
+ </span>
+ </td>
+ </tr>
+
+ [if-any allow_edit][if-any uneditable_fields]
+ <tr id="res_fd_banner"><th></th>
+ <td style="text-align:left; border-radius:25px">
+ <span style="background:var(--chops-orange-50); padding:5px; margin-top:10px;padding-left:10px; padding-right:10px; border-radius:25px">
+ <span style="padding-right:7px">
+ Info: Disabled inputs occur when you are not allowed to edit that restricted field.
+ </span>
+ <i id="res_fd_message" class="material-icons inline-icon" style="font-weight:bold; font-size:14px; vertical-align: text-bottom; cursor: pointer">
+ close</i>
+ </span>
+ </td>
+ </tr>
+ [end][end]
+
+ [for fields]
+ [# TODO(jrobbins): determine applicability dynamically and update fields in JS]
+ [# approval subfields are shown below, not here]
+ [if-any fields.field_def.is_approval_subfield][else][if-any fields.field_def.is_phase_field][else]
+ <tr>
+ <th>[fields.field_name]:</th>
+ <td colspan="2">
+ [if-any allow_edit]
+ [if-any fields.is_editable]
+ [include "field-value-widgets.ezt" False "tmpl" False ""]
+ [else]
+ <input disabled value = "
+ [for fields.values]
+ [fields.values.val]
+ [end]
+ " style="text-align:right; width:12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ [end]
+ [else]
+ [for fields.values]
+ [fields.values.val]
+ [end]
+ [end]
+ </td>
+ <tr>
+ [end][end]
+ [end]
+
+ <tr>
+ <th>Labels:</th>
+ <td>
+ [include "label-fields.ezt" "all" ""]
+ </td>
+ </tr>
+
+ <tr>
+ <th>Template admins:</th>
+ <td>
+ [if-any allow_edit]
+ <input id="admin_names_editor" type="text" name="admin_names" size="75" class="acob" value="[initial_admins]"
+ autocomplete="off">
+ [else]
+ [initial_admins]
+ [end]
+ </td>
+ </tr>
+
+ [if-any approvals]
+ <tr>
+ <th>Launch Gates:</th>
+ <td colspan="7">
+ <input type="checkbox" name="add_approvals" id="cb_add_approvals" [if-any allow_edit][else]disabled[end] [if-any initial_add_approvals]checked="checked"[end]>
+ <label for="cb_add_approvals">Include Gates and Approval Tasks in issue</label>
+ [include "launch-gates-widget.ezt"]
+ <span id="fieldnamefeedback" class="fielderror" style="margin-left:1em">
+ [if-any errors.phase_approvals][errors.phase_approvals][end]
+ </span>
+ </td>
+ </tr>
+ [end]
+
+ [for fields]
+ [if-any fields.field_def.is_approval_subfield]
+ <tr id="subfield-row" class="subfield-row-class">
+ <th>[fields.field_def.parent_approval_name] [fields.field_name]:</th>
+ <td colspan="2">
+ [if-any allow_edit]
+ [if-any fields.is_editable]
+ [include "field-value-widgets.ezt" False "tmpl" False ""]
+ [else]
+ <input disabled value = "
+ [for fields.values]
+ [fields.values.val]
+ [end]
+ " style="text-align:right; width:12em" class="multivalued customfield" aria-labelledby="[fields.field_id]_label">
+ [end]
+ [else]
+ [for fields.values][fields.values.val][end]
+ [end]
+ </td>
+ </tr>
+ [end][end]
+
+ [if-any allow_edit]
+ <tr>
+ <td></td>
+ <td>
+ <input id="submit_btn" type="submit" name="submit" value="Save template">
+ <input id="delete_btn" type="submit" class="secondary" name="deletetemplate" value="Delete Template">
+ </td>
+ </tr>
+ [end]
+
+</table>
+</form>
+
+[include "field-value-widgets-js.ezt"]
+
+[end][# end if not read_only]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+
+ [if-any allow_edit]
+ let addPhasesCheckbox = document.getElementById('cb_add_approvals');
+ if (addPhasesCheckbox) {
+ addPhasesCheckbox.addEventListener('change', toggleGatesView);
+ }
+
+ var acobElements = document.getElementsByClassName("acob");
+ for (var i = 0; i < acobElements.length; ++i) {
+ var el = acobElements[[]i];
+ el.addEventListener("focus", function(event) {
+ _acrob(null);
+ _acof(event);
+ });
+ }
+
+ if ($("status")) {
+ _loadStatusSelect("[projectname]", "status", "[initial_status]");
+ $("status").addEventListener("focus", function(event) {
+ _acrob(null);
+ });
+ }
+
+ if($("res_fd_message")) {
+ $("res_fd_message").onclick = function(){
+ $("res_fd_banner").classList.add("hidden");
+ };
+ };
+
+ [else]
+
+ let labelInputs = document.getElementsByClassName("labelinput");
+ Array.prototype.forEach.call(labelInputs, labelInput => {
+ labelInput.disabled = true;
+ });
+ [end]
+
+ toggleGatesView();
+ function toggleGatesView() {
+ let addPhasesCheckbox = document.getElementById('cb_add_approvals');
+ if (addPhasesCheckbox === null) return;
+ let addPhases = addPhasesCheckbox.checked;
+ let subfieldRows = document.getElementsByClassName('subfield-row-class');
+ let phasefieldRows = document.getElementsByClassName('phasefield-row-class');
+ if (addPhases) {
+ $('launch-gates-table').classList.remove('hidden');
+ for (let i=0; i<subfieldRows.length; i++){
+ subfieldRows[[]i].classList.remove('hidden');
+ }
+ for (let i=0; i<phasefieldRows.length; i++){
+ phasefieldRows[[]i].classList.remove('hidden');
+ }
+ } else{
+ $('launch-gates-table').classList.add('hidden');
+ for (let i=0; i<subfieldRows.length; i++){
+ subfieldRows[[]i].classList.add('hidden');
+ }
+ for (let i=0; i<phasefieldRows.length; i++){
+ phasefieldRows[[]i].classList.add('hidden');
+ }
+ }
+ }
+
+});
+</script>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/tracker/update-issues-hotlists-dialog.ezt b/templates/tracker/update-issues-hotlists-dialog.ezt
new file mode 100644
index 0000000..a453246
--- /dev/null
+++ b/templates/tracker/update-issues-hotlists-dialog.ezt
@@ -0,0 +1,11 @@
+[# TODO(jojwang): refine what buttons are shown when there are no hotlists]
+<div id="update-issues-hotlists" style="display: none">
+ <div id="update-issues-hotlists-dialog">
+ <table id="js-hotlists-table">
+ </table>
+ <menu>
+ <button id="cancel-update-hotlists" type="reset">Cancel</button>
+ <button id="save-issues-hotlists">Save</button>
+ </menu>
+ </div>
+</div>
diff --git a/templates/tracker/web-components-page.ezt b/templates/tracker/web-components-page.ezt
new file mode 100644
index 0000000..88a42e4
--- /dev/null
+++ b/templates/tracker/web-components-page.ezt
@@ -0,0 +1,41 @@
+[if-any local_id]
+ [define title][local_id][end]
+[else]
+ [define title]Monorail[end]
+[end]
+
+[define is_ezt][end]
+[include "../framework/header-shared.ezt"]
+
+[include "../webpack-out/mr-app.ezt"]
+
+<mr-app [if-any logged_in_user]
+ userDisplayName="[logged_in_user.email]"[end]
+ loginUrl="[login_url]"
+ logoutUrl="[logout_url]"
+ versionBase="[version_base]"
+></mr-app>
+
+[include "../framework/polymer-footer.ezt"]
+
+[if-any local_id]
+ <script type="text/javascript" nonce="[nonce]">
+ window.addEventListener('load', () => {
+ window.getTSMonClient().recordIssueDetailSpaTiming();
+ });
+ </script>
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+ runOnLoad(function() {
+ if (typeof(ClientLogger) === "function") {
+ let cl = new ClientLogger("issues");
+ if (cl.started("new-issue")) {
+ cl.logEnd("new-issue", null, 120 * 1000);
+ }
+ if (cl.started("issue-search")) {
+ cl.logEnd("issue-search");
+ }
+ }
+ });
+</script>