Merge lp:~benji/launchpad/better-HTML-generation into lp:launchpad
- better-HTML-generation
- Merge into devel
Proposed by
Benji York
Status: | Merged |
---|---|
Approved by: | Benji York |
Approved revision: | no longer in the source branch. |
Merged at revision: | 12731 |
Proposed branch: | lp:~benji/launchpad/better-HTML-generation |
Merge into: | lp:launchpad |
Diff against target: |
930 lines (+311/-290) 2 files modified
lib/lp/registry/javascript/structural-subscription.js (+301/-281) lib/lp/registry/javascript/tests/test_structural_subscription.js (+10/-9) |
To merge this branch: | bzr merge lp:~benji/launchpad/better-HTML-generation |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | code | Approve | |
Review via email: mp+55960@code.launchpad.net |
Commit message
[r=bac][no-qa] Clean up HTML generation, primarily to avoid XSS.
Description of the change
This branch fixes the way structural subscription JavaScript constructs HTML, moving away from string concatenation to Y.Node.create() and friends.
There's also a fair bit of lint fixing required to quite JSLint.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/registry/javascript/structural-subscription.js' | |||
2 | --- lib/lp/registry/javascript/structural-subscription.js 2011-03-30 15:23:42 +0000 | |||
3 | +++ lib/lp/registry/javascript/structural-subscription.js 2011-04-01 17:57:54 +0000 | |||
4 | @@ -11,9 +11,6 @@ | |||
5 | 11 | 11 | ||
6 | 12 | var namespace = Y.namespace('lp.registry.structural_subscription'); | 12 | var namespace = Y.namespace('lp.registry.structural_subscription'); |
7 | 13 | 13 | ||
8 | 14 | var INNER_HTML = 'innerHTML', | ||
9 | 15 | VALUE = 'value'; | ||
10 | 16 | |||
11 | 17 | var FILTER_COMMENTS = 'filter-comments', | 14 | var FILTER_COMMENTS = 'filter-comments', |
12 | 18 | FILTER_WRAPPER = 'filter-wrapper', | 15 | FILTER_WRAPPER = 'filter-wrapper', |
13 | 19 | ACCORDION_WRAPPER = 'accordion-wrapper', | 16 | ACCORDION_WRAPPER = 'accordion-wrapper', |
14 | @@ -21,8 +18,7 @@ | |||
15 | 21 | ADDED_OR_CHANGED = 'added-or-changed', | 18 | ADDED_OR_CHANGED = 'added-or-changed', |
16 | 22 | ADVANCED_FILTER = 'advanced-filter', | 19 | ADVANCED_FILTER = 'advanced-filter', |
17 | 23 | MATCH_ALL = 'match-all', | 20 | MATCH_ALL = 'match-all', |
20 | 24 | MATCH_ANY = 'match-any', | 21 | MATCH_ANY = 'match-any' |
19 | 25 | SS_COLLAPSIBLE = 'ss-collapsible' | ||
21 | 26 | ; | 22 | ; |
22 | 27 | 23 | ||
23 | 28 | var add_subscription_overlay; | 24 | var add_subscription_overlay; |
24 | @@ -61,7 +57,7 @@ | |||
25 | 61 | function list_contains(list, target) { | 57 | function list_contains(list, target) { |
26 | 62 | // The list may be undefined in some cases. | 58 | // The list may be undefined in some cases. |
27 | 63 | return Y.Lang.isArray(list) && list.indexOf(target) !== -1; | 59 | return Y.Lang.isArray(list) && list.indexOf(target) !== -1; |
29 | 64 | }; | 60 | } |
30 | 65 | 61 | ||
31 | 66 | // Expose to tests. | 62 | // Expose to tests. |
32 | 67 | namespace._list_contains = list_contains; | 63 | namespace._list_contains = list_contains; |
33 | @@ -209,8 +205,9 @@ | |||
34 | 209 | function save_subscription(form_data) { | 205 | function save_subscription(form_data) { |
35 | 210 | var who; | 206 | var who; |
36 | 211 | var has_errors = check_for_errors_in_overlay(add_subscription_overlay); | 207 | var has_errors = check_for_errors_in_overlay(add_subscription_overlay); |
38 | 212 | if (has_errors) | 208 | if (has_errors) { |
39 | 213 | return false; | 209 | return false; |
40 | 210 | } | ||
41 | 214 | if (form_data.recipient[0] === 'user') { | 211 | if (form_data.recipient[0] === 'user') { |
42 | 215 | who = LP.links.me; | 212 | who = LP.links.me; |
43 | 216 | } else { | 213 | } else { |
44 | @@ -223,15 +220,18 @@ | |||
45 | 223 | 220 | ||
46 | 224 | function check_for_errors_in_overlay(overlay) { | 221 | function check_for_errors_in_overlay(overlay) { |
47 | 225 | var has_errors = false; | 222 | var has_errors = false; |
53 | 226 | var errors = new Array(); | 223 | var errors = []; |
54 | 227 | for (var field in overlay.field_errors) { | 224 | var field; |
55 | 228 | if (overlay.field_errors[field]) { | 225 | for (field in overlay.field_errors) { |
56 | 229 | has_errors = true; | 226 | if (overlay.field_errors.hasOwnProperty(field)) { |
57 | 230 | errors.push(field); | 227 | if (overlay.field_errors[field]) { |
58 | 228 | has_errors = true; | ||
59 | 229 | errors.push(field); | ||
60 | 230 | } | ||
61 | 231 | } | 231 | } |
63 | 232 | }; | 232 | } |
64 | 233 | if (has_errors) { | 233 | if (has_errors) { |
66 | 234 | var error_text = errors.pop() | 234 | var error_text = errors.pop(); |
67 | 235 | if (errors.length > 0) { | 235 | if (errors.length > 0) { |
68 | 236 | error_text = errors.join(', ') + ' and ' + error_text; | 236 | error_text = errors.join(', ') + ' and ' + error_text; |
69 | 237 | } | 237 | } |
70 | @@ -242,40 +242,38 @@ | |||
71 | 242 | } else { | 242 | } else { |
72 | 243 | return false; | 243 | return false; |
73 | 244 | } | 244 | } |
75 | 245 | }; | 245 | } |
76 | 246 | 246 | ||
77 | 247 | /** | 247 | /** |
78 | 248 | * Handle the activation of the edit subscription link. | 248 | * Handle the activation of the edit subscription link. |
79 | 249 | */ | 249 | */ |
80 | 250 | function edit_subscription_handler(context, form_data) { | 250 | function edit_subscription_handler(context, form_data) { |
81 | 251 | var has_errors = check_for_errors_in_overlay(add_subscription_overlay); | 251 | var has_errors = check_for_errors_in_overlay(add_subscription_overlay); |
83 | 252 | if (has_errors) | 252 | var filter_id = '#filter-description-'+context.filter_id.toString(); |
84 | 253 | if (has_errors) { | ||
85 | 253 | return false; | 254 | return false; |
86 | 255 | } | ||
87 | 254 | var on = {success: function (new_data) { | 256 | var on = {success: function (new_data) { |
88 | 255 | var filter = new_data.getAttrs(); | 257 | var filter = new_data.getAttrs(); |
97 | 256 | var description_node = Y.one( | 258 | var description_node = Y.one(filter_id); |
98 | 257 | '#filter-description-'+context.filter_id.toString()); | 259 | description_node |
99 | 258 | description_node.set( | 260 | .empty() |
100 | 259 | INNER_HTML, render_filter_description(filter)); | 261 | .appendChild(create_filter_description(filter)); |
101 | 260 | var name_node = Y.one( | 262 | description_node.ancestor('.subscription-filter').one('.filter-name') |
102 | 261 | '#filter-name-'+context.filter_id.toString()); | 263 | .empty() |
103 | 262 | name_node.set( | 264 | .appendChild(render_filter_title(context.filter_info, filter)); |
96 | 263 | INNER_HTML, render_filter_name(context.filter_info, filter)); | ||
104 | 264 | add_subscription_overlay.hide(); | 265 | add_subscription_overlay.hide(); |
105 | 265 | }}; | 266 | }}; |
106 | 266 | patch_bug_filter(context.filter_info.filter, form_data, on); | 267 | patch_bug_filter(context.filter_info.filter, form_data, on); |
107 | 267 | } | 268 | } |
108 | 268 | 269 | ||
109 | 270 | /** | ||
110 | 271 | * Initialize the overlay errors and set up field validators. | ||
111 | 272 | */ | ||
112 | 269 | function setup_overlay_validators(overlay, overlay_id) { | 273 | function setup_overlay_validators(overlay, overlay_id) { |
113 | 270 | overlay.field_validators = { | ||
114 | 271 | tags: get_error_for_tags_list | ||
115 | 272 | }; | ||
116 | 273 | overlay.field_errors = {}; | 274 | overlay.field_errors = {}; |
122 | 274 | for (var field_name in overlay.field_validators) { | 275 | add_input_validator(overlay, overlay_id, 'tags', get_error_for_tags_list); |
123 | 275 | add_input_validator(overlay, overlay_id, field_name, | 276 | } |
119 | 276 | overlay.field_validators[field_name]); | ||
120 | 277 | }; | ||
121 | 278 | }; | ||
124 | 279 | 277 | ||
125 | 280 | /** | 278 | /** |
126 | 281 | * Populate the overlay element with the contents of the add/edit form. | 279 | * Populate the overlay element with the contents of the add/edit form. |
127 | @@ -295,8 +293,9 @@ | |||
128 | 295 | form_submit_callback: function(formdata) { | 293 | form_submit_callback: function(formdata) { |
129 | 296 | // Do not clean up if saving was not successful. | 294 | // Do not clean up if saving was not successful. |
130 | 297 | var save_succeeded = submit_callback(formdata); | 295 | var save_succeeded = submit_callback(formdata); |
132 | 298 | if (save_succeeded !== false) | 296 | if (save_succeeded !== false) { |
133 | 299 | clean_up(); | 297 | clean_up(); |
134 | 298 | } | ||
135 | 300 | } | 299 | } |
136 | 301 | }); | 300 | }); |
137 | 302 | add_subscription_overlay.render(content_box_id); | 301 | add_subscription_overlay.render(content_box_id); |
138 | @@ -373,10 +372,14 @@ | |||
139 | 373 | * @param {String} name Name of the control. | 372 | * @param {String} name Name of the control. |
140 | 374 | */ | 373 | */ |
141 | 375 | function make_cell(item, name) { | 374 | function make_cell(item, name) { |
146 | 376 | return '<td style="padding-left:3px"><label><input type="checkbox" ' + | 375 | var cell = Y.Node.create('<td style="padding-left:3px"><td>'); |
147 | 377 | 'name="' + name +'" ' + | 376 | cell.appendChild('<label></label>') |
148 | 378 | 'value="' + item + '" checked="checked">' + | 377 | .append(Y.Node.create('<input type="checkbox" checked></input>') |
149 | 379 | item + '</label><td>'; | 378 | .set('name', name) |
150 | 379 | .set('value', item)) | ||
151 | 380 | .append(Y.Node.create('<span></span>') | ||
152 | 381 | .set('text', item)); | ||
153 | 382 | return cell; | ||
154 | 380 | } | 383 | } |
155 | 381 | /** | 384 | /** |
156 | 382 | * Make a table. | 385 | * Make a table. |
157 | @@ -388,19 +391,15 @@ | |||
158 | 388 | * @param {Int} num_cols The number of columns for the table to use. | 391 | * @param {Int} num_cols The number of columns for the table to use. |
159 | 389 | */ | 392 | */ |
160 | 390 | function make_table(list, name, num_cols) { | 393 | function make_table(list, name, num_cols) { |
163 | 391 | var html = '<table>'; | 394 | var table = Y.Node.create('<table></table>'); |
164 | 392 | var i; | 395 | var i, row; |
165 | 393 | for (i=0; i<list.length; i++) { | 396 | for (i=0; i<list.length; i++) { |
166 | 394 | if (i % num_cols === 0) { | 397 | if (i % num_cols === 0) { |
171 | 395 | if (i !== 0) { | 398 | row = table.appendChild('<tr></tr>'); |
168 | 396 | html += '</tr>'; | ||
169 | 397 | } | ||
170 | 398 | html += '<tr>'; | ||
172 | 399 | } | 399 | } |
174 | 400 | html += make_cell(list[i], name); | 400 | row.appendChild(make_cell(list[i], name)); |
175 | 401 | } | 401 | } |
178 | 402 | html += '</tr></table>'; | 402 | return table; |
177 | 403 | return html; | ||
179 | 404 | } | 403 | } |
180 | 405 | 404 | ||
181 | 406 | /** | 405 | /** |
182 | @@ -413,16 +412,18 @@ | |||
183 | 413 | * @return {Object} Hash with 'all_name', 'none_name', and 'html' keys. | 412 | * @return {Object} Hash with 'all_name', 'none_name', and 'html' keys. |
184 | 414 | */ | 413 | */ |
185 | 415 | function make_selector_controls(parent) { | 414 | function make_selector_controls(parent) { |
186 | 415 | var selectors_id = parent + '-selectors'; | ||
187 | 416 | var rv = {}; | 416 | var rv = {}; |
197 | 417 | rv.all_name = parent + '-select-all'; | 417 | rv.all_link = Y.Node.create( |
198 | 418 | rv.none_name = parent + '-select-none'; | 418 | '<a href="#" class="select-all">Select all</a>'); |
199 | 419 | rv.html = '<div id="'+ parent + '-selectors" '+ | 419 | rv.none_link = Y.Node.create( |
200 | 420 | 'style="margin-left: 10px;margin-bottom: 10px">' + | 420 | '<a href="#" class="select-none">Select none</a>'); |
201 | 421 | ' <a href="#" id="' + rv.all_name + | 421 | rv.node = Y.Node.create( |
202 | 422 | '">Select all</a> ' + | 422 | '<div style="margin-left: 10px;margin-bottom: 10px"></div>') |
203 | 423 | ' <a href="#" id="' + rv.none_name + | 423 | .set('id', selectors_id) |
204 | 424 | '">Select none</a>' + | 424 | .append(rv.all_link) |
205 | 425 | '</div>'; | 425 | .append(' ') |
206 | 426 | .append(rv.none_link); | ||
207 | 426 | 427 | ||
208 | 427 | return rv; | 428 | return rv; |
209 | 428 | } | 429 | } |
210 | @@ -474,27 +475,23 @@ | |||
211 | 474 | id: "tags_ai", | 475 | id: "tags_ai", |
212 | 475 | contentHeight: {method: "auto"} | 476 | contentHeight: {method: "auto"} |
213 | 476 | } ); | 477 | } ); |
214 | 477 | |||
215 | 478 | tags_container = tags_ai; | 478 | tags_container = tags_ai; |
227 | 479 | 479 | tags_ai.set("bodyContent", Y.Node.create('<div><div></div></div>') | |
228 | 480 | tags_ai.set("bodyContent", | 480 | .append(Y.Node.create('<input type="radio" name="tag_match" checked>') |
229 | 481 | '<div>\n' + | 481 | .set('value', MATCH_ALL)) |
230 | 482 | '<div>\n' + | 482 | .append('Match all tags') |
231 | 483 | ' <input type="radio" name="tag_match" value="' + | 483 | .append(Y.Node.create('<input type="radio" name="tag_match" checked>') |
232 | 484 | MATCH_ALL + '" checked> Match all tags\n' + | 484 | .set('value', MATCH_ANY)) |
233 | 485 | ' <input type="radio" name="tag_match" value="' + | 485 | .append('Match any tags') |
234 | 486 | MATCH_ANY + '"> Match any tags\n' + | 486 | .append( |
235 | 487 | '</div>\n' + | 487 | '<div style="padding-bottom:10px;">' + |
236 | 488 | '<div style="padding-bottom:10px;">\n' + | 488 | ' <input type="text" name="tags" size="60"/>' + |
226 | 489 | ' <input type="text" name="tags" size="60"/>\n' + | ||
237 | 490 | ' <a target="help"'+ | 489 | ' <a target="help"'+ |
238 | 491 | ' href="/+help/structural-subscription-tags.html" ' + | 490 | ' href="/+help/structural-subscription-tags.html" ' + |
239 | 492 | ' class="sprite maybe"> '+ | 491 | ' class="sprite maybe"> '+ |
240 | 493 | '<span class="invisible-link">Structural subscription tags '+ | 492 | '<span class="invisible-link">Structural subscription tags '+ |
245 | 494 | ' help</span></a>\n ' + | 493 | ' help</span></a> ' + |
246 | 495 | '</div>\n' + | 494 | '</div>')); |
243 | 496 | '</div>\n'); | ||
244 | 497 | |||
247 | 498 | accordion.addItem(tags_ai); | 495 | accordion.addItem(tags_ai); |
248 | 499 | 496 | ||
249 | 500 | // Build importances pane. | 497 | // Build importances pane. |
250 | @@ -507,20 +504,17 @@ | |||
251 | 507 | } ); | 504 | } ); |
252 | 508 | var importances = LP.cache.importances; | 505 | var importances = LP.cache.importances; |
253 | 509 | var selectors = make_selector_controls('importances'); | 506 | var selectors = make_selector_controls('importances'); |
259 | 510 | var importances_html = '<div id="importances-wrapper">' + | 507 | importances_ai.set("bodyContent", |
260 | 511 | selectors.html + | 508 | Y.Node.create('<div id="importances-wrapper"></div>') |
261 | 512 | make_table(importances, 'importances', 4) + | 509 | .append(selectors.node) |
262 | 513 | '</div>'; | 510 | .append(make_table(importances, 'importances', 4))); |
258 | 514 | importances_ai.set("bodyContent", importances_html); | ||
263 | 515 | accordion.addItem(importances_ai); | 511 | accordion.addItem(importances_ai); |
264 | 516 | // Wire up the 'all' and 'none' selectors. | 512 | // Wire up the 'all' and 'none' selectors. |
265 | 517 | var all_link = content_node.one('#' + selectors.all_name); | ||
266 | 518 | var none_link = Y.one('#' + selectors.none_name); | ||
267 | 519 | var node = content_node.one('#importances-wrapper'); | 513 | var node = content_node.one('#importances-wrapper'); |
272 | 520 | var select_all_handler = make_select_handler(node, importances, true); | 514 | selectors.all_link.on('click', |
273 | 521 | var select_none_handler = make_select_handler(node, importances, false); | 515 | make_select_handler(node, importances, true)); |
274 | 522 | all_link.on('click', select_all_handler); | 516 | selectors.none_link.on('click', |
275 | 523 | none_link.on('click', select_none_handler); | 517 | make_select_handler(node, importances, false)); |
276 | 524 | 518 | ||
277 | 525 | // Build statuses pane. | 519 | // Build statuses pane. |
278 | 526 | statuses_ai = new Y.AccordionItem( { | 520 | statuses_ai = new Y.AccordionItem( { |
279 | @@ -532,18 +526,17 @@ | |||
280 | 532 | } ); | 526 | } ); |
281 | 533 | var statuses = LP.cache.statuses; | 527 | var statuses = LP.cache.statuses; |
282 | 534 | selectors = make_selector_controls('statuses'); | 528 | selectors = make_selector_controls('statuses'); |
287 | 535 | var status_html = '<div id="statuses-wrapper">' + | 529 | statuses_ai.set("bodyContent", |
288 | 536 | selectors.html + make_table(statuses, 'statuses', 3)+ | 530 | Y.Node.create('<div id="statuses-wrapper"></div>') |
289 | 537 | '</div>'; | 531 | .append(selectors.node) |
290 | 538 | statuses_ai.set("bodyContent", status_html); | 532 | .append(make_table(statuses, 'statuses', 3))); |
291 | 539 | accordion.addItem(statuses_ai); | 533 | accordion.addItem(statuses_ai); |
294 | 540 | all_link = content_node.one('#' + selectors.all_name); | 534 | // Wire up the 'all' and 'none' selectors. |
293 | 541 | none_link = Y.one('#' + selectors.none_name); | ||
295 | 542 | node = content_node.one('#statuses-wrapper'); | 535 | node = content_node.one('#statuses-wrapper'); |
300 | 543 | select_all_handler = make_select_handler(node, statuses, true); | 536 | selectors.all_link.on('click', |
301 | 544 | select_none_handler = make_select_handler(node, statuses, false); | 537 | make_select_handler(node, statuses, true)); |
302 | 545 | all_link.on('click', select_all_handler); | 538 | selectors.none_link.on('click', |
303 | 546 | none_link.on('click', select_none_handler); | 539 | make_select_handler(node, statuses, false)); |
304 | 547 | 540 | ||
305 | 548 | return accordion; | 541 | return accordion; |
306 | 549 | } | 542 | } |
307 | @@ -624,105 +617,125 @@ | |||
308 | 624 | } | 617 | } |
309 | 625 | 618 | ||
310 | 626 | /** | 619 | /** |
311 | 620 | * Add a recipient picker to the overlay. | ||
312 | 621 | */ | ||
313 | 622 | function add_recipient_picker(content_box, hide) { | ||
314 | 623 | var no_recipient_picker = Y.Node.create( | ||
315 | 624 | '<input type="hidden" name="recipient" value="user">' + | ||
316 | 625 | '<span>Yourself</span>'); | ||
317 | 626 | var recipient_picker = Y.Node.create( | ||
318 | 627 | '<label><input type="radio" name="recipient" value="user" checked>' + | ||
319 | 628 | ' Yourself</label><br>' + | ||
320 | 629 | '<label><input type="radio" name="recipient" value="team">' + | ||
321 | 630 | ' One of the teams you administer</label><br>' + | ||
322 | 631 | '<dl style="margin-left:25px;">' + | ||
323 | 632 | ' <dt></dt>' + | ||
324 | 633 | ' <dd>' + | ||
325 | 634 | ' <select name="team" id="structural-subscription-teams">' + | ||
326 | 635 | ' </select>' + | ||
327 | 636 | ' </dd>' + | ||
328 | 637 | '</dl>'); | ||
329 | 638 | var teams = LP.cache.administratedTeams; | ||
330 | 639 | var node = content_box.one('#bug-mail-recipient'); | ||
331 | 640 | node.empty(); | ||
332 | 641 | // Populate the team drop down from LP.cache data, if appropriate. | ||
333 | 642 | if (!hide && teams.length > 0) { | ||
334 | 643 | var select = recipient_picker.one('#structural-subscription-teams'); | ||
335 | 644 | var i; | ||
336 | 645 | for (i=0; i<teams.length; i++) { | ||
337 | 646 | select.append(Y.Node.create('<option></option>') | ||
338 | 647 | .set('text', teams[i].title) | ||
339 | 648 | .set('value', teams[i].link)); | ||
340 | 649 | } | ||
341 | 650 | select.on( | ||
342 | 651 | 'focus', | ||
343 | 652 | function () { | ||
344 | 653 | Y.one('input[value="team"][name="recipient"]').set( | ||
345 | 654 | 'checked', true); | ||
346 | 655 | } | ||
347 | 656 | ); | ||
348 | 657 | node.append(recipient_picker); | ||
349 | 658 | } else { | ||
350 | 659 | node.append(no_recipient_picker); | ||
351 | 660 | } | ||
352 | 661 | } | ||
353 | 662 | |||
354 | 663 | /** | ||
355 | 627 | * Construct the overlay and populate it with the add/edit form. | 664 | * Construct the overlay and populate it with the add/edit form. |
356 | 628 | */ | 665 | */ |
357 | 629 | function setup_overlay(content_box_id, hide_recipient_picker) { | 666 | function setup_overlay(content_box_id, hide_recipient_picker) { |
358 | 630 | var content_node = Y.one(content_box_id); | 667 | var content_node = Y.one(content_box_id); |
454 | 631 | var container = Y.Node.create('<div id="overlay-container"></div>'); | 668 | var container = Y.Node.create( |
455 | 632 | var accordion_overlay_id = 'accordion-overlay'; | 669 | '<div id="overlay-container"><dl>' + |
456 | 633 | var teams = LP.cache.administratedTeams; | 670 | ' <dt>Bug mail recipient</dt>' + |
457 | 634 | var no_recipient_picker = | 671 | ' <dd id="bug-mail-recipient">' + |
458 | 635 | ' <input type="hidden" name="recipient" value="user">\n' + | 672 | ' </dd>' + |
459 | 636 | ' <span>Yourself</span>\n', | 673 | ' <dt>Subscription name</dt>' + |
460 | 637 | recipient_picker = | 674 | ' <dd>' + |
461 | 638 | ' <input type="radio" name="recipient" value="user"\n'+ | 675 | ' <input type="text" name="name">' + |
462 | 639 | ' id="structural-subscription-recipient-user" checked>\n'+ | 676 | ' <a target="help" class="sprite maybe"' + |
463 | 640 | ' <label for="structural-subscription-recipient-user">\n'+ | 677 | ' href="/+help/structural-subscription-name.html"> ' + |
464 | 641 | ' Yourself</label><br>\n' + | 678 | ' <span class="invisible-link">Structural subscription' + |
465 | 642 | ' <input type="radio" name="recipient"\n'+ | 679 | ' description help</span></a> ' + |
466 | 643 | ' id="structural-subscription-recipient-team"\n'+ | 680 | ' </dd>' + |
467 | 644 | ' value="team">\n'+ | 681 | ' <dt>Receive mail for bugs affecting' + |
468 | 645 | ' <label for="structural-subscription-recipient-team">One of\n'+ | 682 | ' <span id="structural-subscription-context-title"></span> that</dt>' + |
469 | 646 | ' the teams you administer</label><br>\n' + | 683 | ' <dd>' + |
470 | 647 | ' <dl style="margin-left:25px;">\n' + | 684 | ' <div id="events">' + |
471 | 648 | ' <dt></dt>\n' + | 685 | ' <input type="radio" name="events"' + |
472 | 649 | ' <dd>\n' + | 686 | ' value="added-or-closed"' + |
473 | 650 | ' <select name="team" id="structural-subscription-teams">\n'+ | 687 | ' id="added-or-closed" checked>' + |
474 | 651 | ' </select>\n' + | 688 | ' <label for="added-or-closed">are added or ' + |
475 | 652 | ' </dd>\n' + | 689 | ' closed</label>' + |
476 | 653 | ' </dl>\n', | 690 | ' <br>' + |
477 | 654 | control_code = | 691 | ' <input type="radio" name="events"' + |
478 | 655 | '<dl>\n' + | 692 | ' value="added-or-changed"' + |
479 | 656 | ' <dt>Bug mail recipient</dt>\n' + | 693 | ' id="added-or-changed">' + |
480 | 657 | ' <dd>\n' + | 694 | ' <label for="added-or-changed">are added or changed in' + |
481 | 658 | ((!hide_recipient_picker && teams.length > 0) ? | 695 | ' any way' + |
482 | 659 | recipient_picker : no_recipient_picker) + | 696 | ' <em id="added-or-changed-more">(more options...)</em>' + |
483 | 660 | ' </dd>\n' + | 697 | ' </label>' + |
484 | 661 | ' <dt>Subscription name</dt>\n' + | 698 | ' </div>' + |
485 | 662 | ' <dd>\n' + | 699 | ' <div id="filter-wrapper" class="ss-collapsible">' + |
486 | 663 | ' <input type="text" name="name">\n' + | 700 | ' <dl style="margin-left:25px;">' + |
487 | 664 | ' <a target="help" class="sprite maybe"\n' + | 701 | ' <dt></dt>' + |
488 | 665 | ' href="/+help/structural-subscription-name.html"> \n' + | 702 | ' <dd>' + |
489 | 666 | ' <span class="invisible-link">Structural subscription\n'+ | 703 | ' <input type="checkbox" name="filters"' + |
490 | 667 | ' description help</span></a>\n ' + | 704 | ' value="filter-comments"' + |
491 | 668 | ' </dd>\n' + | 705 | ' id="filter-comments">' + |
492 | 669 | ' <dt>Receive mail for bugs affecting\n'+ | 706 | ' <label for="filter-comments">Don\'t send mail about' + |
493 | 670 | ' <span id="structural-subscription-context-title">\n'+ | 707 | ' comments</label><br>' + |
494 | 671 | ' '+LP.cache.context.title+'</span> that</dt>\n' + | 708 | ' <input type="checkbox" name="filters"' + |
495 | 672 | ' <dd>\n' + | 709 | ' value="advanced-filter"' + |
496 | 673 | ' <div id="events">\n' + | 710 | ' id="advanced-filter">' + |
497 | 674 | ' <input type="radio" name="events"\n' + | 711 | ' <label for="advanced-filter">Bugs must match this' + |
498 | 675 | ' value="' + ADDED_OR_CLOSED + '"\n'+ | 712 | ' filter <em id="advanced-filter-more">(...)</em>' + |
499 | 676 | ' id="' + ADDED_OR_CLOSED + '" checked>\n'+ | 713 | ' </label><br>' + |
500 | 677 | ' <label for="'+ADDED_OR_CLOSED+'">are added or '+ | 714 | ' <div id="accordion-wrapper" ' + |
501 | 678 | ' closed</label>\n'+ | 715 | ' class="ss-collapsible">' + |
502 | 679 | ' <br>\n' + | 716 | ' <dl>' + |
503 | 680 | ' <input type="radio" name="events"\n'+ | 717 | ' <dt></dt>' + |
504 | 681 | ' value="' + ADDED_OR_CHANGED + '"\n' + | 718 | ' <dd style="margin-left:25px;">' + |
505 | 682 | ' id="' + ADDED_OR_CHANGED + '">\n'+ | 719 | ' <div id="accordion-overlay"' + |
506 | 683 | ' <label for="'+ADDED_OR_CHANGED+'">are added or changed in\n'+ | 720 | ' style="position:relative; overflow:hidden;"></div>' + |
507 | 684 | ' any way\n'+ | 721 | ' </dd>' + |
508 | 685 | ' <em id="'+ADDED_OR_CHANGED+'-more">(more options...)</em>\n'+ | 722 | ' </dl>' + |
509 | 686 | ' </label>\n' + | 723 | ' </div> ' + |
510 | 687 | ' </div>\n' + | 724 | ' </dd>' + |
511 | 688 | ' <div id="' + FILTER_WRAPPER + '" class="ss-collapsible">\n' + | 725 | ' </dl>' + |
512 | 689 | ' <dl style="margin-left:25px;">\n' + | 726 | ' </div> ' + |
513 | 690 | ' <dt></dt>\n' + | 727 | ' </dd>' + |
514 | 691 | ' <dd>\n' + | 728 | ' <dt></dt>' + |
515 | 692 | ' <input type="checkbox" name="filters"\n' + | 729 | '</dl></div>'); |
516 | 693 | ' value="' + FILTER_COMMENTS + '"\n'+ | 730 | |
517 | 694 | ' id="'+FILTER_COMMENTS+'">\n' + | 731 | // Assemble some nodes and set the title. |
518 | 695 | ' <label for="'+FILTER_COMMENTS+'">Don\'t send mail about\n'+ | 732 | content_node |
519 | 696 | ' comments</label><br>\n' + | 733 | .appendChild(container) |
520 | 697 | ' <input type="checkbox" name="filters"\n' + | 734 | .one('#structural-subscription-context-title') |
521 | 698 | ' value="' + ADVANCED_FILTER + '"\n' + | 735 | .set('text', LP.cache.context.title); |
522 | 699 | ' id="' + ADVANCED_FILTER + '">\n' + | 736 | |
523 | 700 | ' <label for="'+ADVANCED_FILTER+'">Bugs must match this\n'+ | 737 | var accordion = create_accordion('#accordion-overlay', content_node); |
524 | 701 | ' filter <em id="'+ADVANCED_FILTER+'-more">(...)</em>\n'+ | 738 | add_recipient_picker(container, hide_recipient_picker); |
430 | 702 | ' </label><br>\n' + | ||
431 | 703 | ' <div id="' + ACCORDION_WRAPPER + '" \n' + | ||
432 | 704 | ' class="' + SS_COLLAPSIBLE + '">\n' + | ||
433 | 705 | ' <dl>\n' + | ||
434 | 706 | ' <dt></dt>\n' + | ||
435 | 707 | ' <dd style="margin-left:25px;">\n' + | ||
436 | 708 | ' <div id="' + accordion_overlay_id + '"\n' + | ||
437 | 709 | ' style="position:relative; '+ | ||
438 | 710 | 'overflow:hidden;"></div>\n' + | ||
439 | 711 | ' </dd>\n' + | ||
440 | 712 | ' </dl>\n' + | ||
441 | 713 | ' </div> \n' + | ||
442 | 714 | ' </dd>\n' + | ||
443 | 715 | ' </dl>\n' + | ||
444 | 716 | ' </div> \n' + | ||
445 | 717 | ' </dd>\n' + | ||
446 | 718 | ' <dt></dt>\n' + | ||
447 | 719 | '</dl>'; | ||
448 | 720 | |||
449 | 721 | content_node.appendChild(container); | ||
450 | 722 | container.appendChild(Y.Node.create(control_code)); | ||
451 | 723 | |||
452 | 724 | var accordion = create_accordion( | ||
453 | 725 | '#' + accordion_overlay_id, content_node); | ||
525 | 726 | 739 | ||
526 | 727 | // Set up click handlers for the events radio buttons. | 740 | // Set up click handlers for the events radio buttons. |
527 | 728 | var radio_group = Y.all('#events input'); | 741 | var radio_group = Y.all('#events input'); |
528 | @@ -735,26 +748,6 @@ | |||
529 | 735 | advanced_filter.on( | 748 | advanced_filter.on( |
530 | 736 | 'change', | 749 | 'change', |
531 | 737 | function() {handle_change(ADVANCED_FILTER, ACCORDION_WRAPPER);}); | 750 | function() {handle_change(ADVANCED_FILTER, ACCORDION_WRAPPER);}); |
532 | 738 | // Populate the team drop down from LP.cache data, if appropriate. | ||
533 | 739 | if (!hide_recipient_picker && teams.length > 0) { | ||
534 | 740 | var select = Y.one('#structural-subscription-teams'); | ||
535 | 741 | var i; | ||
536 | 742 | var team; | ||
537 | 743 | for (i=0; i<teams.length; i++) { | ||
538 | 744 | team = teams[i]; | ||
539 | 745 | var option = Y.Node.create('<option></option>'); | ||
540 | 746 | option.set(INNER_HTML, team.title); | ||
541 | 747 | option.set(VALUE, team.link); | ||
542 | 748 | select.appendChild(option); | ||
543 | 749 | } | ||
544 | 750 | select.on( | ||
545 | 751 | 'focus', | ||
546 | 752 | function () { | ||
547 | 753 | Y.one('input[value="team"][name="recipient"]').set( | ||
548 | 754 | 'checked', true); | ||
549 | 755 | } | ||
550 | 756 | ); | ||
551 | 757 | } | ||
552 | 758 | return '#' + container._node.id; | 751 | return '#' + container._node.id; |
553 | 759 | } // setup_overlay | 752 | } // setup_overlay |
554 | 760 | // Expose in the namespace for testing purposes. | 753 | // Expose in the namespace for testing purposes. |
555 | @@ -807,7 +800,7 @@ | |||
556 | 807 | var overlay_id = setup_overlay(config.content_box, true); | 800 | var overlay_id = setup_overlay(config.content_box, true); |
557 | 808 | var submit_button = Y.Node.create( | 801 | var submit_button = Y.Node.create( |
558 | 809 | '<button type="submit" name="field.actions.create" ' + | 802 | '<button type="submit" name="field.actions.create" ' + |
560 | 810 | 'value="Save Changes" class="lazr-pos lazr-btn" '+ | 803 | 'value="Save Changes" class="lazr-pos lazr-btn" ' + |
561 | 811 | '>OK</button>'); | 804 | '>OK</button>'); |
562 | 812 | // This is a bit of an odd approach, but it lets us retrofit code | 805 | // This is a bit of an odd approach, but it lets us retrofit code |
563 | 813 | // without a large refactoring. When edit_subscription_handler is | 806 | // without a large refactoring. When edit_subscription_handler is |
564 | @@ -848,8 +841,9 @@ | |||
565 | 848 | overlay.field_errors[field_name] = true; | 841 | overlay.field_errors[field_name] = true; |
566 | 849 | // Accordion sets fixed height for the accordion item, | 842 | // Accordion sets fixed height for the accordion item, |
567 | 850 | // so we have to resize the tags container. | 843 | // so we have to resize the tags container. |
569 | 851 | if (field_name == 'tags') | 844 | if (field_name === 'tags') { |
570 | 852 | tags_container.resize(); | 845 | tags_container.resize(); |
571 | 846 | } | ||
572 | 853 | // Firefox prohibits focus from inside the 'focus lost' event | 847 | // Firefox prohibits focus from inside the 'focus lost' event |
573 | 854 | // handler (probably to stop loops), so we need to run | 848 | // handler (probably to stop loops), so we need to run |
574 | 855 | // it from a different context (which we do with setTimeout). | 849 | // it from a different context (which we do with setTimeout). |
575 | @@ -859,7 +853,7 @@ | |||
576 | 859 | overlay.field_errors[field_name] = false; | 853 | overlay.field_errors[field_name] = false; |
577 | 860 | } | 854 | } |
578 | 861 | }); | 855 | }); |
580 | 862 | }; | 856 | } |
581 | 863 | 857 | ||
582 | 864 | function get_error_for_tags_list(value) { | 858 | function get_error_for_tags_list(value) { |
583 | 865 | // See database/schema/trusted.sql valid_name() function | 859 | // See database/schema/trusted.sql valid_name() function |
584 | @@ -874,7 +868,8 @@ | |||
585 | 874 | 'digits 0-9 and symbols "+", "-" or ".", and they ' + | 868 | 'digits 0-9 and symbols "+", "-" or ".", and they ' + |
586 | 875 | 'must start with a lowercase letter or a digit.'); | 869 | 'must start with a lowercase letter or a digit.'); |
587 | 876 | } | 870 | } |
589 | 877 | }; | 871 | } |
590 | 872 | |||
591 | 878 | // Export for testing | 873 | // Export for testing |
592 | 879 | namespace._get_error_for_tags_list = get_error_for_tags_list; | 874 | namespace._get_error_for_tags_list = get_error_for_tags_list; |
593 | 880 | 875 | ||
594 | @@ -956,12 +951,12 @@ | |||
595 | 956 | var i; | 951 | var i; |
596 | 957 | for (i=0; i<teams.length; i++) { | 952 | for (i=0; i<teams.length; i++) { |
597 | 958 | if (teams[i].link === filter_info.subscriber_link){ | 953 | if (teams[i].link === filter_info.subscriber_link){ |
599 | 959 | recipient_label.set(INNER_HTML, teams[i].title); | 954 | recipient_label.set('text', teams[i].title); |
600 | 960 | break; | 955 | break; |
601 | 961 | } | 956 | } |
602 | 962 | } | 957 | } |
603 | 963 | } else { | 958 | } else { |
605 | 964 | recipient_label.set(INNER_HTML, 'Yourself'); | 959 | recipient_label.set('text', 'Yourself'); |
606 | 965 | } | 960 | } |
607 | 966 | content_node.one('[name="name"]').set('value',filter.description); | 961 | content_node.one('[name="name"]').set('value',filter.description); |
608 | 967 | if (is_lifecycle) { | 962 | if (is_lifecycle) { |
609 | @@ -1011,10 +1006,10 @@ | |||
610 | 1011 | context.filter_info = filter_info; | 1006 | context.filter_info = filter_info; |
611 | 1012 | context.filter_id = filter_id; | 1007 | context.filter_id = filter_id; |
612 | 1013 | var title = subscription.target_title; | 1008 | var title = subscription.target_title; |
617 | 1014 | Y.one('#structural-subscription-context-title').set( | 1009 | Y.one('#structural-subscription-context-title') |
618 | 1015 | INNER_HTML, title); | 1010 | .set('text', title); |
619 | 1016 | Y.one('#subscription-overlay-title').set( | 1011 | Y.one('#subscription-overlay-title') |
620 | 1017 | INNER_HTML, 'Edit subscription for '+title+' bugs'); | 1012 | .set('text', 'Edit subscription for '+title+' bugs'); |
621 | 1018 | add_subscription_overlay.show(); | 1013 | add_subscription_overlay.show(); |
622 | 1019 | } | 1014 | } |
623 | 1020 | }; | 1015 | }; |
624 | @@ -1035,14 +1030,14 @@ | |||
625 | 1035 | headers: {'X-HTTP-Method-Override': 'DELETE'}, | 1030 | headers: {'X-HTTP-Method-Override': 'DELETE'}, |
626 | 1036 | on: {success: function(transactionid, response, args){ | 1031 | on: {success: function(transactionid, response, args){ |
627 | 1037 | var subscriber = Y.one( | 1032 | var subscriber = Y.one( |
631 | 1038 | '#subscription-'+subscriber_id.toString()), | 1033 | '#subscription-'+subscriber_id.toString()); |
632 | 1039 | node = subscriber, | 1034 | var to_collapse = subscriber; |
633 | 1040 | filters = subscriber.all('.subscription-filter'); | 1035 | var filters = subscriber.all('.subscription-filter'); |
634 | 1041 | if (!filters.isEmpty()) { | 1036 | if (!filters.isEmpty()) { |
636 | 1042 | node = Y.one( | 1037 | to_collapse = Y.one( |
637 | 1043 | '#subscription-filter-'+filter_id.toString()); | 1038 | '#subscription-filter-'+filter_id.toString()); |
638 | 1044 | } | 1039 | } |
640 | 1045 | collapse_node(node); | 1040 | collapse_node(to_collapse); |
641 | 1046 | }, | 1041 | }, |
642 | 1047 | failure: error_handler.getFailureHandler() | 1042 | failure: error_handler.getFailureHandler() |
643 | 1048 | } | 1043 | } |
644 | @@ -1066,11 +1061,12 @@ | |||
645 | 1066 | var filter_info = sub.filters[j]; | 1061 | var filter_info = sub.filters[j]; |
646 | 1067 | if (!filter_info.subscriber_is_team || | 1062 | if (!filter_info.subscriber_is_team || |
647 | 1068 | filter_info.user_is_team_admin) { | 1063 | filter_info.user_is_team_admin) { |
649 | 1069 | var edit_link = Y.one('#edit-'+filter_id.toString()); | 1064 | var node = Y.one('#subscription-filter-'+filter_id.toString()); |
650 | 1065 | var edit_link = node.one('a.edit-subscription'); | ||
651 | 1070 | var edit_handler = make_edit_handler( | 1066 | var edit_handler = make_edit_handler( |
652 | 1071 | sub, filter_info, filter_id, config, context); | 1067 | sub, filter_info, filter_id, config, context); |
653 | 1072 | edit_link.on('click', edit_handler); | 1068 | edit_link.on('click', edit_handler); |
655 | 1073 | var delete_link = Y.one('#unsubscribe-'+filter_id.toString()); | 1069 | var delete_link = node.one('a.delete-subscription'); |
656 | 1074 | var delete_handler = make_delete_handler( | 1070 | var delete_handler = make_delete_handler( |
657 | 1075 | filter_info.filter, filter_id, i); | 1071 | filter_info.filter, filter_id, i); |
658 | 1076 | delete_link.on('click', delete_handler); | 1072 | delete_link.on('click', delete_handler); |
659 | @@ -1085,22 +1081,27 @@ | |||
660 | 1085 | */ | 1081 | */ |
661 | 1086 | function fill_in_bug_subscriptions(config, context) { | 1082 | function fill_in_bug_subscriptions(config, context) { |
662 | 1087 | validate_config(config); | 1083 | validate_config(config); |
663 | 1084 | |||
664 | 1088 | var listing = Y.one('#subscription-listing'); | 1085 | var listing = Y.one('#subscription-listing'); |
665 | 1089 | var subscription_info = LP.cache.subscription_info; | 1086 | var subscription_info = LP.cache.subscription_info; |
667 | 1090 | var html = '<div class="yui-g"><div id="structural-subscriptions">'; | 1087 | var top_node = Y.Node.create( |
668 | 1088 | '<div class="yui-g"><div id="structural-subscriptions"></div></div>'); | ||
669 | 1091 | var filter_id = 0; | 1089 | var filter_id = 0; |
670 | 1092 | var i; | 1090 | var i; |
671 | 1093 | var j; | 1091 | var j; |
672 | 1094 | for (i=0; i<subscription_info.length; i++) { | 1092 | for (i=0; i<subscription_info.length; i++) { |
673 | 1095 | var sub = subscription_info[i]; | 1093 | var sub = subscription_info[i]; |
675 | 1096 | html += | 1094 | var sub_node = top_node.appendChild(Y.Node.create( |
676 | 1097 | '<div style="margin-top: 2em; padding: 0 1em 1em 1em; '+ | 1095 | '<div style="margin-top: 2em; padding: 0 1em 1em 1em; '+ |
683 | 1098 | ' border: 1px solid #ddd;"'+ | 1096 | ' border: 1px solid #ddd;"></div>') |
684 | 1099 | ' id="subscription-'+i.toString()+'">'+ | 1097 | .set('id', 'subscription-'+i.toString())); |
685 | 1100 | ' <span style="float: left; margin-top: -0.6em; padding: 0 1ex;'+ | 1098 | sub_node.appendChild(Y.Node.create( |
686 | 1101 | ' background-color: #fff;">Subscriptions to'+ | 1099 | ' <span style="float: left; margin-top: -0.6em; '+ |
687 | 1102 | ' <a href="'+sub.target_url+'">'+sub.target_title+'</a>'+ | 1100 | ' padding: 0 1ex; background-color: #fff;"></a>')) |
688 | 1103 | ' </span>'; | 1101 | .appendChild('<span>Subscriptions to </span>') |
689 | 1102 | .appendChild(Y.Node.create('<a></a>') | ||
690 | 1103 | .set('href', sub.target_url) | ||
691 | 1104 | .set('text', sub.target_title)); | ||
692 | 1104 | 1105 | ||
693 | 1105 | for (j=0; j<sub.filters.length; j++) { | 1106 | for (j=0; j<sub.filters.length; j++) { |
694 | 1106 | var filter = sub.filters[j].filter; | 1107 | var filter = sub.filters[j].filter; |
695 | @@ -1110,52 +1111,53 @@ | |||
696 | 1110 | // and see the information you expect to see. | 1111 | // and see the information you expect to see. |
697 | 1111 | LP.cache['structural-subscription-filter-'+filter_id.toString()] = | 1112 | LP.cache['structural-subscription-filter-'+filter_id.toString()] = |
698 | 1112 | filter; | 1113 | filter; |
700 | 1113 | html += | 1114 | var filter_node = sub_node.appendChild(Y.Node.create( |
701 | 1114 | '<div style="margin: 1em 0em 0em 1em"'+ | 1115 | '<div style="margin: 1em 0em 0em 1em"'+ |
708 | 1115 | ' id="subscription-filter-'+filter_id.toString()+'"'+ | 1116 | ' class="subscription-filter"></div>') |
709 | 1116 | ' class="subscription-filter">'+ | 1117 | .set('id', 'subscription-filter-'+filter_id.toString())) |
710 | 1117 | ' <div style="margin-top: 1em">'+ | 1118 | .appendChild(Y.Node.create( |
711 | 1118 | ' <strong id="filter-name-'+ | 1119 | '<div style="margin-top: 1em"></div>')); |
712 | 1119 | filter_id.toString()+'">'+ | 1120 | filter_node.appendChild(Y.Node.create( |
713 | 1120 | render_filter_name(sub.filters[j], filter)+'</strong>'; | 1121 | '<strong class="filter-name"></strong>')) |
714 | 1122 | .appendChild(render_filter_title(sub.filters[j], filter)); | ||
715 | 1123 | |||
716 | 1121 | if (!sub.filters[j].subscriber_is_team || | 1124 | if (!sub.filters[j].subscriber_is_team || |
717 | 1122 | sub.filters[j].user_is_team_admin) { | 1125 | sub.filters[j].user_is_team_admin) { |
718 | 1123 | // User can edit the subscription. | 1126 | // User can edit the subscription. |
720 | 1124 | html += | 1127 | filter_node.appendChild(Y.Node.create( |
721 | 1125 | '<span style="float: right">'+ | 1128 | '<span style="float: right">'+ |
724 | 1126 | '<a href="#" class="sprite modify edit js-action"'+ | 1129 | '<a href="#" class="sprite modify edit js-action '+ |
725 | 1127 | ' id="edit-'+filter_id.toString()+'">'+ | 1130 | ' edit-subscription">'+ |
726 | 1128 | ' Edit this subscription</a> or '+ | 1131 | ' Edit this subscription</a> or '+ |
730 | 1129 | '<a href="#" class="sprite modify remove js-action"'+ | 1132 | '<a href="#" class="sprite modify remove js-action '+ |
731 | 1130 | ' id="unsubscribe-'+filter_id.toString()+'">'+ | 1133 | ' delete-subscription">'+ |
732 | 1131 | ' Unsubscribe</a></span>'; | 1134 | ' Unsubscribe</a></span>')); |
733 | 1132 | } else { | 1135 | } else { |
734 | 1133 | // User cannot edit the subscription, because this is a | 1136 | // User cannot edit the subscription, because this is a |
735 | 1134 | // team and the user does not have admin privileges. | 1137 | // team and the user does not have admin privileges. |
737 | 1135 | html += | 1138 | filter_node.appendChild(Y.Node.create( |
738 | 1136 | '<span style="float: right"><em>'+ | 1139 | '<span style="float: right"><em>'+ |
739 | 1137 | 'You do not have privileges to change this subscription'+ | 1140 | 'You do not have privileges to change this subscription'+ |
741 | 1138 | '</em></span>'; | 1141 | '</em></span>')); |
742 | 1139 | } | 1142 | } |
750 | 1140 | html += '</div>'; | 1143 | |
751 | 1141 | html += | 1144 | filter_node.appendChild(Y.Node.create( |
752 | 1142 | '<div style="padding-left: 1em"'+ | 1145 | '<div style="padding-left: 1em"></div>') |
753 | 1143 | ' id="filter-description-'+filter_id.toString()+'">'+ | 1146 | .set('id', 'filter-description-'+filter_id.toString())) |
754 | 1144 | render_filter_description(filter)+'</div>'; | 1147 | .appendChild(create_filter_description(filter)); |
755 | 1145 | 1148 | ||
749 | 1146 | html += '</div>'; | ||
756 | 1147 | filter_id += 1; | 1149 | filter_id += 1; |
757 | 1148 | } | 1150 | } |
758 | 1149 | 1151 | ||
759 | 1150 | // We can remove this once we enforce at least one filter per | 1152 | // We can remove this once we enforce at least one filter per |
760 | 1151 | // subscription. | 1153 | // subscription. |
761 | 1152 | if (subscription_info[i].filters.length === 0) { | 1154 | if (subscription_info[i].filters.length === 0) { |
763 | 1153 | html += '<strong>All messages</strong>'; | 1155 | sub_node.appendChild( |
764 | 1156 | '<div style="clear: both; padding: 1em 0 0 1em"></div>') | ||
765 | 1157 | .appendChild('<strong>All messages</strong>'); | ||
766 | 1154 | } | 1158 | } |
767 | 1155 | html += '</div>'; | ||
768 | 1156 | } | 1159 | } |
771 | 1157 | html += '</div></div>'; | 1160 | listing.appendChild(top_node); |
770 | 1158 | listing.appendChild(Y.Node.create(html)); | ||
772 | 1159 | 1161 | ||
773 | 1160 | wire_up_edit_links(config, context); | 1162 | wire_up_edit_links(config, context); |
774 | 1161 | } | 1163 | } |
775 | @@ -1163,7 +1165,8 @@ | |||
776 | 1163 | /** | 1165 | /** |
777 | 1164 | * Construct a one-line textual description of a filter's name. | 1166 | * Construct a one-line textual description of a filter's name. |
778 | 1165 | */ | 1167 | */ |
780 | 1166 | function render_filter_name(filter_info, filter) { | 1168 | function render_filter_title(filter_info, filter) { |
781 | 1169 | var title = Y.Node.create('<span></span>'); | ||
782 | 1167 | var description; | 1170 | var description; |
783 | 1168 | if (filter.description) { | 1171 | if (filter.description) { |
784 | 1169 | description = '"'+filter.description+'"'; | 1172 | description = '"'+filter.description+'"'; |
785 | @@ -1171,62 +1174,79 @@ | |||
786 | 1171 | description = '(unnamed)'; | 1174 | description = '(unnamed)'; |
787 | 1172 | } | 1175 | } |
788 | 1173 | if (filter_info.subscriber_is_team) { | 1176 | if (filter_info.subscriber_is_team) { |
791 | 1174 | return '<a href="'+filter_info.subscriber_url+'">'+ | 1177 | title.appendChild(Y.Node.create('<a></a>')) |
792 | 1175 | filter_info.subscriber_title+"</a> subscription: "+description; | 1178 | .set('href', filter_info.subscriber_url) |
793 | 1179 | .set('text', filter_info.subscriber_title); | ||
794 | 1180 | title.appendChild(Y.Node.create('<span></span>')) | ||
795 | 1181 | .set('text', ' subscription: '+description); | ||
796 | 1176 | } else { | 1182 | } else { |
798 | 1177 | return 'Your subscription: '+description; | 1183 | title.set('text', 'Your subscription: '+description); |
799 | 1178 | } | 1184 | } |
800 | 1185 | return title; | ||
801 | 1179 | } | 1186 | } |
802 | 1180 | 1187 | ||
803 | 1181 | /** | 1188 | /** |
804 | 1182 | * Construct a textual description of all of filter's properties. | 1189 | * Construct a textual description of all of filter's properties. |
805 | 1183 | */ | 1190 | */ |
809 | 1184 | function render_filter_description(filter) { | 1191 | function create_filter_description(filter) { |
810 | 1185 | var html = ''; | 1192 | var description = Y.Node.create('<div></div>'); |
811 | 1186 | var filter_items = ''; | 1193 | |
812 | 1194 | var filter_items = []; | ||
813 | 1187 | // Format status conditions. | 1195 | // Format status conditions. |
814 | 1188 | if (filter.statuses.length !== 0) { | 1196 | if (filter.statuses.length !== 0) { |
817 | 1189 | filter_items += '<li> have status ' + | 1197 | filter_items.push(Y.Node.create('<li></li>') |
818 | 1190 | filter.statuses.join(', '); | 1198 | .set('text', 'have status '+filter.statuses.join(', '))); |
819 | 1191 | } | 1199 | } |
820 | 1192 | 1200 | ||
821 | 1193 | // Format importance conditions. | 1201 | // Format importance conditions. |
822 | 1194 | if (filter.importances.length !== 0) { | 1202 | if (filter.importances.length !== 0) { |
825 | 1195 | filter_items += '<li> are of importance ' + | 1203 | filter_items.push(Y.Node.create('<li></li>') |
826 | 1196 | filter.importances.join(', '); | 1204 | .set('text', 'are of importance '+filter.importances.join(', '))); |
827 | 1197 | } | 1205 | } |
828 | 1198 | 1206 | ||
829 | 1199 | // Format tag conditions. | 1207 | // Format tag conditions. |
830 | 1200 | if (filter.tags.length !== 0) { | 1208 | if (filter.tags.length !== 0) { |
832 | 1201 | filter_items += '<li> are tagged with '; | 1209 | var tag_desc = Y.Node.create('<li>are tagged with </li>') |
833 | 1210 | .append(Y.Node.create('<strong></strong>')) | ||
834 | 1211 | .append(Y.Node.create('<span> of these tags: </span>')) | ||
835 | 1212 | .append(Y.Node.create('<span></span>') | ||
836 | 1213 | .set('text', filter.tags.join(', '))); | ||
837 | 1214 | |||
838 | 1202 | if (filter.find_all_tags) { | 1215 | if (filter.find_all_tags) { |
840 | 1203 | filter_items += '<strong>all</strong>'; | 1216 | tag_desc.one('strong').set('text', 'all'); |
841 | 1204 | } else { | 1217 | } else { |
843 | 1205 | filter_items += '<strong>any</strong>'; | 1218 | tag_desc.one('strong').set('text', 'any'); |
844 | 1206 | } | 1219 | } |
847 | 1207 | filter_items += ' of these tags: ' + | 1220 | filter_items.push(tag_desc); |
846 | 1208 | filter.tags.join(', '); | ||
848 | 1209 | } | 1221 | } |
849 | 1210 | 1222 | ||
850 | 1211 | // If there were any conditions to list, stich them in with an | 1223 | // If there were any conditions to list, stich them in with an |
851 | 1212 | // intro. | 1224 | // intro. |
855 | 1213 | if (filter_items !== '') { | 1225 | if (filter_items.length > 0) { |
856 | 1214 | html += 'You are subscribed to bugs that'+ | 1226 | var ul = Y.Node.create('<ul class="bulleted"></ul>'); |
857 | 1215 | '<ul class="bulleted">'+filter_items+'</ul>'; | 1227 | Y.each(filter_items, function (li) {ul.appendChild(li);}); |
858 | 1228 | description.appendChild( | ||
859 | 1229 | Y.Node.create('<span>You are subscribed to bugs that</span>')); | ||
860 | 1230 | description.appendChild(ul); | ||
861 | 1216 | } | 1231 | } |
862 | 1217 | 1232 | ||
863 | 1218 | // Format event details. | 1233 | // Format event details. |
864 | 1234 | var events; // When will email be sent? | ||
865 | 1219 | if (filter.bug_notification_level === 'Discussion') { | 1235 | if (filter.bug_notification_level === 'Discussion') { |
867 | 1220 | html += 'You will recieve an email when any change '+ | 1236 | events = 'You will recieve an email when any change '+ |
868 | 1221 | 'is made or a comment is added.'; | 1237 | 'is made or a comment is added.'; |
869 | 1222 | } else if (filter.bug_notification_level === 'Details') { | 1238 | } else if (filter.bug_notification_level === 'Details') { |
871 | 1223 | html += 'You will recieve an email when any changes '+ | 1239 | events = 'You will recieve an email when any changes '+ |
872 | 1224 | 'are made to the bug. Bug comments will not be sent.'; | 1240 | 'are made to the bug. Bug comments will not be sent.'; |
873 | 1225 | } else if (filter.bug_notification_level === 'Lifecycle') { | 1241 | } else if (filter.bug_notification_level === 'Lifecycle') { |
875 | 1226 | html += 'You will recieve an email when bugs are '+ | 1242 | events = 'You will recieve an email when bugs are '+ |
876 | 1227 | 'opened or closed.'; | 1243 | 'opened or closed.'; |
877 | 1244 | } else { | ||
878 | 1245 | throw new Error('Unrecognized events.'); | ||
879 | 1228 | } | 1246 | } |
881 | 1229 | return html; | 1247 | description.appendChild(Y.Node.create(events)); |
882 | 1248 | |||
883 | 1249 | return description; | ||
884 | 1230 | } | 1250 | } |
885 | 1231 | 1251 | ||
886 | 1232 | /** | 1252 | /** |
887 | 1233 | 1253 | ||
888 | === modified file 'lib/lp/registry/javascript/tests/test_structural_subscription.js' | |||
889 | --- lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-03-30 15:23:42 +0000 | |||
890 | +++ lib/lp/registry/javascript/tests/test_structural_subscription.js 2011-04-01 17:57:54 +0000 | |||
891 | @@ -147,11 +147,12 @@ | |||
892 | 147 | test_make_selector_controls: function() { | 147 | test_make_selector_controls: function() { |
893 | 148 | // Verify the creation of select all/none controls. | 148 | // Verify the creation of select all/none controls. |
894 | 149 | var selectors = module.make_selector_controls('sharona'); | 149 | var selectors = module.make_selector_controls('sharona'); |
900 | 150 | Assert.areEqual('sharona-select-all', selectors['all_name']); | 150 | Assert.areEqual( |
901 | 151 | Assert.areEqual('sharona-select-none', selectors['none_name']); | 151 | 'Select all', selectors.all_link.get('text')); |
902 | 152 | Assert.areEqual( | 152 | Assert.areEqual( |
903 | 153 | '<div id="sharona-selectors"', | 153 | 'Select none', selectors.none_link.get('text')); |
904 | 154 | selectors['html'].slice(0, 27)); | 154 | Assert.areEqual( |
905 | 155 | 'sharona-selectors', selectors.node.get('id')); | ||
906 | 155 | } | 156 | } |
907 | 156 | }); | 157 | }); |
908 | 157 | suite.add(test_case); | 158 | suite.add(test_case); |
909 | @@ -419,8 +420,8 @@ | |||
910 | 419 | // accordion pane. | 420 | // accordion pane. |
911 | 420 | module.setup(this.configuration); | 421 | module.setup(this.configuration); |
912 | 421 | var checkboxes = Y.all('input[name="importances"]'); | 422 | var checkboxes = Y.all('input[name="importances"]'); |
915 | 422 | var select_all = Y.one('#importances-select-all'); | 423 | var select_all = Y.one('#importances-selectors > a.select-all'); |
916 | 423 | var select_none = Y.one('#importances-select-none'); | 424 | var select_none = Y.one('#importances-selectors > a.select-none'); |
917 | 424 | Assert.isTrue(test_checked(checkboxes, true)); | 425 | Assert.isTrue(test_checked(checkboxes, true)); |
918 | 425 | // Simulate a click on the select_none control. | 426 | // Simulate a click on the select_none control. |
919 | 426 | Y.Event.simulate(Y.Node.getDOMNode(select_none), 'click'); | 427 | Y.Event.simulate(Y.Node.getDOMNode(select_none), 'click'); |
920 | @@ -435,8 +436,8 @@ | |||
921 | 435 | // accordion pane. | 436 | // accordion pane. |
922 | 436 | module.setup(this.configuration); | 437 | module.setup(this.configuration); |
923 | 437 | var checkboxes = Y.all('input[name="statuses"]'); | 438 | var checkboxes = Y.all('input[name="statuses"]'); |
926 | 438 | var select_all = Y.one('#statuses-select-all'); | 439 | var select_all = Y.one('#statuses-selectors > a.select-all'); |
927 | 439 | var select_none = Y.one('#statuses-select-none'); | 440 | var select_none = Y.one('#statuses-selectors > a.select-none'); |
928 | 440 | Assert.isTrue(test_checked(checkboxes, true)); | 441 | Assert.isTrue(test_checked(checkboxes, true)); |
929 | 441 | // Simulate a click on the select_none control. | 442 | // Simulate a click on the select_none control. |
930 | 442 | Y.Event.simulate(Y.Node.getDOMNode(select_none), 'click'); | 443 | Y.Event.simulate(Y.Node.getDOMNode(select_none), 'click'); |
This is a vast improvement over our original way of creating the html content for the overlay. Thanks for cleaning it all up.
As we discussed on IRC I am a little concerned that there are still large sections of string concatenation to create the recipient picker and overlay container. You pointed out it is all constant string values so it shouldn't present a problem. In the future we can always break those sections down if it proves to be needed.