Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Side by Side Diff: appengine/monorail/static/js/tracker/tracker-keystrokes.js

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /* Copyright 2016 The Chromium Authors. All Rights Reserved.
2 *
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file or at
5 * https://developers.google.com/open-source/licenses/bsd
6 */
7
8 /**
9 * This file contains JS functions that implement keystroke accelerators
10 * for Monorail.
11 */
12
13 /**
14 * Array of HTML elements where the kibbles cursor can be. E.g.,
15 * the TR elements of an issue list, or the TR's for comments on an issue.
16 */
17 var TKR_cursorStops;
18
19 /**
20 * Integer index into TKR_cursorStops of the currently selected cursor
21 * stop, or undefined if nothing has been selected yet.
22 */
23 var TKR_selected = undefined;
24
25
26 /**
27 * Scroll to the issue search field, set keyboard focus there, and
28 * select all of its text contents. We use <span id="qq"> around
29 * that form field because IE has a broken getElementById that
30 * confuses id with form element names. We do this in a setTimeout()
31 * so that the keystroke that triggers it ('/') will not be typed into
32 * the search box itself.
33 */
34 function TKR_focusArtifactSearchField() {
35 var el = TKR_getArtifactSearchField();
36 el.focus(); // forces browser to scroll to make field visible.
37 el.select();
38 }
39
40
41 /**
42 * Always hide the keystroke help overlay, if it has been loaded.
43 */
44 function TKR_closeKeystrokeHelp() {
45 var dialog = document.getElementById('keys_help');
46 if (dialog) {
47 dialog.style.display = 'none';
48 }
49 }
50
51 /**
52 * Show or hide the keystroke help overlay. If it has not been loaded
53 * yet, make the request to load it.
54 */
55 function TKR_toggleKeystrokeHelp() {
56 var dialog = document.getElementById('keys_help');
57 if (dialog) {
58 dialog.style.display = dialog.style.display ? '' : 'none';
59 } else {
60 TKR_buildKeystrokeHelp();
61 }
62 }
63
64 function TKR_createChild(parentEl, tag, optClassName, optID, optText, optStyle) {
65 var el = document.createElement(tag);
66 if (optClassName) el.classList.add(optClassName);
67 if (optID) el.id = optID;
68 if (optText) el.innerText = optText;
69 if (optStyle) el.setAttribute('style', optStyle);
70 parentEl.appendChild(el);
71 return el;
72 }
73
74 function TKR_createKeysHelpHeader(row, text) {
75 TKR_createChild(row, 'td');
76 TKR_createChild(row, 'th', null, null, text);
77 return row;
78 }
79
80 function TKR_createKeysHelpItem(row, key1, key2, doc) {
81 var keyCell = TKR_createChild(row, 'td', 'shortcut');
82 TKR_createChild(keyCell, 'span', 'keystroke', null, key1);
83 if (key2) {
84 keyCell.appendChild(document.createTextNode(' / '));
85 TKR_createChild(keyCell, 'span', 'keystroke', null, key2);
86 }
87 TKR_createChild(keyCell, 'b', null, null, ' :');
88
89 TKR_createChild(row, 'td', null, null, doc);
90 return keyCell;
91 }
92
93 /**
94 * Build the keystroke help dialog. It is not part of the template because it
95 * not used on the vast majority of pages viewed.
96 */
97 function TKR_buildKeystrokeHelp() {
98 var helpArea = document.getElementById('helparea');
99 var dialog = TKR_createChild(
100 helpArea, 'div', 'fullscreen-popup', 'keys_help');
101 var closeX = TKR_createChild(
102 dialog, 'a', null, null, 'Close', 'float:right; font-size:140%');
103 closeX.href = '#';
104 closeX.addEventListener('click', function () {
105 $('keys_help').style.display = 'none';
106 });
107 TKR_createChild(
108 dialog, 'div', null, null, 'Issue tracker keyboard shortcuts',
109 'font-size: 140%');
110 TKR_createChild(dialog, 'hr');
111
112 var keysTable = TKR_createChild(
113 dialog, 'table', null, null, null, 'width: 100%');
114 var headerRow = TKR_createChild(keysTable, 'tr');
115 TKR_createKeysHelpHeader(headerRow, 'Issue list');
116 TKR_createKeysHelpHeader(headerRow, 'Issue details');
117 TKR_createKeysHelpHeader(headerRow, 'Anywhere')
118 var row1 = TKR_createChild(keysTable, 'tr');
119 TKR_createKeysHelpItem(row1, 'k', 'j', 'up/down in the list');
120 TKR_createKeysHelpItem(row1, 'k', 'j', 'prev/next issue in list');
121 TKR_createKeysHelpItem(row1, '/', null, 'focus on the issue search field');
122 var row2 = TKR_createChild(keysTable, 'tr');
123 TKR_createKeysHelpItem(row2, 'o', '<Enter>', 'open the current issue');
124 TKR_createKeysHelpItem(row2, 'u', null, 'up to issue list');
125 TKR_createKeysHelpItem(row2, 'c', null, 'compose a new issue');
126 var row3 = TKR_createChild(keysTable, 'tr');
127 TKR_createKeysHelpItem(row3, 'Shift-O', null, 'open issue in new tab');
128 TKR_createKeysHelpItem(row3, 'p', 'n', 'prev/next comment');
129 TKR_createKeysHelpItem(row3, 's', null, 'star the current issue');
130 var row4 = TKR_createChild(keysTable, 'tr');
131 TKR_createKeysHelpItem(row4, 'x', null, 'select the current issue');
132 TKR_createKeysHelpItem(row4, 'r', null, 'add comment & make changes');
133 TKR_createKeysHelpItem(row4, '?', null, 'show this help dialog');
134
135 var footer = TKR_createChild(
136 dialog, 'div', null, null, null,
137 'font-weight:normal; margin-top: 3em;');
138 TKR_createChild(footer, 'hr');
139 TKR_createChild(footer, 'div', null, null,
140 ('Note: Only signed in users can star issues or add comments, ' +
141 'and only project members can select issues for bulk edits.'));
142 }
143
144
145 /**
146 * Register keystrokes that apply to all pages in the current component.
147 * E.g., keystrokes that should work on every page under the "Issues" tab.
148 * @param {string} listUrl Rooted URL of the artifact list.
149 * @param {string} entryUrl Rooted URL of the artifact entry page.
150 * @param {string} currentPageType One of 'list', 'entry', or 'detail'.
151 */
152 function TKR_setupKibblesComponentKeys(listUrl, entryUrl, currentPageType) {
153 kibbles.keys.addKeyPressListener(
154 '/',
155 function() {
156 window.setTimeout(TKR_focusArtifactSearchField, 10);
157 });
158 if (currentPageType != 'entry') {
159 kibbles.keys.addKeyPressListener(
160 'c', function() { TKR_go(entryUrl); });
161 }
162 if (currentPageType != 'list') {
163 kibbles.keys.addKeyPressListener(
164 'u', function() { TKR_go(listUrl); });
165 }
166 kibbles.keys.addKeyPressListener('?', TKR_toggleKeystrokeHelp);
167
168 kibbles.keys.addKeyPressListener('ESC', TKR_closeKeystrokeHelp);
169 }
170
171
172 /**
173 * On the artifact list page, go tp the artifact at the kibbles cursor.
174 * @param {number} linkCellIndex row child that is expected to hold a link.
175 */
176 function TKR_openArtifactAtCursor(linkCellIndex, newWindow) {
177 if (TKR_selected >= 0 && TKR_selected < TKR_cursorStops.length) {
178 var cell = TKR_cursorStops[TKR_selected].children[linkCellIndex];
179 var anchor = cell.children[0];
180 if (anchor) {
181 TKR_go(anchor.getAttribute('href'), newWindow);
182 }
183 }
184 }
185
186
187 /**
188 * On the artifact list page, toggle the checkbox for the artifact at
189 * the kibbles cursor.
190 * @param {number} cbCellIndex row child that is expected to hold a checkbox.
191 */
192 function TKR_selectArtifactAtCursor(cbCellIndex) {
193 if (TKR_selected >= 0 && TKR_selected < TKR_cursorStops.length) {
194 var cell = TKR_cursorStops[TKR_selected].children[cbCellIndex];
195 var cb = cell.firstChild;
196 while (cb && cb.tagName != 'INPUT') {
197 cb = cb.nextSibling;
198 }
199 if (cb) {
200 cb.checked = cb.checked ? '' : 'checked';
201 TKR_highlightRow(cb);
202 }
203 }
204 }
205
206 /**
207 * On the artifact list page, toggle the star for the artifact at
208 * the kibbles cursor.
209 * @param {number} cbCellIndex row child that is expected to hold a checkbox
210 * and star widget.
211 * @param {string} set_star_token The security token.
212 */
213 function TKR_toggleStarArtifactAtCursor(cbCellIndex, set_star_token) {
214 if (TKR_selected >= 0 && TKR_selected < TKR_cursorStops.length) {
215 var cell = TKR_cursorStops[TKR_selected].children[cbCellIndex];
216 var starIcon = cell.firstChild;
217 while (starIcon && starIcon.tagName != 'A') {
218 starIcon = starIcon.nextSibling;
219 }
220 if (starIcon) {
221 _TKR_toggleStar(
222 starIcon, issueRefs[TKR_selected]['project_name'],
223 issueRefs[TKR_selected]['id'], set_star_token);
224 }
225 }
226 }
227
228 /**
229 * Updates the style on new stop and clears the style on the former stop.
230 * @param {Object} newStop the cursor stop that the user is selecting now.
231 * @param {Object} formerStop the old cursor stop, if any.
232 */
233 function TKR_updateCursor(newStop, formerStop) {
234 TKR_selected = undefined;
235 if (formerStop) {
236 formerStop.element.classList.remove('cursor_on');
237 formerStop.element.classList.add('cursor_off');
238 }
239 if (newStop && newStop.element) {
240 newStop.element.classList.remove('cursor_off');
241 newStop.element.classList.add('cursor_on');
242 TKR_selected = newStop.index;
243 }
244 }
245
246
247 /**
248 * Walk part of the page DOM to find elements that should be kibbles
249 * cursor stops. E.g., the rows of the issue list results table.
250 * @return {Array} an array of html elements.
251 */
252 function TKR_findCursorRows() {
253 var rows = [];
254 var cursorarea = document.getElementById('cursorarea');
255 TKR_accumulateCursorRows(cursorarea, rows);
256 return rows;
257 }
258
259
260 /**
261 * Recusrively walk part of the page DOM to find elements that should
262 * be kibbles cursor stops. E.g., the rows of the issue list results
263 * table. The cursor stops are appended to the given rows array.
264 * @param {Element} parent html element to start on.
265 * @param {Array} rows array of html TR or DIV elements, each cursor stop will
266 * be added to this array.
267 */
268 function TKR_accumulateCursorRows(parent, rows) {
269 for (var i = 0; i < parent.childNodes.length; i++) {
270 var elem = parent.childNodes[i];
271 var name = elem.tagName;
272 if (name && (name == 'TR' || name == 'DIV')) {
273 if (elem.className.indexOf('cursor') >= 0) {
274 elem.cursorIndex = rows.length;
275 rows.push(elem);
276 }
277 }
278 TKR_accumulateCursorRows(elem, rows);
279 }
280 }
281
282
283 /**
284 * Initialize kibbles cursors stops for the current page.
285 * @param {boolean} selectFirstStop True if the first stop should be
286 * selected before the user presses any keys.
287 */
288 function TKR_setupKibblesCursorStops(selectFirstStop) {
289 kibbles.skipper.addStopListener(
290 kibbles.skipper.LISTENER_TYPE.PRE, TKR_updateCursor);
291
292 // Set the 'offset' option to return the middle of the client area
293 // an option can be a static value, or a callback
294 kibbles.skipper.setOption('padding_top', 50);
295
296 // Set the 'offset' option to return the middle of the client area
297 // an option can be a static value, or a callback
298 kibbles.skipper.setOption('padding_bottom', 50);
299
300 // register our stops with skipper
301 TKR_cursorStops = TKR_findCursorRows();
302 for (var i = 0; i < TKR_cursorStops.length; i++) {
303 var element = TKR_cursorStops[i];
304 kibbles.skipper.append(element);
305
306 if (element.className.indexOf('cursor_on') >= 0) {
307 kibbles.skipper.setCurrentStop(i);
308 }
309 }
310 }
311
312
313 /**
314 * Initialize kibbles keystrokes for an artifact entry page.
315 * @param {string} listUrl Rooted URL of the artifact list.
316 * @param {string} entryUrl Rooted URL of the artifact entry page.
317 */
318 function TKR_setupKibblesOnEntryPage(listUrl, entryUrl) {
319 TKR_setupKibblesComponentKeys(listUrl, entryUrl, 'entry');
320 }
321
322
323 /**
324 * Initialize kibbles keystrokes for an artifact list page.
325 * @param {string} listUrl Rooted URL of the artifact list.
326 * @param {string} entryUrl Rooted URL of the artifact entry page.
327 * @param {string} projectName Name of the current project.
328 * @param {number} linkCellIndex table column that is expected to
329 * link to individual artifacts.
330 * @param {number} opt_checkboxCellIndex table column that is expected
331 * to contain a selection checkbox.
332 * @param {string} set_star_token The security token.
333 */
334 function TKR_setupKibblesOnListPage(
335 listUrl, entryUrl, projectName, linkCellIndex,
336 opt_checkboxCellIndex, set_star_token) {
337 TKR_setupKibblesCursorStops(true);
338
339 kibbles.skipper.addFwdKey('j');
340 kibbles.skipper.addRevKey('k');
341
342 if (opt_checkboxCellIndex != undefined) {
343 var cbCellIndex = opt_checkboxCellIndex;
344 kibbles.keys.addKeyPressListener(
345 'x', function() { TKR_selectArtifactAtCursor(cbCellIndex); });
346 kibbles.keys.addKeyPressListener(
347 's',
348 function() {
349 TKR_toggleStarArtifactAtCursor(cbCellIndex, set_star_token);
350 });
351 }
352 kibbles.keys.addKeyPressListener(
353 'o', function() { TKR_openArtifactAtCursor(linkCellIndex, false); });
354 kibbles.keys.addKeyPressListener(
355 'O', function() { TKR_openArtifactAtCursor(linkCellIndex, true); });
356 kibbles.keys.addKeyPressListener(
357 'enter', function() { TKR_openArtifactAtCursor(linkCellIndex); });
358
359 TKR_setupKibblesComponentKeys(listUrl, entryUrl, 'list');
360 }
361
362
363 /**
364 * Initialize kibbles keystrokes for an artifact detail page.
365 * @param {string} listUrl Rooted URL of the artifact list.
366 * @param {string} entryUrl Rooted URL of the artifact entry page.
367 * @param {string} prevUrl Rooted URL of previous artifact in list.
368 * @param {string} nextUrl Rooted URL of next artifact in list.
369 * @param {string} projectName name of the current project.
370 * @param {boolean} userCanComment True if the user may add a comment.
371 * @param {boolean} userCanStar True if the user may add a star.
372 * @param {string} set_star_token The security token.
373 */
374 function TKR_setupKibblesOnDetailPage(
375 listUrl, entryUrl, prevUrl, nextUrl, projectName, localId,
376 userCanComment, userCanStar, set_star_token) {
377 TKR_setupKibblesCursorStops(false);
378 kibbles.skipper.addFwdKey('n');
379 kibbles.skipper.addRevKey('p');
380 if (prevUrl) {
381 kibbles.keys.addKeyPressListener(
382 'k', function() { TKR_go(prevUrl); });
383 }
384 if (nextUrl) {
385 kibbles.keys.addKeyPressListener(
386 'j', function() { TKR_go(nextUrl); });
387 }
388 if (userCanComment) {
389 kibbles.keys.addKeyPressListener(
390 'r',
391 function() {
392 window.setTimeout(TKR_openIssueUpdateForm, 10);
393 });
394 }
395 if (userCanStar) {
396 kibbles.keys.addKeyPressListener(
397 's',
398 function() {
399 var star = document.getElementById('star');
400 TKR_toggleStar(star, projectName, localId, set_star_token);
401 TKR_syncStarIcons(star, 'star2');
402 });
403 }
404 TKR_setupKibblesComponentKeys(listUrl, entryUrl, 'detail');
405 }
OLDNEW
« no previous file with comments | « appengine/monorail/static/js/tracker/tracker-fields.js ('k') | appengine/monorail/static/js/tracker/tracker-nav.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698