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>&lsaquo;</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>&rsaquo;</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">&nbsp;</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%">&nbsp;</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">&nbsp;
+    <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>&lsaquo;</b> Prev</a>[end]
+        [pagination.start] - [pagination.last] of [pagination.total_count]
+        [if-any pagination.next_url]<a href="[pagination.next_url]">Next <b>&rsaquo;</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]&#9733;[else]&#9734;[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]&#9733;[else]&#9734;[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]&#9733;[else]&#9734;[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]&#9733;[else]&#9734;[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]&#9733;[else]&#9734;[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]&#9733;[else]&#9734;[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>