1 // Copyright (C) 2010 Ojan Vafai. All rights reserved.
2 // Copyright (C) 2010 Adam Barth. All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
7 // 1. Redistributions of source code must retain the above copyright notice,
8 // this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright notice,
11 // this list of conditions and the following disclaimer in the documentation
12 // and/or other materials provided with the distribution.
14 // THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
15 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 // DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
18 // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26 WebKitCommitters = (function() {
27 var COMMITTERS_URL = 'https://svn.webkit.org/repository/webkit/trunk/Tools/Scripts/webkitpy/common/config/contributors.json';
30 function statusToType(status) {
31 if (status === 'reviewer')
33 if (status === 'committer')
38 function parseCommittersPy(text) {
39 var contributors = JSON.parse(text);
43 for (var name in contributors) {
44 var record = contributors[name];
47 emails: record.emails,
49 type: statusToType(record.status),
54 function loadCommitters(callback) {
55 var xhr = new XMLHttpRequest();
56 xhr.open('GET', COMMITTERS_URL);
58 xhr.onload = function() {
59 parseCommittersPy(xhr.responseText);
63 xhr.onerror = function() {
64 console.log('Unable to load contributors.json');
71 function getCommitters(callback) {
73 callback(m_committers);
77 loadCommitters(function() {
78 callback(m_committers);
83 "getCommitters": getCommitters
88 var SINGLE_EMAIL_INPUTS = ['email1', 'email2', 'requester', 'requestee', 'assigned_to'];
89 var EMAIL_INPUTS = SINGLE_EMAIL_INPUTS.concat(['cc', 'newcc', 'new_watchedusers']);
97 function contactsMatching(prefix) {
102 for (var i = 0; i < m_committers.length; i++) {
103 if (isMatch(m_committers[i], prefix))
104 list.push(m_committers[i]);
109 function startsWith(str, prefix) {
110 return str.toLowerCase().indexOf(prefix.toLowerCase()) == 0;
113 function startsWithAny(arry, prefix) {
114 for (var i = 0; i < arry.length; i++) {
115 if (startsWith(arry[i], prefix))
121 function isMatch(contact, prefix) {
122 if (startsWithAny(contact.emails, prefix))
125 if (contact.irc && startsWithAny(contact.irc, prefix))
128 var names = contact.name.split(' ');
129 for (var i = 0; i < names.length; i++) {
130 if (startsWith(names[i], prefix))
137 function isMenuVisible() {
138 return getMenu().style.display != 'none';
141 function showMenu(shouldShow) {
142 getMenu().style.display = shouldShow ? '' : 'none';
145 function updateMenu() {
146 var newPrefix = m_focusedInput.value;
148 newPrefix = newPrefix.slice(getStart(), getEnd());
149 newPrefix = newPrefix.replace(/^\s+/, '');
150 newPrefix = newPrefix.replace(/\s+$/, '');
153 if (m_prefix == newPrefix)
156 m_prefix = newPrefix;
158 var contacts = contactsMatching(m_prefix);
159 if (contacts.length == 0 || contacts.length == 1 && contacts[0].emails[0] == m_prefix) {
165 for (var i = 0; i < contacts.length; i++) {
166 var contact = contacts[i];
167 html.push('<div style="padding:1px 2px;" ' + 'email=' +
168 contact.emails[0] + '>' + contact.name + ' - ' + contact.emails[0]);
170 html.push(' (:' + contact.irc + ')');
172 html.push(' (' + contact.type + ')');
175 getMenu().innerHTML = html.join('');
180 function getIndex(item) {
181 for (var i = 0; i < getMenu().childNodes.length; i++) {
182 if (item == getMenu().childNodes[i])
185 console.error("Couldn't find item.");
189 return m_menus[m_focusedInput.name];
192 function createMenu(name, input) {
193 if (!m_menus[name]) {
194 var menu = document.createElement('div');
196 "position:absolute;border:1px solid black;background-color:white;-webkit-box-shadow:3px 3px 3px #888;";
197 menu.style.minWidth = m_focusedInput.offsetWidth + 'px';
198 m_focusedInput.parentNode.insertBefore(menu, m_focusedInput.nextSibling);
200 menu.addEventListener('mousedown', function(e) {
201 selectItem(getIndex(e.target));
205 menu.addEventListener('mouseup', function(e) {
206 if (m_selectedIndex == getIndex(e.target))
207 insertSelectedItem();
210 m_menus[name] = menu;
214 function getStart() {
215 var index = m_focusedInput.value.lastIndexOf(',', m_focusedInput.selectionStart - 1);
222 var index = m_focusedInput.value.indexOf(',', m_focusedInput.selectionStart);
224 return m_focusedInput.value.length;
228 function getItem(index) {
229 return getMenu().childNodes[index];
232 function selectItem(index) {
233 if (index < 0 || index >= getMenu().childNodes.length)
236 if (m_selectedIndex != undefined) {
237 getItem(m_selectedIndex).style.backgroundColor = '';
238 getItem(m_selectedIndex).style.color = '';
241 getItem(index).style.backgroundColor = '#039';
242 getItem(index).style.color = 'white';
244 m_selectedIndex = index;
247 function insertSelectedItem() {
248 var selectedEmail = getItem(m_selectedIndex).getAttribute('email');
249 var oldValue = m_focusedInput.value;
251 var newValue = oldValue.slice(0, getStart()) + selectedEmail + oldValue.slice(getEnd());
252 if (SINGLE_EMAIL_INPUTS.indexOf(m_focusedInput.name) == -1 &&
253 newValue.charAt(newValue.length - 1) != ',')
254 newValue = newValue + ',';
256 m_focusedInput.value = newValue;
260 function handleKeyDown(e) {
261 if (!isMenuVisible())
264 switch (e.keyIdentifier) {
266 selectItem(m_selectedIndex - 1);
271 selectItem(m_selectedIndex + 1);
276 insertSelectedItem();
282 function handleKeyUp(e) {
283 if (e.keyIdentifier == 'Enter')
286 if (m_focusedInput.selectionStart == m_focusedInput.selectionEnd)
292 function enableAutoComplete(input) {
293 m_focusedInput = input;
296 createMenu(m_focusedInput.name);
297 // Turn off autocomplete to avoid showing the browser's dropdown menu.
298 m_focusedInput.setAttribute('autocomplete', 'off');
299 m_focusedInput.addEventListener('keyup', handleKeyUp, false);
300 m_focusedInput.addEventListener('keydown', handleKeyDown, false);
301 m_focusedInput.addEventListener('blur', function() {
306 // Turn on autocomplete on submit to avoid breaking autofill on back/forward navigation.
307 m_focusedInput.form.addEventListener("submit", function() {
308 m_focusedInput.setAttribute("autocomplete", "on");
315 for (var i = 0; i < EMAIL_INPUTS.length; i++) {
316 var field = document.getElementsByName(EMAIL_INPUTS[i])[0];
318 field.addEventListener("focus", function(e) { enableAutoComplete(e.target); }, false);
321 WebKitCommitters.getCommitters(function (committers) {
322 m_committers = committers;