diff options
author | Byron Jones <glob@mozilla.com> | 2015-03-24 06:45:44 +0100 |
---|---|---|
committer | Byron Jones <glob@mozilla.com> | 2015-03-24 06:45:44 +0100 |
commit | 3ac701266452d3509776fe58f9e1b2b8e9f33c1e (patch) | |
tree | 88124baaadb529b1c9809f6b3fa20384c1870780 /extensions/BugModal/web/bug_modal.js | |
parent | 11bd061970f8b9c98e6af43a4c8c7ca4bfff9eb3 (diff) | |
download | bugzilla-3ac701266452d3509776fe58f9e1b2b8e9f33c1e.tar.gz bugzilla-3ac701266452d3509776fe58f9e1b2b8e9f33c1e.tar.xz |
Bug 1096798: prototype modal show_bug view
Diffstat (limited to 'extensions/BugModal/web/bug_modal.js')
-rw-r--r-- | extensions/BugModal/web/bug_modal.js | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js new file mode 100644 index 000000000..c3fd84e97 --- /dev/null +++ b/extensions/BugModal/web/bug_modal.js @@ -0,0 +1,730 @@ +/* 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. */ + +$(function() { + 'use strict'; + + // all keywords for autocompletion (lazy-loaded on edit) + var keywords = []; + + // products with descriptions (also lazy-loaded) + var products = []; + + // scroll to an element + function scroll_to(el, complete) { + var offset = el.offset(); + $('html, body') + .animate({ + scrollTop: offset.top - 20, + scrollLeft: offset.left = 20 + }, + 200, + complete + ); + } + + // expand all modules + $('#expand-all-btn') + .click(function(event) { + event.preventDefault(); + var btn = $(event.target); + if (btn.data('expanded-modules')) { + btn.data('expanded-modules').slideToggle(200, 'swing', function() { + btn.data('expanded-spinners').html('▸'); + }); + btn.data('expanded-modules', false); + btn.text('Expand All'); + } + else { + var modules = $('.module-content:hidden'); + var spinners = $([]); + modules.each(function() { + spinners.push($(this).parent('.module').find('.module-spinner')[0]); + }); + btn.data('expanded-modules', modules); + btn.data('expanded-spinners', spinners); + modules.slideToggle(200, 'swing', function() { + spinners.html('▾'); + }); + btn.text('Collapse'); + } + }); + + // expand/colapse module + $('.module-header') + .click(function(event) { + event.preventDefault(); + var target = $(event.target); + var latch = target.hasClass('module-header') ? target.children('.module-latch') : target.parent('.module-latch'); + var spinner = $(latch.children('.module-spinner')[0]); + var module = $(latch.parents('.module')[0]); + var content = $(module.children('.module-content')[0]); + content.slideToggle(200, 'swing', function() { + spinner.html(content.is(':visible') ? '▾' : '▸'); + }); + }); + + // 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(); + }); + + // comment collapse/expand + $('.comment-spinner') + .click(function(event) { + event.preventDefault(); + var spinner = $(event.target); + var id = spinner.attr('id').match(/\d+$/)[0]; + // switch to full header for initially collapsed comments + if (spinner.attr('id').match(/^ccs-/)) { + $('#cc-' + id).hide(); + $('#ch-' + id).show(); + } + $('#ct-' + id).slideToggle('fast', function() { + $('#c' + id).find('.activity').toggle(); + spinner.text($('#ct-' + id + ':visible').length ? '-' : '+'); + }); + }); + + // url --> unsafe warning + $('.unsafe-url') + .click(function(event) { + event.preventDefault(); + if (confirm('This is considered an unsafe URL and could possibly be harmful. ' + + 'The full URL is:\n\n' + $(event.target).attr('title') + '\n\nContinue?')) + { + try { + window.open($(event.target).attr('title')); + } catch(ex) { + alert('Malformed URL'); + } + } + }); + + // last comment btn + $('#last-comment-btn') + .click(function(event) { + event.preventDefault(); + var id = $('.comment:last')[0].parentNode.id; + scroll_to($('#' + id)); + window.location.hash = id; + }); + + // top btn + $('#top-btn') + .click(function(event) { + event.preventDefault(); + scroll_to($('body')); + }); + + // use non-native tooltips for relative times and bug summaries + $('.rel-time, .bz_bug_link').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') + .click(function(event) { + event.preventDefault(); + var latch = $($(event.target).data('latch')); + var el_for = $($(event.target).data('for')); + + if (latch.data('expanded')) { + latch.data('expanded', false).html('▸'); + el_for.hide(); + } + else { + latch.data('expanded', true).html('▾'); + el_for.show(); + } + }); + + // cc list + $('#cc-latch, #cc-summary') + .click(function(event) { + event.preventDefault(); + var latch = $('#cc-latch'); + + if (latch.data('expanded')) { + latch.data('expanded', false).html('▸'); + $('#cc-list').hide(); + } + else { + latch.data('expanded', true).html('▾'); + $('#cc-list').show(); + if (!latch.data('fetched')) { + $('#cc-list').html( + '<img src="extensions/BugModal/web/throbber.gif" width="16" height="11"> Loading...' + ); + bugzilla_ajax( + { + url: 'rest/bug_modal/cc/' + BUGZILLA.bug_id + }, + function(data) { + $('#cc-list').html(data.html); + latch.data('fetched', true); + } + ); + } + } + }); + + // copy summary to clipboard + if ($('#copy-summary').length) { + var zero = new ZeroClipboard($('#copy-summary')); + zero.on({ + 'error': function(event) { + console.log(event.message); + zero.destroy(); + $('#copy-summary').hide(); + + }, + 'copy': function(event) { + var clipboard = event.clipboardData; + clipboard.setData('text/plain', 'Bug ' + BUGZILLA.bug_id + ' - ' + $('#field-value-short_desc').text()); + } + }); + } + + // + // anything after this point is only executed for logged in users + // + + if (BUGZILLA.user.id === 0) return; + + // 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 + $('#module-details .module-header').each(function() { + if ($(this.parentNode).find('.module-content:visible').length === 0) { + $(this).click(); + } + }); + + // 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 + // (this should probably be a simple jquery plugin) + keywords = data.keywords; + $('#keywords') + .bind('keydown', function(event) { + if (event.keyCode == $.ui.keyCode.TAB && $(this).autocomplete('instance').menu.active) + { + event.preventDefault(); + } + }) + .blur(function() { + $(this).val($(this).val().replace(/,\s*$/, '')); + }) + .autocomplete({ + source: function(request, response) { + response($.ui.autocomplete.filter(keywords, request.term.split(/,\s*/).pop())); + }, + focus: function() { + return false; + }, + select: function(event, ui) { + var terms = this.value.split(/,\s*/); + terms.pop(); + terms.push(ui.item.value); + terms.push(''); + this.value = terms.join(', '); + return false; + } + }); + + $('#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); + + // cc add/remove + $('#cc-btn') + .click(function(event) { + event.preventDefault(); + var is_cced = $(event.target).data('is-cced') == '1'; + + var cc_change; + if (is_cced) { + cc_change = { remove: [ BUGZILLA.user.login ] }; + $('#cc-btn') + .text('Follow') + .data('is-cced', '0') + .prop('disabled', true); + } + else { + cc_change = { add: [ BUGZILLA.user.login ] }; + $('#cc-btn') + .text('Stop following') + .data('is-cced', '1') + .prop('disabled', true); + } + + 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'); + } + }, + function(message) { + $('#cc-btn').prop('disabled', false); + } + ); + + }); + + // 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').val()); + }); + + // top comment button, scroll the textarea into view + $('.comment-btn') + .click(function(event) { + event.preventDefault(); + // focus first to grow the textarea, so we scroll to the correct location + $('#comment').focus(); + scroll_to($('#bottom-save-btn')); + }); + + // needinfo in people section -> scroll to near-comment ui + $('#needinfo-scroll') + .click(function(event) { + event.preventDefault(); + scroll_to($('#needinfo_role'), function() { $('#needinfo_role').focus(); }); + }); + + // knob + $('#bug_status') + .change(function(event) { + if (event.target.value == "RESOLVED" || event.target.value == "VERIFIED") { + $('#resolution').change().show(); + } + else { + $('#resolution').hide(); + $('#duplicate-container').hide(); + $('#mark-as-dup-btn').show(); + } + }) + .change(); + $('#resolution') + .change(function(event) { + if (event.target.value == "DUPLICATE") { + $('#duplicate-container').show(); + $('#mark-as-dup-btn').hide(); + $('#dup_id').focus(); + } + else { + $('#duplicate-container').hide(); + $('#mark-as-dup-btn').show(); + } + }) + .change(); + $('#mark-as-dup-btn') + .click(function(event) { + event.preventDefault(); + $('#bug_status').val('RESOLVED').change(); + $('#resolution').val('DUPLICATE').change(); + $('#dup_id').focus(); + }); + + // 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); + }); + + // add attachments + $('#attachments-add-btn') + .click(function(event) { + event.preventDefault(); + window.location.replace('attachment.cgi?bugid=' + BUGZILLA.bug_id + '&action=enter'); + }); + + // take button + $('#take-btn') + .click(function(event) { + event.preventDefault(); + $('#field-assigned_to .edit-hide').hide(); + $('#field-assigned_to .edit-show').show(); + $('#assigned_to').val(BUGZILLA.user.login).focus().select(); + $('#top-save-btn').show(); + }); + + // 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); + } + scroll_to($('#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 .edit-hide').hide(); + $('#field-status-edit .edit-show').show(); + $('#field-status-edit').show(); + $('#bug_status').val('RESOLVED').change(); + $('#resolution').val($(event.target).text()).change(); + $('#top-save-btn').show(); + if ($(event.target).text() == "DUPLICATE") { + scroll_to($('body')); + } + else { + scroll_to($('body'), function() { $('#resolution').focus(); }); + } + }); + $('.status-btn') + .click(function(event) { + event.preventDefault(); + $('#field-status-view').hide(); + $('#field-status-edit .edit-hide').hide(); + $('#field-status-edit .edit-show').show(); + $('#field-status-edit').show(); + $('#bug_status').val($(event.target).data('status')).change(); + $('#top-save-btn').show(); + scroll_to($('body'), function() { $('#bug_status').focus(); }); + }); + + // 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.replace('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(); + $('#cf_user_story').show().focus().select(); + $('#top-save-btn').show(); + }); + $('#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(); + scroll_to($('#bottom-save-btn')); + } + }); + + // 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'); + }); + + // new bug button + $.contextMenu({ + selector: '#new-bug-btn', + trigger: 'left', + items: [ + { + name: 'Create a new Bug', + callback: function() { + window.open('enter_bug.cgi', '_blank'); + } + }, + { + name: '… in this product', + callback: function() { + window.open('enter_bug.cgi?product=' + encodeURIComponent($('#product').val()), '_blank'); + } + }, + { + name: '… in this component', + callback: function() { + window.open('enter_bug.cgi?' + + 'product=' + encodeURIComponent($('#product').val()) + + '&component=' + encodeURIComponent($('#component').val()), '_blank'); + } + }, + { + name: '… that blocks this bug', + callback: function() { + window.open('enter_bug.cgi?format=__default__' + + '&product=' + encodeURIComponent($('#product').val()) + + '&blocked=' + BUGZILLA.bug_id, '_blank'); + } + }, + { + name: '… that depends on this bug', + callback: function() { + window.open('enter_bug.cgi?format=__default__' + + '&product=' + encodeURIComponent($('#product').val()) + + '&dependson=' + BUGZILLA.bug_id, '_blank'); + } + }, + { + name: '… as a clone of this bug', + callback: function() { + window.open('enter_bug.cgi?format=__default__' + + '&product=' + encodeURIComponent($('#product').val()) + + '&cloned_bug_id=' + BUGZILLA.bug_id, '_blank'); + } + }, + { + name: '… as a clone, in a different product', + callback: function() { + window.open('enter_bug.cgi?format=__default__' + + '&cloned_bug_id=' + BUGZILLA.bug_id, '_blank'); + } + }, + ] + }); + +}); + +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(/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'; + } + $.ajax(request) + .done(function(data) { + if (data.error) { + $('#xhr-error').html(data.message); + $('#xhr-error').show('fast'); + if (error_fn) + error_fn(data.message); + } + else if (done_fn) { + done_fn(data); + } + }) + .error(function(data) { + $('#xhr-error').html(data.responseJSON.message); + $('#xhr-error').show('fast'); + if (error_fn) + error_fn(data.responseJSON.message); + }); +} + +// no-ops +function initHidingOptionsForIE() {} +function showFieldWhen() {} |