diff options
Diffstat (limited to 'js/comment-tagging.js')
-rw-r--r-- | js/comment-tagging.js | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/js/comment-tagging.js b/js/comment-tagging.js new file mode 100644 index 000000000..b6f02f00e --- /dev/null +++ b/js/comment-tagging.js @@ -0,0 +1,387 @@ +/* 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. */ + +var Dom = YAHOO.util.Dom; + +YAHOO.bugzilla.commentTagging = { + ctag_div : false, + ctag_add : false, + counter : 0, + min_len : 3, + max_len : 24, + tags_by_no: {}, + nos_by_tag: {}, + current_id: 0, + current_no: -1, + can_edit : false, + pending : {}, + + label : '', + min_len_error: '', + max_len_error: '', + + init : function(can_edit) { + this.can_edit = can_edit; + this.ctag_div = Dom.get('bz_ctag_div'); + this.ctag_add = Dom.get('bz_ctag_add'); + YAHOO.util.Event.on(this.ctag_add, 'keypress', this.onKeyPress); + YAHOO.util.Event.onDOMReady(function() { + YAHOO.bugzilla.commentTagging.updateCollapseControls(); + }); + if (!can_edit) return; + + var ds = new YAHOO.util.XHRDataSource("jsonrpc.cgi"); + ds.connTimeout = 30000; + ds.connMethodPost = true; + ds.connXhrMode = "cancelStaleRequests"; + ds.maxCacheEntries = 5; + ds.responseSchema = { + metaFields : { error: "error", jsonRpcId: "id"}, + resultsList : "result" + }; + + var ac = new YAHOO.widget.AutoComplete('bz_ctag_add', 'bz_ctag_autocomp', ds); + ac.maxResultsDisplayed = 7; + ac.generateRequest = function(query) { + query = YAHOO.lang.trim(query); + YAHOO.bugzilla.commentTagging.last_query = query; + YAHOO.bugzilla.commentTagging.counter = YAHOO.bugzilla.commentTagging.counter + 1; + YAHOO.util.Connect.setDefaultPostHeader('application/json', true); + return YAHOO.lang.JSON.stringify({ + version: "1.1", + method : "Bug.search_comment_tags", + id : YAHOO.bugzilla.commentTagging.counter, + params : { + Bugzilla_api_token: BUGZILLA.api_token, + query : query, + limit : 10 + } + }); + }; + ac.minQueryLength = this.min_len; + ac.autoHighlight = false; + ac.typeAhead = true; + ac.queryDelay = 0.5; + ac.dataReturnEvent.subscribe(function(type, args) { + args[0].autoHighlight = args[2].length == 1; + }); + }, + + toggle : function(comment_id, comment_no) { + if (!this.ctag_div) return; + var tags_container = Dom.get('ct_' + comment_no); + + if (this.current_id == comment_id) { + // hide + this.current_id = 0; + this.current_no = -1; + Dom.addClass(this.ctag_div, 'bz_default_hidden'); + this.hideError(); + window.focus(); + + } else { + // show or move + this.rpcRefresh(comment_id, comment_no); + this.current_id = comment_id; + this.current_no = comment_no; + this.ctag_add.value = ''; + tags_container.parentNode.insertBefore(this.ctag_div, tags_container); + Dom.removeClass(this.ctag_div, 'bz_default_hidden'); + Dom.removeClass(tags_container, 'bz_default_hidden'); + var comment = Dom.get('comment_text_' + comment_no); + if (Dom.hasClass(comment, 'collapsed')) { + var link = Dom.get('comment_link_' + comment_no); + expand_comment(link, comment, comment_no); + } + window.setTimeout(function() { + YAHOO.bugzilla.commentTagging.ctag_add.focus(); + }, 50); + } + }, + + hideInput : function() { + if (this.current_id != 0) { + this.toggle(this.current_id, this.current_no); + } + this.hideError(); + }, + + showError : function(comment_id, comment_no, error) { + var bz_ctag_error = Dom.get('bz_ctag_error'); + var tags_container = Dom.get('ct_' + comment_no); + tags_container.parentNode.appendChild(bz_ctag_error, tags_container); + Dom.get('bz_ctag_error_msg').innerHTML = YAHOO.lang.escapeHTML(error); + Dom.removeClass(bz_ctag_error, 'bz_default_hidden'); + }, + + hideError : function() { + Dom.addClass('bz_ctag_error', 'bz_default_hidden'); + }, + + onKeyPress : function(evt) { + evt = evt || window.event; + var charCode = evt.charCode || evt.keyCode; + if (evt.keyCode == 27) { + // escape + YAHOO.bugzilla.commentTagging.hideInput(); + YAHOO.util.Event.stopEvent(evt); + + } else if (evt.keyCode == 13) { + // return + YAHOO.util.Event.stopEvent(evt); + var tags = YAHOO.bugzilla.commentTagging.ctag_add.value.split(/[ ,]/); + var comment_id = YAHOO.bugzilla.commentTagging.current_id; + var comment_no = YAHOO.bugzilla.commentTagging.current_no; + YAHOO.bugzilla.commentTagging.hideInput(); + try { + YAHOO.bugzilla.commentTagging.add(comment_id, comment_no, tags); + } catch(e) { + YAHOO.bugzilla.commentTagging.showError(comment_id, comment_no, e.message); + } + } + }, + + showTags : function(comment_id, comment_no, tags) { + // remove existing tags + var tags_container = Dom.get('ct_' + comment_no); + while (tags_container.hasChildNodes()) { + tags_container.removeChild(tags_container.lastChild); + } + // add tags + if (tags != '') { + if (typeof(tags) == 'string') { + tags = tags.split(','); + } + for (var i = 0, l = tags.length; i < l; i++) { + tags_container.appendChild(this.buildTagHtml(comment_id, comment_no, tags[i])); + } + } + // update tracking array + this.tags_by_no['c' + comment_no] = tags; + this.updateCollapseControls(); + }, + + updateCollapseControls : function() { + var container = Dom.get('comment_tags_collapse_expand_container'); + if (!container) return; + // build list of tags + this.nos_by_tag = {}; + for (var id in this.tags_by_no) { + if (this.tags_by_no.hasOwnProperty(id)) { + for (var i = 0, l = this.tags_by_no[id].length; i < l; i++) { + var tag = this.tags_by_no[id][i].toLowerCase(); + if (!this.nos_by_tag.hasOwnProperty(tag)) { + this.nos_by_tag[tag] = []; + } + this.nos_by_tag[tag].push(id); + } + } + } + var tags = []; + for (var tag in this.nos_by_tag) { + if (this.nos_by_tag.hasOwnProperty(tag)) { + tags.push(tag); + } + } + tags.sort(); + if (tags.length) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(this.label)); + var ul = document.createElement('ul'); + ul.id = 'comment_tags_collapse_expand'; + div.appendChild(ul); + for (var i = 0, l = tags.length; i < l; i++) { + var tag = tags[i]; + var li = document.createElement('li'); + ul.appendChild(li); + var a = document.createElement('a'); + li.appendChild(a); + Dom.setAttribute(a, 'href', '#'); + YAHOO.util.Event.addListener(a, 'click', function(evt, tag) { + YAHOO.bugzilla.commentTagging.toggleCollapse(tag); + YAHOO.util.Event.stopEvent(evt); + }, tag); + li.appendChild(document.createTextNode(' (' + this.nos_by_tag[tag].length + ')')); + a.innerHTML = YAHOO.lang.escapeHTML(tag); + } + while (container.hasChildNodes()) { + container.removeChild(container.lastChild); + } + container.appendChild(div); + } else { + while (container.hasChildNodes()) { + container.removeChild(container.lastChild); + } + } + }, + + toggleCollapse : function(tag) { + var nos = this.nos_by_tag[tag]; + if (!nos) return; + toggle_all_comments('collapse'); + for (var i = 0, l = nos.length; i < l; i++) { + var comment_no = nos[i].match(/\d+$/)[0]; + var comment = Dom.get('comment_text_' + comment_no); + var link = Dom.get('comment_link_' + comment_no); + expand_comment(link, comment, comment_no); + } + }, + + buildTagHtml : function(comment_id, comment_no, tag) { + var el = document.createElement('span'); + Dom.setAttribute(el, 'id', 'ct_' + comment_no + '_' + tag); + Dom.addClass(el, 'bz_comment_tag'); + if (this.can_edit) { + var a = document.createElement('a'); + Dom.setAttribute(a, 'href', '#'); + YAHOO.util.Event.addListener(a, 'click', function(evt, args) { + YAHOO.bugzilla.commentTagging.remove(args[0], args[1], args[2]) + YAHOO.util.Event.stopEvent(evt); + }, [comment_id, comment_no, tag]); + a.appendChild(document.createTextNode('x')); + el.appendChild(a); + el.appendChild(document.createTextNode("\u00a0")); + } + el.appendChild(document.createTextNode(tag)); + return el; + }, + + add : function(comment_id, comment_no, add_tags) { + // build list of current tags from html + var tags = new Array(); + var spans = Dom.getElementsByClassName('bz_comment_tag', undefined, 'ct_' + comment_no); + for (var i = 0, l = spans.length; i < l; i++) { + tags.push(spans[i].textContent.substr(2)); + } + // add new tags + var new_tags = new Array(); + for (var i = 0, l = add_tags.length; i < l; i++) { + var tag = YAHOO.lang.trim(add_tags[i]); + // validation + if (tag == '') + continue; + if (tag.length < YAHOO.bugzilla.commentTagging.min_len) + throw new Error(this.min_len_error) + if (tag.length > YAHOO.bugzilla.commentTagging.max_len) + throw new Error(this.max_len_error) + // append new tag + if (bz_isValueInArrayIgnoreCase(tags, tag)) + continue; + new_tags.push(tag); + tags.push(tag); + } + tags.sort(); + // update + this.showTags(comment_id, comment_no, tags); + this.rpcUpdate(comment_id, comment_no, new_tags, undefined); + }, + + remove : function(comment_id, comment_no, tag) { + var el = Dom.get('ct_' + comment_no + '_' + tag); + if (el) { + el.parentNode.removeChild(el); + this.rpcUpdate(comment_id, comment_no, undefined, [ tag ]); + } + }, + + // If multiple updates are triggered quickly, overlapping refresh events + // are generated. We ignore all events except the last one. + incPending : function(comment_id) { + if (this.pending['c' + comment_id] == undefined) { + this.pending['c' + comment_id] = 1; + } else { + this.pending['c' + comment_id]++; + } + }, + + decPending : function(comment_id) { + if (this.pending['c' + comment_id] != undefined) + this.pending['c' + comment_id]--; + }, + + hasPending : function(comment_id) { + return this.pending['c' + comment_id] != undefined + && this.pending['c' + comment_id] > 0; + }, + + rpcRefresh : function(comment_id, comment_no, noRefreshOnError) { + this.incPending(comment_id); + YAHOO.util.Connect.setDefaultPostHeader('application/json', true); + YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', + { + success: function(res) { + YAHOO.bugzilla.commentTagging.decPending(comment_id); + data = YAHOO.lang.JSON.parse(res.responseText); + if (data.error) { + YAHOO.bugzilla.commentTagging.handleRpcError( + comment_id, comment_no, data.error.message, noRefreshOnError); + return; + } + + if (!YAHOO.bugzilla.commentTagging.hasPending(comment_id)) + YAHOO.bugzilla.commentTagging.showTags( + comment_id, comment_no, data.result.comments[comment_id].tags); + }, + failure: function(res) { + YAHOO.bugzilla.commentTagging.decPending(comment_id); + YAHOO.bugzilla.commentTagging.handleRpcError( + comment_id, comment_no, res.responseText, noRefreshOnError); + } + }, + YAHOO.lang.JSON.stringify({ + version: "1.1", + method: 'Bug.comments', + params: { + Bugzilla_api_token: BUGZILLA.api_token, + comment_ids: [ comment_id ], + include_fields: [ 'tags' ] + } + }) + ); + }, + + rpcUpdate : function(comment_id, comment_no, add, remove) { + this.incPending(comment_id); + YAHOO.util.Connect.setDefaultPostHeader('application/json', true); + YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', + { + success: function(res) { + YAHOO.bugzilla.commentTagging.decPending(comment_id); + data = YAHOO.lang.JSON.parse(res.responseText); + if (data.error) { + YAHOO.bugzilla.commentTagging.handleRpcError(comment_id, comment_no, data.error.message); + return; + } + + if (!YAHOO.bugzilla.commentTagging.hasPending(comment_id)) + YAHOO.bugzilla.commentTagging.showTags(comment_id, comment_no, data.result); + }, + failure: function(res) { + YAHOO.bugzilla.commentTagging.decPending(comment_id); + YAHOO.bugzilla.commentTagging.handleRpcError(comment_id, comment_no, res.responseText); + } + }, + YAHOO.lang.JSON.stringify({ + version: "1.1", + method: 'Bug.update_comment_tags', + params: { + Bugzilla_api_token: BUGZILLA.api_token, + comment_id: comment_id, + add: add, + remove: remove + } + }) + ); + }, + + handleRpcError : function(comment_id, comment_no, message, noRefreshOnError) { + YAHOO.bugzilla.commentTagging.showError(comment_id, comment_no, message); + if (!noRefreshOnError) { + YAHOO.bugzilla.commentTagging.rpcRefresh(comment_id, comment_no, true); + } + } +} |