Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/templates/sitewide/403-page.ezt b/templates/sitewide/403-page.ezt
new file mode 100644
index 0000000..8d05784
--- /dev/null
+++ b/templates/sitewide/403-page.ezt
@@ -0,0 +1,12 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+<h3>Permission denied</h3>
+
+<h4>What happened?</h4>
+
+<p>You do not have permission to view the requested page.</p>
+
+[if-any reason]<p>Reason: [reason].</p>[end]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/group-admin-page.ezt b/templates/sitewide/group-admin-page.ezt
new file mode 100644
index 0000000..ccef3ed
--- /dev/null
+++ b/templates/sitewide/group-admin-page.ezt
@@ -0,0 +1,18 @@
+[define title]User Group: [groupname][end]
+[define category_css]css/ph_list.css[end]
+[include "../framework/header.ezt" "showusergrouptabs"]
+
+
+<form action="groupadmin.do" method="POST" autocomplete="off">
+ <input type="hidden" name="token" value="[form_token]">
+
+ <h4>Group membership visibility</h4>
+
+ The group members may be viewed by:
+ [include "../framework/group-setting-fields.ezt"]
+ <br>
+
+ <input type="submit" id="savechanges" name="btn" value="Save changes" class="submit">
+</form>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/group-create-page.ezt b/templates/sitewide/group-create-page.ezt
new file mode 100644
index 0000000..707f380
--- /dev/null
+++ b/templates/sitewide/group-create-page.ezt
@@ -0,0 +1,40 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+<h2>Create a new user group</h2>
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="createGroup.do" method="POST" id="create_group_form"
+ style="margin:1em">
+ <input type="hidden" name="token" value="[form_token]">
+
+ Group email address:<br>
+ <input size="30" type="text" id="groupname" name="groupname" value="[initial_name]">
+ <span class="graytext">Example: group-name@example.com</span>
+ <div class="fielderror">
+ <span id="groupnamefeedback"></span>
+ [if-any errors.groupname][errors.groupname][end]
+ </div>
+ <br>
+
+ Members viewable by:
+ [include "../framework/group-setting-fields.ezt"]
+ <br>
+
+ <input type="submit" id="submit_btn" name="btn" value="Create group">
+</form>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ $("create_group_form").addEventListener("submit", function() {
+ $("submit_btn").value = "Creating group...";
+ $("submit_btn").disabled = "disabled";
+ });
+});
+</script>
+
+[end][# not read-only]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/group-detail-page.ezt b/templates/sitewide/group-detail-page.ezt
new file mode 100644
index 0000000..4b19aca
--- /dev/null
+++ b/templates/sitewide/group-detail-page.ezt
@@ -0,0 +1,105 @@
+[define title]User Group: [groupname][end]
+[define category_css]css/ph_list.css[end]
+[include "../framework/header.ezt" "showusergrouptabs"]
+[include "../framework/js-placeholders.ezt"]
+
+<form method="POST" action="edit.do">
+<input type="hidden" name="token" value="[form_token]">
+<div id="colcontrol">
+ <div class="list">
+ [if-any pagination.visible]
+ <div class="pagination">
+ [if-any pagination.prev_url]<a href="[pagination.prev_url]"><b>‹</b> Prev</a>[end]
+ Members [pagination.start] - [pagination.last] of [pagination.total_count]
+ [if-any pagination.next_url]<a href="[pagination.next_url]">Next <b>›</b></a>[end]
+ </div>
+ [end]
+ <b>User Group: [groupname]</b>
+ [if-any offer_membership_editing]
+ <input type="button" value="Add members" style="font-size:80%; margin-left:1em"
+ id="add_members_button">
+ <input type="submit" value="Remove members" style="font-size:80%; margin-left:1em"
+ id="removebtn" name="removebtn" disabled="disabled">
+ [# TODO(jrobbins): extra confirmation when removing yourself as group owner.]
+ [end]
+ </div>
+
+ <p>Group type: [group_type]</p>
+
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped vt" id="resultstable" width="100%">
+ <tbody>
+ <tr id="headingrow">
+ [if-any offer_membership_editing]
+ <th style="border-right:0; padding-right:2px"> </th>
+ [end]
+ <th style="white-space:nowrap">Member</th>
+ <th style="white-space:nowrap">Role</th>
+ </tr>
+
+ [if-any pagination.visible_results]
+ [for pagination.visible_results]
+ <tr>
+ [if-any offer_membership_editing]
+ <td style="padding-right:2px">
+ <input type="checkbox" name="remove"
+ value="[pagination.visible_results.email]">
+ </td>
+ [end]
+ <td class="id" style="text-align:left">
+ [include "../framework/user-link.ezt" pagination.visible_results]
+ </td>
+ <td style="text-align:left" width="90%">
+ <a href="[pagination.visible_results.profile_url]">[pagination.visible_results.role]</a>
+ </td>
+ </tr>
+ [end]
+ [else]
+ <tr><td colspan="40">
+ This user group has no members.
+ </td></tr>
+ [end]
+
+
+ </tbody>
+ </table>
+</div>
+
+[include "../project/people-add-members-form.ezt" "group"]
+
+</form>
+
+
+[if-any offer_membership_editing]
+ <script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function _countChecked(opt_className) {
+ var numChecked = 0;
+ var inputs = document.getElementsByTagName('input');
+ for (var i = 0; i < inputs.length; i++) {
+ var el = inputs[[]i];
+ if (el.type == 'checkbox' && el.name == 'remove' && el.checked &&
+ (!opt_className || opt_className == el.className)) {
+ numChecked++;
+ }
+ }
+ return numChecked;
+ }
+
+ function _enableRemoveButton() {
+ var removeButton = document.getElementById('removebtn');
+ if (_countChecked() > 0) {
+ removeButton.disabled = false;
+ } else {
+ removeButton.disabled = true;
+ }
+ }
+
+ setInterval(_enableRemoveButton, 700);
+
+ $("add_members_button").addEventListener("click", _openAddMembersForm);
+});
+ </script>
+[end]
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/group-list-page.ezt b/templates/sitewide/group-list-page.ezt
new file mode 100644
index 0000000..14bdf57
--- /dev/null
+++ b/templates/sitewide/group-list-page.ezt
@@ -0,0 +1,95 @@
+[define title]User Groups[end]
+[define category_css]css/ph_list.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+[include "../framework/js-placeholders.ezt"]
+
+<form method="POST" action='/hosting/deleteGroup.do'>
+<input type="hidden" name="token" value="[form_token]">
+<div id="colcontrol">
+ <div class="list">
+ <b>User Groups</b>
+ [if-any offer_group_deletion]
+ <input type="submit" value="Delete Groups" style="margin-left:1em"
+ id="removebtn" name="removebtn" disabled="disabled">
+ [end]
+ [if-any offer_group_creation]
+ <a href="/hosting/createGroup" class="buttonify" style="margin-left:1em">Create Group</a>
+ [end]
+ </div>
+
+ <table cellspacing="0" cellpadding="2" border="0" class="results striped" id="resultstable" width="100%">
+ <tbody>
+ [if-any groups]
+
+ <tr id="headingrow">
+ [if-any offer_group_deletion]
+ <th style="border-right:0; padding-right:2px" width="2%"> </th>
+ [end]
+ <th style="white-space:nowrap">Name</th>
+ <th style="white-space:nowrap">Size</th>
+ <th style="white-space:nowrap">Member list visibility</th>
+ </tr>
+
+ [for groups]
+ <tr>
+ [if-any offer_group_deletion]
+ <td style="padding-right:2px" width="2%">
+ <input type="checkbox" name="remove"
+ value="[groups.group_id]">
+ </td>
+ [end]
+ <td class="id" style="text-align:left"><a href="[groups.detail_url]">[groups.name]</a></td>
+ <td><a href="[groups.detail_url]">[groups.num_members]</a></td>
+ <td><a href="[groups.detail_url]">[groups.who_can_view_members]</a></td>
+ </tr>
+ [end]
+
+ [else]
+ <tr>
+ <td colspan="40" class="id">
+ <div style="padding: 3em; text-align: center">
+ No user groups have been defined.
+ </div>
+ </td>
+ </tr>
+ [end]
+
+
+ </tbody>
+ </table>
+</div>
+
+</form>
+
+[if-any offer_group_deletion]
+ <script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ function _countChecked(opt_className) {
+ var numChecked = 0;
+ var inputs = document.getElementsByTagName('input');
+ for (var i = 0; i < inputs.length; i++) {
+ var el = inputs[[]i];
+ if (el.type == 'checkbox' && el.name == 'remove' && el.checked &&
+ (!opt_className || opt_className == el.className)) {
+ numChecked++;
+ }
+ }
+ return numChecked;
+ }
+
+ function _enableRemoveButton() {
+ var removeButton = document.getElementById('removebtn');
+ if (_countChecked() > 0) {
+ removeButton.disabled = false;
+ } else {
+ removeButton.disabled = true;
+ }
+ }
+
+ setInterval(_enableRemoveButton, 700);
+
+});
+ </script>
+[end]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/hosting-home-page.ezt b/templates/sitewide/hosting-home-page.ezt
new file mode 100644
index 0000000..6f066fb
--- /dev/null
+++ b/templates/sitewide/hosting-home-page.ezt
@@ -0,0 +1,85 @@
+[define show_search_metadata]True[end]
+[define robots_no_index]true[end]
+[define category_css]css/ph_list.css[end]
+
+[include "../framework/header.ezt" "hidesearch"]
+
+[define prod_hosting_base_url]/hosting/[end]
+
+[if-any read_only][else]
+ [if-any can_create_project learn_more_link]
+ <div style="margin-top:3em; text-align:center;">
+ <div style="text-align:center;margin:1em">
+ [if-any can_create_project]
+ <a href="/hosting/createProject">Create a new project</a>
+ [end]
+
+ [if-any learn_more_link]
+ <a href="[learn_more_link]">Learn more about [site_name]</a>
+ [end]
+ </div>
+ </div>
+ [end]
+[end]
+
+<a href="/projects" style="display: block; padding: 0.5em 8px; width: 50%;
+ text-align: center; margin: auto; border: var(--chops-normal-border);
+ border-radius: 8px;">
+Preview a new project list for Monorail.
+</a>
+
+<div id="controls">
+ [include "../sitewide/project-list-controls.ezt" arg1]
+</div>
+
+<div id="project_list">
+ [if-any projects]
+ <table id="resultstable" class="resultstable results" width="100%" border="0" cellspacing="0" cellpadding="18">
+ <tr>
+ [if-any logged_in_user]<th></th>[end]
+ <th style="text-align:left">Name</th>
+ [if-any logged_in_user]<th style="text-align:left; white-space:nowrap">Your role</th>[end]
+ <th style="text-align:left">Stars</th>
+ <th style="text-align:left">Updated</th>
+ <th style="text-align:left">Summary</th>
+ </tr>
+ [for projects]
+ <tr data-url="[projects.relative_home_url]">
+ [include "project-list-row.ezt"]
+ </tr>
+ [end]
+ </table>
+ [else]
+ <p style="text-align:center;padding:0; margin:2em">
+ There were no visible projects found.
+ </p>
+ [end]
+</div>
+
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ var stars = document.getElementsByClassName("star");
+ for (var i = 0; i < stars.length; ++i) {
+ var star = stars[[]i];
+ star.addEventListener("click", function (event) {
+ var projectName = event.target.getAttribute("data-project-name");
+ _TKR_toggleStar(event.target, projectName);
+ });
+ }
+
+ 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);
+
+
+});
+</script>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/moved-page.ezt b/templates/sitewide/moved-page.ezt
new file mode 100644
index 0000000..391f957
--- /dev/null
+++ b/templates/sitewide/moved-page.ezt
@@ -0,0 +1,24 @@
+[include "../framework/header.ezt" "hidetabs"]
+
+<h3>Project has moved</h3>
+
+<h4>What happened?</h4>
+
+<p>Project "[project_name]" has moved to another location on the Internet.</p>
+
+<div style="margin:2em" class="help">
+ <b style="margin:0.5em">Your options:</b>
+
+ <ul>
+ [if-any moved_to_url]
+ <li>View the project at:
+ <a href="[moved_to_url]">[moved_to_url]</a></li>
+ [end]
+ <li><a href="http://www.google.com/search?q=[project_name]">Search the web</a>
+ for pages about "[project_name]".
+ </li>
+
+ </ul>
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/project-404-page.ezt b/templates/sitewide/project-404-page.ezt
new file mode 100644
index 0000000..9bc2878
--- /dev/null
+++ b/templates/sitewide/project-404-page.ezt
@@ -0,0 +1,6 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showtabs"]
+
+<center style="margin-top: 4em;">The page you asked for does not exist.</center>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/project-create-page.ezt b/templates/sitewide/project-create-page.ezt
new file mode 100644
index 0000000..9bcb447
--- /dev/null
+++ b/templates/sitewide/project-create-page.ezt
@@ -0,0 +1,117 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "hidetabs"]
+
+<h2>Create a new project</h2>
+
+[if-any read_only][include "../framework/read-only-rejection.ezt"]
+[else]
+
+<form action="createProject.do" method="POST" id="create_project_form"
+ style="margin:1em" enctype="multipart/form-data">
+ <input type="hidden" name="token" value="[form_token]">
+
+
+ Project name:<br>
+ <input size="30" type="text" id="projectname" name="projectname" autocomplete="off"
+ value="[initial_name]">
+ <span class="graytext">Example: my-project-name</span>
+ <div class="fielderror">
+ <span id="projectnamefeedback">
+ [if-any errors.projectname][errors.projectname][end]
+ </span>
+ </div>
+
+ [include "../framework/project-descriptive-fields.ezt"]
+ <br>
+
+ Viewable by:
+ [include "../framework/project-access-part.ezt" "checksubmit"]
+ <br>
+
+ <input type="submit" id="submit_btn" name="btn" value="Create project">
+</form>
+
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ [# TODO(jrobbins): move this to compiled Javascript. ]
+ var submit = document.getElementById('submit_btn');
+ submit.disabled = 'disabled';
+ var projectname = document.getElementById('projectname');
+ var access = document.getElementById('access');
+ var summary = document.getElementById('summary');
+ var description = document.getElementById('description');
+ var cg = document.getElementById('cg');
+ var oldName = '';
+ projectname.focus();
+ var solelyDigits = /^[[]-0-9]+$/
+ var hasUppercase = /[[]A-Z]/
+ var projectRE = /^[[]a-z0-9][[]-a-z0-9]*$/
+
+ function checkprojectname() {
+ name = projectname.value;
+ if (name != oldName) {
+ oldName = name;
+ feedback = document.getElementById('projectnamefeedback');
+ submit.disabled='disabled';
+ if (name == '') {
+ feedback.textContent = '';
+ } else if (hasUppercase.test(name)) {
+ feedback.textContent = 'Must be all lowercase';
+ } else if (solelyDigits.test(name)) {
+ feedback.textContent = 'Must include a lowercase letter';
+ } else if (!projectRE.test(name)) {
+ feedback.textContent = 'Invalid project name';
+ } else if (name.length > [max_project_name_length]) {
+ feedback.textContent = 'Project name is too long';
+ } else if(name[[]name.length - 1] == '-') {
+ feedback.textContent = "Project name cannot end with a '-'";
+ } else {
+ feedback.textContent = '';
+ checkname();
+ checksubmit();
+ }
+ }
+ }
+
+ var checkname = debounce(function() {
+ _CP_checkProjectName(projectname.value);
+ });
+
+ function checkempty(elemId) {
+ var elem = document.getElementById(elemId);
+ feedback = document.getElementById(elemId + 'feedback');
+ if (elem.value.length == 0) {
+ feedback.textContent = 'Please enter a ' + elemId;
+ } else {
+ feedback.textContent = ' ';
+ }
+ checksubmit();
+ }
+
+ function checksubmit() {
+ feedback = document.getElementById('projectnamefeedback');
+ submit.disabled='disabled';
+ if (projectname.value.length > 0 &&
+ summary.value.length > 0 &&
+ description.value.length > 0 &&
+ (cg == undefined || cg.value.length > 1) &&
+ feedback.textContent == '') {
+ submit.disabled='';
+ }
+ }
+ setInterval(checkprojectname, 700); [# catch changes that were not keystrokes.]
+ $("projectname").addEventListener("keyup", checkprojectname);
+ $("summary").addEventListener("keyup", function() { checkempty("summary"); });
+ $("description").addEventListener("keyup", function() { checkempty("description"); });
+ $("create_project_form").addEventListener("submit", function () {
+ $("submit_btn").value = "Creating project...";
+ $("submit_btn").disabled = "disabled";
+ });
+
+});
+</script>
+
+[end][# not read-only]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/project-list-controls.ezt b/templates/sitewide/project-list-controls.ezt
new file mode 100644
index 0000000..7a88cee
--- /dev/null
+++ b/templates/sitewide/project-list-controls.ezt
@@ -0,0 +1,10 @@
+<div class="list">
+ <h4 style="display:inline">List of Projects</h4>
+ [if-any projects]
+ <div class="pagination">
+ [if-any pagination.prev_url]<a href="[pagination.prev_url]"><b>‹</b> Prev</a>[end]
+ [pagination.start] - [pagination.last] of [pagination.total_count]
+ [if-any pagination.next_url]<a href="[pagination.next_url]">Next <b>›</b></a>[end]
+ </div>
+ [end]
+</div>
\ No newline at end of file
diff --git a/templates/sitewide/project-list-row.ezt b/templates/sitewide/project-list-row.ezt
new file mode 100644
index 0000000..a1ce4ae
--- /dev/null
+++ b/templates/sitewide/project-list-row.ezt
@@ -0,0 +1,54 @@
+[# This displays one list row of the project search results.
+
+No parameters are used, but it expects the "projects" loop variable to
+hold the current project.]
+
+[if-any logged_in_user]
+ [# Display star for logged in user to star this project]
+ <td>
+ [if-any logged_in_user]
+ <a class="star"
+ style="color:[if-any projects.starred]cornflowerblue[else]gray[end]"
+ title="[if-any projects.starred]Un-s[else]S[end]tar this project" data-project-name="[projects.project_name]">
+ [if-any projects.starred]★[else]☆[end]
+ </a>
+ [end]
+ </td>
+[end]
+
+[# Project name link to this project]
+<td style="white-space:nowrap" class="id">
+ <a href="[projects.relative_home_url]/" style="font-size:medium">
+ [projects.project_name]
+ </a>
+</td>
+
+[# Display membership and star only if user is logged in]
+[if-any logged_in_user]
+ [# User's membership status of this project]
+ <td>
+ [if-any projects.membership_desc][projects.membership_desc][end]
+ </td>
+[end]
+
+[# Display how many have starred this project]
+<td style="white-space:nowrap">
+ [is projects.num_stars "0"]
+ [else]
+ <span id="star_count-[projects.project_name]">[projects.num_stars]</span>
+ [end]
+</td>
+
+[# When project was last updated]
+<td style="white-space:nowrap">
+ [if-any projects.last_updated_exists]
+ [projects.recent_activity]
+ [end]
+</td>
+
+[# The short summary of this project]
+<td style="width:100%">
+ [is projects.limited_summary ""][else]
+ [projects.limited_summary]<br>
+ [end]
+</td>
diff --git a/templates/sitewide/unified-settings.ezt b/templates/sitewide/unified-settings.ezt
new file mode 100644
index 0000000..1f2c79c
--- /dev/null
+++ b/templates/sitewide/unified-settings.ezt
@@ -0,0 +1,93 @@
+[# common form fields for changing user settings ]
+<input type="hidden" name="token" value="[form_token]">
+
+
+<h4>Privacy</h4>
+<div style="margin:0 0 2em 2em">
+ <input type="checkbox" name="obscure_email" id="obscure_email" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_pb.obscure_email_bool]checked="checked"[end] >
+ <label for="obscure_email">
+ When [if-any self]I participate[else]this user participates[end]
+ in projects, show non-members [if-any self]my[else]this user's[end] email address as
+ "[settings_user.obscured_username]...@[settings_user.domain]", instead of
+ showing the full address.
+ </label>
+
+ <br><br>
+</div>
+
+<h4>Notifications</h4>
+<div style="margin:0 0 2em 2em">
+ [# TODO(jrobbins): re-implement issue preview on hover in polymer.]
+
+ <p>
+ Whenever an issue is changed by another user, send
+ [if-any self]me[else]this user[end] an email:
+ </p>
+ <input type="checkbox" name="notify" id="notify" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_pb.notify_issue_change_bool]checked="checked"[end] >
+ <label for="notify">
+ If [if-any self]I am[else]this user is[end] in the issue's <b>owner</b> or <b>CC</b> fields.
+ </label><br>
+ <input type="checkbox" name="notify_starred" id="notify_starred" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_pb.notify_starred_issue_change_bool]checked="checked"[end] >
+ <label for="notify_starred">
+ If [if-any self]I[else]this user[end] <b>starred</b> the issue.
+ </label>
+
+ <p>
+ When a date specified in an issue arrives, and that date field is configured to notify
+ issue participants:
+ </p>
+ <input type="checkbox" name="notify_starred_ping" id="notify_starred_ping" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_pb.notify_starred_ping_bool]checked="checked"[end] >
+ <label for="notify_starred_ping">
+ Also send a notification if [if-any self]I[else]this user[end] <b>starred</b> the issue.
+ </label><br>
+
+ <p>
+ Email notifications sent to me should:
+ </p>
+ <input type="checkbox" name="email_compact_subject" id="email_compact_subject" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_pb.email_compact_subject_bool]checked="checked"[end] >
+ <label for="email_compact_subject">
+ Format the subject line compactly
+ </label><br>
+ <input type="checkbox" name="email_view_widget" id="email_view_widget" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_pb.email_view_widget_bool]checked="checked"[end] >
+ <label for="email_view_widget">
+ Include a "View Issue" button in Gmail
+ </label><br>
+ <br>
+</div>
+
+<h4>Community interactions</h4>
+<div style="margin:0 0 2em 2em">
+ <input type="checkbox" name="restrict_new_issues" id="restrict_new_issues" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_prefs.restrict_new_issues]checked="checked"[end] >
+ <label for="restrict_new_issues">
+ When entering a new issue, add Restrict-View-Google to the form.
+ </label><br>
+
+ <input type="checkbox" name="public_issue_notice" id="public_issue_notice" value="1"
+ [if-any read_only]disabled="disabled"[end]
+ [if-any settings_user_prefs.public_issue_notice]checked="checked"[end] >
+ <label for="public_issue_notice">
+ When viewing a public issue, display a banner.
+ </label><br>
+</div>
+
+<h4>Availability</h4>
+<div style="margin:0 0 2em 2em">
+ Vacation message:
+ <input type="text" size="50" name="vacation_message" id="vacation_message"
+ value="[settings_user_pb.vacation_message]"
+ [if-any read_only]disabled="disabled"[end] >
+</div>
diff --git a/templates/sitewide/user-clear-bouncing-page.ezt b/templates/sitewide/user-clear-bouncing-page.ezt
new file mode 100644
index 0000000..5c9123d
--- /dev/null
+++ b/templates/sitewide/user-clear-bouncing-page.ezt
@@ -0,0 +1,26 @@
+[include "../framework/header.ezt" "showusertabs" "t1"]
+
+<div id="colcontrol">
+<h2>Reset bouncing email</h2>
+
+[if-any last_bounce_str]
+ <p>
+ <b>Email to this user bounced:</b>
+ [last_bounce_str]
+ </p>
+[end]
+
+
+<p>If you believe that email sent to this user will no longer bounce,
+ press the button below to clear the email bouncing status.</p>
+
+<form action="clearBouncing.do" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+ <input id="submit_btn" type="submit" name="btn"
+ value="Clear bouncing status">
+</form>
+
+</div>
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/user-profile-page-polymer.ezt b/templates/sitewide/user-profile-page-polymer.ezt
new file mode 100644
index 0000000..fe10704
--- /dev/null
+++ b/templates/sitewide/user-profile-page-polymer.ezt
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Monorail: Polymer Profile Page</title>
+
+[include "header-shared.ezt"]
+
+[include "../webpack-out/mr-profile-page.ezt"]
+
+<mr-profile-page
+ viewedUserId="[viewed_user_id]"
+ viewedUser="[viewed_user_display_name]" [if-any logged_in_user]
+ user="[logged_in_user.email]"[end]
+ loginUrl="[login_url]"
+ logoutUrl="[logout_url]"
+ lastVisitStr="[last_visit_str]"
+ starredUsers="[starred_users_json]"
+></mr-profile-page>
+
+[include "../framework/polymer-footer.ezt"]
diff --git a/templates/sitewide/user-profile-page.ezt b/templates/sitewide/user-profile-page.ezt
new file mode 100644
index 0000000..7517d15
--- /dev/null
+++ b/templates/sitewide/user-profile-page.ezt
@@ -0,0 +1,474 @@
+[define category_css]css/ph_list.css[end]
+[include "../framework/header.ezt" "showusertabs" "t1"]
+[include "../framework/js-placeholders.ezt"]
+<div id="colcontrol">
+
+<h2>
+ [if-any viewing_self][else]
+ [if-any user_stars_enabled]
+ [if-any logged_in_user]
+ [if-any read_only][else]
+ [if-any user_stars_enabled]
+ [if-any logged_in_user]
+ [if-any read_only][else]
+ <a id="user_star"
+ style="color:[if-any is_user_starred]cornflowerblue[else]gray[end]"
+ title="[if-any is_user_starred]Un-s[else]S[end]tar this user">
+ [if-any is_user_starred]★[else]☆[end]
+ </a>
+ [end]
+ [end]
+ [end]
+ [end]
+ [end]
+ [end]
+ [end]
+
+ [viewed_user_display_name]
+</h2>
+
+<p>
+ <b>Last visit:</b>
+ [last_visit_str]
+</p>
+
+[if-any last_bounce_str]
+ <p>
+ <b>Email to this user bounced:</b>
+ [last_bounce_str]
+ [define offer_clear_bouncing]No[end]
+ [if-any viewing_self][define offer_clear_bouncing]Yes[end][end]
+ [if-any perms._EditOtherUsers][define offer_clear_bouncing]Yes[end][end]
+ [is offer_clear_bouncing "Yes"]
+ <a href="[viewed_user.profile_url]clearBouncing" style="margin-left:2em">Clear</a>
+ [end]
+ </p>
+[end]
+
+[if-any vacation_message]
+ <p>
+ <b>Vacation message:</b>
+ [vacation_message]
+ </p>
+[end]
+
+[if-any linked_parent]
+ <p>
+ <b>Linked parent account:</b>
+ [include "../framework/user-link.ezt" linked_parent]
+ [if-any offer_unlink perms._EditOtherUsers]
+ <input type="button" class="unlink_account secondary"
+ data-parent="[linked_parent.email]"
+ data-child="[viewed_user.email]"
+ value="Unlink">
+ [end]
+ </p>
+[end]
+
+[if-any linked_children]
+ <p>
+ <b>Linked child accounts:</b>
+ [for linked_children]
+ [include "../framework/user-link.ezt" linked_children]
+ [if-any offer_unlink perms._EditOtherUsers]
+ <input type="button" class="unlink_account secondary"
+ data-parent="[viewed_user.email]"
+ data-child="[linked_children.email]"
+ value="Unlink">
+ [end]
+ [end]
+ </p>
+[end]
+
+[if-any incoming_invite_users]
+ <b>Accept linked sub-account:</b>
+ [for incoming_invite_users]
+ <div>
+ [include "../framework/user-link.ezt" incoming_invite_users]
+ [if-any can_edit_invites][# TODO(jrobbins): allow site admin to accept invites for other users.]
+ <input type="button" class="incoming_invite" data-email="[incoming_invite_users.email]" value="Accept">
+ [# TODO(jrobbins): Button to decline invite.]
+ [end]
+ </div>
+ [end]
+[else][if-any outgoing_invite_users]
+ <b>Waiting for acceptance by parent-account:</b>
+ [for outgoing_invite_users]
+ <div>
+ [include "../framework/user-link.ezt" outgoing_invite_users]
+ </div>
+ [end]
+[else][if-any possible_parent_accounts]
+ <b>Link this account to:</b>
+ <select id="parent_to_invite">
+ <option value="" selected="selected">----</option>
+ [for possible_parent_accounts]
+ <option value="[possible_parent_accounts]">[possible_parent_accounts]</option>
+ [end]
+ </select>
+ [if-any can_edit_invites][# TODO(jrobbins): allow site admin to create invites for other users.]
+ <button id="create_linked_account_invite" disabled="disabled">Link</button>
+ [end]
+[end][end][end]
+
+
+[if-any user_stars_enabled]
+<p>
+<b>Starred developers:</b>
+[if-any starred_users]
+[for starred_users]
+ [include "../framework/user-link.ezt" starred_users][if-index starred_users last][else], [end]
+[end]
+[else]<i>None</i>[end]
+</p>
+[end]
+<br>
+
+<div class="list">
+ <table style="width: 100%;" cellspacing="0" cellpadding="0">
+ <tbody><tr>
+ <th style="text-align: left;">Projects
+ </th>
+ </tr></tbody>
+ </table>
+</div>
+
+<table cellspacing="0" cellpadding="2" border="0" class="results striped" id="projecttable" width="100%">
+ <tbody>
+ <tr id="headingrow">
+ [if-any logged_in_user]
+ <th style="white-space:nowrap; width:3%;"></th>
+ [end]
+ <th style="white-space:nowrap; width:15%;">Role</th>
+ <th style="white-space:nowrap; width:25%;">Project</th>
+ <th style="white-space:nowrap; width:57%;">Summary</th>
+ </tr>
+ [if-any owner_of_projects committer_of_projects contributor_to_projects]
+ [if-any owner_of_projects]
+ [for owner_of_projects]
+ <tr data-url="[owner_of_projects.relative_home_url]" data-project-name="[owner_of_projects.project_name]">
+ [if-any logged_in_user]
+ <td class="rowwidgets">
+ <a class="star"
+ style="color:[if-any owner_of_projects.starred]cornflowerblue[else]gray[end]"
+ title="[if-any owner_of_projects.starred]Un-s[else]S[end]tar this project"
+ data-project-name="[owner_of_projects.project_name]">
+ [if-any owner_of_projects.starred]★[else]☆[end]
+ </a>
+ </td>
+ [end]
+ <td>Owner</td>
+ <td class="id" name="owner">
+ <a href="[owner_of_projects.relative_home_url]/">[owner_of_projects.project_name]</a>
+ [is owner_of_projects.state_name "HIDDEN"]<span style="color:red"> - hidden</span>[end]
+ </td>
+ <td>[owner_of_projects.summary]</td>
+ </tr>
+ [end]
+ [end]
+ [if-any committer_of_projects]
+ [for committer_of_projects]
+ <tr data-url="[committer_of_projects.relative_home_url]" data-project-name="[committer_of_projects.project_name]">
+ [if-any logged_in_user]
+ <td class="rowwidgets">
+ <a class="star"
+ style="color:[if-any committer_of_projects.starred]cornflowerblue[else]gray[end]"
+ title="[if-any committer_of_projects.starred]Un-s[else]S[end]tar this project"
+ data-project-name="[committer_of_projects.project_name]">
+ [if-any committer_of_projects.starred]★[else]☆[end]
+ </a>
+ </td>
+ [end]
+ <td>Committer</td>
+ <td class="id" name="committer">
+ <a href="[committer_of_projects.relative_home_url]/">[committer_of_projects.project_name]
+ </a>
+ </td>
+ <td>
+ [committer_of_projects.summary]
+ </td>
+ </tr>
+ [end]
+ [end]
+
+ [if-any contributor_to_projects]
+ [for contributor_to_projects]
+ <tr data-url="[contributor_to_projects.relative_home_url]" data-project-name="[contributor_to_projects.project_name]">
+ [if-any logged_in_user]
+ <td class="rowwidgets">
+ <a class="star"
+ style="color:[if-any contributor_to_projects.starred]cornflowerblue[else]gray[end]"
+ title="[if-any contributor_to_projects.starred]Un-s[else]S[end]tar this project"
+ data-project-name="[contributor_to_projects.project_name]">
+ [if-any contributor_to_projects.starred]★[else]☆[end]
+ </a>
+ </td>
+ [end]
+ <td>Contributor</td>
+ <td class="id" name="contributor">
+ <a href="[contributor_to_projects.relative_home_url]/">[contributor_to_projects.project_name]
+ </a>
+ [is contributor_to_projects.state_name "HIDDEN"]<span style="color:red"> - hidden</span>[end]</td>
+ <td>
+ [contributor_to_projects.summary]
+ </td>
+ </tr>
+ [end]
+ [end]
+
+ [else]
+ <tr>
+ <td colspan="4"><i>No projects.</i></td>
+ <tr>
+ [end]
+ </tbody>
+</table>
+
+
+[if-any starred_projects]
+<br>
+<div class="list">
+ <table style="width: 100%;" cellspacing="0" cellpadding="0">
+ <tbody><tr>
+ <th style="text-align: left;">
+ Starred by [if-any viewing_self]me[else]
+ [viewed_user_display_name]
+ [end]
+ </th>
+ </tr></tbody>
+ </table>
+</div>
+<table cellspacing="0" cellpadding="2" border="0" class="results striped" id="starredtable" width="100%">
+ <tbody>
+ <tr id="headingrow">
+ [if-any logged_in_user]
+ <th style="white-space:nowrap; width:3%;"></th>
+ [end]
+ <th style="white-space:nowrap; width:25%;">Name</th>
+ <th style="white-space:nowrap; width:57%;">Summary</th>
+ </tr>
+
+ [for starred_projects]
+ <tr data-url="[starred_projects.relative_home_url]" data-project-name="[starred_projects.project_name]">
+ [if-any logged_in_user]
+ <td class="rowwidgets">
+ <a class="star"
+ style="color:[if-any starred_projects.starred]cornflowerblue[else]gray[end]"
+ title="[if-any starred_projects.starred]Un-s[else]S[end]tar this project"
+ data-project-name="[starred_projects.project_name]">
+ [if-any starred_projects.starred]★[else]☆[end]
+ </a>
+ </td>
+ [end]
+ <td class="id" name="starred_project">
+ <a href="[starred_projects.relative_home_url]/">[starred_projects.project_name]</a>
+ [is starred_projects.state_name "HIDDEN"]<span style="color:red"> - hidden</span>[end]
+ </td>
+ <td>
+ [starred_projects.summary]
+ </td>
+ </tr>
+ [end]
+
+</table>
+[end]
+
+[if-any owner_of_archived_projects]
+<br>
+<div class="list">
+ <table style="width: 100%;" cellspacing="0" cellpadding="0">
+ <tbody><tr>
+ <th style="text-align: left;">Archived projects
+ </th>
+ </tr></tbody>
+ </table>
+</div>
+<table cellspacing="0" cellpadding="2" border="0" class="results striped" id="archivedtable" width="100%">
+ <tbody>
+ <tr id="headingrow">
+ <th style="white-space:nowrap; width:25%;">Name</th>
+ <th style="white-space:nowrap; width:60%;">Summary</th>
+ </tr>
+ [for owner_of_archived_projects]
+ <tr data-url="[owner_of_archived_projects.relative_home_url]/adminAdvanced">
+ <td class="id" name="deleted_project">[owner_of_archived_projects.project_name] -
+ <a href="[owner_of_archived_projects.relative_home_url]/adminAdvanced">Unarchive or delete</a>
+ </td>
+ <td>
+ [owner_of_archived_projects.summary]
+ </td>
+ </tr>
+ [end]
+</table>
+[end]
+
+[if-any user_groups]
+<br>
+<div class="list">
+ <table style="width: 100%;" cellspacing="0" cellpadding="0">
+ <tbody><tr>
+ <th style="text-align: left;">User groups
+ </th>
+ </tr></tbody>
+ </table>
+</div>
+<table cellspacing="0" cellpadding="2" border="0" class="results striped" id="usergrouptable" width="100%">
+ <tbody>
+ <tr id="headingrow">
+ <th style="white-space:nowrap; width:25%;">Name</th>
+ </tr>
+ [for user_groups]
+ <tr data-url="[user_groups.profile_url]">
+ <td class="id">
+ <a href="[user_groups.profile_url]">[user_groups.email]</a>
+ </td>
+ </tr>
+ [end]
+ </tbody>
+</table>
+[end]
+
+[if-any can_ban]
+ <form action="ban.do" method="POST">
+ <input type="hidden" name="token" value="[ban_token]">
+ <h4>Banned for abuse</h4>
+ <div style="margin:0 0 2em 2em">
+ <input type="checkbox" name="banned" id="banned" value="1"
+ [if-any settings_user_is_banned]checked="checked"[end] >
+ <label for="banned">This user is banned because:</label>
+ <input type="text" size="50" name="banned_reason" id="banned_reason" value="[settings_user_pb.banned]">
+ </div>
+
+ <div style="margin:0 0 2em 2em">
+ <input id="submit_btn" type="submit" name="btn"
+ value="Update banned status">
+ </div>
+
+ </form>
+
+ [if-any viewed_user_is_spammer]
+ <form action="banSpammer.do" method="POST">
+ <input type="hidden" name="token" value="[ban_spammer_token]">
+ <input type="hidden" size="50" name="banned_reason" id="banned_reason" value="">
+ <input type="submit" name="undoBanSpammerButton" id="undo_ban_spammer_btn" value="Un-ban this user as a spammer">
+ </form>
+ [end]
+
+
+ [if-any viewed_user_may_be_spammer]
+ <form action="banSpammer.do" method="POST">
+ <input type="hidden" name="token" value="[ban_spammer_token]">
+ <input type="hidden" name="banned" value="True">
+ <input type="hidden" size="50" name="banned_reason" id="banned_reason" value="Spam">
+ <input type="submit" name="banSpammerButton" id="ban_spammer_btn" value="Ban this user as a spammer">
+ </form>
+ [end]
+
+[end]
+
+[if-any perms._EditOtherUsers]
+<h3 style="clear:both">Edit user</h3>
+ <form action="edit.do" method="POST">
+ <input type="hidden" name="token" value="[form_token]">
+ <h4>Site administration</h4>
+ <div style="margin:0 0 2em 2em">
+ <input type="checkbox" name="site_admin" id="site_admin" value="1" [if-any viewed_user_pb.is_site_admin_bool]checked="checked"[end] >
+ <label for="site_admin">This user is a site administrator (a super user)</label>
+ </div>
+
+ [include "unified-settings.ezt"]
+
+ <div style="margin:0 0 2em 2em">
+ <input id="submit_btn" type="submit" name="btn"
+ value="Save changes">
+ </div>
+
+ </form>
+[end]
+
+[if-any can_delete_user]
+<h3 style="clear:both">Delete user account</h3>
+ <p>Deleting a user account deletes the user and most user owned items from the site.
+ The user's email will be removed from any issues that the user participated in.
+ Hotlists owned by the user will either be transferred to another editor or get deleted.
+ Any Project Rules that the user is involved in will get deleted.
+ </p>
+ <div style="margin:0 0 2em 2em">
+ <input id="delete_btn" type="submit" name="btn" value="Delete user account">
+ <div id="delete_error" class="fielderror"></div>
+ </div>
+[end]
+
+</div>
+</div>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+ if ($("user_star")) {
+ [# The user viewing this page wants to star the user *on* this page]
+ $("user_star").addEventListener("click", function () {
+ _TKR_toggleStar($("user_star"), null, null, "[viewed_user_id]", null, null);
+ });
+ }
+
+ var stars = document.getElementsByClassName("star");
+ for (var i = 0; i < stars.length; ++i) {
+ var star = stars[[]i];
+ star.addEventListener("click", function (event) {
+ var projectName = event.target.getAttribute("data-project-name");
+ _TKR_toggleStar(event.target, projectName);
+ });
+ }
+
+ function _handleProjectClick(event) {
+ var target = event.target;
+ if (target.tagName == "A")
+ return;
+
+ if (target.classList.contains("rowwidgets") || target.parentNode.classList.contains("rowwidgets"))
+ return;
+ if (target.tagName != "TR") target = target.parentNode;
+ _go(target.attributes[[]"data-url"].value,
+ (event.metaKey || event.ctrlKey || event.button == 1));
+ };
+ $("projecttable").addEventListener("click", _handleProjectClick);
+ if ($("starredtable")) {
+ $("starredtable").addEventListener("click", _handleProjectClick);
+ }
+ if ($("archivedtable")) {
+ $("archivedtable").addEventListener("click", _handleProjectClick);
+ }
+
+ if ($("banned_reason")) {
+ $("banned_reason").addEventListener("keyup", function() {
+ $("banned").checked = $("banned_reason").value != "";
+ });
+ }
+
+ if ($("ban_spammer_btn")) {
+ $("ban_spammer_btn").addEventListener("click", function(evt) {
+ var ok = window.confirm("This will remove all issues and comments " +
+ "created by this user. Continue?");
+ if (!ok) {
+ evt.preventDefault();
+ }
+ });
+ }
+
+ if ($("delete_btn")) {
+ $("delete_btn").addEventListener("click", async function(event) {
+ const expungeCall = window.prpcClient.call(
+ 'monorail.Users', 'ExpungeUser', {email: "[viewed_user_display_name]"});
+ expungeCall.then((resp) => {
+ location.replace(location.origin);
+ }).catch((reason) => {
+ $("delete_error").textContent = reason;
+ });
+ });
+ }
+});
+</script>
+<script type="module" defer src="[version_base]/static/js/sitewide/linked-accounts.js" nonce="[nonce]"></script>
+
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/user-settings-page.ezt b/templates/sitewide/user-settings-page.ezt
new file mode 100644
index 0000000..5e8ef2c
--- /dev/null
+++ b/templates/sitewide/user-settings-page.ezt
@@ -0,0 +1,17 @@
+[define category_css]css/ph_detail.css[end]
+[include "../framework/header.ezt" "showusertabs" "t1"]
+
+<div style="max-width:50em">
+
+<h3>User Preferences</h3>
+
+<form action="settings.do" method="POST">
+ [include "unified-settings.ezt"]
+ [if-any read_only][else]
+ <input id="submit_btn" type="submit" name="btn" value="Save preferences">
+ [end]
+</form>
+
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/user-updates-page.ezt b/templates/sitewide/user-updates-page.ezt
new file mode 100644
index 0000000..70aaca9
--- /dev/null
+++ b/templates/sitewide/user-updates-page.ezt
@@ -0,0 +1,7 @@
+[define page_css]css/d_updates_page.css[end]
+
+[include "../framework/header.ezt" "showusertabs" "t3"]
+
+[include "../features/updates-page.ezt"]
+
+[include "../framework/footer.ezt"]
diff --git a/templates/sitewide/usergrouptabs.ezt b/templates/sitewide/usergrouptabs.ezt
new file mode 100644
index 0000000..1f29b43
--- /dev/null
+++ b/templates/sitewide/usergrouptabs.ezt
@@ -0,0 +1,15 @@
+[# Display a row of tabs for servlets with URLs starting with /u/username.
+
+ Args:
+ arg0: String like "t1", "t2", "t3" to identify the currently active tab.
+]
+
+<div class="[admin_tab_mode]">
+ <div class="at isf">
+ <span class="inst1"><a href="/g/[groupid]/">People</a></span>
+ [if-any offer_membership_editing]
+ <span class="inst2"><a href="/g/[groupid]/groupadmin">Administer</a></span>
+ [end]
+ </div>
+</div>
+
diff --git a/templates/sitewide/usertabs.ezt b/templates/sitewide/usertabs.ezt
new file mode 100644
index 0000000..8216bcb
--- /dev/null
+++ b/templates/sitewide/usertabs.ezt
@@ -0,0 +1,32 @@
+[# Display a row of tabs for servlets with URLs starting with /u/username.
+
+ Args:
+ arg0: String like "t1", "t2", "t3" to identify the currently active tab.
+]
+
+<div class="at isf [user_tab_mode]">
+ <span class="inst2">
+ <a href="[viewed_user.profile_url]">[if-any viewing_self]My Profile[else]User Profile[end]</a>
+ </span>
+
+ <span class="inst5">
+ <a href="[viewed_user.profile_url]updates">Updates</a>
+ </span>
+
+ [if-any viewing_self]
+ <span class="inst3">
+ <a href="/hosting/settings">Settings</a>
+ </span>
+ [end]
+
+ [if-any offer_saved_queries_subtab]
+ <span class="inst4">
+ <a href="[viewed_user.profile_url]queries">Saved Queries</a>
+ </span>
+ [end]
+
+ <span class="inst6">
+ <a href="[viewed_user.profile_url]hotlists">Hotlists</a>
+ </span>
+
+</div>