Merge lp:~adiroiban/launchpad/bug-359180 into lp:launchpad
- bug-359180
- Merge into devel
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+16422@code.launchpad.net |
Commit message
Description of the change
Adi Roiban (adiroiban) wrote : | # |
Aaron Bentley (abentley) wrote : | # |
This looks mostly good. Please rename tabindex_chain to something else (we discussed "translations_
The current interaction of autofocus_html_id and Y.lp.pofile.
Adi Roiban (adiroiban) wrote : | # |
Thanks for the review!
Here is the latest diff.
=== modified file 'lib/canonical/
--- lib/canonical/
+++ lib/canonical/
@@ -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(
// 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(
+ self.setFocus(
// 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.
@@ -182,7 +181,13 @@
*/
self.initializ
- var fields = tabindex_
+ if (translations_
+ // If no translations fiels are displayed on the page
+ // don't initialize the translations order
+ return;
+ }
+
+ var fields = translations_
// The last field is Save & Continue button
fields.
@@ -192,7 +197,7 @@
var html_parts = fields[
var original_stem = html_parts[0] + '_' + html_parts[1];
- var translation_stem = original_stem + '_' + html_parts[2];
+ var translation_stem = fields[
var select_widget = (
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -935,17 +935,17 @@
return first_field[
except IndexError:
- return False
+ return ""
@property
- def tabindex_
+ def translations_
try:
- tabindex = []
+ order = []
for message in self.translatio
- tabindex.append(
+ order.append(
- return ' '.join(tabindex)
+ return ' '.join(order)
except IndexError:
return ""
=== modified file 'lib/lp/
Aaron Bentley (abentley) : | # |
Preview Diff
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 & 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 & 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 |
= 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. en.wikipedia. org/wiki/ Access_ key
Wikipedia says that Accesskeys in Firefox are enabled with Shift+Alt but I was not able to activate them on my Firefox.
http://
-----
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: /translations. launchpad. dev/ubuntu/ hoary/+ source/ evolution/ +pots/evolution -2.2/es/ +translate? start=0
https:/
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...