Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/templates/framework/admin-email-sender-part.ezt b/templates/framework/admin-email-sender-part.ezt
new file mode 100644
index 0000000..783a87f
--- /dev/null
+++ b/templates/framework/admin-email-sender-part.ezt
@@ -0,0 +1,15 @@
+[if-any project_is_restricted]
+<p style="width:35em; border: 1px solid #933; padding: 3px">
+ <b style="color:#933">Important</b>: Access to this project is restricted, so
+ please do not specify a public mailing list address for all notifications.
+ Use only private mailing lists to avoid unwanted disclosures. If you make
+ your project public later, choose a new mailing list at that time.
+</p>
+[end]
+
+<p>
+ Notifications will be sent from:
+ <tt>[email_from_addr]</tt><br>
+ You may need to add this address as an allowed poster to your mailing list.<br>
+ If using Google Groups, add the address directly with no email delivery.
+</p>
diff --git a/templates/framework/alert.ezt b/templates/framework/alert.ezt
new file mode 100644
index 0000000..45a61aa
--- /dev/null
+++ b/templates/framework/alert.ezt
@@ -0,0 +1,35 @@
+ <table id="alert-table" align="center" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 6px[if-any alerts.show][else];display: none[end]">
+ <tr><td class="notice" id="notice">
+ [if-any alerts.updated]
+ <a href="[project_home_url]/issues/detail?id=[alerts.updated]">Issue [alerts.updated]</a>
+ has been updated.
+ [end]
+
+ [if-any alerts.moved]
+ Issue has been moved to
+ <a href="/p/[alerts.moved_to_project]/issues/detail?id=[alerts.moved_to_id]">
+ [alerts.moved_to_project]:[alerts.moved_to_id]
+ </a>
+ [end]
+
+ [if-any alerts.copied]
+ <a href="[project_home_url]/issues/detail?id=[alerts.copied_from_id]">Issue [alerts.copied_from_id]</a>
+ has been copied to
+ <a href="/p/[alerts.copied_to_project]/issues/detail?id=[alerts.copied_to_id]">
+ [alerts.copied_to_project]:[alerts.copied_to_id]
+ </a>
+ [end]
+
+ [if-any alerts.saved]
+ Changes have been saved
+ [end]
+
+ [if-any alerts.deleted]
+ [is alerts.deleted "1"]
+ Item deleted
+ [else]
+ [alerts.deleted] items deleted
+ [end]
+ [end]
+ </td></tr>
+ </table>
diff --git a/templates/framework/artifact-collision-page.ezt b/templates/framework/artifact-collision-page.ezt
new file mode 100644
index 0000000..0c34479
--- /dev/null
+++ b/templates/framework/artifact-collision-page.ezt
@@ -0,0 +1,30 @@
+[include "../framework/header.ezt" "showtabs"]
+
+[# Note: No need for UI element permission checking here. ]
+
+<h3>Update Collision</h3>
+
+<h4>What happened?</h4>
+
+<p>While you were viewing or updating [artifact_name], another user
+submitted an update to it. That user's update has already
+taken effect. Your update cannot be saved because your changes could
+overwrite the other user's changes.</p>
+
+<p>Note: if you have been viewing and updating [artifact_name] in multiple
+browser windows or tabs, it is possible that the "other user" is
+actually yourself.</p>
+
+
+<div style="margin:2em" class="help">
+ <b style="margin:0.5em">Your options:</b>
+
+ <ul>
+ <li>Start over: view the up-to-date
+ <a href="[artifact_detail_url]">[artifact_name]</a>
+ and consider making your changes again.</li>
+ </ul>
+
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/framework/artifact-list-admin-part.ezt b/templates/framework/artifact-list-admin-part.ezt
new file mode 100644
index 0000000..e24a236
--- /dev/null
+++ b/templates/framework/artifact-list-admin-part.ezt
@@ -0,0 +1,129 @@
+[# If any value is supplied for arg0, the user will also be able
+ to edit grid preferences.]
+<h4>[if-any arg0]List and grid preferences[else]List preferences[end]</h4>
+<div class="section">
+
+ <div class="closed">
+ <div>Default query for project members:
+ <a class="ifClosed toggleHidden" href="#"
+ style="font-size:90%; margin-left:.5em">Learn more</a>
+ </div>
+
+ <div id="colhelp" class="ifOpened help">
+ <div>
+ You may enter a default query for project members. They will run
+ this query when they click on the "Issues" tab.
+ </div>
+ </div>
+ <br>
+ </div>
+
+ <input type="text" size="75" name="member_default_query"
+ value="[config.member_default_query]" id="searchq"
+ [if-any perms.EditProject][else]readonly="readonly"[end]
+ class="acob" style="margin-left:.7em">
+ <br>
+ <br>
+ <br>
+
+ <div class="closed">
+ <div>Default columns shown in list view:
+ <a class="ifClosed toggleHidden" href="#"
+ style="font-size:90%; margin-left:.5em">Learn more</a>
+ </div>
+
+ <div id="colhelp" class="ifOpened help">
+ <div>
+ You may enter a series of column names separated by spaces. The
+ columns will be displayed in order on the list view page.
+ </div>
+ <br>
+ <div>
+ Columns may be the names of built-in attributes, e.g., "Summary"
+ or "Stars". Columns may also be prefixes of the labels on items.
+ To experiment with label prefixes, label some items with
+ Key-Value labels, then click the "..." menu in the far upper right
+ heading of the list view.
+ </div>
+ </div>
+ <br>
+ </div>
+
+ <input type="text" size="75" name="default_col_spec" value="[config.default_col_spec]"
+ [if-any perms.EditProject][else]readonly="readonly"[end]
+ class="acob" style="margin-left:.7em">
+ <br>
+ <br>
+ <br>
+
+ <div class="closed">
+ <div>Default sorting order:
+ <a class="ifClosed toggleHidden" href="#"
+ style="font-size:90%; margin-left:.7em">Learn more</a>
+ </div>
+
+ <div class="ifOpened help">
+ <div>
+ You may enter a series of column names separated by spaces. Items
+ will be sorted by the first column specified. If two items have
+ the same value in the first column, the items' values in the second
+ column will be used to break the tie, and so on. Use a leading
+ minus-sign to reverse the sort order within a column.
+ </div>
+ <br>
+ <div>
+ To experiment with column sorting, click the list view header cells and
+ choose "Sort up" or "Sort down". The sorting specification used becomes
+ part of the page URL.
+ </div>
+ </div>
+ <br>
+ </div>
+
+ <input type="text" size="75" name="default_sort_spec" value="[config.default_sort_spec]"
+ [if-any perms.EditProject][else]readonly="readonly"[end]
+ class="acob" style="margin-left:.7em">
+
+
+ [if-any arg0]
+ <br>
+ <br>
+ <br>
+
+ <div class="closed">
+ <div>Default grid axes:
+ <a class="ifClosed toggleHidden" href="#"
+ style="font-size:90%; margin-left:.7em">Learn more</a>
+ </div>
+
+ <div class="ifOpened help">
+ <div>
+ You may enter one attribute name for the default grid rows and one for
+ the default grid columns. For example, "milestone" and "priority". Or,
+ you may leave each field blank.
+ </div>
+ <br>
+ <div>
+ To experiment with grid axes, click the "grid" link in the list view and
+ use the drop-down menus to select row and column attributes.
+ </div>
+ </div>
+ <br>
+ </div>
+
+ <span style="margin-left:.7em">
+ Rows: <input type="text" size="10" name="default_y_attr" value="[config.default_y_attr]"
+ [if-any perms.EditProject][else]readonly="readonly"[end]
+ class="acob">
+ </span>
+
+ <span style="margin-left:.7em">
+ Columns: <input type="text" size="10" name="default_x_attr" value="[config.default_x_attr]"
+ [if-any perms.EditProject][else]readonly="readonly"[end]
+ class="acob">
+ </span>
+
+ [end]
+
+</div>
+
diff --git a/templates/framework/artifact-list-pagination-part.ezt b/templates/framework/artifact-list-pagination-part.ezt
new file mode 100644
index 0000000..8896c14
--- /dev/null
+++ b/templates/framework/artifact-list-pagination-part.ezt
@@ -0,0 +1,18 @@
+[if-any pagination]
+ [if-any pagination.visible]
+ <div class="pagination">
+ [if-any pagination.prev_url]
+ <a href="[pagination.prev_url]"><b>‹</b> Prev</a>
+ [end]
+ [if-any pagination.start]
+ [pagination.start] - [pagination.last]
+ [end]
+ [if-any pagination.total_count]
+ of [pagination.total_count][if-any pagination.limit_reached]+[end]
+ [end]
+ [if-any pagination.next_url]
+ <a href="[pagination.next_url]">Next <b>›</b></a>
+ [end]
+ </div>
+ [end]
+[end]
diff --git a/templates/framework/banned-page.ezt b/templates/framework/banned-page.ezt
new file mode 100644
index 0000000..4b3effb
--- /dev/null
+++ b/templates/framework/banned-page.ezt
@@ -0,0 +1,31 @@
+[include "../framework/header.ezt" "hidetabs"]
+
+<h3>Access Not Allowed</h3>
+
+<h4>What happened?</h4>
+
+<p>
+[if-any is_plus_address]
+ We do not accept accounts with "+" in the email address.
+[else]
+ You are not allowed to access this service.
+[end]
+</p>
+
+<p>Please <a href="mailto:[feedback_email]">contact us</a> if you believe that you should be able to access this service. (This is a Google Group; what you write will be visible on the Internet.)</p>
+
+[# Note: we do not show the reason for being banned. ]
+
+
+<div style="margin:2em" class="help">
+ <b style="margin:0.5em">Your options:</b>
+
+ <ul>
+ <li>Participate in the open source community through other websites.</li>
+ <li><a href="[logout_url_goto_home]">Sign out</a> and access this site as
+ an anonymous user.</li>
+ <li><a href="mailto:[feedback_email]">Contact us</a> for further assistance.</li>
+ </ul>
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/framework/banner_message.ezt b/templates/framework/banner_message.ezt
new file mode 100644
index 0000000..fc31310
--- /dev/null
+++ b/templates/framework/banner_message.ezt
@@ -0,0 +1,8 @@
+[if-any site_banner_message]
+ <div style="font-weight:bold; color:var(--chops-field-error-color); padding:5px; margin-top:10px; text-align:center; background:var(--chops-orange-50);">
+ [site_banner_message]
+ [if-any banner_time]
+ <chops-timestamp timestamp="[banner_time]"></chops-timestamp>
+ [end]
+ </div>
+[end]
diff --git a/templates/framework/comment-pagination-part.ezt b/templates/framework/comment-pagination-part.ezt
new file mode 100644
index 0000000..6867c57
--- /dev/null
+++ b/templates/framework/comment-pagination-part.ezt
@@ -0,0 +1,8 @@
+[if-any cmnt_pagination.prev_url]
+ <a href="[cmnt_pagination.prev_url]" style="margin-right:.7em"><b>‹</b> Newer</a>
+[end]
+Showing comments [cmnt_pagination.last] - [cmnt_pagination.start]
+[if-any cmnt_pagination.total_count]of [cmnt_pagination.total_count][end]
+[if-any cmnt_pagination.next_url]
+ <a href="[cmnt_pagination.next_url]" style="margin-left:.7em">Older <b>›</b></a>
+[end]
diff --git a/templates/framework/component-validation-row.ezt b/templates/framework/component-validation-row.ezt
new file mode 100644
index 0000000..5d37b84
--- /dev/null
+++ b/templates/framework/component-validation-row.ezt
@@ -0,0 +1,5 @@
+<tr>
+ <td colspan="3">
+ <div id="component_blocksubmitarea" class="blockingsubmit"><span id="component_blocksubmitmsg"></span></div>
+ </td>
+</tr>
diff --git a/templates/framework/database-maintenance.ezt b/templates/framework/database-maintenance.ezt
new file mode 100644
index 0000000..fe8d7b4
--- /dev/null
+++ b/templates/framework/database-maintenance.ezt
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <link rel="icon" type="image/vnd.microsoft.icon" href="/static/images/monorail.ico">
+ <title>This bug tracker is unavailable due to database issues.</title>
+ <meta name="ROBOTS" content="NOARCHIVE">
+ <link type="text/css" rel="stylesheet" href="/static/css/ph_core.css">
+</head>
+<body>
+ <h2>This bug tracker is currently unavailable due to database issues.</h2>
+ Please <a href="[requested_url]">try again</a> later.
+</body>
+</html>
diff --git a/templates/framework/debug.ezt b/templates/framework/debug.ezt
new file mode 100644
index 0000000..7903de5
--- /dev/null
+++ b/templates/framework/debug.ezt
@@ -0,0 +1,50 @@
+[is dbg "off"]
+ [if-any perms._ViewDebug]
+ <div class="debug">
+ - <a href="[debug_uri]">Reload w/ debug info</a>
+ </div>
+ [end]
+[else]
+ [# Note that this only handles the top two levels of (sub)phases.
+ # If you nest phases further than that (which we haven't wanted/needed to
+ # do so far), you'll have to modify this code in order to render it.]
+ <style type="text/css">
+ .debug, .debug a { color: #444; font-size: x-small}
+ .debug td, .debug th { background: #ddf}
+ .debug th { text-align: left; font-family: courier; font-size: small}
+ </style>
+
+ <div class="debug">Profile Data
+ <table class="ifOpened" cellpadding="2" cellspacing="2" border="0" style="padding-left: 1em">
+ [for profiler.top_phase.subphases]
+ <tr>
+ <th style="white-space:nowrap">[profiler.top_phase.subphases.name]:</th>
+ <td align="right">[profiler.top_phase.subphases.ms][is profiler.top_phase.subphases.ms "in_progress"][else] ms[end]</td>
+ <td><table cellspacing="1" cellpadding="0"><tr>
+ [for profiler.top_phase.subphases.subphases]
+ <td title="[profiler.top_phase.subphases.subphases.name]: [profiler.top_phase.subphases.subphases.ms]ms"
+ width="[is profiler.top_phase.subphases.subphases.ms "in_progress"]100%[else][profiler.top_phase.subphases.subphases.ms][end]"
+ style="padding:2px;color:#fff;background:#[profiler.top_phase.subphases.subphases.color]">[profiler.top_phase.subphases.subphases.ms]</td>
+ [end]
+
+ [if-any profiler.top_phase.subphases.uncategorized_ms]
+ <td title="uncategorized: [profiler.top_phase.subphases.uncategorized_ms]ms"
+ width="[profiler.top_phase.subphases.uncategorized_ms]"
+ style="padding:1px">[profiler.top_phase.subphases.uncategorized_ms]</td>
+ [end]
+ </tr></table>
+ </td>
+ </tr>
+ [end]
+ </table>
+ </div><br>
+ [for debug]
+ <div class="debug">[debug.title]
+ <table cellpadding="2" cellspacing="2" border="0" style="padding-left: 1em">
+ [for debug.collection]
+ <tr><th>[debug.collection.key]</th><td>[debug.collection.val]</td></tr>
+ [end]
+ </table>
+ </div><br>
+ [end]
+[end]
diff --git a/templates/framework/display-project-logo.ezt b/templates/framework/display-project-logo.ezt
new file mode 100644
index 0000000..fd787c6
--- /dev/null
+++ b/templates/framework/display-project-logo.ezt
@@ -0,0 +1,29 @@
+[# This template displays the project logo with the file name and a View link.
+
+ arg0: Whether to display a checkbox to delete the logo.
+]
+
+<table cellspacing="5" cellpadding="2" border="0">
+ <tr>
+ <td>
+ <b>[logo_view.filename]</b>
+ </td>
+ <td>
+ <a href="[logo_view.viewurl]" target="_blank" style="margin-left:.2em">View</a>
+ </td>
+ </tr>
+ <tr>
+ <td colspan=2>
+ <a href="[logo_view.viewurl]" target="_blank">
+ <img src="[logo_view.thumbnail_url]" class="preview">
+ </a>
+ </td>
+ </tr>
+ [if-any arg0]
+ <tr>
+ <td colspan=2>
+ <input type="checkbox" name="delete_logo" id="delete_logo"> Delete this logo
+ </td>
+ </tr>
+ [end]
+</table>
diff --git a/templates/framework/excessive-activity-page.ezt b/templates/framework/excessive-activity-page.ezt
new file mode 100644
index 0000000..e7b10ad
--- /dev/null
+++ b/templates/framework/excessive-activity-page.ezt
@@ -0,0 +1,31 @@
+[include "../framework/header.ezt" "hidetabs"]
+
+<h3>Action Limit Exceeded</h3>
+
+<h4>What happened?</h4>
+
+<div style="width:60em">
+
+<p>You have performed the requested action too many times in a 24-hour
+time period. Or, you have performed the requested action too many
+times since the creation of your account.</p>
+
+<p>We place limits on the number of actions that can be performed by
+each user in order to reduce the potential for abuse. We feel that we have set
+these limits high enough that legitimate use will very rarely
+reach them. Without these limits, a few abusive users could degrade
+the quality of this site for everyone.</p>
+
+
+<div style="margin:2em" class="help">
+ <b style="margin:0.5em">Your options:</b>
+
+ <ul>
+ <li>Wait 24 hours and then try this action again.</li>
+ <li>Ask another member of your project to perform the action for you.</li>
+ <li><a href="mailto:[feedback_email]">Contact us</a> for further assistance.</li>
+ </ul>
+</div>
+
+</div>
+[include "../framework/footer.ezt"]
diff --git a/templates/framework/file-content-js.ezt b/templates/framework/file-content-js.ezt
new file mode 100644
index 0000000..72e882e
--- /dev/null
+++ b/templates/framework/file-content-js.ezt
@@ -0,0 +1,89 @@
+[# TODO(jrobbins): move this into compiled javascript. ]
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var numsGenState = {table_base_id: 'nums_table_'};
+ var srcGenState = {table_base_id: 'src_table_'};
+ var alignerRunning = false;
+ var startOver = false;
+
+ function setLineNumberHeights() {
+ if (alignerRunning) {
+ startOver = true;
+ return;
+ }
+ numsGenState.chunk_id = 0;
+ numsGenState.table = document.getElementById('nums_table_0');
+ numsGenState.row_num = 0;
+
+ if (!numsGenState.table) {
+ return; // Silently exit if no file is present.
+ }
+
+ srcGenState.chunk_id = 0;
+ srcGenState.table = document.getElementById('src_table_0');
+ srcGenState.row_num = 0;
+
+ alignerRunning = true;
+ continueToSetLineNumberHeights();
+ }
+
+ function rowGenerator(genState) {
+ if (genState.row_num < genState.table.rows.length) {
+ var currentRow = genState.table.rows[[]genState.row_num];
+ genState.row_num++;
+ return currentRow;
+ }
+
+ var newTable = document.getElementById(
+ genState.table_base_id + (genState.chunk_id + 1));
+ if (newTable) {
+ genState.chunk_id++;
+ genState.row_num = 0;
+ genState.table = newTable;
+ return genState.table.rows[[]0];
+ }
+
+ return null;
+ }
+
+ var MAX_ROWS_PER_PASS = 1000;
+
+ function continueToSetLineNumberHeights() {
+ var rowsInThisPass = 0;
+ var numRow = 1;
+ var srcRow = 1;
+
+ while (numRow && srcRow && rowsInThisPass < MAX_ROWS_PER_PASS) {
+ numRow = rowGenerator(numsGenState);
+ srcRow = rowGenerator(srcGenState);
+ rowsInThisPass++;
+
+ if (numRow && srcRow) {
+ if (numRow.offsetHeight != srcRow.offsetHeight) {
+ numRow.firstChild.style.height = srcRow.offsetHeight + 'px';
+ }
+ }
+ }
+
+ if (rowsInThisPass >= MAX_ROWS_PER_PASS) {
+ setTimeout(continueToSetLineNumberHeights, 10);
+ } else {
+ alignerRunning = false;
+ if (startOver) {
+ startOver = false;
+ setTimeout(setLineNumberHeights, 500);
+ }
+ }
+
+ }
+
+ function initLineNumberHeights() {
+ // Do 2 complete passes, because there can be races
+ // between this code and prettify.
+ startOver = true;
+ setTimeout(setLineNumberHeights, 250);
+ window.addEventListener('resize', setLineNumberHeights);
+ }
+ initLineNumberHeights();
+});
+</script>
diff --git a/templates/framework/file-content-part.ezt b/templates/framework/file-content-part.ezt
new file mode 100644
index 0000000..5a89915
--- /dev/null
+++ b/templates/framework/file-content-part.ezt
@@ -0,0 +1,46 @@
+[# Safely display user-content text, such a program source code, with
+ line numbers.
+
+Other EZT variables used:
+ file_lines: List of lines in the file, each with a line number and content.
+ should_prettify: whether the text should be syntax highlighted.
+ prettify_class: additional CSS class used to tell prettify.js how to
+ best syntax highlight this source file.
+]
+
+[# Display the line numbers and source lines in separate columns.
+ See corresponding comments L1, L2, L3 and S1, S2, S3 below.
+ This is messy because the pre tags have significant whitespace, so we
+ break lines inside the tags themslves to make our templates readable.]
+<table class="opened"><tr>
+<td id="nums">
+[# L1. Start with a nocursor row at the top to space the line numbers down the
+ same amount as the source code lines w/ their initial cursor_hidden row.]
+<pre><table width="100%"><tr class="nocursor"><td></td></tr></table></pre>
+
+[# L2. Display each line number in a row that we can refer
+ to by ID, and make each line number a self-link w/ anchor.]
+<pre><table width="100%" id="nums_table_0">[for file_lines]<tr id="gr_[file_lines.num]"
+><td id="[file_lines.num]"><a href="#[file_lines.num]">[file_lines.num]</a></td></tr
+>[end]</table></pre>
+
+[# L3. Finish the line numbers column with another nocursor row to match
+ the spacing of the source code column's final cursor_hidden row.]
+<pre><table width="100%"><tr class="nocursor"><td></td></tr></table></pre>
+</td>
+<td id="lines">
+
+[# S1. Start the source code column with a cursor row. ]
+<pre><table width="100%"><tr class="cursor_stop cursor_hidden"><td></td></tr></table></pre>
+
+[# S2. Display each source code line in a table row and cell
+ that we can identify by id.]
+<pre [if-any should_prettify]class="prettyprint [prettify_class]"[end]><table id="src_table_0">[for file_lines]<tr
+id=sl_[file_lines.num]
+><td class="source">[file_lines.line]<br></td></tr
+>[end]</table></pre>
+
+[# S3. Finish the line numbers column with another cursor stop.]
+<pre><table width="100%"><tr class="cursor_stop cursor_hidden"><td></td></tr></table></pre>
+</td>
+</tr></table>
diff --git a/templates/framework/filter-rule-admin-part.ezt b/templates/framework/filter-rule-admin-part.ezt
new file mode 100644
index 0000000..4822265
--- /dev/null
+++ b/templates/framework/filter-rule-admin-part.ezt
@@ -0,0 +1,155 @@
+<style>
+ #rules th, #rules td { padding-bottom: 1em }
+</style>
+
+[# If any value is supplied for arg0, the user will be able to set actions
+ that set default owner, set default status, and add CC users.]
+<h4 id="filters">Filter rules</h4>
+<div class="section">
+
+ <div class="closed">
+ <div>Filter rules can help you fill in defaults and stay organized.
+ <a class="ifClosed toggleHidden" href="#"
+ style="font-size:90%; margin-left:.5em">Learn more</a>
+ </div>
+
+ <div id="filterhelp" class="ifOpened help">
+ Filter rules can help your team triage issues by automatically
+ filling in default values based on other values. They can be used
+ in the same way that you might use message filters in an email client.
+ Filter rules are evaluated after each edit, not just on new items. And,
+ filter rules only add values or set default values, they never override
+ values that were explicitly set by a user.<br>
+ <br>
+ Note that exclusive prefixes still apply. So, if a user has set a label
+ with one of the exclusive prefixes, a rule that adds another label with
+ the same prefix will have no effect.
+ </div>
+ <br>
+
+ <table border="0" id="rules">
+ <tr>
+ <th></th>
+ <th style="text-align:left">If the issue matches this query:</th>
+ <th colspan="2" style="text-align:left">Then, [if-any arg0]do the following[else]add these labels[end]:</th>
+ <th></th>
+ </tr>
+
+ [for rules]
+ <tr>
+ <td style="text-align:right" width="20">[rules.idx].</td>
+ <td><input type="text" name="predicate[rules.idx]" size="60" value="[rules.predicate]"
+ autocomplete="off" id="predicate_existing_[rules.idx]" class="acob"></td>
+ <td>
+ [if-any arg0]
+ <select name="action_type[rules.idx]">
+ <option value="" disabled="disabled" [is rules.action_type ""]selected="selected"[end]>Choose...</option>
+ <option value="default_status" [is rules.action_type "default_status"]selected="selected"[end]>Set default status:</option>
+ <option value="default_owner" [is rules.action_type "default_owner"]selected="selected"[end]>Set default owner:</option>
+ <option value="add_ccs" [is rules.action_type "add_ccs"]selected="selected"[end]>Add Cc:</option>
+ <option value="add_labels" [is rules.action_type "add_labels"]selected="selected"[end]>Add labels:</option>
+ <option value="also_notify" [is rules.action_type "also_notify"]selected="selected"[end]>Also notify email:</option>
+ <option value="warning" [is rules.action_type "warning"]selected="selected"[end]>Show warning:</option>
+ </select>
+ [end]
+ </td>
+ <td>
+ <input type="text" name="action_value[rules.idx]" size="70" value="[rules.action_value]" class="acob">
+ </td>
+ <td></td>
+ </tr>
+ [end]
+
+ [for new_rule_indexes]
+ <tr id="newrow[new_rule_indexes]" [if-index new_rule_indexes first][else]style="display:none"[end]>
+ <td style="text-align:right" width="20">[new_rule_indexes].</td>
+ <td><input type="text" name="new_predicate[new_rule_indexes]" size="60" value=""
+ class="showNextRuleRow acob" data-index="[new_rule_indexes]"
+ autocomplete="off" id="predicate_new_[new_rule_indexes]"></td>
+ <td>
+ [if-any arg0]
+ <select name="new_action_type[new_rule_indexes]">
+ <option value="" disabled="disabled" selected="selected">Choose...</option>
+ <option value="default_status">Set default status:</option>
+ <option value="default_owner">Set default owner:</option>
+ <option value="add_ccs">Add Cc:</option>
+ <option value="add_labels">Add labels:</option>
+ <option value="also_notify">Also notify email:</option>
+ <option value="warning">Show warning:</option>
+ </select>
+ [end]
+ </td>
+ <td>
+ <input type="text" name="new_action_value[new_rule_indexes]" size="70" value="" class="acob">
+ [# TODO(jrobbins): figure out a way to display error messages on each rule. ]
+ </td>
+ <td width="40px">
+ [if-index new_rule_indexes last][else]
+ <span id="addrow[new_rule_indexes]" class="fakelink" class="fakelink" data-index="[new_rule_indexes]">Add a row</span
+ [end]
+ </td>
+ </tr>
+ [end]
+
+ </table>
+ </div>
+
+ [if-any errors.rules]
+ [for errors.rules]
+ <div class="fielderror">[errors.rules]</div>
+ [end]
+ <script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ document.location.hash = 'filters';
+});
+ </script>
+ [end]
+
+</div>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function showNextRuleRow(i) {
+ if (i < [max_rules]) {
+ _showID('newrow' + (i + 1));
+ _hideID('addrow' + i);
+ }
+ }
+
+ var addARowLinks = document.getElementsByClassName("fakelink");
+ for (var i = 0; i < addARowLinks.length; ++i) {
+ var link = addARowLinks[[]i];
+ link.addEventListener("click", function(event) {
+ var index = Number(event.target.getAttribute("data-index"));
+ showNextRuleRow(index);
+ });
+ }
+
+ var typeToAddARow = document.getElementsByClassName("showNextRuleRow");
+ for (var i = 0; i < typeToAddARow.length; ++i) {
+ var el = typeToAddARow[[]i];
+ el.addEventListener("keydown", function(event) {
+ var index = Number(event.target.getAttribute("data-index"));
+ showNextRuleRow(index);
+ });
+ }
+
+ 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);
+ });
+ }
+
+ 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>
diff --git a/templates/framework/footer-script.ezt b/templates/framework/footer-script.ezt
new file mode 100644
index 0000000..5e338ea
--- /dev/null
+++ b/templates/framework/footer-script.ezt
@@ -0,0 +1,34 @@
+[# The order of imports matters in this file. Scripts are imported after other scripts they depend on.]
+
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/common.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/listen.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/xmlhttp.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/shapes.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/geom.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/popup_controller.js" nonce="[nonce]"></script>
+
+[if-any is_ezt]
+ [# Note that this file will be requested twice on some pages, but chrome is smart enough
+ to not even request it the second time.]
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-ajax.js" nonce="[nonce]"></script>
+
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/externs.js" nonce="[nonce]"></script>
+[end]
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/ac.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-ac.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-install-ac.js" nonce="[nonce]"></script>
+[if-any is_ezt]
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-components.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-dd.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-display.js" nonce="[nonce]"></script>
+[end]
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-editing.js" nonce="[nonce]"></script>
+[if-any is_ezt]
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-fields.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-keystrokes.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-nav.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-update-issues-hotlists.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-util.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-onload.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/clientmon.js" nonce="[nonce]"></script>
+[end]
diff --git a/templates/framework/footer-shared.ezt b/templates/framework/footer-shared.ezt
new file mode 100644
index 0000000..9630e4c
--- /dev/null
+++ b/templates/framework/footer-shared.ezt
@@ -0,0 +1,107 @@
+[# This template displays the part of the footer used by both web components and EZT pages. ]
+
+<div id="footer">
+ [if-any old_ui_url]
+ <a href="[old_ui_url]">
+ View in the old UI
+ </a>
+ [else][if-any new_ui_url]
+ <a href="[new_ui_url]">
+ View in the new UI
+ </a>
+ [end][end]
+ [is projectname "fuchsia"]
+ <a href="https://bugs.fuchsia.dev/p/fuchsia/issues/entry?template=Report+Community+Abuse" title="Monorail [app_version]">Report Abuse</a>
+ [end]
+ <a href="https://bugs.chromium.org/p/monorail/adminIntro" title="Monorail [app_version]">About Monorail</a>
+ <a href="https://chromium.googlesource.com/infra/infra/+/main/appengine/monorail/doc/userguide/README.md">User Guide</a>
+ <a href="https://chromium.googlesource.com/infra/infra/+/main/appengine/monorail/doc/release-notes.md">Release Notes</a>
+ <a href="https://bugs.chromium.org/p/monorail/issues/entry?template=Online%20Feedback" target="_blank">Feedback on Monorail</a>
+ <a href="https://chromium.googlesource.com/infra/infra/+/main/appengine/monorail/doc/terms.md">Terms</a>
+ <a href="https://www.google.com/policies/privacy/">Privacy</a>
+</div>
+
+[include "debug.ezt"]
+
+[include "../webpack-out/ezt-footer-scripts-package.ezt"]
+
+<script type="module" nonce="[nonce]">
+// Load and instantiate pRPC client before any other script.
+window.prpcClient = new AutoRefreshPrpcClient(
+ CS_env.token, CS_env.tokenExpiresSec);
+</script>
+
+[if-any is_ezt]
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/externs.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/env.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-ajax.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-cues.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-display.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-menu.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-myhotlists.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-stars.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/framework/project-name-check.js" nonce="[nonce]"></script>
+ <script type="text/javascript" defer src="[version_base]/static/js/graveyard/xmlhttp.js" nonce="[nonce]"></script>
+[end]
+[include "footer-script.ezt"]
+
+
+[if-any is_ezt]
+ <script type="text/javascript" nonce="[nonce]">
+ runOnLoad(function() {
+ var toggles = document.getElementsByClassName("toggleHidden");
+ for (var i = 0; i < toggles.length; ++i) {
+ var toggle = toggles[[]i];
+ toggle.addEventListener("click", function (event) {
+ _toggleHidden(event.target);
+ event.preventDefault();
+ });
+ }
+
+ toggles = document.getElementsByClassName("toggleCollapse");
+ for (var i = 0; i < toggles.length; ++i) {
+ var toggle = toggles[[]i];
+ toggle.addEventListener("click", function (event) {
+ _toggleCollapse(event.target);
+ event.preventDefault();
+ });
+ }
+
+ [if-any form_token]
+ var tokenFields = document.querySelectorAll("input[[]name=token]");
+ for (var i = 0; i < tokenFields.length; ++i) {
+ var field = tokenFields[[]i];
+ field.form.addEventListener("submit", function(event) {
+ refreshTokens(
+ event, "[form_token]", "[form_token_path]", [token_expires_sec]);
+ });
+ }
+ [end]
+
+ [if-any project]
+ _fetchUserProjects(false);
+ [end]
+ _onload();
+
+ });
+ </script>
+[else]
+ <script type="text/javascript" nonce="[nonce]">
+ runOnLoad(function() {
+ TKR_install_ac();
+ });
+ </script>
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ // CrDX Feedback Button
+ (function(i,s,o,g,r,a,m){i[[]'CrDXObject']=r;i[[]r]=i[[]r]||function(){
+ (i[[]r].q=i[[]r].q||[]).push(arguments)},a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.setAttribute('nonce','[nonce]');
+ a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','https://storage.googleapis.com/chops-feedback/feedback.js','crdx');
+
+ crdx('setFeedbackButtonLink', 'https://bugs.chromium.org/p/monorail/issues/entry?template=Online%20Feedback');
+});
+</script>
diff --git a/templates/framework/footer.ezt b/templates/framework/footer.ezt
new file mode 100644
index 0000000..963b61d
--- /dev/null
+++ b/templates/framework/footer.ezt
@@ -0,0 +1,42 @@
+</div> [# End <div id="maincol"> from header.ezt]
+
+[include "footer-shared.ezt"]
+
+<script type="text/javascript" nonce="[nonce]">
+// Google Analytics
+(function(i,s,o,g,r,a,m){i[[]'GoogleAnalyticsObject']=r;i[[]r]=i[[]r]||function(){
+(i[[]r].q=i[[]r].q||[[]]).push(arguments)},i[[]r].l=1*new Date();a=s.createElement(o),
+m=s.getElementsByTagName(o)[[]0];a.async=1;a.setAttribute('nonce','[nonce]');
+a.src=g;m.parentNode.insertBefore(a,m)
+})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+(function setupGoogleAnalytics() {
+ const _EMAIL_REGEX =
+ ["/([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})/"];
+
+ ga('create', '[analytics_id]', {'siteSpeedSampleRate': 100});
+
+ [if-any logged_in_user]
+ ga('set', 'dimension1', 'Logged in');
+ [else]
+ ga('set', 'dimension1', 'Not logged in');
+ [end]
+
+ const path = window.location.href.slice(window.location.origin.length);
+ if (path.startsWith('/u')) {
+ [# Keep anything that looks like an email address out of GA.]
+ ga('set', 'title', 'A user page');
+ ga('set', 'location', path.replace(_EMAIL_REGEX, 'user@example.com'));
+ }
+
+ ga('send', 'pageview');
+})();
+</script>
+
+<ezt-app-base [if-any logged_in_user]
+ userDisplayName="[logged_in_user.email]"[end]
+ projectName="[projectname]"
+></ezt-app-base>
+
+</body>
+</html>
diff --git a/templates/framework/group-setting-fields.ezt b/templates/framework/group-setting-fields.ezt
new file mode 100644
index 0000000..4d62ab2
--- /dev/null
+++ b/templates/framework/group-setting-fields.ezt
@@ -0,0 +1,95 @@
+[# Diplay a widget to choose group visibility level, or read-only text showing
+ the visibility level. Read-only text is used when the user does not have
+ permission to edit, or if there is only one available choice.
+]
+
+[define vis_menu_was_shown]False[end]
+
+[if-any read_only][else]
+ <select name="visibility" id="visibility" [if-any import_group]disabled="disabled"[end]>
+ <option value="" disabled="disabled" [if-any initial_visibility][else]selected="selected"[end]>
+ Select a visibility level...
+ </option>
+ [for visibility_levels]
+ <option value="[visibility_levels.key]"
+ [if-any initial_visibility]
+ [is initial_visibility.key visibility_levels.key]selected="selected"[end]
+ [end]>
+ [visibility_levels.name]
+ </option>
+ [end]
+ </select>
+ [define vis_menu_was_shown]True[end]
+
+ <br><br>
+ Friend projects: <br>
+ <input size="60" type="text" id="friendprojects" name="friendprojects" value="[initial_friendprojects]">
+ <div class="fielderror">
+ <span id="friendprojectsfeedback"></span>
+ [if-any errors.friendprojects][errors.friendprojects][end]
+ </div>
+
+ <br><br>
+ <input type="checkbox" name="import_group" id="import_group"
+ [if-any import_group]checked="checked"[end]
+ [if-any groupadmin]disabled="disabled"[end] >
+ <label for="import_group">Import from external group</label>
+
+ <div class="fielderror">
+ <span id="groupimportfeedback"></span>
+ [if-any errors.groupimport][errors.groupimport][end]
+ </div>
+
+ <br>
+ External group type:
+ <select name="group_type" id="group_type"
+ [if-any import_group][else]disabled="disabled"[end]
+ [if-any groupadmin]disabled="disabled"[end] >
+ <option value="" disabled="disabled" [if-any initial_group_type][else]selected="selected"[end]>
+ Select a group type...
+ </option>
+ [for group_types]
+ <option value="[group_types.key]"
+ [if-any initial_group_type]
+ [is initial_group_type.key group_types.key]selected="selected"[end]
+ [end]>
+ [group_types.name]
+ </option>
+ [end]
+ </select>
+ <br><br>
+
+ <script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ cur_vis_value = $("visibility").value;
+
+ function _updateSettings() {
+ if ($("import_group").checked) {
+ $("group_type").disabled = false;
+ cur_vis_value = $("visibility").value;
+ $("visibility").value = 0;
+ $("visibility").disabled = true;
+ $("friendprojects").disabled = true;
+ } else {
+ $("group_type").disabled = true;
+ $("visibility").value = cur_vis_value;
+ $("visibility").disabled = false;
+ $("friendprojects").disabled = false;
+ }
+ }
+
+ $("import_group").addEventListener("click", _updateSettings);
+});
+ </script>
+[end]
+
+[is vis_menu_was_shown "False"]
+ [initial_visibility.name]
+ <input type="hidden" name="visibility" value="[initial_visibility.key]">
+[end]
+
+<div class="formerror">
+ [if-any errors.access]
+ <div class="emphasis">[errors.access]</div>
+ [end]
+</div>
\ No newline at end of file
diff --git a/templates/framework/header-shared.ezt b/templates/framework/header-shared.ezt
new file mode 100644
index 0000000..614fe5c
--- /dev/null
+++ b/templates/framework/header-shared.ezt
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+[# This is the part of header.ezt used by both the legacy
+ Monorail UI, and the new Polymer pages.
+]
+<html lang="en">
+<head>
+ <link rel="icon" type="image/vnd.microsoft.icon" href="/static/images/monorail.ico">
+ [if-any link_rel_canonical]
+ <link rel="canonical" href="[link_rel_canonical]">
+ [end]
+
+ <script type="text/javascript" nonce="[nonce]">
+ [# Javascript object containing basic page data. ]
+ window.CS_env = {
+ 'absoluteBaseUrl': '[format "js"][absolute_base_url][end]',
+ 'app_version': '[format "js"][app_version][end]',
+ 'token': '[format "js"][xhr_token][end]',
+ 'tokenExpiresSec': [format "js"][token_expires_sec][end],
+ 'loggedInUserEmail':
+ [if-any logged_in_user]
+ '[format "js"][logged_in_user.email][end]'
+ [else]
+ null
+ [end],
+ 'login_url': '[format "js"][login_url][end]',
+ 'logout_url': '[format "js"][logout_url][end]',
+ 'profileUrl':
+ [if-any logged_in_user]
+ '[format "js"][logged_in_user.profile_url][end]'
+ [else]
+ null
+ [end],
+ 'projectName': '[format "js"][projectname][end]',
+ 'projectIsRestricted': [if-any project_is_restricted]true[else]false[end],
+ 'is_member': '[format "js"][is_member][end]',
+ 'gapi_client_id': '[format "js"][gapi_client_id][end]',
+ };
+ </script>
+
+ [# Improve the snippet that appears in search]
+ [if-any show_search_metadata]
+ <meta name="Description" content="Monorail is simple, reliable, and flexible issue tracking tool.">
+ <meta name="robots" content="NOODP">
+ [end]
+
+ <title>
+ [if-any title][title] - [end]
+ [if-any title_summary][title_summary] - [end]
+ [if-any projectname]
+ [projectname] -
+ [else]
+ [if-any viewing_user_page][viewed_user.display_name] - [end]
+ [end]
+ [if-any title_summary][else]
+ [if-any project_summary][project_summary] - [end]
+ [end]
+ [site_name]
+ </title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="referrer" content="no-referrer">
+ [if-any robots_no_index]
+ <meta name="ROBOTS" content="NOINDEX,NOARCHIVE">
+ [else]
+ <meta name="ROBOTS" content="NOARCHIVE">
+ [end]
+ <meta name="viewport" content="width=device-width, minimum-scale=1.0">
+ <link type="text/css" rel="stylesheet" href="[version_base]/static/css/chopsui-normal.css">
+
+ [if-any is_ezt]
+ <link type="text/css" rel="stylesheet" href="[version_base]/static/css/ph_core.css">
+ <link type="text/css" rel="stylesheet" media="(max-width:425px)"
+ href="[version_base]/static/css/ph_mobile.css">
+
+ [if-any category_css]
+ <link type="text/css" rel="stylesheet" href="[version_base]/static/[category_css]">
+ [end]
+ [if-any category2_css]
+ <link type="text/css" rel="stylesheet" href="[version_base]/static/[category2_css]">
+ [end]
+ [if-any page_css]
+ <link type="text/css" rel="stylesheet" href="[version_base]/static/[page_css]">
+ [end]
+ [end]
+
+ <!-- Lazy load icons. -->
+ <link rel="stylesheet"
+ href="https://fonts.googleapis.com/icon?family=Material+Icons"
+ media="none"
+ id="icons-stylesheet">
+ <script type="module" async defer nonce="[nonce]">
+ document.getElementById('icons-stylesheet').media = 'all';
+ </script>
+ [# NO MORE SCRIPTS IN HEAD, it makes page loading too slow.]
+</head>
+
+[# Tiny script used sitewide. ]
+<script type="text/javascript" nonce="[nonce]">
+ function _go(url, newWindow) {
+ if (newWindow)
+ window.open(url, '_blank');
+ else
+ document.location = url;
+ }
+ function $(id) { return document.getElementById(id); }
+
+ var loadQueue = [];
+ function runOnLoad(fn) { loadQueue.push(fn); }
+
+ window.onload = function() {
+ for (var i = 0; i < loadQueue.length; i++)
+ loadQueue[[]i]();
+ delete loadQueue;
+ };
+</script>
diff --git a/templates/framework/header.ezt b/templates/framework/header.ezt
new file mode 100644
index 0000000..fe798af
--- /dev/null
+++ b/templates/framework/header.ezt
@@ -0,0 +1,39 @@
+[# This is the main header file that is included in all Monorail servlets that render a page.
+
+ Args:
+ arg0: Can be "showtabs", "showusertabs" or "showusergrouptabs" to select which top-plevel tabs are shown.
+ arg1: String like "t1", "t2", "t3" to identify the currently active tab.
+]
+[define is_ezt]Yes[end]
+[include "header-shared.ezt"]
+
+[include "../webpack-out/ezt-element-package.ezt"]
+
+<body class="[main_tab_mode] [if-any perms.EditIssue]perms_EditIssue[end]">
+
+[# Tiny script used sitewide. ]
+<script type="text/javascript" nonce="[nonce]">
+ function _go(url, newWindow) {
+ if (newWindow)
+ window.open(url, '_blank');
+ else
+ document.location = url;
+ }
+ function $(id) { return document.getElementById(id); }
+
+ var loadQueue = [];
+ function runOnLoad(fn) { loadQueue.push(fn); }
+
+ window.onload = function() {
+ for (var i = 0; i < loadQueue.length; i++)
+ loadQueue[[]i]();
+ delete loadQueue;
+ };
+</script>
+
+[include "maintabs.ezt" arg0 arg1]
+
+[include "banner_message.ezt"]
+
+<div id="maincol">
+[include "alert.ezt"]
diff --git a/templates/framework/js-placeholders.ezt b/templates/framework/js-placeholders.ezt
new file mode 100644
index 0000000..ae3548a
--- /dev/null
+++ b/templates/framework/js-placeholders.ezt
@@ -0,0 +1,12 @@
+[# Empty function definitions because we load the JS at bottom of page.
+ Without this, some rollovers or on-click handlers might give errors for
+ the first 200ms or so after the page loads. With them, they simply are
+ no-ops. ]
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+function _showBelow(){}
+function _toggleStar(){}
+function _goIssue(){}
+function _goFile(){}
+});
+</script>
diff --git a/templates/framework/label-validation-row.ezt b/templates/framework/label-validation-row.ezt
new file mode 100644
index 0000000..4c2d1a3
--- /dev/null
+++ b/templates/framework/label-validation-row.ezt
@@ -0,0 +1,6 @@
+<tr>
+ <td colspan="3">
+ <div id="confirmarea" class="novel"><span id="confirmmsg"></span></div>
+ <div id="blocksubmitarea" class="blockingsubmit"><span id="blocksubmitmsg"></span></div>
+ </td>
+</tr>
diff --git a/templates/framework/maintabs.ezt b/templates/framework/maintabs.ezt
new file mode 100644
index 0000000..416b992
--- /dev/null
+++ b/templates/framework/maintabs.ezt
@@ -0,0 +1,99 @@
+[# Show top-level tabs.
+
+ Args:
+ arg0: Can be "showtabs", or "showusertabs" to select which
+ top-level tabs are shown.
+ arg1: String like "t1", "t2", "t3" to identify the currently active tab.
+]
+[if-any projectname]
+
+[# Non-fixed container around mr-header to allow the fixed header to "take up space". ]
+<div style="width: 100%; height: var(--monorail-header-height); margin-bottom: -1px;">
+ <mr-header
+ [if-any logged_in_user]
+ userDisplayName="[logged_in_user.email]"
+ [end]
+ projectThumbnailUrl="[project_thumbnail_url]"
+ projectName="[projectname]"
+ loginUrl="[login_url]"
+ logoutUrl="[logout_url]"
+ ></mr-header>
+</div>
+[else]
+<table id="monobar" width="100%" cellpadding="0" cellspacing="0" role="presentation">
+ <tr>
+ <th class="padded">
+ <a href="/" id="wordmark">[site_name]</a>
+ </th>
+ [if-any viewed_user]
+ <th class="padded">
+ User: <a href="[viewed_user.profile_url]">[viewed_user.display_name]</a>
+ [if-any viewed_user_pb.is_site_admin_bool]<i>(Administrator)</i>[end]
+ </th>
+ [end]
+ [if-any hotlist_id]
+ <th class="toptabs padded">
+ <a href="[hotlist.url]" title="[hotlist_id]"
+ id = "hotlists-dropdown">Hotlist: [hotlist.name] <small>▼</small></a>
+ <a href="[hotlist.url]" class="[is main_tab_mode "ht2"]active[end]">Issues</a>
+ <a href="[hotlist.url]/people" class="[is main_tab_mode "ht3"]active[end]">People</a>
+ <a href="[hotlist.url]/details" class="[is main_tab_mode "ht4"]active[end]">Settings</a>
+ </th>
+ [end]
+
+ <td width="100%" id="userbar">
+ [include "user-bar.ezt"]
+ </td>
+ </tr>
+</table>
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ if ($("hotlists-dropdown"))
+ $("hotlists-dropdown").addEventListener("click", function(event) {
+ event.preventDefault();
+ });
+});
+</script>
+
+[is arg0 "showtabs"]
+ <div class="subt">
+ [include "projecttabs.ezt"]
+ </div>
+[else][is arg0 "showusertabs"]
+ <div class="subt">
+ [include "../sitewide/usertabs.ezt" arg1]
+ </div>
+[else][is arg0 "showusergrouptabs"]
+ <div class="subt">
+ [include "../sitewide/usergrouptabs.ezt" arg1]
+ </div>
+[end][end][end]
+
+[if-any warnings]
+ <table align="center" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 6px">
+ [for warnings]
+ <tr><td class="notice">
+ [warnings]
+ </td></tr>
+ [end]
+ </table>
+[end]
+[if-any errors.query]
+ <table align="center" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 6px">
+ <tr><td class="notice">
+ [errors.query]
+ </td></tr>
+ </table>
+[end]
+
+[if-any site_read_only][else]
+ [if-any project_alert]
+ <div style="font-weight: bold; color: #c00; margin-top: 5px; display: block;">
+ [project_alert]
+ </div>
+ [end]
+[end]
+
+[include "../features/cues.ezt"]
diff --git a/templates/framework/polymer-footer.ezt b/templates/framework/polymer-footer.ezt
new file mode 100644
index 0000000..a83d40f
--- /dev/null
+++ b/templates/framework/polymer-footer.ezt
@@ -0,0 +1,14 @@
+[include "footer-shared.ezt"]
+
+<script type="text/javascript" nonce="[nonce]">
+// Google Analytics
+(function(i,s,o,g,r,a,m){i[[]'GoogleAnalyticsObject']=r;i[[]r]=i[[]r]||function(){
+(i[[]r].q=i[[]r].q||[[]]).push(arguments)},i[[]r].l=1*new Date();a=s.createElement(o),
+m=s.getElementsByTagName(o)[[]0];a.async=1;a.setAttribute('nonce','[nonce]');
+a.src=g;m.parentNode.insertBefore(a,m)
+})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+(function setupGoogleAnalytics() {
+ ga('create', '[analytics_id]', {'siteSpeedSampleRate': 100});
+})();
+</script>
diff --git a/templates/framework/project-access-part.ezt b/templates/framework/project-access-part.ezt
new file mode 100644
index 0000000..2400dfe
--- /dev/null
+++ b/templates/framework/project-access-part.ezt
@@ -0,0 +1,36 @@
+[# Diplay a widget to choose project access level, or read-only text showing
+ the access level. Read-only text is used when the user does not have
+ permission to edit, or if there is only one available choice.
+]
+
+[define access_menu_was_shown]False[end]
+
+[if-any read_only][else]
+ [if-any offer_access_level]
+ <select name="access" id="access">
+ <option value="" disabled="disabled" [if-any initial_access][else]selected="selected"[end]>
+ Select an access level...
+ </option>
+ [for available_access_levels]
+ <option value="[available_access_levels.key]"
+ [if-any initial_access]
+ [is initial_access.key available_access_levels.key]selected="selected"[end]
+ [end]>
+ [available_access_levels.name]
+ </option>
+ [end]
+ </select>
+ [define access_menu_was_shown]True[end]
+ [end]
+[end]
+
+[is access_menu_was_shown "False"]
+ [initial_access.name]
+ <input type="hidden" name="access" value="[initial_access.key]">
+[end]
+
+<div class="formerror">
+ [if-any errors.access]
+ <div class="emphasis">[errors.access]</div>
+ [end]
+</div>
diff --git a/templates/framework/project-descriptive-fields.ezt b/templates/framework/project-descriptive-fields.ezt
new file mode 100644
index 0000000..b237f20
--- /dev/null
+++ b/templates/framework/project-descriptive-fields.ezt
@@ -0,0 +1,41 @@
+Summary:<br>
+<input type="text" id="summary" name="summary" size="75" value="[initial_summary]"><br>
+<div class="fielderror">
+ <span id="summaryfeedback">[if-any errors.summary][errors.summary][end]</span>
+</div>
+
+Description:<br>
+<textarea id="description" name="description" rows="20" cols="90" wrap="soft"
+ >[initial_description]</textarea><br>
+<div class="fielderror">
+ <span id="descriptionfeedback">[if-any errors.description][errors.description][end]</span>
+</div>
+
+Project home page (optional):<br/>
+<input type="text" id="project_home" name="project_home" size="75" value="[initial_project_home]"><br>
+<div class="fielderror">
+ <span id="project_homefeedback">[if-any errors.project_home][errors.project_home][end]</span>
+</div>
+
+Project documentation page (optional):<br/>
+<input type="text" id="docs_url" name="docs_url" size="75" value="[initial_docs_url]"><br>
+<div class="fielderror">
+ <span id="docs_urlfeedback">[if-any errors.docs_url][errors.docs_url][end]</span>
+</div>
+
+Project source browser (optional):<br/>
+<input type="text" id="source_url" name="source_url" size="75" value="[initial_source_url]"><br>
+<div class="fielderror">
+ <span id="source_urlfeedback">[if-any errors.source_url][errors.source_url][end]</span>
+</div>
+
+[if-any logo_view.viewurl]
+ Project logo:<br>
+ [include "display-project-logo.ezt" True]
+[else]
+ Upload project logo (optional, will be resized to 110x30):<br/>
+ <input type="file" name="logo" id="logo">
+ <div class="fielderror">
+ <span id="logofeedback">[if-any errors.logo][errors.logo][end]</span>
+ </div>
+[end]
diff --git a/templates/framework/projecttabs.ezt b/templates/framework/projecttabs.ezt
new file mode 100644
index 0000000..6a05c74
--- /dev/null
+++ b/templates/framework/projecttabs.ezt
@@ -0,0 +1,25 @@
+[is main_tab_mode "t4"]
+ <div class="[admin_tab_mode]">
+ <div class="at isf">
+ <span class="inst1"><a href="/p/[projectname]/adminIntro">Introduction</a></span>
+ <span class="inst3"><a href="/p/[projectname]/adminStatuses">Statuses</a></span>
+ <span class="inst4"><a href="/p/[projectname]/adminLabels">Labels and fields</a></span>
+ [if-any perms.EditProject][# Rule might be too sensitive for non-members to view.]
+ <span class="inst5"><a href="/p/[projectname]/adminRules">Rules</a></span>
+ [end]
+ <span class="inst6"><a href="/p/[projectname]/adminTemplates">Templates</a></span>
+ <span class="inst7"><a href="/p/[projectname]/adminComponents">Components</a></span>
+ <span class="inst8"><a href="/p/[projectname]/adminViews">Views</a></span>
+ </div>
+ </div>
+[end]
+
+
+[is main_tab_mode "t6"]
+ <div class="[admin_tab_mode]">
+ <div class="at isf">
+ <span class="inst1"><a href="/p/[projectname]/admin">General</a></span>
+ <span class="inst9"><a href="/p/[projectname]/adminAdvanced">Advanced</a></span>
+ </div>
+ </div>
+[end]
diff --git a/templates/framework/read-only-rejection.ezt b/templates/framework/read-only-rejection.ezt
new file mode 100644
index 0000000..3147d9a
--- /dev/null
+++ b/templates/framework/read-only-rejection.ezt
@@ -0,0 +1,10 @@
+<span style="color:#a30">
+ [if-any site_read_only]
+ This operation cannot be done when the site is read-only.
+ Please come back later.
+ [else]
+ [if-any project_read_only]
+ READ-ONLY
+ [end]
+ [end]
+</span>
diff --git a/templates/framework/saved-queries-admin-part.ezt b/templates/framework/saved-queries-admin-part.ezt
new file mode 100644
index 0000000..6514e30
--- /dev/null
+++ b/templates/framework/saved-queries-admin-part.ezt
@@ -0,0 +1,135 @@
+[# arg0 is either "user" for user saved queries or "project" for canned queries]
+<style>
+ #queries th, #queries td { padding-bottom: 1em }
+</style>
+
+<table border="0" id="queries">
+ <tr>
+ <th></th>
+ <th style="text-align:left">Saved query name:</th>
+ [is arg0 "user"]
+ <th style="text-align:left">Project(s):</th>
+ [end]
+ <th colspan="2" style="text-align:left">Query:</th>
+ [is arg0 "user"]
+ <th style="text-align:left">Subscription options:</th>
+ [end]
+ <th></th>
+ </tr>
+
+ [for canned_queries]
+ <tr>
+ <td style="text-align:right" width="20">[canned_queries.idx].
+ <input type="hidden" name="savedquery_id_[canned_queries.idx]" value="[canned_queries.query_id]">
+ </td>
+ <td><input type="text" name="savedquery_name_[canned_queries.idx]" size="35" value="[canned_queries.name]" class="acob"></td>
+ [is arg0 "user"]
+ <td><input type="text" name="savedquery_projects_[canned_queries.idx]" size="35" value="[canned_queries.projects]"
+ class="acob" autocomplete="off" id="savedquery_projects_[canned_queries.idx]"></td>
+ [end]
+
+ <td>
+ <select name="savedquery_base_[canned_queries.idx]">
+ [define can][canned_queries.base_query_id][end]
+ [include "../tracker/issue-can-widget.ezt" "admin"]
+ </select>
+ </td>
+ <td>
+ <input type="text" name="savedquery_query_[canned_queries.idx]" size="50" value="[canned_queries.query]" autocomplete="off" id="query_existing_[canned_queries.idx]" class="acob">
+ </td>
+ [is arg0 "user"]
+ <td>
+ <select id="savedquery_sub_mode_[canned_queries.idx]" name="savedquery_sub_mode_[canned_queries.idx]">
+ <option [is canned_queries.subscription_mode "noemail"]selected="select"[end] value="noemail"
+ >No emails</option>
+ <option [is canned_queries.subscription_mode "immediate"]selected="select"[end] value="immediate">Notify Immediately</option>
+ [# TODO(jrobbins): <option disabled="disabled">Notify Daily</option>]
+ [# TODO(jrobbins): <option disabled="disabled">Notify Weekly on Monday</option>]
+ </select>
+ </td>
+ [end]
+ <td></td>
+ </tr>
+ [end]
+
+ [is arg0 "user"]
+ [define can]1[end][# All blank lines for user queries default to "All Issues" scope.]
+ [else]
+ [define can]2[end][# All blank lines for project queries default to "Open issues" scope.]
+ [end]
+ [for new_query_indexes]
+ <tr id="newquery[new_query_indexes]" [if-index new_query_indexes first][else]style="display:none"[end]>
+ <td style="text-align:right" width="20">[new_query_indexes].</td>
+ <td><input type="text" name="new_savedquery_name_[new_query_indexes]"
+ class="showNextQueryRow acob" data-index="[new_query_indexes]"
+ size="35" value="" placeholder="Required"></td>
+ [is arg0 "user"]
+ <td><input type="text" name="new_savedquery_projects_[new_query_indexes]" size="35" value="" class="acob"
+ autocomplete="off" id="new_savedquery_projects_[new_query_indexes]" placeholder="Optional"></td>
+ [end]
+ <td>
+ <select name="new_savedquery_base_[new_query_indexes]">
+ [include "../tracker/issue-can-widget.ezt" "admin"]
+ </select>
+ </td>
+ <td>
+ <input type="text" name="new_savedquery_query_[new_query_indexes]" size="50" value="" autocomplete="off" id="query_new_[new_query_indexes]" class="acob" placeholder="Optional. Example- "label:Security owner:me"">
+ </td>
+ [is arg0 "user"]
+ <td>
+ <select id="new_savedquery_sub_mode_[new_query_indexes]" name="new_savedquery_sub_mode_[new_query_indexes]">
+ <option selected="selected" value="noemail">No emails</option>
+ <option value="immediate">Notify Immediately</option>
+ [# TODO(jrobbins): <option disabled="disabled">Notify Daily</option>]
+ [# TODO(jrobbins): <option disabled="disabled">Notify Weekly</option>]
+ </select>
+ </td>
+ [end]
+ <td width="40px">
+ [if-index new_query_indexes last][else]
+ <span id="addquery[new_query_indexes]" class="fakelink" data-index="[new_query_indexes]">Add a row</span
+ [end]
+ </td>
+ </tr>
+ [end]
+
+</table>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function showNextQueryRow(i) {
+ if (i < [max_queries]) {
+ _showID('newquery' + (i + 1));
+ _hideID('addquery' + i);
+ }
+ }
+ _fetchUserProjects(true);
+
+ var addARowLinks = document.getElementsByClassName("fakelink");
+ for (var i = 0; i < addARowLinks.length; ++i) {
+ var link = addARowLinks[[]i];
+ link.addEventListener("click", function(event) {
+ var index = Number(event.target.getAttribute("data-index"));
+ showNextQueryRow(index);
+ });
+ }
+
+ var typeToAddARow = document.getElementsByClassName("showNextQueryRow");
+ for (var i = 0; i < typeToAddARow.length; ++i) {
+ var el = typeToAddARow[[]i];
+ el.addEventListener("keydown", function(event) {
+ var index = Number(event.target.getAttribute("data-index"));
+ showNextQueryRow(index);
+ });
+ }
+
+ 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>
diff --git a/templates/framework/user-bar.ezt b/templates/framework/user-bar.ezt
new file mode 100644
index 0000000..a80fe90
--- /dev/null
+++ b/templates/framework/user-bar.ezt
@@ -0,0 +1,11 @@
+<span style="padding: 0 1em">
+ [if-any logged_in_user]
+ <mr-account-dropdown
+ userDisplayName="[logged_in_user.email]"
+ loginUrl="[login_url]"
+ logoutUrl="[logout_url]"
+ ></mr-account-dropdown>
+ [else]
+ <a href="[login_url]"><u>Sign in</u></a>
+ [end]
+</span>
diff --git a/templates/framework/user-link-availability.ezt b/templates/framework/user-link-availability.ezt
new file mode 100644
index 0000000..cc02db7
--- /dev/null
+++ b/templates/framework/user-link-availability.ezt
@@ -0,0 +1,18 @@
+[# arg0: The UserView to display.
+ arg1: "Yes" to show a shortened reason as visible text on the page.
+]
+<div class="userlink_avail" title="[arg0.display_name][if-any arg0.avail_message]:
+[arg0.avail_message][end]">
+ [if-any arg0.avail_message]
+ <span class="availability_[arg0.avail_state]">█</span>
+ [end]
+ [if-any arg0.profile_url]
+ <a href="[arg0.profile_url]">[arg0.display_name]</a>[else]<a>[arg0.display_name]</a>[end]
+</div>
+[is arg1 "Yes"]
+ [if-any arg0.avail_message]
+ <div class="availability_[arg0.avail_state]" title="[arg0.display_name]:
+[arg0.avail_message]"
+ >[arg0.avail_message_short]</div>
+ [end]
+[end]
diff --git a/templates/framework/user-link.ezt b/templates/framework/user-link.ezt
new file mode 100644
index 0000000..18d7e6c
--- /dev/null
+++ b/templates/framework/user-link.ezt
@@ -0,0 +1 @@
+[if-any arg0.profile_url]<a style="white-space: nowrap" href="[arg0.profile_url]" title="[arg0.display_name]">[arg0.display_name]</a>[else][arg0.display_name][end]
\ No newline at end of file