diff options
author | Sebastin Santy <sebastinssanty@gmail.com> | 2017-06-10 05:18:53 +0200 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2017-06-10 05:18:53 +0200 |
commit | 9243cbb3b6f014563d15c8b7ababdd55a722627a (patch) | |
tree | 40aa9450f816610d73c9254c74567bb390bad667 /extensions/BugModal/web | |
parent | b1abfe7caca072a36c312d6961ff9fbbfe306933 (diff) | |
download | bugzilla-9243cbb3b6f014563d15c8b7ababdd55a722627a.tar.gz bugzilla-9243cbb3b6f014563d15c8b7ababdd55a722627a.tar.xz |
Bug 1365342 - Extract the preview comment functionality and make it a reusable template
* Putting variables.none at the top
* Put the message at the top
* Remove header_done
* Add an #xhr-error to show bugzilla_ajax() errors
Diffstat (limited to 'extensions/BugModal/web')
-rw-r--r-- | extensions/BugModal/web/common_bug_modal.css | 983 | ||||
-rw-r--r-- | extensions/BugModal/web/common_bug_modal.js | 1504 |
2 files changed, 2487 insertions, 0 deletions
diff --git a/extensions/BugModal/web/common_bug_modal.css b/extensions/BugModal/web/common_bug_modal.css new file mode 100644 index 000000000..4af850b99 --- /dev/null +++ b/extensions/BugModal/web/common_bug_modal.css @@ -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 index 000000000..3a5a149fb --- /dev/null +++ b/extensions/BugModal/web/common_bug_modal.js @@ -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('▸'); + latch.attr('aria-expanded', false); + info.hide(); + } + else { + label = label.replace(/^show/, 'hide'); + latch.data('expanded', true).html('▾'); + 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('▸'); + $('#cc-list').hide(); + } + else { + latch.data('expanded', true).html('▾'); + 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); |