Hi, This is the diff. It is still quite big as the original branch was submitted 2 months ago and was one of my first patch for LP. Meanwhile I discovered the JavaScript guidelines and looking again at the code I found many places where the previous code could be improved. Please let me know if there are any other actions I need to do to make this branch ready for review? === modified file 'lib/canonical/launchpad/javascript/translations/pofile.js' --- lib/canonical/launchpad/javascript/translations/pofile.js 2010-02-22 11:41:11 +0000 +++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-02-25 00:53:26 +0000 @@ -1,7 +1,7 @@ /** Copyright (c) 2009, Canonical Ltd. All rights reserved. * * @module lp.pofile - * @requires event, node + * @requires anim, cookie, event-key, event, node */ YUI.add('lp.pofile', function(Y) { @@ -42,8 +42,48 @@ }; +var hide_notification = function(node) { + var hide_anim = new Y.Anim({ + node: node, + to: { height: 0, + marginTop: 0, marginBottom: 0, + paddingTop: 0, paddingBottom: 0 } + }); + node.setStyle('border', 'none'); + hide_anim.set('duration', 0.4); + hide_anim.on('end', function(e) { + node.setStyle('display', 'none'); + }); + hide_anim.run(); +} + +self.updateNotificationBox = function(e) { + var notice = Y.one('.important-notice-container'); + var balloon = notice.one('.important-notice-balloon'); + var dismiss_notice_cookie = ('translation-docs-for-' + + documentation_cookie); + + // Check the cookie to see if the user has already dismissed + // the notification box for this session. + var already_seen = Y.Cookie.get(dismiss_notice_cookie, Boolean); + if (already_seen) { + notice.setStyle('display', 'none'); + } + + var cancel_button = notice.one( + '.important-notice-cancel-button'); + // Cancel button starts out hidden. If user has JavaScript, + // then we want to show it. + cancel_button.setStyle('visibility', 'visible'); + cancel_button.on('click', function(e) { + e.halt(); + hide_notification(balloon); + Y.Cookie.set(dismiss_notice_cookie, true); + }); +}; + + self.setFocus = function(field) { - //Y.log(e.type + ":" + e.keyCode + ': ' + field); // if there is nofield, do nothing if (Y.one('#' + field)) { Y.one('#' + field).focus(); @@ -75,7 +115,7 @@ // The replacement regex strips all tags from the html. to.set('value', unescapeHTML( from.get('innerHTML').replace(/<\/?[^>]+>/gi, ""))); - selectWidget(select_id); + selectWidgetByID(select_id); }; @@ -97,7 +137,6 @@ var singular_select = translation_stem + '_translation_0_new_select'; var translation_singular = translation_stem + '_translation_0_new'; var translation_plural = translation_stem + '_translation_'; - //Y.log(e.type + ":" + e.keyCode + ': ' + singular_select); // Copy singular text copyOriginalTextOne( original_singular, translation_singular, singular_select); @@ -112,43 +151,102 @@ }; -var selectWidget = function(widget) { - if (Y.one('#' + widget)) { - Y.one('#' + widget).set('checked', true); +var selectWidgetByID = function(widget) { + var node = Y.one('#' + widget); + if (node) { + node.set('checked', true); + } +}; + + +var toggleWidget = function(widget) { + var node = Y.one('#' + widget); + if (node) { + if (node.get('checked')) { + node.set('checked', false); + } else { + node.set('checked', true); + } } }; var selectTranslation = function(e, widget) { - //Y.log(e.type + ":" + e.keyCode + ': ' + widget); // Don't select when tabbing, navigating and simply pressing // enter to submit the form. // Looks like this is not needed for Epiphany and Chromium if (e.keyCode == 9 || e.keyCode == 13 || - (e.shiftKey && e.keyCode == 74) || - (e.shiftKey && e.keyCode == 75)) { + e.keyCode == 38 || e.keyCode == 40 || + (e.shiftKey && e.altKey && e.keyCode == 74) || + (e.shiftKey && e.altKey && e.keyCode == 75)) { return; } - selectWidget(widget); + selectWidgetByID(widget); }; -/** - * Initialize event-key bindings such as moving to the next or previous - * field, or copying original text - */ -self.initializeKeyBindings = function(e) { - - if (translations_order.length < 1) { - // If no translations fiels are displayed on the page - // don't initialize the translations order - return; - } - - var fields = translations_order.split(' '); - // The last field is Save & Continue button - fields.push('save_and_continue_button'); - +var initializeGlobalKeyBindings = function(fields) { + + Y.get('document').on("keyup", function(e) { + // Shift+Alt+s - Save form + if (e.shiftKey && e.altKey && e.keyCode == 83) { + Y.one('#save_and_continue_button').invoke('click'); + } + // Shift+Alt+f - Go to search field + if (e.shiftKey && e.altKey && e.keyCode == 70) { + self.setFocus('search_box'); + } + // Shift+Alt+b - Go to first translation field + if (e.shiftKey && e.altKey && e.keyCode == 66) { + self.setFocus(fields[0]); + } + // Shift+Alt+n - Go to next page in batch + if (e.shiftKey && e.altKey && e.keyCode == 78) { + if (link = Y.one('#batchnav_next')){ + window.location.assign(link.get('href')) + } + } + // Shift+Alt+p - Go to previous page in batch + if (e.shiftKey && e.altKey && e.keyCode == 80) { + if (link = Y.one('#batchnav_previous')){ + window.location.assign(link.get('href')) + } + } + // Shift+Alt+a - Go to first page in batch + if (e.shiftKey && e.altKey && e.keyCode == 65) { + if (link = Y.one('#batchnav_first')){ + window.location.assign(link.get('href')) + } + } + // Shift+Alt+l - Go to last page in batch + if (e.shiftKey && e.altKey && e.keyCode == 76) { + if (link = Y.one('#batchnav_last')){ + window.location.assign(link.get('href')) + } + } + }); +} + + +var initializeSuggestionsKeyBindings = function(stem) { + + suggestions = Y.all('.' + stem.replace(/_new/,"") + ' input'); + suggestions.each(function(node) { + // Only add keybinding for the first 9 suggestions + var index = suggestions.indexOf(node); + if (index < 10) { + // Shift+Alt+NUMBER - Mark suggestion NUMBER + Y.on('key', function(e, id) { + selectWidgetByID(id); + }, + '#' + stem, 'down:' + Number(index+49) + '+shift+alt', + Y, node.get('id')); + } + }); +} + + +var initializeFieldsKeyBindings = function (fields) { for (var key = 0; key < fields.length; key++) { var next = key + 1; var previous = key - 1; @@ -170,12 +268,40 @@ // Set next field and copy text for all but last field // (last is Save & Continue button) if (key < fields.length - 1) { + // Shift+Alt+j - Go to next translation Y.on( 'key', setNextFocus, '#' + fields[key], 'down:74+shift+alt', Y, fields[next]); + // Shift+Alt+KEY_DOWN - Go to next translation + Y.on( + 'key', setNextFocus, '#' + fields[key], + 'down:40+shift+alt', Y, fields[next]); + // Shift+Alt+c - Copy original text Y.on( 'key', copyOriginalTextAll, '#' + fields[key], 'down:67+shift+alt', Y, original_stem, translation_stem); + + // Shift+Alt+r - Toggle someone should review + Y.on( + 'key', + function(e, stem) { + toggleWidget(stem + '_force_suggestion'); + }, + '#' + fields[key], 'down:82+shift+alt', Y, original_stem); + + // Shift+Alt+d - Toggle dismiss all translations + Y.on( + 'key', function(e, stem) { + toggleWidget(stem + '_dismiss'); + }, '#' + fields[key], 'down:68+shift+alt', Y, original_stem); + + // Shift+Alt+0 - Mark current translation + Y.on( + 'key', function(e, key) { + selectWidgetByID(key.replace(/_new/, "_radiobutton")); + }, '#' + fields[key], 'down:48+shift+alt', Y, fields[key]); + + initializeSuggestionsKeyBindings(fields[key]); } // Set previous field for all but first field @@ -183,13 +309,40 @@ var parts = fields[previous].split('_'); var singular_copy_text = ( parts[0] + '_' + parts[1] + '_singular_copy_text'); + // Shift+Alt+k - Go to previous translation Y.on( 'key', setPreviousFocus, '#' + fields[key], 'down:75+shift+alt', Y, fields[previous], singular_copy_text); + // Shift+Alt+KEY_UP - Go to previous translation + Y.on( + 'key', setPreviousFocus, '#' + fields[key], + 'down:38+shift+alt', Y, fields[previous], + singular_copy_text); } } +} + + +/** + * Initialize event-key bindings such as moving to the next or previous + * field, or copying original text + */ +self.initializeKeyBindings = function(e) { + + if (translations_order.length < 1) { + // If no translations fiels are displayed on the page + // don't initialize the translations order + return; + } + + var fields = translations_order.split(' '); + // The last field is Save & Continue button + fields.push('save_and_continue_button'); + + initializeGlobalKeyBindings(fields); + initializeFieldsKeyBindings(fields); }; -}, "0.1", {"requires": ["event", "event-key", "node"]}); +}, "0.1", {"requires": ["event", "event-key", "node", "cookie", "anim"]}); === modified file 'lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt' --- lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt 2010-01-27 07:38:02 +0000 +++ lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt 2010-02-24 21:42:30 +0000 @@ -29,6 +29,7 @@ tal:condition="first_page_url" tal:attributes="href first_page_url" class="first" + id="batchnav_first" rel="first" >First Previous