]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1365342 - Extract the preview comment functionality and make it a reusable template
authorSebastin Santy <sebastinssanty@gmail.com>
Sat, 10 Jun 2017 03:18:53 +0000 (08:48 +0530)
committerDylan William Hardison <dylan@hardison.net>
Sat, 10 Jun 2017 03:18:53 +0000 (23:18 -0400)
* Putting variables.none at the top

* Put the message at the top

* Remove header_done

* Add an #xhr-error to show bugzilla_ajax() errors

extensions/BugModal/template/en/default/bug_modal/common_header.html.tmpl [new file with mode: 0644]
extensions/BugModal/template/en/default/bug_modal/common_new_comment.html.tmpl [new file with mode: 0644]
extensions/BugModal/web/common_bug_modal.css [new file with mode: 0644]
extensions/BugModal/web/common_bug_modal.js [new file with mode: 0644]
template/en/default/bug/new_bug.html.tmpl

diff --git a/extensions/BugModal/template/en/default/bug_modal/common_header.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/common_header.html.tmpl
new file mode 100644 (file)
index 0000000..3d71f16
--- /dev/null
@@ -0,0 +1,84 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+  # License, v. 2.0. If a copy of the MPL was not distributed with this
+  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+  #
+  # This Source Code Form is "Incompatible With Secondary Licenses", as
+  # defined by the Mozilla Public License, v. 2.0.
+  #%]
+
+[%
+  PROCESS global/variables.none.tmpl;
+
+  title = title
+
+  generate_api_token = 1;
+
+  # these aren't always defined
+  UNLESS bodyclasses.defined;
+    bodyclasses = [];
+  END;
+  UNLESS javascript_urls.defined;
+    javascript_urls = [];
+  END;
+  UNLESS style_urls.defined;
+    style_urls = [];
+  END;
+  UNLESS jquery.defined;
+    jquery = [];
+  END;
+
+  # right now we need yui for tracking flags and needinfo extensions
+  no_yui = 0;
+
+  # add body classes for sec-groups, etc
+  bodyclasses.push("bug_modal");
+
+  # assets
+  javascript_urls.push(
+    "extensions/ProdCompSearch/web/js/prod_comp_search.js",
+    "extensions/BugModal/web/common_bug_modal.js",
+    "extensions/BugModal/web/comments.js",
+    "extensions/BugModal/web/dropdown.js",
+    "js/bugzilla-readable-status-min.js",
+    "js/field.js",
+    "js/comments.js",
+    "js/util.js"
+  );
+  jquery.push(
+    "datetimepicker",
+    "contextMenu",
+    "visibility"
+  );
+  style_urls.push(
+    "extensions/BugModal/web/common_bug_modal.css",
+    "extensions/BugModal/web/dropdown.css",
+    "skins/custom/bug_groups.css",
+    "js/jquery/plugins/datetimepicker/datetimepicker.css",
+    "js/jquery/plugins/contextMenu/contextMenu.css"
+  );
+%]
+
+[% javascript = BLOCK %]
+
+  [%# expose useful data to js %]
+
+  BUGZILLA.user = {
+    id: [% user.id FILTER none %],
+    login: '[% user.login FILTER js %]',
+    is_insider: [% user.is_insider ? "true" : "false" %],
+    is_timetracker: [% user.is_timetracker ? "true" : "false" %],
+    can_tag: [% user.can_tag_comments ? "true" : "false" %],
+    settings: {
+      quote_replies: '[% user.settings.quote_replies.value FILTER js %]',
+      zoom_textareas: [% user.settings.zoom_textareas.value == "on" ? "true" : "false" %],
+      remember_collapsed: [% user.settings.ui_remember_collapsed.value == "on" ? "true" : "false" %]
+    }
+  };
+
+  [% IF user.is_timetracker %]
+    BUGZILLA.remaining_time = [% bug.remaining_time FILTER js %]; // holds the original value
+  [% END %]
+  BUGZILLA.bug_secure = [% bug.groups_in.size ? 'true' : 'false' %];
+[% END %]
+
+[% Hook.process("end") %]
\ No newline at end of file
diff --git a/extensions/BugModal/template/en/default/bug_modal/common_new_comment.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/common_new_comment.html.tmpl
new file mode 100644 (file)
index 0000000..2ba3948
--- /dev/null
@@ -0,0 +1,55 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+  # License, v. 2.0. If a copy of the MPL was not distributed with this
+  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+  #
+  # This Source Code Form is "Incompatible With Secondary Licenses", as
+  # defined by the Mozilla Public License, v. 2.0.
+  #%]
+
+[%#
+  # comment: comment object
+  # bug: bug object
+  #%]
+
+<div id="add-comment">
+
+  [% IF bug && !bug.check_can_change_field('longdesc', 0, 1) %]
+    <div id="new-comment-notice">
+      You are not allowed to make an additional comment on this [% terms.bug %].
+    </div>
+    [% RETURN %]
+  [% END %]
+
+  [% IF bug && user.is_insider %]
+    <div id="add-comment-private"
+      title="Make comment visible only to members of the '[% Param('insidergroup') FILTER html %]' group"
+    >
+      <input type="checkbox" name="comment_is_private" id="add-comment-private-cb"
+          value="1" comment_id="[% comment.count FILTER none %]">
+      <label for="add-comment-private-cb">Private</label>
+    </div>
+  [% END %]
+
+  <ul id="comment-tabs" role="tablist">
+    <li id="comment-edit-tab" data-focus="comment" role="tab" tabindex="0" aria-controls="comment-edit-tabpanel" aria-selected="true">
+      Add Comment
+    </li>
+    [%~ ~%]
+    <li id="comment-preview-tab" role="tab" tabindex="-1" aria-controls="comment-preview-tabpanel" aria-selected="false">
+      Preview
+      <img id="preview-throbber" src="extensions/BugModal/web/throbber.gif" width="16" height="11" style="display:none">
+    </li>
+  </ul>
+
+  <div id="comment-edit-tabpanel" class="comment-tabpanel" role="tabpanel" aria-labelledby="comment-edit-tab">
+    <textarea rows="5" cols="80" name="comment" id="comment" aria-labelledby="comment-edit-tab"></textarea>
+  </div>
+  <div id="comment-preview-tabpanel" class="comment-tabpanel" role="tabpanel" aria-labelledby="comment-preview-tab" style="display:none">
+    <pre id="comment-preview" class="comment-text"></pre>
+  </div>
+
+  <div id="bugzilla-etiquette">
+    <a href="page.cgi?id=etiquette.html" target="_blank" tabindex="-1">
+      Comments Subject to Etiquette and Contributor Guidelines</a>
+  </div>
+</div>
diff --git a/extensions/BugModal/web/common_bug_modal.css b/extensions/BugModal/web/common_bug_modal.css
new file mode 100644 (file)
index 0000000..4af850b
--- /dev/null
@@ -0,0 +1,983 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+/* generic */
+
+.container {
+    display: table-cell;
+    width: 100%;
+}
+
+.layout-table {
+    border-spacing: 0;
+}
+
+.layout-table td {
+    padding: 0;
+}
+
+.inline {
+    display: table-cell !important;
+    width: auto !important;
+}
+
+.gravatar {
+    vertical-align: middle;
+    margin-right: 5px;
+}
+
+.flag .vcard {
+    display: inline;
+}
+
+.group-padlock {
+    vertical-align: middle;
+    margin-right: 5px;
+}
+
+button.major {
+    font-size: 11px;
+    padding: 4px 8px;
+}
+
+button.minor {
+    background-color: #eee;
+    background-image: linear-gradient(#fcfcfc, #eee);
+    color: #000;
+    font-size: 11px;
+    font-weight: 500;
+    padding: 4px 8px;
+    margin-bottom: 1px;
+    text-shadow: none;
+    -web-kit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1);
+    -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1);
+    box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 0 1px 0 rgba(0,0,0,0.1);
+}
+
+button.minor:hover {
+    -webkit-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd;
+    -moz-box-shadow: 0 1px 0 0 rgba(0,0,0,0.2), inset 0 -1px 0 0 rgba(0,0,0,0.3), inset 0 12px 24px 2px #ddd;
+    box-shadow: 0 1px 0 0 rgba(0,0,0,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.1), inset 0 12px 24px 2px #ddd;
+}
+
+select[multiple], .text_input, .yui-ac-input, input {
+    font-size: 12px !important;
+}
+
+.spin-toggle {
+    cursor: pointer;
+    display: inline;
+}
+
+.spin-toggle:hover {
+    text-decoration: underline;
+}
+
+.spin-latch {
+    color: #999;
+    padding-right: 5px;
+}
+
+.attention {
+    -webkit-box-shadow: 0 0 2px 2px #f88;
+    -moz-box-shadow: 0 0 2px 2px #f88;
+    box-shadow: 0 0 2px 2px #f88;
+}
+
+a.activity-ref {
+    color: #000;
+}
+
+/* modules */
+
+.module {
+    color: #000;
+    border-radius: 2px;
+    margin-top: 5px;
+    font-size: 13px;
+}
+
+.module.module-collapsed .module-content {
+    border: 1px solid red;
+}
+
+.module-header {
+    background: #eee;
+    -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+}
+
+.module-header:hover {
+    outline: 1px solid #ccc;
+}
+
+.module-latch {
+    padding: 2px 10px;
+    cursor: pointer;
+}
+
+.module-spinner {
+    color: #999;
+    display: table-cell;
+    width: 10px;
+}
+
+.module-spinner::before {
+    content: "\25BE";
+}
+
+.module-spinner[aria-expanded="false"]::before {
+    content: "\25B8";
+}
+
+.module-title {
+    font-weight: bold;
+}
+
+.module-title, .module-subtitle {
+    font-size: 13px;
+    display: table-cell;
+    padding-left: 5px;
+}
+
+.module-subtitle {
+    font-weight: normal;
+    padding-right: 5px;
+    color: #666;
+}
+
+.module .fields-lhs {
+    min-width: 450px;
+    display: table-cell;
+    vertical-align: top;
+}
+
+.module .fields-rhs {
+    min-width: 450px;
+    display: table-cell;
+    vertical-align: top;
+    width: 100%;
+}
+
+.module-content {
+    padding: 2px 5px;
+    background: #fff;
+    -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+}
+
+.module .field {
+    margin-top: 4px;
+    vertical-align: top;
+    display: table;
+    width: 100%;
+}
+
+.module .field.right {
+}
+
+.module .field .name {
+    display: table-cell;
+    width: 100px;
+    min-width: 100px;
+    text-align: right;
+    vertical-align: top;
+    padding-right: 10px;
+    color: #666;
+}
+
+.module .field .name .help {
+    color: #666;
+    cursor: help;
+}
+
+.module .field.inline .name {
+    min-width: 0px;
+    width: auto;
+    padding-left: 10px;
+}
+
+.module .indent {
+    padding-left: 10px;
+}
+
+.module .field .value {
+    display: table-cell;
+}
+
+.module .field .value.wide {
+    display: block;
+}
+
+.module .field .value.edit {
+    width: 100%;
+}
+
+.module .field .value input {
+    width: 100%;
+}
+
+.module .field .value input[type="checkbox"] {
+    width: auto;
+}
+
+.module .field .value.short input {
+    width: 170px;
+}
+
+.field-button {
+    float: right;
+    margin-left: 8px;
+}
+
+.field-edit-container {
+    overflow-y: hidden;
+}
+
+/* field types */
+
+input[type="number"] {
+    text-align: right;
+    width: 5em !important;
+}
+
+.cf_date-img, .cf_datetime-img {
+    vertical-align: middle;
+}
+
+/* specific fields */
+
+#field-value-bug_id {
+    font-weight: bold;
+}
+
+#field-value-short_desc {
+    margin: 0;
+    font-weight: bold;
+    font-size: 120%;
+}
+
+#field-status_summary {
+    border-top: 1px dotted silver;
+    padding-top: 4px;
+}
+
+#status-assignee, #status-assignee .vcard, #status-needinfo, #status-needinfo .vcard {
+    display: inline;
+}
+
+#status-assignee, #status-needinfo {
+    margin-left: 8px;
+}
+
+#duplicate-container, #duplicate-actions, #assigned-container,
+#bottom-duplicate-container, #bottom-duplicate-actions {
+    display: table-cell;
+    vertical-align: top;
+    padding-left: 8px;
+}
+
+#dup_id {
+    margin-left: 4px;
+}
+
+#resolve-as, #bottom-status {
+    display: inline;
+}
+
+#after-comment-commit-button {
+    margin-left: -8px;
+    margin-bottom: 4px;
+}
+
+#needinfo_from_autocomplete {
+    width: auto;
+}
+
+#needinfo_role_identity {
+    margin-left: 5px;
+}
+
+#user-story {
+    margin: 0;
+    white-space: pre-wrap;
+    min-height: 2em;
+}
+
+#user-story-actions {
+    float: right;
+}
+
+#new-comment-notice {
+    padding: 20px 8px;
+    margin-bottom: 50px;
+}
+
+#product-info, #component-info {
+    color: #484;
+    white-space: normal;
+}
+
+#product-latch, #component-latch {
+    padding-right: 0;
+    cursor: pointer;
+}
+
+#cc-latch {
+    color: #999;
+}
+
+#cc-latch {
+    cursor: pointer;
+}
+
+#cc-list {
+    max-height: 150px;
+    overflow-y: auto;
+    clear: both;
+    white-space: nowrap;
+}
+
+#cc-list .vcard {
+    display: inline-block;
+}
+
+#cc-list button {
+    padding: 2px 4px;
+}
+
+.cc-remove {
+    font-size: 120%;
+}
+
+.cc-removed {
+    text-decoration: line-through;
+}
+
+#add-cc-btn {
+    margin-left: 8px;
+}
+
+#add-cc {
+    width: 100%;
+}
+
+.cc-loadable {
+    cursor: pointer;
+}
+
+.cc-loadable:hover {
+    text-decoration: underline;
+}
+
+/* actions */
+
+#top-actions {
+    margin: 4px 0;
+}
+
+#top-actions .save-btn {
+    float: right;
+}
+
+#bottom-actions {
+    margin-top: 8px;
+    margin-bottom: 50px;
+    max-width: 1024px;
+}
+
+#bottom-right-actions {
+    float: right;
+}
+
+.edit-textarea-set-btn {
+    float: right;
+}
+
+/* attachments */
+
+#module-attachments .module-content {
+    padding: 0;
+}
+
+#attachments {
+    width: 100%;
+}
+
+#attachments td {
+    padding: 4px 8px;
+    vertical-align: top;
+    font-size: 13px;
+    border-bottom: 1px dotted silver;
+}
+
+#attachments .attach-desc-td {
+    width: 100%;
+}
+
+#attachments .attach-desc {
+    font-weight: bold;
+}
+
+#attachments .attach-info {
+    font-size: 11px;
+}
+
+#attachments .attach-time {
+    font-size: 11px;
+}
+
+#attachments .attach-actions {
+    white-space: nowrap;
+}
+
+#attachments .attach-flag {
+    white-space: nowrap;
+}
+
+#attachments .flag-name-status {
+    font-weight: bold;
+}
+
+#attachments .attach-obsolete .attach-desc {
+    text-decoration: line-through;
+}
+
+#attachments .attach-patch .attach-desc-td {
+    background: #ffc;
+    background-image: linear-gradient(to right, #ffc, #fff);
+}
+
+#attachments .bz_private {
+    background: #fff;
+}
+
+#attachments .bz_private .attach-desc-td {
+    border-left: 4px solid darkred;
+}
+
+#attachments .vcard {
+    display: inline;
+}
+
+#attachments-actions button {
+    margin: 2px;
+}
+
+#attachments .attach-flag .vcard {
+    white-space: nowrap;
+}
+
+/* flags */
+
+.flags td {
+    font-size: 13px !important;
+}
+
+.flag-name {
+    text-align: right;
+}
+
+td.flag-name, td.flag-requestee {
+    padding-left: 5px;
+}
+
+td.flag-value select {
+    margin-left: 5px;
+}
+
+td.flag-requestee {
+    width: 100%;
+}
+
+.flags .vcard {
+    white-space: nowrap;
+}
+
+.tracking-flags td, .tracking-flags th {
+    padding: 0 5px;
+}
+
+.tracking-flags th {
+    font-weight: normal;
+    text-align: left;
+    color: #666;
+}
+
+.tracking-flag-name, .tracking-flag-tracking {
+    text-align: right;
+    white-space: nowrap;
+}
+
+/* groups */
+
+.group-disabled {
+    color: #888;
+}
+
+/* comments and activity */
+
+#comment-actions {
+    margin-top: 4px;
+    text-align: right;
+}
+
+.change-set {
+    clear: both;
+    -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    -moz-box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
+    border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+    margin-top: 8px;
+    border: 1px solid #ddd;
+}
+
+.change-set:target {
+    outline: 2px solid #0095dd;
+}
+
+.change-set .comment, .change-set .change {
+    padding-bottom: 1px;
+}
+
+.change-head {
+    width: 100%;
+    background: #eee;
+}
+
+.change-gravatar {
+    padding-left: 8px !important;
+}
+
+.change-gravatar .vcard {
+    width: 36px;
+    text-align: center;
+}
+
+.change-author {
+    width: 100%;
+    vertical-align: top;
+    padding: 5px 0 !important;
+}
+
+.change-author .vcard {
+    display: inline;
+    font-weight: bold;
+}
+
+.change-author .user-role {
+    margin-left: 1em;
+    color: #448844;
+}
+
+
+.change-name, .change-time, .comment-private {
+    display: inline;
+}
+
+h3.change-name {
+    font-size: small;
+    font-weight: normal;
+}
+
+.comment-actions {
+    white-space: nowrap;
+    vertical-align: top;
+    padding: 2px 2px 0 0 !important;
+}
+
+.change-spinner {
+    width: 29px;
+}
+
+.comment-text {
+    white-space: pre-wrap;
+    line-height: 1.2;
+    font-size: 13px;
+    font-family: "Droid Sans Mono",Menlo,Monaco,"Courier New",monospace;
+    background: #fff;
+    color: #222;
+    margin: 1px 0 0 0;
+    overflow: auto;
+    padding: 8px;
+    border-top: 1px solid #ddd;
+}
+
+body.platform-Win32 .comment-text, body.platform-Win64 .comment-text {
+    font-family: "Fira Mono", monospace;
+}
+
+.comment-text span.quote, .comment-text span.quote_wrapped {
+    background: #eee !important;
+    color: #444 !important;
+    display: block !important;
+    padding: 5px !important;
+    display: inline-block !important;
+    width: 99% !important;
+}
+
+.comment-tags {
+    padding: 0 8px 2px 8px !important;
+}
+
+.comment-tag {
+    border: 1px solid #eee;
+    padding: 2px 6px 2px 4px;
+    margin-right: 2px;
+    border-radius: 4px;
+    background-color: #fff;
+    color: #000;
+}
+
+.comment-tag a {
+    padding-right: 4px;
+    cursor: pointer;
+}
+
+#ctag {
+    margin-bottom: 4px;
+}
+
+#ctag button {
+    margin-top: 2px;
+}
+
+#ctag a {
+    margin-left: 8px;
+}
+
+#ctag-error {
+    padding-left: 5px;
+    background-color: #faa;
+    color: #444;
+    border-radius: 2px;
+    margin-top: 2px;
+}
+
+.comment-collapse-reason {
+    padding: 5px 7px !important;
+    width: 100%;
+}
+
+.default-collapsed {
+    background: inherit;
+    color: #888;
+}
+
+.default-collapsed .comment-actions {
+    padding: 2px;
+}
+
+.private-comment {
+    color: #8b0000;
+    background: #f3eeee;
+}
+
+.activity {
+    padding: 5px 8px;
+    background: #eee;
+    border-top: 1px solid #ddd;
+}
+
+.activity-deleted {
+    text-decoration: line-through;
+}
+
+/* add comment */
+
+#add-comment {
+    margin-top: 20px;
+}
+
+#add-comment-private,
+#bugzilla-etiquette {
+    float: right;
+}
+
+#comment {
+    border: 1px solid #ccc;
+}
+
+#comment, #comment-preview {
+    clear: both;
+    width: 100%;
+    box-sizing: border-box !important;
+    margin: 0 0 1em;
+    max-width: 1024px;
+}
+
+#comment-preview {
+    border: 1px solid #fff;
+    -moz-box-shadow: none;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+    padding: 4px 3px 5px;
+}
+
+#preview-throbber {
+    margin-left: 8px;
+}
+
+#comment-tabs {
+    margin: 0px;
+    padding: 0px;
+    list-style: none;
+}
+
+#comment-tabs li {
+    display: inline-block;
+    padding: 4px 8px;
+    cursor: pointer;
+    border: 1px solid silver;
+    border-top-left-radius: 2px;
+    border-top-right-radius: 2px;
+}
+
+#comment-tabs li[aria-selected="true"] {
+    background: #fff;
+    border-bottom: 1px solid #fff;
+}
+
+.preview-error {
+    color: #666;
+    font-style: italic;
+}
+
+/* controls */
+
+#summary-container {
+    display: table-cell;
+    width: 100%;
+    vertical-align: top;
+}
+
+#xhr-error {
+    background: #fff;
+    color: #000;
+    border-radius: 2px;
+    border: 1px solid maroon;
+    margin: 5px 0px;
+    padding: 5px;
+}
+
+#floating-message {
+    position: absolute;
+    left: 50%;
+    margin: 4px;
+}
+
+#floating-message-text {
+    position: relative;
+    left: -50%;
+    cursor: default;
+    background: #fff9db;
+    color: #666458;
+    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5), inset 0 0 1px #000;
+    border-radius: 4px;
+    padding: 4px 8px;
+}
+
+#mode-container {
+    display: table-cell;
+    white-space: nowrap;
+    text-align: right;
+    padding: 5px;
+    margin: 5px;
+}
+
+#mode-btn, #commit-btn {
+    margin: 0 0 5px 0;
+}
+
+#mode-btn-loading, #mode-btn-editing {
+    display: none;
+}
+
+#edit-throbber {
+    margin-right: 5px;
+}
+
+#product-throbber {
+    margin-left: 8px;
+}
+
+#commit {
+    margin: 5px;
+}
+
+#mode-container .button-row {
+    margin-top: 1px;
+    border-left: 5px solid white;
+}
+
+/* theme */
+
+#bugzilla-body {
+    max-width: 1024px;
+    min-width: 800px;
+    width: 100%;
+}
+
+.vcard {
+    white-space: normal;
+}
+
+.vcard a.disabled {
+    color: #888;
+}
+
+input[type=text][disabled], input:not([type])[disabled] {
+    color: #888 !important;
+}
+
+.xdsoft_datetimepicker button, .xdsoft_datetimepicker button:hover {
+    -webkit-box-shadow: none;
+    -moz-box-shadow: none;
+    box-shadow: none;
+}
+
+div.ui-widget-content {
+    background: #fff;
+}
+
+div.ui-tooltip {
+    padding: 4px;
+    font-size: 13px;
+    font-family: inherit;
+    max-width: 500px;
+}
+
+.yui-ac {
+    width: 100%;
+}
+
+/* lightbox */
+
+.lightbox img {
+    margin-right: 4px;
+    vertical-align: sub;
+}
+
+#lb_img {
+    background-color: #fff;
+    border: 1px solid #666;
+    -webkit-box-shadow: 0 0 10px #555;
+    -moz-box-shadow: 0 0 10px #555;
+    box-shadow: 0 0 10px #555;
+    padding: 10px;
+    max-width: 90%;
+    margin: 20px auto;
+    cursor: pointer;
+}
+
+#lb_overlay {
+    position: fixed;
+    background: none repeat scroll 0 0 rgba(0, 0, 0, 0.5);
+    width: 100%;
+    height: 100%;
+    top: 0px;
+    left: 0px;
+    text-align: center;
+    z-index: 2;
+}
+
+#lb_overlay2 {
+    position: absolute;
+    left: 0px;
+    width: 100%;
+    text-align: center;
+    z-index: 2;
+}
+
+#lb_text {
+    color: #fff;
+    font-weight: bold;
+    text-shadow: 1px 1px 1px #000;
+    position: fixed;
+    top: 4px;
+    left: 8px;
+    z-index: 3;
+    cursor: default;
+}
+
+#lb_close_btn {
+    position: fixed;
+    top: 8px;
+    right: 8px;
+}
+
+/* product search */
+
+#field-product {
+    white-space: nowrap;
+}
+
+#product-search-container {
+    white-space: nowrap;
+}
+
+#product-search, #product-search-cancel {
+    margin-left: 8px;
+}
+
+#product-search-error {
+    margin-left: 8px;
+    vertical-align: middle;
+}
+
+.pcs-form {
+    display: inline;
+}
+
+.pcs-header {
+    display: none;
+}
+
+#pcs {
+    width: 235px;
+}
+
+/* search navigation */
+
+#search-nav {
+    background: rgba(255, 255, 255, 0.3);
+    padding: 4px 8px;
+}
+
+#search-nav-label {
+    font-weight: bold;
+}
+
+.search-nav-link, .search-nav-disabled {
+    margin-left: 4px;
+}
+
+#search-nav-reget {
+    margin-left: 8px;
+}
+
+.search-nav-disabled {
+    color: #777;
+}
+
+/* clipboard shenanigans */
+
+#clip-container {
+    position: fixed;
+    top: 0px;
+    left: 0px;
+    width: 0px;
+    height: 0px;
+    z-index: 100;
+    opacity: 0;
+}
+
+#user-guide {
+    padding-top: 5px;
+}
diff --git a/extensions/BugModal/web/common_bug_modal.js b/extensions/BugModal/web/common_bug_modal.js
new file mode 100644 (file)
index 0000000..3a5a149
--- /dev/null
@@ -0,0 +1,1504 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+// expand/collapse module
+function slide_module(module, action, fast) {
+    if (!module.attr('id'))
+        return;
+    var latch = module.find('.module-latch');
+    var spinner = module.find('.module-spinner');
+    var content = $(module.children('.module-content')[0]);
+    var duration = fast ? 0 : 200;
+
+    function slide_done() {
+        var is_visible = content.is(':visible');
+        spinner.attr({
+            'aria-expanded': is_visible,
+            'aria-label': is_visible ? latch.data('label-expanded') : latch.data('label-collapsed'),
+        });
+        if (BUGZILLA.user.settings.remember_collapsed)
+            localStorage.setItem(module.attr('id') + '.visibility', is_visible ? 'show' : 'hide');
+    }
+
+    if (action == 'show') {
+        content.slideDown(duration, 'swing', slide_done);
+    }
+    else if (action == 'hide') {
+        content.slideUp(duration, 'swing', slide_done);
+    }
+    else {
+        content.slideToggle(duration, 'swing', slide_done);
+    }
+}
+
+function init_module_visibility() {
+    if (!BUGZILLA.user.settings.remember_collapsed)
+        return;
+    $('.module').each(function() {
+        var that = $(this);
+        var id = that.attr('id');
+        if (!id) return;
+        if (that.data('non-stick')) return;
+        var stored = localStorage.getItem(id + '.visibility');
+        if (stored) {
+            slide_module(that, stored, true);
+        }
+    });
+}
+
+$(function() {
+    'use strict';
+
+    // update relative dates
+    var relative_timer_duration = 60000;
+    var relative_timer_id = window.setInterval(relativeTimer, relative_timer_duration);
+    $(document).on('show.visibility', function() {
+        relative_timer_id = window.setInterval(relativeTimer, relative_timer_duration);
+    });
+    $(document).on('hide.visibility', function() {
+        window.clearInterval(relative_timer_id);
+    });
+
+    function relativeTimer() {
+        var now = Math.floor(new Date().getTime() / 1000);
+        $('.rel-time').each(function() {
+            $(this).text(timeAgo(now - $(this).data('time')));
+        });
+        $('.rel-time-title').each(function() {
+            $(this).attr('title', timeAgo(now - $(this).data('time')));
+        });
+    }
+
+    // all keywords for autocompletion (lazy-loaded on edit)
+    var keywords = [];
+
+    // products with descriptions (also lazy-loaded)
+    var products = [];
+
+    // restore edit mode after navigating back
+    function restoreEditMode() {
+        if (!$('#editing').val())
+            return;
+        $('.module')
+            .each(function() {
+                slide_module($(this), 'hide', true);
+            });
+        $($('#editing').val().split(' '))
+            .each(function() {
+                slide_module($('#' + this), 'show', true);
+            });
+        $('#mode-btn').click();
+        $('.save-btn').prop('disabled', false);
+        $('#editing').val('');
+    }
+
+    // expand/colapse module
+    $('.module-latch')
+        .click(function(event) {
+            event.preventDefault();
+            slide_module($(this).parents('.module'));
+        })
+        .keydown(function(event) {
+            // expand/colapse module with the enter or space key
+            if (event.keyCode === 13 || event.keyCode === 32) {
+                event.preventDefault();
+                slide_module($(this).parents('.module'));
+            }
+        });
+
+    // toggle obsolete attachments
+    $('#attachments-obsolete-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $(event.target).text(($('#attachments tr:hidden').length ? 'Hide' : 'Show') + ' Obsolete Attachments');
+            $('#attachments tr.attach-obsolete').toggle();
+        });
+
+    // url --> unsafe warning
+    $('.bug-url')
+        .click(function(event) {
+            var that = $(this);
+            event.stopPropagation();
+            if (!that.data('safe')) {
+                event.preventDefault();
+                if (confirm('This is considered an unsafe URL and could possibly be harmful. ' +
+                            'The full URL is:\n\n' + that.attr('href') + '\n\nContinue?'))
+                {
+                    try {
+                        window.open(that.attr('href'));
+                    } catch(ex) {
+                        alert('Malformed URL');
+                    }
+                }
+            }
+        });
+
+    // top btn
+    $('#top-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $.scrollTo($('body'));
+        });
+
+    // bottom btn
+    $('#bottom-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $.scrollTo($('#bottom-actions'));
+        });
+
+    // hide floating message when clicked
+    $('#floating-message')
+        .click(function(event) {
+            event.preventDefault();
+            $(this).hide();
+        });
+
+    // use non-native tooltips for relative/absolute times and bug summaries
+    $('.rel-time, .rel-time-title, .abs-time-title, .bz_bug_link, .tt').tooltip({
+        position: { my: "left top+8", at: "left bottom", collision: "flipfit" },
+        show: { effect: 'none' },
+        hide: { effect: 'none' }
+    });
+
+    // tooltips create a new ui-helper-hidden-accessible div each time a
+    // tooltip is shown.  this is never removed leading to memory leak and
+    // bloated dom.  http://bugs.jqueryui.com/ticket/10689
+    $('.ui-helper-hidden-accessible').remove();
+
+    // product/component info
+    $('.spin-toggle, #product-latch, #component-latch')
+        .click(function(event) {
+            spin_toggle(event);
+        }).keydown(function(event) {
+            // allow space or enter to toggle visibility
+            if (event.keyCode == 13 || event.keyCode == 32) {
+                spin_toggle(event);
+            }
+        });
+
+    function spin_toggle(event) {
+        event.preventDefault();
+        var type  = $(event.target).data('for');
+        var latch = $('#' + type + '-latch');
+        var name  = $('#' + type + '-name');
+        var info  = $('#' + type + '-info');
+        var label = latch.attr('aria-label');
+
+        if (latch.data('expanded')) {
+            label = label.replace(/^hide/, 'show');
+            latch.data('expanded', false).html('&#9656;');
+            latch.attr('aria-expanded', false);
+            info.hide();
+        }
+        else {
+            label = label.replace(/^show/, 'hide');
+            latch.data('expanded', true).html('&#9662;');
+            latch.attr('aria-expanded', true);
+            info.show();
+        }
+        latch.attr('aria-label', label);
+        name.attr('title', label);
+    }
+
+    // cc list
+
+    function ccListLoading() {
+        $('#cc-list').html(
+            '<img src="extensions/BugModal/web/throbber.gif" width="16" height="11"> Loading...'
+        );
+    }
+
+    function ccListUpdate() {
+        bugzilla_ajax(
+            {
+                url: 'rest/bug_modal/cc/' + BUGZILLA.bug_id
+            },
+            function(data) {
+                $('#cc-list').html(data.html);
+                $('#cc-latch').data('fetched', true);
+                $('#cc-list .cc-user').hover(
+                    function() {
+                        $('#ccr-' + $(this).data('n')).css('visibility', 'visible');
+                    },
+                    function() {
+                        $('#ccr-' + $(this).data('n')).css('visibility', 'hidden');
+                    }
+                );
+                $('#cc-list .cc-remove')
+                    .click(function(event) {
+                        event.preventDefault();
+                        $('#top-save-btn').show();
+                        var n = $(this).data('n');
+                        var ccu = $('#ccu-' + n);
+                        if (ccu.hasClass('cc-removed')) {
+                            ccu.removeClass('cc-removed');
+                            $('#cc-' + n).remove();
+                        }
+                        else {
+                            $('#removecc').val('on');
+                            ccu.addClass('cc-removed');
+                            $('<input>').attr({
+                                type: 'hidden',
+                                id: 'cc-' + n,
+                                value: $('#ccr-' + n).data('login'),
+                                name: 'cc'
+                            }).appendTo('#changeform');
+                        }
+                    });
+            }
+        );
+    }
+
+    if (BUGZILLA.user.id) {
+        $('#cc-summary').addClass('cc-loadable');
+        $('#cc-latch, #cc-summary')
+            .click(function(event) {
+                cc_toggle(event);
+            }).keydown(function(event) {
+                // allow space or enter to toggle visibility
+                if (event.keyCode == 13 || event.keyCode == 32) {
+                    cc_toggle(event);
+                }
+            });
+    }
+
+    function cc_toggle(event) {
+        event.preventDefault();
+        var latch = $('#cc-latch');
+        var label = latch.attr('aria-label');
+        if (latch.data('expanded')) {
+            label = label.replace(/^hide/, 'show');
+            latch.data('expanded', false).html('&#9656;');
+            $('#cc-list').hide();
+        }
+        else {
+            latch.data('expanded', true).html('&#9662;');
+            label = label.replace(/^show/, 'hide');
+            $('#cc-list').show();
+            if (!latch.data('fetched')) {
+                ccListLoading();
+                ccListUpdate();
+            }
+        }
+        latch.attr('aria-label', label);
+        $('#cc-summary').attr('aria-label', label);
+    }
+
+    // copy summary to clipboard
+
+    function clipboardSummary() {
+        return 'Bug ' + BUGZILLA.bug_id + ' - ' + $('#field-value-short_desc').text();
+    }
+
+    if ($('#copy-summary').length) {
+        var hasExecCopy = false;
+        try {
+            hasExecCopy = document.queryCommandSupported("copy");
+        } catch(ex) {
+            // ignore
+        }
+
+        if (hasExecCopy) {
+            $('#copy-summary')
+                .click(function() {
+                    // execCommand("copy") only works on selected text
+                    $('#clip-container').show();
+                    $('#clip').val(clipboardSummary()).select();
+                    document.execCommand("copy");
+                    $('#clip-container').hide();
+                });
+        }
+        else {
+            // we don't know if flash is enabled without waiting for load to timeout
+            // remember the flash enabled state between pages
+            var hasFlash = true;
+            if (localStorage.getItem('hasFlash') === null) {
+                $('#copy-summary').hide();
+            }
+            else {
+                hasFlash = localStorage.getItem('hasFlash');
+            }
+            if (hasFlash) {
+                var s = document.createElement("script");
+                s.onload = function () {
+                  ZeroClipboard.config({ flashLoadTimeout: 5000 });
+                  var zero = new ZeroClipboard($('#copy-summary'));
+                  zero.on({
+                      'ready': function(event) {
+                          $('#copy-summary').show();
+                          localStorage.setItem('hasFlash', true);
+                      },
+                      'error': function(event) {
+                          console.log(event.message);
+                          zero.destroy();
+                          $('#global-zeroclipboard-html-bridge').remove();
+                          $('#copy-summary').hide();
+                          localStorage.removeItem('hasFlash');
+                      },
+                      'copy': function(event) {
+                          var clipboard = event.clipboardData;
+                          clipboard.setData('text/plain', clipboardSummary());
+                      }
+                  });
+                };
+                s.src = "extensions/BugModal/web/ZeroClipboard/ZeroClipboard.min.js";
+                document.getElementsByTagName('head')[0].appendChild(s);
+            }
+        }
+    }
+
+    // lightboxes
+    $('.lightbox, .comment-text .lightbox + span:first-of-type a:first-of-type')
+        .click(function(event) {
+            if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)
+                return;
+            event.preventDefault();
+            lb_show(this);
+        });
+
+    // when copying the bug id and summary, reformat to remove \n and alias
+    $(document).on(
+        'copy', function(event) {
+            var selection = document.getSelection().toString().trim();
+            var match = selection.match(/^(Bug \d+)\s*\n(.+)$/) ||
+                selection.match(/^(Bug \d+)\s+\([^\)]+\)\s*\n(.+)$/);
+            if (match) {
+                var content = match[1] + ' - ' + match[2].trim();
+                if (event.originalEvent.clipboardData) {
+                    event.originalEvent.clipboardData.setData('text/plain', content);
+                }
+                else if (window.clipboardData) {
+                    window.clipboardData.setData('Text', content);
+                }
+                else {
+                    return;
+                }
+                event.preventDefault();
+            }
+        });
+
+    // action button actions
+
+    // reset
+    $('#action-reset')
+        .click(function(event) {
+            event.preventDefault();
+            var visible = $(this).data('modules');
+            $('.module-content').each(function() {
+                var content = $(this);
+                var moduleID = content.parent('.module').attr('id');
+                var isDefault = $.inArray(moduleID, visible) !== -1;
+                if (content.is(':visible') && !isDefault) {
+                    slide_module($('#' + moduleID), 'hide');
+                }
+                else if (content.is(':hidden') && isDefault) {
+                    slide_module($('#' + moduleID), 'show');
+                }
+            });
+        })
+        .data('modules', $('.module-content:visible').map(function() {
+            return $(this).parent('.module').attr('id');
+        }));
+
+    // expand all modules
+    $('#action-expand-all')
+        .click(function(event) {
+            event.preventDefault();
+            $('.module-content:hidden').each(function() {
+                slide_module($(this).parent('.module'));
+            });
+        });
+
+    // collapse all modules
+    $('#action-collapse-all')
+        .click(function(event) {
+            event.preventDefault();
+            $('.module-content:visible').each(function() {
+                slide_module($(this).parent('.module'));
+            });
+        });
+
+    // add comment menuitem, scroll the textarea into view
+    $('#action-add-comment, #add-comment-btn')
+        .click(function(event) {
+            event.preventDefault();
+            // focus first to grow the textarea, so we scroll to the correct location
+            $('#comment').focus();
+            $.scrollTo($('#bottom-save-btn'));
+        });
+
+    // last comment menuitem
+    $('#action-last-comment')
+        .click(function(event) {
+            event.preventDefault();
+            var id = $('.comment:last')[0].parentNode.id;
+            $.scrollTo(id);
+        });
+
+    // show bug history
+    $('#action-history')
+        .click(function(event) {
+            event.preventDefault();
+            document.location.href = 'show_activity.cgi?id=' + BUGZILLA.bug_id;
+        });
+
+    // use scrollTo for in-page activity links
+    $('.activity-ref')
+        .click(function(event) {
+            event.preventDefault();
+            $.scrollTo($(this).attr('href').substr(1));
+        });
+
+    // Update readable bug status
+    var rbs = $("#readable-bug-status");
+    var rbs_text = bugzillaReadableStatus.readable(rbs.data('readable-bug-status'));
+    rbs.text(rbs_text);
+    
+    if (BUGZILLA.user.id === 0) return;
+
+    //
+    // anything after this point is only executed for logged in users
+    //
+
+    // dirty field tracking
+    $('#changeform select').each(function() {
+        var that = $(this);
+        var dirty = $('#' + that.attr('id') + '-dirty');
+        if (!dirty) return;
+        var isMultiple = that.attr('multiple');
+
+        // store the option that had the selected attribute when we
+        // initially loaded
+        var value = that.find('option[selected]').map(function() { return this.value; }).toArray();
+        if (value.length === 0 && !that.attr('multiple'))
+            value = that.find('option:first').map(function() { return this.value; }).toArray();
+        that.data('preselected', value);
+
+        // if the user hasn't touched a field, override the browser's choice
+        // with bugzilla's
+        if (!dirty.val())
+            that.val(value);
+    });
+
+    // edit/save mode button
+    $('#mode-btn')
+        .click(function(event) {
+            event.preventDefault();
+
+            // hide buttons, old error messages
+            $('#mode-btn-readonly').hide();
+
+            // toggle visibility
+            $('.edit-hide').hide();
+            $('.edit-show').show();
+
+            // expand specific modules during the initial edit
+            if (!$('#editing').val())
+                slide_module($('#module-details'), 'show');
+
+            // if there's no current user-story, it's a better experience if it's editable by default
+            if ($('#cf_user_story').val() === '') {
+                $('#user-story-edit-btn').click();
+            }
+
+            // "loading.." ui
+            $('#mode-btn-loading').show();
+            $('#cancel-btn').prop('disabled', true);
+            $('#mode-btn').prop('disabled', true);
+
+            // load the missing select data
+            bugzilla_ajax(
+                {
+                    url: 'rest/bug_modal/edit/' + BUGZILLA.bug_id
+                },
+                function(data) {
+                    $('#mode-btn').hide();
+
+                    // populate select menus
+                    $.each(data.options, function(key, value) {
+                        var el = $('#' + key);
+                        if (!el) return;
+                        var selected = el.val();
+                        el.empty();
+                        $(value).each(function(i, v) {
+                            el.append($('<option>', { value: v.name, text: v.name }));
+                        });
+                        el.val(selected);
+                        if (el.attr('multiple') && value.length < 5) {
+                            el.attr('size', value.length);
+                        }
+                    });
+
+                    // build our product description hash
+                    $.each(data.options.product, function() {
+                        products[this.name] = this.description;
+                    });
+
+                    // keywords is a multi-value autocomplete
+                    keywords = data.keywords;
+                    $('#keywords')
+                        .devbridgeAutocomplete({
+                            lookup: function(query, done) {
+                                query = query.toLowerCase();
+                                var matchStart =
+                                    $.grep(keywords, function(keyword) {
+                                        return keyword.toLowerCase().substr(0, query.length) === query;
+                                    });
+                                var matchSub =
+                                    $.grep(keywords, function(keyword) {
+                                        return keyword.toLowerCase().indexOf(query) !== -1 &&
+                                            $.inArray(keyword, matchStart) === -1;
+                                    });
+                                var suggestions =
+                                    $.map($.merge(matchStart, matchSub), function(suggestion) {
+                                        return { value: suggestion };
+                                    });
+                                done({ suggestions: suggestions });
+                            },
+                            tabDisabled: true,
+                            delimiter: /,\s*/,
+                            minChars: 0,
+                            autoSelectFirst: false,
+                            triggerSelectOnValidInput: false,
+                            formatResult: function(suggestion, currentValue) {
+                                // disable <b> wrapping of matched substring
+                                return suggestion.value.htmlEncode();
+                            },
+                            onSearchStart: function(params) {
+                                var that = $(this);
+                                // adding spaces shouldn't initiate a new search
+                                var parts = that.val().split(/,\s*/);
+                                var query = parts[parts.length - 1];
+                                return query === $.trim(query);
+                            },
+                            onSelect: function() {
+                                this.value = this.value + ', ';
+                                this.focus();
+                            }
+                        })
+                        .addClass('bz_autocomplete');
+
+                    $('#cancel-btn').prop('disabled', false);
+                    $('#top-save-btn').show();
+                    $('#cancel-btn').show();
+                    $('#commit-btn').show();
+                },
+                function() {
+                    $('#mode-btn-readonly').show();
+                    $('#mode-btn-loading').hide();
+                    $('#mode-btn').prop('disabled', false);
+                    $('#mode-btn').show();
+                    $('#cancel-btn').hide();
+                    $('#commit-btn').hide();
+
+                    $('.edit-show').hide();
+                    $('.edit-hide').show();
+                }
+            );
+        });
+    $('#mode-btn').prop('disabled', false);
+
+    // disable the save buttons while posting
+    $('.save-btn')
+        .click(function(event) {
+            event.preventDefault();
+            if (document.changeform.checkValidity && !document.changeform.checkValidity())
+                return;
+            $('.save-btn').attr('disabled', true);
+            this.form.submit();
+
+            // remember expanded modules
+            $('#editing').val(
+                $('.module .module-content:visible')
+                    .parent()
+                    .map(function(el) { return $(this).attr('id'); })
+                    .toArray()
+                    .join(' ')
+            );
+        })
+        .attr('disabled', false);
+
+    // cc toggle (follow/stop following)
+    $('#cc-btn')
+        .click(function(event) {
+            event.preventDefault();
+            var is_cced = $(event.target).data('is-cced') == '1';
+
+            var cc_change;
+            var cc_count = $('#cc-summary').data('count');
+            if (is_cced) {
+                cc_change = { remove: [ BUGZILLA.user.login ] };
+                cc_count--;
+                $('#cc-btn')
+                    .text('Follow')
+                    .data('is-cced', '0')
+                    .prop('disabled', true);
+            }
+            else {
+                cc_change = { add: [ BUGZILLA.user.login ] };
+                cc_count++;
+                $('#cc-btn')
+                    .text('Stop Following')
+                    .data('is-cced', '1')
+                    .prop('disabled', true);
+            }
+            is_cced = !is_cced;
+
+            // update visible count
+            $('#cc-summary').data('count', cc_count);
+            if (cc_count == 1) {
+                $('#cc-summary').text(is_cced ? 'Just you' : '1 person');
+            }
+            else {
+                $('#cc-summary').text(cc_count + ' people');
+            }
+
+            // clear/update user list
+            $('#cc-latch').data('fetched', false);
+            if ($('#cc-latch').data('expanded'))
+                ccListLoading();
+
+            // show message
+            $('#floating-message-text')
+                .text(is_cced ? 'You are now following this bug' : 'You are no longer following this bug');
+            $('#floating-message')
+                .fadeIn(250)
+                .delay(2500)
+                .fadeOut();
+
+            // show/hide "add me to the cc list"
+            if (is_cced) {
+                $('#add-self-cc-container').hide();
+                $('#add-self-cc').attr('disabled', true);
+            }
+            else {
+                $('#add-self-cc-container').show();
+                $('#add-self-cc').attr('disabled', false);
+            }
+
+            bugzilla_ajax(
+                {
+                    url: 'rest/bug/' + BUGZILLA.bug_id,
+                    type: 'PUT',
+                    data: JSON.stringify({ cc: cc_change })
+                },
+                function(data) {
+                    $('#cc-btn').prop('disabled', false);
+                    if (!(data.bugs[0].changes && data.bugs[0].changes.cc))
+                        return;
+                    if (data.bugs[0].changes.cc.added == BUGZILLA.user.login) {
+                        $('#cc-btn')
+                            .text('Stop Following')
+                            .data('is-cced', '1');
+                    }
+                    else if (data.bugs[0].changes.cc.removed == BUGZILLA.user.login) {
+                        $('#cc-btn')
+                            .text('Follow')
+                            .data('is-cced', '0');
+                    }
+                    if ($('#cc-latch').data('expanded'))
+                        ccListUpdate();
+                },
+                function(message) {
+                    $('#cc-btn').prop('disabled', false);
+                    if ($('#cc-latch').data('expanded'))
+                        ccListUpdate();
+                }
+            );
+
+        });
+
+    // cancel button, reset the ui back to read-only state
+    // for now, do this with a redirect to self
+    // ideally this should revert all field back to their initially loaded
+    // values and switch the ui back to read-only mode without the redirect
+    $('#cancel-btn')
+        .click(function(event) {
+            event.preventDefault();
+            window.location.replace($('#this-bug').attr('href'));
+        });
+
+    // Open help page
+    $('#help-btn')
+        .click(function(event) {
+            event.preventDefault();
+            window.open("https://wiki.mozilla.org/BMO/UserGuide", "_blank");
+        });
+
+    // needinfo in people section -> scroll to near-comment ui
+    $('#needinfo-scroll')
+        .click(function(event) {
+            event.preventDefault();
+            $.scrollTo($('#needinfo_role'), function() { $('#needinfo_role').focus(); });
+        });
+
+    // knob
+    $('#bug_status, #bottom-bug_status')
+        .change(function(event) {
+            var that = $(this);
+            var val = that.val();
+            var other = $(that.attr('id') == 'bug_status' ? '#bottom-bug_status' : '#bug_status');
+            other.val(val);
+            if (val == "RESOLVED" || val == "VERIFIED") {
+                $('#resolution, #bottom-resolution').change().show();
+            }
+            else {
+                $('#resolution, #bottom-resolution').hide();
+                $('#duplicate-container, #bottom-duplicate-container').hide();
+                $('#mark-as-dup-btn, #bottom-mark-as-dup-btn').show();
+            }
+        })
+        .change();
+    $('#resolution, #bottom-resolution')
+        .change(function(event) {
+            var that = $(this);
+            var val = that.val();
+            var other = $(that.attr('id') == 'resolution' ? '#bottom-resolution' : '#resolution');
+            other.val(val);
+            var bug_status = $('#bug_status').val();
+            if ((bug_status == "RESOLVED" || bug_status == "VERIFIED") && val == "DUPLICATE") {
+                $('#duplicate-container, #bottom-duplicate-container').show();
+                $('#mark-as-dup-btn, #bottom-mark-as-dup-btn').hide();
+                $(that.attr('id') == 'resolution' ? '#dup_id' : '#bottom-dup_id').focus();
+            }
+            else {
+                $('#duplicate-container, #bottom-duplicate-container').hide();
+                $('#mark-as-dup-btn, #bottom-mark-as-dup-btn').show();
+            }
+        })
+        .change();
+    $('#mark-as-dup-btn, #bottom-mark-as-dup-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#bug_status').val('RESOLVED').change();
+            $('#resolution').val('DUPLICATE').change();
+            $($(this).attr('id') == 'mark-as-dup-btn' ? '#dup_id' : '#bottom-dup_id').focus();
+        });
+    $('#dup_id, #bottom-dup_id')
+        .change(function(event) {
+            var that = $(this);
+            var other = $(that.attr('id') == 'dup_id' ? '#bottom-dup_id' : '#dup_id');
+            other.val(that.val());
+        });
+
+    // add see-also button
+    $('.bug-urls-btn')
+        .click(function(event) {
+            event.preventDefault();
+            var name = event.target.id.replace(/-btn$/, '');
+            $(event.target).hide();
+            $('#' + name).show().focus();
+        });
+
+    // bug flag value <select>
+    $('.bug-flag')
+        .change(function(event) {
+            var target = $(event.target);
+            var id = target.prop('id').replace(/^flag(_type)?-(\d+)/, "#requestee$1-$2");
+            if (target.val() == '?') {
+                $(id + '-container').show();
+                $(id).focus().select();
+            }
+            else {
+                $(id + '-container').hide();
+            }
+        });
+
+    // tracking flags
+    $('.tracking-flags select')
+        .change(function(event) {
+            tracking_flag_change(event.target);
+        });
+
+    // take button
+    $('.take-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#field-status-view').hide();
+            $('#field-status-edit').show();
+            if ($('#bug_status option').filter(function() { return $(this).val() == 'ASSIGNED'; }).length) {
+                $('#assigned-container').show();
+            }
+            var field = $(this).data('field');
+            $('#field-' + field + '.edit-hide').hide();
+            $('#field-' + field + '.edit-show').show();
+            $('#' + field).val(BUGZILLA.user.login).focus().select();
+            $('#top-save-btn').show();
+            if ($('#set-default-assignee').is(':checked')) {
+                $('#set-default-assignee').click();
+            }
+        });
+
+    // mark as assigned
+    $('#mark-as-assigned-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#bug_status').val('ASSIGNED').change();
+        });
+
+    // reply button
+    $('.reply-btn')
+        .click(function(event) {
+            event.preventDefault();
+            var comment_id = $(event.target).data('reply-id');
+            var comment_author = $(event.target).data('reply-name');
+
+            var prefix = "(In reply to " + comment_author + " from comment #" + comment_id + ")\n";
+            var reply_text = "";
+            if (BUGZILLA.user.settings.quote_replies == 'quoted_reply') {
+                var text = $('#ct-' + comment_id).text();
+                reply_text = prefix + wrapReplyText(text);
+            }
+            else if (BUGZILLA.user.settings.quote_replies == 'simply_reply') {
+                reply_text = prefix;
+            }
+
+            // quoting a private comment, check the 'private' cb
+            $('#add-comment-private-cb').prop('checked',
+                $('#add-comment-private-cb:checked').length || $('#is-private-' + comment_id + ':checked').length);
+
+            // remove embedded links to attachment details
+            reply_text = reply_text.replace(/(attachment\s+\d+)(\s+\[[^\[\n]+\])+/gi, '$1');
+
+            if ($('#comment').val() != reply_text) {
+                $('#comment').val($('#comment').val() + reply_text);
+            }
+            $.scrollTo($('#comment'), function() { $('#comment').focus(); });
+        });
+
+    // add comment --> enlarge on focus
+    if (BUGZILLA.user.settings.zoom_textareas) {
+        $('#comment')
+            .focus(function(event) {
+                $(event.target).attr('rows', 25);
+            });
+    }
+
+    // add comment --> private
+    $('#add-comment-private-cb')
+        .click(function(event) {
+            if ($(event.target).prop('checked')) {
+                $('#comment').addClass('private-comment');
+            }
+            else {
+                $('#comment').removeClass('private-comment');
+            }
+        });
+
+    // show "save changes" button if there are any immediately editable elements
+    if ($('.module select:visible').length || $('.module input:visible').length) {
+        $('#top-save-btn').show();
+    }
+
+    // status/resolve as buttons
+    $('.resolution-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#field-status-view').hide();
+            $('#field-status-edit').show();
+            $('#field-status-edit .name').show();
+            $('#bug_status').val('RESOLVED').change();
+            $('#bottom-resolution').val($(event.target).text()).change();
+            $('#top-save-btn').show();
+            $('#resolve-as').hide();
+            $('#bottom-status').show();
+            $('#bottom-dup_id').focus();
+        });
+    $('.status-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#field-status-view').hide();
+            $('#field-status-edit').show();
+            $('#bug_status').val($(event.target).data('status')).change();
+            $('#top-save-btn').show();
+            $('#resolve-as').hide();
+            $('#bottom-status').show();
+        });
+
+    // vote button
+    // ideally this should function like CC and xhr it, but that would require
+    // a rewrite of the voting extension
+    $('#vote-btn')
+        .click(function(event) {
+            event.preventDefault();
+            window.location.href = 'page.cgi?id=voting/user.html&bug_id=' + BUGZILLA.bug_id + '#vote_' + BUGZILLA.bug_id;
+        });
+
+    // user-story
+    $('#user-story-edit-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#user-story').hide();
+            $('#user-story-edit-btn').hide();
+            $('#top-save-btn').show();
+            $('#cf_user_story').show();
+            // don't focus the user-story field when restoring edit mode after navigation
+            if ($('#editing').val() === '')
+                $('#cf_user_story').focus().select();
+        });
+    $('#user-story-reply-btn')
+        .click(function(event) {
+            event.preventDefault();
+            var text = "(Commenting on User Story)\n" + wrapReplyText($('#cf_user_story').val());
+            var current = $('#comment').val();
+            if (current != text) {
+                $('#comment').val(current + text);
+                $('#comment').focus();
+                $.scrollTo($('#bottom-save-btn'));
+            }
+        });
+
+    // cab review 'gate'
+    $('#cab-review-gate-close')
+        .click(function(event) {
+            event.preventDefault();
+            $('#cab-review-gate').hide();
+            $('#cab-review-edit').show();
+        });
+
+    // custom textarea fields
+    $('.edit-textarea-btn')
+        .click(function(event) {
+            event.preventDefault();
+            var id = $(event.target).attr('id').replace(/-edit$/, '');
+            $(event.target).hide();
+            $('#' + id + '-view').hide();
+            $('#' + id).show().focus().select();
+        });
+
+    // date/datetime pickers
+    $('.cf_datetime').datetimepicker({
+        format: 'Y-m-d G:i:s',
+        datepicker: true,
+        timepicker: true,
+        scrollInput: false,
+        lazyInit: false, // there's a bug which prevents img->show from working with lazy:true
+        closeOnDateSelect: true
+    });
+    $('.cf_date').datetimepicker({
+        format: 'Y-m-d',
+        datepicker: true,
+        timepicker: false,
+        scrollInput: false,
+        lazyInit: false,
+        closeOnDateSelect: true
+    });
+    $('.cf_datetime-img, .cf_date-img')
+        .click(function(event) {
+            var id = $(event.target).attr('id').replace(/-img$/, '');
+            $('#' + id).datetimepicker('show');
+        });
+
+    // timetracking
+    $('#work_time').change(function() {
+        // subtracts time spent from remaining time
+        // prevent negative values if work_time > fRemainingTime
+        var new_time = Math.max(BUGZILLA.remaining_time - $('#work_time').val(), 0.0);
+        // get upto 2 decimal places
+        $('#remaining_time').val(Math.round((new_time * 100)/100).toFixed(1));
+    });
+    $('#remaining_time').change(function() {
+        // if the remaining time is changed manually, update BUGZILLA.remaining_time
+        BUGZILLA.remaining_time = $('#remaining_time').val();
+    });
+
+    // "reset to default" checkboxes
+    $('#product, #component')
+        .change(function(event) {
+            $('.set-default-container').show();
+            $('#set-default-assignee').prop('checked', $('#assigned_to').val() == BUGZILLA.default_assignee).change();
+            $('#set-default-qa-contact').prop('checked', $('#qa_contact').val() == BUGZILLA.default_qa_contact).change();
+            slide_module($('#module-people'), 'show');
+        });
+    $('.set-default')
+        .change(function(event) {
+            var cb = $(event.target);
+            var input = $('#' + cb.data('for'));
+            input.attr('disabled', cb.prop('checked'));
+        })
+        .change();
+
+    // hotkeys
+    $(window)
+        .keydown(function(event) {
+            if (!(event.ctrlKey || event.metaKey))
+                return;
+            switch(String.fromCharCode(event.which).toLowerCase()) {
+                // ctrl+e or meta+e = enter edit mode
+                case 'e':
+                    if (event.shiftKey)
+                        return;
+                    // don't conflict with text input shortcut
+                    if (document.activeElement.nodeNode == 'INPUT' || document.activeElement.nodeName == 'TEXTAREA')
+                        return;
+                    if ($('#cancel-btn:visible').length === 0) {
+                        event.preventDefault();
+                        $('#mode-btn').click();
+                    }
+                    break;
+
+                // ctrl+shift+p = toggle comment preview
+                case 'p':
+                    if (event.metaKey || !event.shiftKey)
+                        return;
+                    if (document.activeElement.id == 'comment') {
+                        event.preventDefault();
+                        $('#comment-preview-tab').click();
+                    }
+                    else if ($('#comment-preview:visible').length !== 0) {
+                        event.preventDefault();
+                        $('#comment-edit-tab').click();
+                    }
+                    break;
+            }
+        });
+
+    // add cc button
+    $('#add-cc-btn')
+        .click(function(event) {
+            event.preventDefault();
+            $('#add-cc-btn').hide();
+            $('#add-cc-container').show();
+            $('#top-save-btn').show();
+            $('#add-cc').focus();
+        });
+
+    // Add user to cc list if they mark the bug as security sensitive
+    $('.restrict_sensitive')
+        .change(function(event) {
+            $('#add-self-cc:not(:checked)').attr('checked', true);
+        });
+
+    // product change --> load components, versions, milestones, groups
+    $('#product').data('default', $('#product').val());
+    $('#component, #version, #target_milestone').each(function() {
+        $(this).data('default', $(this).val());
+    });
+    $('#product')
+        .change(function(event) {
+            $('#product-throbber').show();
+            $('#component, #version, #target_milestone').attr('disabled', true);
+
+            slide_module($('#module-tracking'), 'show');
+
+            $.each($('input[name=groups]'), function() {
+                if (this.checked) {
+                    slide_module($('#module-security'), 'show');
+                    return false;
+                }
+            });
+
+            bugzilla_ajax(
+                {
+                    url: 'rest/bug_modal/new_product/' + BUGZILLA.bug_id + '?product=' + encodeURIComponent($('#product').val())
+                },
+                function(data) {
+                    $('#product-throbber').hide();
+                    $('#component, #version, #target_milestone').attr('disabled', false);
+                    var is_default = $('#product').val() == $('#product').data('default');
+
+                    // populate selects
+                    $.each(data, function(key, value) {
+                        if (key == 'groups') return;
+                        var el = $('#' + key);
+                        if (!el) return;
+                        el.empty();
+                        var selected = el.data('preselect');
+                        $(value).each(function(i, v) {
+                            el.append($('<option>', { value: v.name, text: v.name }));
+                            if (typeof selected === 'undefined' && v.selected)
+                                selected = v.name;
+                        });
+                        el.val(selected);
+                        el.prop('required', true);
+                        if (is_default) {
+                            el.removeClass('attention');
+                            el.val(el.data('default'));
+                        }
+                        else {
+                            el.addClass('attention');
+                        }
+                    });
+
+                    // update groups
+                    var dirtyGroups = [];
+                    var any_groups_checked = 0;
+                    $('#module-security').find('input[name=groups]').each(function() {
+                        var that = $(this);
+                        var defaultChecked = !!that.attr('checked');
+                        if (defaultChecked !== that.is(':checked')) {
+                            dirtyGroups.push({ name: that.val(), value: that.is(':checked') });
+                        }
+                        if (that.is(':checked')) {
+                            any_groups_checked = 1;
+                        }
+                    });
+                    $('#module-security .module-content')
+                        .html(data.groups)
+                        .addClass('attention');
+                    $.each(dirtyGroups, function() {
+                        $('#module-security').find('input[value=' + this.name + ']').prop('checked', this.value);
+                    });
+                    // clear any default groups if user was making bug public
+                    // unless the group is mandatory for the new product
+                    if (!any_groups_checked) {
+                        $('#module-security').find('input[name=groups]').each(function() {
+                            var that = $(this);
+                            if (!that.data('mandatory')) {
+                                that.prop('checked', false);
+                            }
+                        });
+                    }
+                },
+                function() {
+                    $('#product-throbber').hide();
+                    $('#component, #version, #target_milestone').attr('disabled', false);
+                }
+            );
+        });
+
+    // product/component search
+    $('#product-search')
+        .click(function(event) {
+            event.preventDefault();
+            $('#product').hide();
+            $('#product-search').hide();
+            $('#product-search-cancel').show();
+            $('.pcs-form').show();
+            $('#pcs').val('').focus();
+        });
+    $('#product-search-cancel')
+        .click(function(event) {
+            event.preventDefault();
+            $('#product-search-error').hide();
+            $('.pcs-form').hide();
+            $('#product').show();
+            $('#product-search-cancel').hide();
+            $('#product-search').show();
+        });
+    $('#pcs')
+        .devbridgeAutocomplete('setOptions', {
+            onSelect: function(suggestion) {
+                $('#product-search-error').hide();
+                $('.pcs-form').hide();
+                $('#product-search-cancel').hide();
+                $('#product-search').show();
+                if ($('#product').val() != suggestion.data.product) {
+                    $('#component').data('preselect', suggestion.data.component);
+                    $('#product').val(suggestion.data.product).change();
+                }
+                else {
+                    $('#component').val(suggestion.data.component);
+                }
+                $('#product').show();
+            }
+        });
+    $(document)
+        .on('pcs:search', function(event) {
+            $('#product-search-error').hide();
+        })
+        .on('pcs:results', function(event) {
+            $('#product-search-error').hide();
+        })
+        .on('pcs:no_results', function(event) {
+            $('#product-search-error')
+                .prop('title', 'No components found')
+                .show();
+        })
+        .on('pcs:too_many_results', function(event, el) {
+            $('#product-search-error')
+                .prop('title', 'Results limited to ' + el.data('max_results') + ' components')
+                .show();
+        })
+        .on('pcs:error', function(event, message) {
+            $('#product-search-error')
+                .prop('title', message)
+                .show();
+        });
+
+    // comment preview
+    var last_comment_text = '';
+    $('#comment-tabs li').click(function() {
+        var that = $(this);
+        if (that.attr('aria-selected') === 'true')
+            return;
+
+        // ensure preview's height matches the comment
+        var comment = $('#comment');
+        var preview = $('#comment-preview');
+        var comment_height = comment[0].offsetHeight;
+
+        // change tabs
+        $('#comment-tabs li').attr({ tabindex: -1, 'aria-selected': false });
+        $('.comment-tabpanel').hide();
+        that.attr({ tabindex: 0, 'aria-selected': true });
+        var tabpanel = $('#' + that.attr('aria-controls')).show();
+        var focus = that.data('focus');
+        if (focus !== '') {
+            $('#' + focus).focus();
+        }
+
+        // update preview
+        preview.css('height', comment_height + 'px');
+        if (tabpanel.attr('id') != 'comment-preview-tabpanel' || last_comment_text == comment.val())
+            return;
+        $('#preview-throbber').show();
+        preview.html('');
+        bugzilla_ajax(
+            {
+                url: 'rest/bug/comment/render',
+                type: 'POST',
+                data: { text: comment.val() },
+                hideError: true
+            },
+            function(data) {
+                $('#preview-throbber').hide();
+                preview.html(data.html);
+            },
+            function(message) {
+                $('#preview-throbber').hide();
+                var container = $('<div/>');
+                container.addClass('preview-error');
+                container.text(message);
+                preview.html(container);
+            }
+        );
+        last_comment_text = comment.val();
+    }).keydown(function(event) {
+        var that = $(this);
+        var tabs = $('#comment-tabs li');
+        var target;
+
+        // enable keyboard navigation on tabs
+        switch (event.keyCode) {
+            case 35: // End
+                target = tabs.last();
+                break;
+            case 36: // Home
+                target = tabs.first();
+                break;
+            case 37: // Left arrow
+                target = that.prev('[role="tab"]');
+                break;
+            case 39: // Right arrow
+                target = that.next('[role="tab"]');
+                break;
+        }
+
+        if (target && target.length) {
+            event.preventDefault();
+            target.click().focus();
+        }
+    });
+
+    // dirty field tracking
+    $('#changeform select')
+        .change(function() {
+            var that = $(this);
+            var dirty = $('#' + that.attr('id') + '-dirty');
+            if (!dirty) return;
+            if (that.attr('multiple')) {
+                var preselected = that.data('preselected');
+                var selected = that.val();
+                var isDirty = preselected.length != selected.length;
+                if (!isDirty) {
+                    for (var i = 0, l = preselected.length; i < l; i++) {
+                        if (selected[i] != preselected[i]) {
+                            isDirty = true;
+                            break;
+                        }
+                    }
+                }
+                dirty.val(isDirty ? '1' : '');
+            }
+            else {
+                dirty.val(that.val() === that.data('preselected')[0] ? '' : '1');
+            }
+        });
+
+    // finally switch to edit mode if we navigate back to a page that was editing
+    $(window).on('pageshow', restoreEditMode);
+    restoreEditMode();
+});
+
+function confirmUnsafeURL(url) {
+    return confirm(
+        'This is considered an unsafe URL and could possibly be harmful.\n' +
+        'The full URL is:\n\n' + url + '\n\nContinue?');
+}
+
+// fix url after bug creation/update
+if (history && history.replaceState) {
+    var href = document.location.href;
+    if (!href.match(/new-bug/) && !href.match(/show_bug\.cgi/)) {
+        history.replaceState(null, BUGZILLA.bug_title, 'show_bug.cgi?id=' + BUGZILLA.bug_id);
+        document.title = BUGZILLA.bug_title;
+    }
+    if (href.match(/show_bug\.cgi\?.*list_id=/)) {
+        href = href.replace(/[\?&]+list_id=(\d+|cookie)/, '');
+        history.replaceState(null, BUGZILLA.bug_title, href);
+    }
+}
+
+// ajax wrapper, to simplify error handling and auth
+function bugzilla_ajax(request, done_fn, error_fn) {
+    $('#xhr-error').hide('');
+    $('#xhr-error').html('');
+    request.url += (request.url.match('\\?') ? '&' : '?') +
+        'Bugzilla_api_token=' + encodeURIComponent(BUGZILLA.api_token);
+    if (request.type != 'GET') {
+        request.contentType = 'application/json';
+        request.processData = false;
+        if (request.data && request.data.constructor === Object) {
+            request.data = JSON.stringify(request.data);
+        }
+    }
+    return $.ajax(request)
+        .done(function(data) {
+            if (data.error) {
+                if (!request.hideError) {
+                    $('#xhr-error').html(data.message);
+                    $('#xhr-error').show('fast');
+                }
+                if (error_fn)
+                    error_fn(data.message);
+            }
+            else if (done_fn) {
+                done_fn(data);
+            }
+        })
+        .fail(function(data) {
+            if (data.statusText === 'abort')
+                return;
+            var message = data.responseJSON ? data.responseJSON.message : 'Unexpected Error'; // all errors are unexpected :)
+            if (!request.hideError) {
+                $('#xhr-error').html(message);
+                $('#xhr-error').show('fast');
+            }
+            if (error_fn)
+                error_fn(message);
+        });
+}
+
+// lightbox
+
+function lb_show(el) {
+    $(window).trigger('close');
+    $(document).bind('keyup.lb', function(event) {
+        if (event.keyCode == 27) {
+            lb_close(event);
+        }
+    });
+    var overlay = $('<div>')
+        .prop('id', 'lb_overlay')
+        .css({ opacity: 0 })
+        .appendTo('body');
+    var overlay2 = $('<div>')
+        .prop('id', 'lb_overlay2')
+        .css({ top: $(window).scrollTop() + 5 })
+        .appendTo('body');
+    var title = $('<div>')
+        .prop('id', 'lb_text')
+        .appendTo(overlay2);
+    var img = $('<img>')
+        .prop('id', 'lb_img')
+        .prop('src', el.href)
+        .prop('alt', 'Loading...')
+        .css({ opacity: 0 })
+        .appendTo(overlay2)
+        .click(function(event) {
+            event.stopPropagation();
+            window.location.href = el.href;
+        });
+    var close_btn = $('<button>')
+        .prop('id', 'lb_close_btn')
+        .prop('type', 'button')
+        .addClass('minor')
+        .text('Close')
+        .appendTo(overlay2);
+    title.text(el.title);
+    overlay.add(overlay2).click(lb_close);
+    img.add(overlay).animate({ opacity: 1 }, 200);
+}
+
+function lb_close(event) {
+    event.preventDefault();
+    $(document).unbind('keyup.lb');
+    $('#lb_overlay, #lb_overlay2, #lb_close_btn, #lb_img, #lb_text').remove();
+}
+
+$(function() {
+    $("button.button-link").on("click", function (event) {
+        event.preventDefault();
+        window.location = $(this).data("href");
+    });
+});
+
+// extensions
+
+(function($) {
+    $.extend({
+        // Case insensative $.inArray (http://api.jquery.com/jquery.inarray/)
+        // $.inArrayIn(value, array [, fromIndex])
+        //  value (type: String)
+        //    The value to search for
+        //  array (type: Array)
+        //    An array through which to search.
+        //  fromIndex (type: Number)
+        //    The index of the array at which to begin the search.
+        //    The default is 0, which will search the whole array.
+        inArrayIn: function(elem, arr, i) {
+            // not looking for a string anyways, use default method
+            if (typeof elem !== 'string') {
+                return $.inArray.apply(this, arguments);
+            }
+            // confirm array is populated
+            if (arr) {
+                var len = arr.length;
+                i = i ? (i < 0 ? Math.max(0, len + i) : i) : 0;
+                elem = elem.toLowerCase();
+                for (; i < len; i++) {
+                    if (i in arr && arr[i].toLowerCase() == elem) {
+                        return i;
+                    }
+                }
+            }
+            // stick with inArray/indexOf and return -1 on no match
+            return -1;
+        },
+
+        // Bring an element into view, leaving space for the outline.
+        // If passed a string, it will be treated as an id - the page will scroll
+        // unanimated and the url will be added to the browser's history.
+        // If passed an element, an smooth scroll will take place and no entry
+        // will be added to the history.
+        scrollTo: function(target, complete) {
+            if (typeof target === 'string') {
+                var el = $('#' + target);
+                window.location.hash = target;
+                var $html = $('html');
+                if (Math.abs($html.scrollTop() - el.offset().top) <= 1) {
+                    $html.scrollTop($html.scrollTop() - 10);
+                }
+                $html.scrollLeft(0);
+            }
+            else {
+                var offset = target.offset();
+                $('html')
+                    .animate({
+                            scrollTop: offset.top - 20,
+                            scrollLeft: 0
+                        },
+                        200,
+                        complete
+                    );
+            }
+        }
+
+    });
+})(jQuery);
index 0be0903805c85e404f58e2ac9df32fbb906bbe84..96ec7d8d3a9636be8afb045d41f6431a820ed9cd 100644 (file)
@@ -5,16 +5,27 @@
 # This Source Code Form is "Incompatible With Secondary Licenses", as
 # defined by the Mozilla Public License, v. 2.0.%]
 
-[% PROCESS "global/field-descs.none.tmpl" %]
-
+[% PROCESS global/variables.none.tmpl %]
 [% title = BLOCK %]Enter [% terms.Bug %] [% END %]
-
-[% PROCESS global/header.html.tmpl
-  title = title
-  generate_api_token = 1
-  no_yui = 1
+[% PROCESS bug_modal/common_header.html.tmpl 
+title = title
 %]
-
+[% PROCESS global/header.html.tmpl %]
 <p>Coming Soon!</p>
+<p>This is a demonstration of a reusable comment component</p>
+[%
+    IF user.id;
+      INCLUDE bug_modal/common_new_comment.html.tmpl;
+    ELSE;
+      %]
+        <div id="new-comment-notice">
+          You need to <a href="new-bug&amp;GoAheadAndLogIn=1">log in</a>
+          before you can file a [% terms.bug %].
+        </div>
+      [%
+    END;
+  %]
+
+<div style="display: none" id="xhr-error"></div>
 
-[% PROCESS global/footer.html.tmpl %]
+[% PROCESS global/footer.html.tmpl %]
\ No newline at end of file