Merge lp:~adiroiban/launchpad/bug-359180 into lp:launchpad

Proposed by Adi Roiban
Status: Merged
Approved by: Aaron Bentley
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-359180
Merge into: lp:launchpad
Diff against target: 576 lines (+278/-61)
11 files modified
lib/canonical/launchpad/javascript/translations/pofile.js (+194/-2)
lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt (+4/-0)
lib/lp/app/templates/base-layout-macros.pt (+2/-0)
lib/lp/translations/browser/pofile.py (+22/-2)
lib/lp/translations/browser/translationmessage.py (+22/-0)
lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt (+2/-2)
lib/lp/translations/stories/standalone/xx-pofile-translate.txt (+2/-1)
lib/lp/translations/templates/currenttranslationmessage-translate-one.pt (+1/-6)
lib/lp/translations/templates/pofile-translate.pt (+12/-46)
lib/lp/translations/templates/translationmessage-translate.pt (+8/-2)
lib/lp/translations/templates/translations-macros.pt (+9/-0)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-359180
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+16422@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (3.4 KiB)

= Bug 359180 =
Would be useful if the First - Previous - Next - Last link in the translation interface had a keyboard shortcut for fast accessing them. Don't need to be visible, but at least documented somewhere.

Maybe even the Save & Continue button.

== Pre-implementation notes ==

Talking with Danilo we agreed not to use tabindex as it may disturb screen readers.

HTML Accesskeys were used for links, while YUI3 key-event here used for navigation between input fields and copying the original text.

== Implementation details ==

Shift+Alt+a - First (using accesskey)
Shift+Alt+n - Next (using accesskey)
Shift+Alt+p - Previous (using accesskey)
Shift+Alt+l - Last (using accesskey)
Shift+Alt+S - Save and continue (using accesskey)
Shift+Alt+Down - Next field (using YUI3 key-event)
Shift+Alt+Up - Previous field (using YUI3 key-event)
Shift+Alt+C - Copy original text (both singular and plural) (using YUI3 key-event)

First field is autofocused.

I went for Shift+Alt as they are used for Acceskey, and I assume browsers will try to reserve them.

Once keybindings are agreed they should be documented on help.launchpad.net

Accesskeys were tested on Epiphany and Chromium.
Wikipedia says that Accesskeys in Firefox are enabled with Shift+Alt but I was not able to activate them on my Firefox.
http://en.wikipedia.org/wiki/Access_key

-----

The legacy JS code from canonical/launchpad/icing/build/lp/lp.js and from pofile-tranlate.pt was moved to javascript/translations/pofile.js.

Those functions from lp.js was rewritten using YUI3. Maybe we can no delete them from lp.js.

== Tests ==
I was not able to produce a reasonable windmill test since it is not trivial to find the current focused node or to see if a node is focused.
This requires adding onFocus and onBlur trigger for all DOM nodes.

== Demo and Q/A ==
Log in as admin or as a person with rights on adding translations to a pofile.

Go to a pofile translate page:
https://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?start=0

The first field should have focus and you can start translating right away.

Adding a new translation should automatically select the radio button in front of it.

Pressing Alt + Shift + C will copy the original text. Current text will be discarded.

Pressing Alt + Shift + Down will set the focus to the next input field. Last field is Save and Continue button.

Pressing Alt + Shift + Up will set the focus to the previous input field. Nothing will happen when pressing on the first field.

Pressing Alt + Shift + A from anywhere in the page will point the browser to the fist page in the batch navigation.

Pressing Alt + Shift + L from anywhere in the page will point the browser to the last page in the batch navigation.

Pressing Alt + Shift + N from anywhere in the page will point the browser to the next page in the batch navigation.

Pressing Alt + Shift + P from anywhere in the page will point the browser to the previous page in the batch navigation.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canon...

Read more...

Revision history for this message
Aaron Bentley (abentley) wrote :

This looks mostly good. Please rename tabindex_chain to something else (we discussed "translations_order") since it doesn't affect tabbing.

The current interaction of autofocus_html_id and Y.lp.pofile.initializeKeyBindings means that javascript errors will happen if no autofocus_html_id is present. Please fix this (e.g. by addding empty variables, as you suggested).

review: Needs Fixing (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (6.0 KiB)

Thanks for the review!

Here is the latest diff.

=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
--- lib/canonical/launchpad/javascript/translations/pofile.js 2009-12-21 11:36:04 +0000
+++ lib/canonical/launchpad/javascript/translations/pofile.js 2009-12-22 07:44:31 +0000
@@ -86,7 +86,7 @@
 };

-var setFocus = function(e, field) {
+self.setFocus = function(field) {
     //Y.log(e.type + ":" + e.keyCode + ': ' + field);
     // if there is nofield, do nothing
     if (Y.one('#' + field)) {
@@ -96,7 +96,7 @@

 var setNextFocus = function(e, field) {
- setFocus(e,field);
+ self.setFocus(field);
     // stopPropagation() and preventDefault()
     e.halt();
 };
@@ -106,8 +106,8 @@

     // Original singular test is focused first to make sure
     // it is visible when scrolling up
- setFocus(e, original);
- setFocus(e, field);
+ self.setFocus(original);
+ self.setFocus(field);
     // stopPropagation() and preventDefault()
     e.halt();
 };
@@ -116,7 +116,6 @@
 var copyOriginalTextOne = function(from_id, to_id, select_id) {
     var from = Y.one('#' + from_id);
     var to = Y.one('#' + to_id);
-
     // The replacement regex strips all tags from the html.
     to.set('value', unescapeHTML(
         from.get('innerHTML').replace(/<\/?[^>]+>/gi, "")));
@@ -182,7 +181,13 @@
  */
 self.initializeKeyBindings = function(e) {

- var fields = tabindex_chain.split(' ');
+ 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');

@@ -192,7 +197,7 @@

         var html_parts = fields[key].split('_');
         var original_stem = html_parts[0] + '_' + html_parts[1];
- var translation_stem = original_stem + '_' + html_parts[2];
+ var translation_stem = fields[key].replace(/_translation_(\d)+_new/,"");
         var select_widget = (
             translation_stem + '_' + html_parts[3] + '_' +
             html_parts[4] + '_new_select');

=== modified file 'lib/lp/translations/browser/pofile.py'
--- lib/lp/translations/browser/pofile.py 2009-12-20 11:12:16 +0000
+++ lib/lp/translations/browser/pofile.py 2009-12-22 06:16:13 +0000
@@ -935,17 +935,17 @@
             first_field = first_message.translation_dictionaries[0]
             return first_field['html_id_translation']
         except IndexError:
- return False
+ return ""

     @property
- def tabindex_chain(self):
+ def translations_order(self):
         try:
- tabindex = []
+ order = []
             for message in self.translationmessage_views:
                 for dictionary in message.translation_dictionaries:
- tabindex.append(
+ order.append(
                         dictionary['html_id_translation'] + '_new')
- return ' '.join(tabindex)
+ return ' '.join(order)

         except IndexError:
             return ""

=== modified file 'lib/lp/translations/b...

Read more...

Revision history for this message
Aaron Bentley (abentley) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
2--- lib/canonical/launchpad/javascript/translations/pofile.js 2009-11-23 19:29:02 +0000
3+++ lib/canonical/launchpad/javascript/translations/pofile.js 2009-12-25 11:06:20 +0000
4@@ -1,7 +1,7 @@
5 /** Copyright (c) 2009, Canonical Ltd. All rights reserved.
6 *
7 * @module lp.pofile
8- * @requires event, node
9+ * @requires event, event-key, node, cookie, anim
10 */
11
12 YUI.add('lp.pofile', function(Y) {
13@@ -41,4 +41,196 @@
14 }
15 };
16
17-}, "0.1", {"requires": ["event", "node"]});
18+var hide_notification = function(node) {
19+ var hide_anim = new Y.Anim({
20+ node: node,
21+ to: { height: 0,
22+ marginTop: 0, marginBottom: 0,
23+ paddingTop: 0, paddingBottom: 0 }
24+ });
25+ node.setStyle('border', 'none');
26+ hide_anim.set('duration', 0.4);
27+ hide_anim.on('end', function(e) {
28+ node.setStyle('display', 'none');
29+ });
30+ hide_anim.run();
31+};
32+
33+
34+self.updateNotificationBox = function(e) {
35+ var notice = Y.one('.important-notice-container');
36+ if (notice === null) {
37+ return;
38+ }
39+ var balloon = notice.one('.important-notice-balloon');
40+ var dismiss_notice_cookie = ('translation-docs-for-' +
41+ documentation_cookie);
42+
43+ // Check the cookie to see if the user has already dismissed
44+ // the notification box for this session.
45+ var already_seen = Y.Cookie.get(dismiss_notice_cookie, Boolean);
46+ if (already_seen) {
47+ notice.setStyle('display', 'none');
48+ }
49+
50+ var cancel_button = notice.one(
51+ '.important-notice-cancel-button');
52+ // Cancel button starts out hidden. If user has JavaScript,
53+ // then we want to show it.
54+ cancel_button.setStyle('visibility', 'visible');
55+ cancel_button.on('click', function(e) {
56+ e.halt();
57+ hide_notification(balloon);
58+ Y.Cookie.set(dismiss_notice_cookie, true);
59+ });
60+};
61+
62+
63+self.setFocus = function(field) {
64+ //Y.log(e.type + ":" + e.keyCode + ': ' + field);
65+ // if there is nofield, do nothing
66+ if (Y.one('#' + field)) {
67+ Y.one('#' + field).focus();
68+ }
69+};
70+
71+
72+var setNextFocus = function(e, field) {
73+ self.setFocus(field);
74+ // stopPropagation() and preventDefault()
75+ e.halt();
76+};
77+
78+
79+var setPreviousFocus = function(e, field, original) {
80+
81+ // Original singular test is focused first to make sure
82+ // it is visible when scrolling up
83+ self.setFocus(original);
84+ self.setFocus(field);
85+ // stopPropagation() and preventDefault()
86+ e.halt();
87+};
88+
89+
90+var copyOriginalTextOne = function(from_id, to_id, select_id) {
91+ var from = Y.one('#' + from_id);
92+ var to = Y.one('#' + to_id);
93+ // The replacement regex strips all tags from the html.
94+ to.set('value', unescapeHTML(
95+ from.get('innerHTML').replace(/<\/?[^>]+>/gi, "")));
96+ selectWidget(select_id);
97+};
98+
99+
100+var copyOriginalTextPlural = function (from_id,
101+ to_id_pattern, nplurals) {
102+ // skip when x is 0, as that is the singular
103+ for (var x = 1; x < nplurals; x++) {
104+ var to_id = to_id_pattern + x + "_new";
105+ var to_select = to_id_pattern + x + "_new_select";
106+ copyOriginalTextOne(from_id, to_id, to_select);
107+ }
108+};
109+
110+
111+var copyOriginalTextAll = function(e, original_stem, translation_stem) {
112+
113+ var original_singular = original_stem + '_singular';
114+ var original_plural = original_stem + '_plural';
115+ var singular_select = translation_stem + '_translation_0_new_select';
116+ var translation_singular = translation_stem + '_translation_0_new';
117+ var translation_plural = translation_stem + '_translation_';
118+ //Y.log(e.type + ":" + e.keyCode + ': ' + singular_select);
119+ // Copy singular text
120+ copyOriginalTextOne(
121+ original_singular, translation_singular, singular_select);
122+
123+ // Copy plural text if needed
124+ if (Y.one('#' + translation_plural + '1')) {
125+ copyOriginalTextPlural(
126+ original_plural, translation_plural, plural_forms);
127+ }
128+ // stopPropagation() and preventDefault()
129+ e.halt();
130+};
131+
132+
133+var selectWidget = function(widget) {
134+ if (Y.one('#' + widget)) {
135+ Y.one('#' + widget).set('checked', true);
136+ }
137+};
138+
139+
140+var selectTranslation = function(e, widget) {
141+ //Y.log(e.type + ":" + e.keyCode + ': ' + widget);
142+ // Don't select when tabbing, navigating Up or Down and simply pressing
143+ // enter to submit the form.
144+ if (e.keyCode == 9 || e.keyCode == 13 ||
145+ e.keyCode == 40 || e.keyCode == 38) {
146+ return;
147+ }
148+ selectWidget(widget);
149+};
150+
151+
152+/**
153+ * Initialize event-key bindings such as moving to the next or previous
154+ * field, or copying original text
155+ */
156+self.initializeKeyBindings = function(e) {
157+
158+ if (translations_order.length < 1) {
159+ // If no translations fiels are displayed on the page
160+ // don't initialize the translations order
161+ return;
162+ }
163+
164+ var fields = translations_order.split(' ');
165+ // The last field is Save & Continue button
166+ fields.push('save_and_continue_button');
167+
168+ for (var key = 0; key < fields.length; key++) {
169+ var next = key + 1;
170+ var previous = key - 1;
171+
172+ var html_parts = fields[key].split('_');
173+ var original_stem = html_parts[0] + '_' + html_parts[1];
174+ var translation_stem = fields[key].replace(/_translation_(\d)+_new/,"");
175+ var select_widget = (
176+ translation_stem + '_' + html_parts[3] + '_' +
177+ html_parts[4] + '_new_select');
178+
179+ Y.on(
180+ 'change', selectTranslation,
181+ '#' + fields[key], Y, select_widget);
182+ Y.on(
183+ 'keypress', selectTranslation,
184+ '#' + fields[key], Y, select_widget);
185+
186+ // Set next field and copy text for all but last field
187+ // (last is Save & Continue button)
188+ if (key < fields.length - 1) {
189+ Y.on(
190+ 'key', setNextFocus, '#' + fields[key],
191+ 'down:40+shift+alt', Y, fields[next]);
192+ Y.on(
193+ 'key', copyOriginalTextAll, '#' + fields[key],
194+ 'down:67+shift+alt', Y, original_stem, translation_stem);
195+ }
196+
197+ // Set previous field for all but first field
198+ if (key > 0) {
199+ var parts = fields[previous].split('_');
200+ var singular_copy_text = (
201+ parts[0] + '_' + parts[1] + '_singular_copy_text');
202+ Y.on(
203+ 'key', setPreviousFocus, '#' + fields[key],
204+ 'down:38+shift+alt', Y, fields[previous],
205+ singular_copy_text);
206+ }
207+ }
208+};
209+
210+}, "0.1", {"requires": ["event", "event-key", "node", 'cookie', 'anim']});
211
212=== modified file 'lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt'
213--- lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt 2009-07-17 17:59:07 +0000
214+++ lib/canonical/launchpad/templates/batchnavigator-navigation-links.pt 2009-12-25 11:06:20 +0000
215@@ -29,6 +29,7 @@
216 tal:attributes="href first_page_url"
217 class="first"
218 rel="first"
219+ accesskey="A"
220 >First</a>
221 <span tal:condition="not:first_page_url" class="first inactive"
222 >First</span>
223@@ -38,6 +39,7 @@
224 tal:attributes="href prev_page_url"
225 class="previous"
226 rel="previous"
227+ accesskey="P"
228 >Previous</a>
229 <span tal:condition="not:prev_page_url" class="previous inactive"
230 >Previous</span>
231@@ -47,6 +49,7 @@
232 tal:attributes="href next_page_url"
233 class="next"
234 rel="next"
235+ accesskey="N"
236 ><strong>Next</strong></a>
237 <span tal:condition="not:next_page_url" class="next inactive"
238 ><strong>Next</strong></span>
239@@ -57,6 +60,7 @@
240 tal:attributes="href last_page_url"
241 class="last"
242 rel="last"
243+ accesskey="L"
244 >Last</a>
245 <span tal:condition="not:last_page_url" class="last inactive"
246 >Last</span>
247
248=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
249--- lib/lp/app/templates/base-layout-macros.pt 2009-12-16 19:59:06 +0000
250+++ lib/lp/app/templates/base-layout-macros.pt 2009-12-25 11:06:20 +0000
251@@ -80,6 +80,8 @@
252 <script type="text/javascript"
253 tal:attributes="src string:${yui}/event/event.js"></script>
254 <script type="text/javascript"
255+ tal:attributes="src string:${yui}/event/event-key.js"></script>
256+ <script type="text/javascript"
257 tal:attributes="src string:${yui}/event-custom/event-custom.js"></script>
258 <script type="text/javascript"
259 tal:attributes="src string:${yui}/event-simulate/event-simulate.js"></script>
260
261=== modified file 'lib/lp/translations/browser/pofile.py'
262--- lib/lp/translations/browser/pofile.py 2009-09-17 19:05:29 +0000
263+++ lib/lp/translations/browser/pofile.py 2009-12-25 11:06:20 +0000
264@@ -503,8 +503,6 @@
265
266 DEFAULT_BATCH_SIZE = 50
267
268- page_title = "Contributions"
269-
270 @property
271 def _person_name(self):
272 """Person's display name. Graceful about unknown persons."""
273@@ -930,6 +928,28 @@
274 def completeness(self):
275 return '%.0f%%' % self.context.translatedPercentage()
276
277+ @property
278+ def autofocus_html_id(self):
279+ try:
280+ first_message = self.translationmessage_views[0]
281+ first_field = first_message.translation_dictionaries[0]
282+ return first_field['html_id_translation']
283+ except IndexError:
284+ return ""
285+
286+ @property
287+ def translations_order(self):
288+ try:
289+ order = []
290+ for message in self.translationmessage_views:
291+ for dictionary in message.translation_dictionaries:
292+ order.append(
293+ dictionary['html_id_translation'] + '_new')
294+ return ' '.join(order)
295+
296+ except IndexError:
297+ return ""
298+
299
300 class POExportView(BaseExportView):
301
302
303=== modified file 'lib/lp/translations/browser/translationmessage.py'
304--- lib/lp/translations/browser/translationmessage.py 2009-09-17 08:41:05 +0000
305+++ lib/lp/translations/browser/translationmessage.py 2009-12-25 11:06:20 +0000
306@@ -829,6 +829,28 @@
307 self._redirectToNextPage()
308 return True
309
310+ @property
311+ def autofocus_html_id(self):
312+ try:
313+ first_message = self.translationmessage_view
314+ first_field = first_message.translation_dictionaries[0]
315+ return first_field['html_id_translation']
316+ except IndexError:
317+ return ""
318+
319+ @property
320+ def translations_order(self):
321+ try:
322+ order = []
323+ message = self.translationmessage_view
324+ for dictionary in message.translation_dictionaries:
325+ order.append(dictionary['html_id_translation'] + '_new')
326+ return ' '.join(order)
327+
328+ except IndexError:
329+ return ""
330+
331+
332 class CurrentTranslationMessageView(LaunchpadView):
333 """Holds all data needed to show an ITranslationMessage.
334
335
336=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt'
337--- lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt 2009-07-01 20:45:39 +0000
338+++ lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt 2009-12-25 11:06:20 +0000
339@@ -43,7 +43,7 @@
340 >>> print browser.url
341 http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?start=19&batch=1
342 >>> print find_tag_by_id(browser.contents, 'msgset_149_es_translation_0_new')
343- <textarea ... name="msgset_149_es_translation_0_new" ...>
344+ <textarea ... name="msgset_149_es_translation_0_new"...>
345
346 foo
347
348@@ -63,7 +63,7 @@
349 >>> print find_tag_by_id(
350 ... browser.contents,
351 ... 'msgset_149_es_translation_0_new') #doctest: -NORMALIZE_WHITESPACE
352- <textarea ... name="msgset_149_es_translation_0_new" ...>
353+ <textarea ... name="msgset_149_es_translation_0_new"...>
354 foo</textarea>
355
356 Now, Check that even though the user forgot the trailing new line char,
357
358=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate.txt'
359--- lib/lp/translations/stories/standalone/xx-pofile-translate.txt 2009-12-07 18:42:21 +0000
360+++ lib/lp/translations/stories/standalone/xx-pofile-translate.txt 2009-12-25 11:06:20 +0000
361@@ -158,7 +158,7 @@
362 ... print id
363 msgset_130
364 ...
365- msgset_130_singular
366+ msgset_130_singular...
367
368 HTML element identifiers for suggestions and translations on this form are
369 constructed as an underscore-separated sequence of:
370@@ -181,6 +181,7 @@
371 msgset_130_es_suggestion_562_0_origin
372 msgset_130_es_suggestion_562_0_radiobutton
373 msgset_130_singular
374+ msgset_130_singular_copy_text
375
376 Radio buttons are grouped by their name attribute. The translate page shows
377 each translatable message with one radiobutton to select the existing
378
379=== modified file 'lib/lp/translations/templates/currenttranslationmessage-translate-one.pt'
380--- lib/lp/translations/templates/currenttranslationmessage-translate-one.pt 2009-12-07 18:42:21 +0000
381+++ lib/lp/translations/templates/currenttranslationmessage-translate-one.pt 2009-12-25 11:06:20 +0000
382@@ -42,6 +42,7 @@
383 <a href=""
384 tal:condition="not:context/potmsgset/is_translation_credit"
385 tal:attributes="
386+ id string:${view/html_id}_singular_copy_text;
387 onClick string:
388 javascript:copyInnerHTMLById(
389 '${view/html_id}_singular',
390@@ -504,8 +505,6 @@
391 lang context/pofile/language/dashedcode;
392 dir context/pofile/language/abbreviated_text_dir;
393 value translation_dictionary/submitted_translation;
394- onKeyPress string:javascript:selectWidget('${translation_dictionary/html_id_translation}_new_select', event);
395- onChange string:javascript:selectWidget('${translation_dictionary/html_id_translation}_new_select', event);
396 "
397 class="translate expandable"
398 />
399@@ -528,8 +527,6 @@
400 name string:${translation_dictionary/html_id_translation}_new;
401 lang context/pofile/language/dashedcode;
402 dir context/pofile/language/abbreviated_text_dir;
403- onKeyPress string:javascript:selectWidget('${translation_dictionary/html_id_translation}_new_select', event);
404- onChange string:javascript:selectWidget('${translation_dictionary/html_id_translation}_new_select', event);
405 ">
406 <tal:content replace="translation_dictionary/submitted_translation" /></textarea>
407 </tal:with-content>
408@@ -548,8 +545,6 @@
409 name string:${translation_dictionary/html_id_translation}_new;
410 lang context/pofile/language/dashedcode;
411 dir context/pofile/language/abbreviated_text_dir;
412- onKeyPress string:javascript:selectWidget('${translation_dictionary/html_id_translation}_new_select', event);
413- onChange string:javascript:selectWidget('${translation_dictionary/html_id_translation}_new_select', event);
414 "></textarea>
415 </tal:without-content>
416 </tal:multi-line>
417
418=== modified file 'lib/lp/translations/templates/pofile-translate.pt'
419--- lib/lp/translations/templates/pofile-translate.pt 2009-12-10 13:08:33 +0000
420+++ lib/lp/translations/templates/pofile-translate.pt 2009-12-25 11:06:20 +0000
421@@ -17,53 +17,16 @@
422 tal:condition="devmode"
423 tal:define="lp_js string:${icingroot}/build"
424 tal:attributes="src string:${lp_js}/translations/pofile.js"></script>
425+
426 <script type="text/javascript">
427 registerLaunchpadFunction(insertAllExpansionButtons);
428-
429- LPS.use('node', 'cookie', 'anim', 'lp.pofile', function(Y) {
430-
431- var hide_notification = function(node) {
432- var hide_anim = new Y.Anim({
433- node: node,
434- to: { height: 0,
435- marginTop: 0, marginBottom: 0,
436- paddingTop: 0, paddingBottom: 0 }
437- });
438- node.setStyle('border', 'none');
439- hide_anim.set('duration', 0.4);
440- hide_anim.on('end', function(e) {
441- node.setStyle('display', 'none');
442- });
443- hide_anim.run();
444- }
445-
446- var updateNotificationBox = function(e) {
447- var notice = Y.one('.important-notice-container');
448- var balloon = notice.one('.important-notice-balloon');
449- var dismiss_notice_cookie = ('translation-docs-for-' +
450- documentation_cookie);
451-
452- // Check the cookie to see if the user has already dismissed
453- // the notification box for this session.
454- var already_seen = Y.Cookie.get(dismiss_notice_cookie, Boolean);
455- if (already_seen) {
456- notice.setStyle('display', 'none');
457- }
458-
459- var cancel_button = notice.one(
460- '.important-notice-cancel-button');
461- // Cancel button starts out hidden. If user has JavaScript,
462- // then we want to show it.
463- cancel_button.setStyle('visibility', 'visible');
464- cancel_button.on('click', function(e) {
465- e.halt();
466- hide_notification(balloon);
467- Y.Cookie.set(dismiss_notice_cookie, true);
468- });
469- };
470-
471+ LPS.use('lp.pofile', function(Y) {
472 Y.on('domready', Y.lp.pofile.setupSuggestionDismissal);
473- Y.on('domready', updateNotificationBox);
474+ Y.on('domready', Y.lp.pofile.initializeKeyBindings);
475+ Y.on('domready', Y.lp.pofile.updateNotificationBox);
476+ Y.on('domready', function(e) {
477+ Y.lp.pofile.setFocus(autofocus_field);
478+ });
479 });
480 </script>
481 </div>
482@@ -134,7 +97,7 @@
483 <input type="hidden" name="batch" value=""
484 tal:attributes="value view/size" />
485 <input type="hidden" name="show" value="all" />
486- <label for="search_box">Search:</label>
487+ <label for="search_box" accesskey="F">Search:</label>
488 <input id="search_box" type="text" name="search"
489 title="Enter text to search for"
490 tal:attributes="value view/search_text" />
491@@ -262,6 +225,8 @@
492 <td style="text-align: right;">
493 <input type="submit"
494 name="submit_translations"
495+ accesskey="S"
496+ id="save_and_continue_button"
497 value="Save &amp; Continue"
498 />
499 </td>
500@@ -269,7 +234,6 @@
501 </tbody>
502 </table>
503 </form>
504-
505 <!-- Paging doodads. -->
506 <tal:navigation
507 replace="structure view/batchnav/@@+navigation-links-lower" />
508@@ -277,6 +241,8 @@
509 <tal:status replace="structure context/@@+access" />
510 <tal:contributors replace="structure context/@@+contributors" />
511 </tal:havepluralforms>
512+ <metal:pofile-js-footer
513+ use-macro="context/@@+translations-macros/pofile-js-footer" />
514 </div>
515 </body>
516 </html>
517
518=== modified file 'lib/lp/translations/templates/translationmessage-translate.pt'
519--- lib/lp/translations/templates/translationmessage-translate.pt 2009-12-10 13:08:33 +0000
520+++ lib/lp/translations/templates/translationmessage-translate.pt 2009-12-25 11:06:20 +0000
521@@ -20,6 +20,10 @@
522 <script type="text/javascript">
523 LPS.use('node', 'lp.pofile', function(Y) {
524 Y.on('domready', Y.lp.pofile.setupSuggestionDismissal);
525+ Y.on('domready', Y.lp.pofile.initializeKeyBindings);
526+ Y.on('domready', function(e) {
527+ Y.one('#' + autofocus_field).focus();
528+ });
529 });
530 </script>
531 </div>
532@@ -76,12 +80,13 @@
533 <th colspan="5" style="text-align: right;">
534 <input type="submit"
535 name="submit_translations"
536+ accesskey="S"
537+ id="save_and_continue_button"
538 value="Save &amp; Continue" />
539 </th>
540 </tr>
541 </tfoot>
542 </table>
543-
544 </form>
545
546 <!-- Paging doodads. -->
547@@ -89,8 +94,9 @@
548 replace="structure view/batchnav/@@+navigation-links-lower" />
549 <tal:status replace="structure context/pofile/@@+access" />
550 </tal:havepluralforms>
551+ <metal:pofile-js-footer
552+ use-macro="context/@@+translations-macros/pofile-js-footer" />
553 </div>
554-
555 </body>
556
557 </html>
558
559=== modified file 'lib/lp/translations/templates/translations-macros.pt'
560--- lib/lp/translations/templates/translations-macros.pt 2009-12-07 18:42:21 +0000
561+++ lib/lp/translations/templates/translations-macros.pt 2009-12-25 11:06:20 +0000
562@@ -129,6 +129,15 @@
563 </ul>
564 </metal:nav-pofile-subpages>
565
566+<metal:pofile-js-footer define-macro="pofile-js-footer">
567+ <script type="text/javascript"
568+ tal:content="
569+ structure string:<!--
570+ var autofocus_field = '${view/autofocus_html_id}_new';
571+ var translations_order = '${view/translations_order}';
572+ var plural_forms = ${context/plural_forms};
573+ // -->" />
574+</metal:pofile-js-footer>
575
576 </tal:root>
577