Merge lp:~rockstar/launchpad/update-review-table-on-comment into lp:launchpad/db-devel

Proposed by Paul Hummer
Status: Merged
Approved by: Paul Hummer
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~rockstar/launchpad/update-review-table-on-comment
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~rockstar/launchpad/clean-code-windmill-tests
Diff against target: 931 lines (+437/-396)
6 files modified
Makefile (+0/-1)
lib/canonical/launchpad/javascript/lp/comment.js (+28/-0)
lib/canonical/launchpad/javascript/lp/lp-mochi.js (+383/-0)
lib/canonical/launchpad/javascript/lp/lp.js (+0/-392)
lib/lp/app/templates/base-layout-macros.pt (+11/-3)
lib/lp/code/windmill/tests/test_branchmergeproposal_review.py (+15/-0)
To merge this branch: bzr merge lp:~rockstar/launchpad/update-review-table-on-comment
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve
Review via email: mp+17439@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) wrote :

Hi Tim-

  This branch updates the branch reviewer's table when someone uses the inline
form to review. If the comment has a review, it also gives it a green flash
for the new review.

Cheers,
Paul

Revision history for this message
Tim Penhey (thumper) wrote :

Land it!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2010-01-14 22:59:54 +0000
+++ Makefile 2010-01-21 23:36:33 +0000
@@ -126,7 +126,6 @@
126 -n launchpad \126 -n launchpad \
127 -s lib/canonical/launchpad/javascript \127 -s lib/canonical/launchpad/javascript \
128 -b $(LP_BUILT_JS_ROOT) \128 -b $(LP_BUILT_JS_ROOT) \
129 lib/canonical/launchpad/icing/MochiKit.js \
130 $(shell $(HERE)/utilities/yui-deps.py) \129 $(shell $(HERE)/utilities/yui-deps.py) \
131 lib/canonical/launchpad/icing/lazr/build/lazr.js130 lib/canonical/launchpad/icing/lazr/build/lazr.js
132131
133132
=== modified file 'lib/canonical/launchpad/javascript/lp/comment.js'
--- lib/canonical/launchpad/javascript/lp/comment.js 2009-12-15 02:54:01 +0000
+++ lib/canonical/launchpad/javascript/lp/comment.js 2010-01-21 23:36:33 +0000
@@ -89,9 +89,14 @@
89 this.post_comment(bind(function(message_entry) {89 this.post_comment(bind(function(message_entry) {
90 this.get_comment_HTML(90 this.get_comment_HTML(
91 message_entry, bind(this.insert_comment_HTML, this));91 message_entry, bind(this.insert_comment_HTML, this));
92 this._add_comment_success();
92 }, this));93 }, this));
93 },94 },
94 /**95 /**
96 * A callable hook for firing extra events.
97 */
98 _add_comment_success: function() {},
99 /**
95 * Post the comment to the Launchpad API100 * Post the comment to the Launchpad API
96 *101 *
97 * @method post_comment102 * @method post_comment
@@ -390,6 +395,29 @@
390 CodeReviewComment.superclass.syncUI.apply(this);395 CodeReviewComment.superclass.syncUI.apply(this);
391 var review_type_disabled = (this.get_vote() === null);396 var review_type_disabled = (this.get_vote() === null);
392 this.review_type.set('disabled', review_type_disabled);397 this.review_type.set('disabled', review_type_disabled);
398 },
399 /**
400 * A callable hook for firing extra events.
401 */
402 _add_comment_success: function() {
403 var VOTES_TABLE_PATH = '+votes';
404 Y.io(VOTES_TABLE_PATH, {
405 on: {
406 success: function(id, response) {
407 var target = Y.one('#votes-target');
408 target.set('innerHTML', response.responseText);
409
410 var username = LP.client.links.me.substring(2);
411 var new_reviewer = Y.one('#review-' + username);
412 if (Y.lang.isValue(new_reviewer)) {
413 var anim = Y.lazr.anim.green_flash({
414 node: new_reviewer});
415 anim.run();
416 }
417 },
418 failure: function() {}
419 }
420 });
393 }421 }
394});422});
395Y.lp.CodeReviewComment = CodeReviewComment;423Y.lp.CodeReviewComment = CodeReviewComment;
396424
=== added file 'lib/canonical/launchpad/javascript/lp/lp-mochi.js'
--- lib/canonical/launchpad/javascript/lp/lp-mochi.js 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/javascript/lp/lp-mochi.js 2010-01-21 23:36:33 +0000
@@ -0,0 +1,383 @@
1// Copyright 2010 Canonical Ltd. All rights reserved.
2//
3// Launchpad JavaScript core functions that require the MochiKit library.
4
5function getContentArea() {
6 // to end all doubt on where the content sits. It also felt a bit
7 // silly doing this over and over in every function, even if it is
8 // a tiny operation. Just guarding against someone changing the
9 // names again, in the name of semantics or something.... ;)
10 var node = document.getElementById('maincontent');
11 if (!node) {node = $('content');}
12 if (!node) {node = $('mainarea');}
13 return node;
14}
15
16function toggleCollapsible(e) {
17 // this is the function that collapses/expands fieldsets.
18
19 // "this" is the node that the event is attached to
20 var node = this;
21
22 // walk up the node hierarchy until we find the <legend> element
23 while (node.nodeName.toLowerCase() != 'legend') {
24 node = node.parentNode;
25 if (!node) {
26 return false;
27 }
28 }
29
30 // the expander image is legend -> a -> img
31 var icon = node.firstChild.firstChild;
32 var legend = node;
33
34 if (icon.getAttribute('src').indexOf('/@@/treeCollapsed') != -1) {
35 // that was an ugly check, but IE rewrites image sources to
36 // absolute urls from some sick reason....
37 icon.setAttribute('src','/@@/treeExpanded');
38 swapElementClass(
39 legend.parentNode.lastChild, 'collapsed', 'expanded');
40 swapElementClass(
41 legend.parentNode.childNodes[1], 'expanded', 'collapsed');
42 } else {
43 icon.setAttribute('src','/@@/treeCollapsed');
44 swapElementClass(
45 legend.parentNode.lastChild, 'expanded', 'collapsed');
46 swapElementClass(
47 legend.parentNode.childNodes[1], 'collapsed', 'expanded');
48 }
49 return false;
50}
51
52function activateCollapsibles() {
53 // a script that searches for sections that can be (or are
54 // already) collapsed - and enables the collapse-behavior
55
56 // usage : give the class "collapsible" to a fieldset also, give
57 // it a <legend> with some descriptive text. you can also add the
58 // class "collapsed" amounting to a total of
59 // <fieldset class="collapsible collapsed"> to make the section
60 // pre-collapsed
61
62 // terminate if we hit a non-compliant DOM implementation
63 if (!document.getElementsByTagName) {
64 return false;
65 }
66 if (!document.getElementById) {
67 return false;
68 }
69
70 // only search in the content-area
71 var contentarea = getContentArea();
72 if (!contentarea) {
73 return false;
74 }
75
76 // gather all objects that are to be collapsed
77 // we only do fieldsets for now. perhaps DIVs later...
78 var collapsibles = contentarea.getElementsByTagName('fieldset');
79
80 for (var i = 0; i < collapsibles.length; i++) {
81 if (collapsibles[i].className.indexOf('collapsible') == -1) {
82 continue;
83 }
84
85 var legends = collapsibles[i].getElementsByTagName('LEGEND');
86
87 // get the legend
88 // if there is no legend, we do not touch the fieldset at all.
89 // we assume that if there is a legend, there is only
90 // one. nothing else makes any sense
91 if (!legends.length) {
92 continue;
93 }
94 var legend = legends[0];
95
96 //create an anchor to handle click-events
97 var anchor = document.createElement('a');
98 anchor.href = '#';
99 anchor.onclick = toggleCollapsible;
100
101 // add the icon/button with its functionality to the legend
102 var icon = document.createElement('img');
103 icon.setAttribute('src','/@@/treeExpanded');
104 icon.setAttribute('class','collapseIcon');
105 icon.setAttribute('height','14');
106 icon.setAttribute('width','14');
107
108 // insert the icon icon at the start of the anchor
109 anchor.appendChild(icon);
110
111 // reparent all the legend's children into a span, and the span
112 // into an anchor. The span is used to underline the legend
113 // text; because the img is inside the anchor, we can't
114 // underline the whole anchor.
115 var span = document.createElement('span');
116 while (legend.hasChildNodes()) {
117 var child = legend.firstChild;
118 legend.removeChild(child);
119 span.appendChild(child);
120 }
121 anchor.appendChild(span);
122
123 // add the anchor to the legend
124 legend.appendChild(anchor);
125
126 // wrap the contents inside a div to make turning them on and
127 // off simpler. unless something very strange happens, this
128 // new div should always be the last childnode we'll give it a
129 // class to make sure.
130
131 var hiderWrapper = document.createElement('div');
132 hiderWrapper.setAttribute('class','collapseWrapper');
133
134 // also add a new div describing that the element is collapsed.
135 var collapsedDescription = document.createElement('div');
136 collapsedDescription.setAttribute('class','collapsedText');
137 collapsedDescription.style.display = 'none';
138
139 // if the fieldset has the class of "collapsed", pre-collapse
140 // it. This can be used to preserve valuable UI-space
141 if (collapsibles[i].className.indexOf('collapsed') != -1 ) {
142 icon.setAttribute('src','/@@/treeCollapsed');
143 collapsedDescription.style.display = 'block';
144 setElementClass(hiderWrapper, 'collapsed');
145 // Unhide the fieldset, now that all of its children are hidden:
146 removeElementClass(collapsibles[i], 'collapsed');
147 }
148
149 // now we have the wrapper div.. Stuff all the contents inside it
150 var nl = collapsibles[i].childNodes.length;
151 for (var j = 0; j < nl; j++){
152 var node = collapsibles[i].childNodes[0];
153 if (node.nodeName == 'LEGEND') {
154 if (collapsibles[i].childNodes.length > 1) {
155 hiderWrapper.appendChild(collapsibles[i].childNodes[1]);
156 }
157 } else {
158 hiderWrapper.appendChild(collapsibles[i].childNodes[0]);
159 }
160 }
161 // and add it to the document
162 collapsibles[i].appendChild(hiderWrapper);
163 collapsibles[i].insertBefore(collapsedDescription, hiderWrapper);
164 }
165}
166
167function toggleFoldable(e) {
168 var ELEMENT_NODE = 1;
169 var node = this;
170 while (node.nextSibling) {
171 node = node.nextSibling;
172 if (node.nodeType != ELEMENT_NODE) {
173 continue;
174 }
175 if (node.className.indexOf('foldable') == -1) {
176 continue;
177 }
178 if (node.style.display == 'none') {
179 node.style.display = 'inline';
180 } else {
181 node.style.display = 'none';
182 }
183 }
184}
185
186function activateFoldables() {
187 // Create links to toggle the display of foldable content.
188 var included = getElementsByTagAndClassName(
189 'span', 'foldable', document);
190 var quoted = getElementsByTagAndClassName(
191 'span', 'foldable-quoted', document);
192 var elements = concat(included, quoted);
193 for (var i = 0; i < elements.length; i++) {
194 var span = elements[i];
195 if (span.className == 'foldable-quoted') {
196 var quoted_lines = span.getElementsByTagName('br');
197 if (quoted_lines && quoted_lines.length <= 11) {
198 // We do not hide short quoted passages (12 lines) by default.
199 continue;
200 }
201 }
202
203 var ellipsis = document.createElement('a');
204 ellipsis.style.textDecoration = 'underline';
205 ellipsis.href = VOID_URL;
206 ellipsis.onclick = toggleFoldable;
207 ellipsis.appendChild(document.createTextNode('[...]'));
208
209 span.parentNode.insertBefore(ellipsis, span);
210 span.insertBefore(document.createElement('br'), span.firstChild);
211 span.style.display = 'none';
212 if (span.nextSibling) {
213 // Text lines follows this span.
214 var br = document.createElement('br');
215 span.parentNode.insertBefore(br, span.nextSibling);
216 }
217 }
218}
219
220function convertTextInputToTextArea(text_input_id, rows) {
221 var current_text_input = getElement(text_input_id);
222 var new_text_area = document.createElement("textarea");
223 var attributes = {
224 'id': text_input_id,
225 'rows': rows,
226 'name': getNodeAttribute(current_text_input, 'name'),
227 'lang': getNodeAttribute(current_text_input, 'lang'),
228 'dir': getNodeAttribute(current_text_input, 'dir')
229 };
230
231 updateNodeAttributes(new_text_area, attributes);
232
233 // we set the javascript events because updateNodeAttributes gets confused
234 // with those events, because it says that 'event' is not defined. 'event'
235 // is one of the arguments of the javascript call that is being copied.
236 new_text_area.setAttribute(
237 'onKeyPress', getNodeAttribute(current_text_input, 'onkeypress'));
238 new_text_area.setAttribute(
239 'onChange', getNodeAttribute(current_text_input, 'onchange'));
240 new_text_area.value = current_text_input.value;
241 swapDOM(current_text_input, new_text_area);
242 return new_text_area;
243}
244
245function upgradeToTextAreaForTranslation(text_input_id) {
246 var rows = 6;
247 var current_text_input = $(text_input_id);
248 var text_area = convertTextInputToTextArea(text_input_id, rows);
249 text_area.focus();
250}
251
252function insertExpansionButton(expandable_field) {
253 var button = createDOM(
254 'button', {
255 'style': 'padding: 0;',
256 'title': 'Makes the field larger, so you can see more text.'
257 }
258 );
259 var icon = createDOM(
260 'img', {
261 'alt': 'Enlarge Field',
262 'src': '/+icing/translations-add-more-lines.gif'
263 }
264 );
265 appendChildNodes(button, icon);
266 function buttonOnClick(e) {
267 upgradeToTextAreaForTranslation(expandable_field.id);
268 e.preventDefault();
269 removeElement(button);
270 return false;
271 }
272 connect(button, 'onclick', buttonOnClick);
273 insertSiblingNodesAfter(expandable_field, button);
274}
275
276function insertAllExpansionButtons() {
277 var expandable_fields = getElementsByTagAndClassName(
278 'input', 'expandable');
279 forEach(expandable_fields, insertExpansionButton);
280}
281
282function copyInnerHTMLById(from_id, to_id) {
283 var from = getElement(from_id);
284 var to = getElement(to_id);
285
286 // The replacement regex strips all tags from the html.
287 to.value = unescapeHTML(from.innerHTML.replace(/<\/?[^>]+>/gi, ""));
288
289}
290
291function writeTextIntoPluralTranslationFields(from_id,
292 to_id_pattern, nplurals) {
293 // skip when x is 0, as that is the singular
294 for (var x = 1; x < nplurals; x++) {
295 var to_id = to_id_pattern + x + "_new";
296 var to_select = to_id_pattern + x + "_new_select";
297 copyInnerHTMLById(from_id, to_id);
298 document.getElementById(to_select).checked = true;
299 }
300}
301
302function activateConstrainBugExpiration() {
303 // Constrain enable_bug_expiration to the Launchpad Bugs radio input.
304 // The Launchpad bug tracker is either the first item in a product's
305 // bugtracker field, or it is a distribution's official_malone field.
306 var bug_tracker_input = getElement('field.bugtracker.0');
307 if (! bug_tracker_input) {
308 bug_tracker_input = getElement('field.official_malone');
309 }
310 var bug_expiration_input = getElement('field.enable_bug_expiration');
311 if (! bug_tracker_input || ! bug_expiration_input) {
312 return;
313 }
314 // Disable enable_bug_expiration onload if Launchpad is not the
315 // bug tracker.
316 if (! bug_tracker_input.checked) {
317 bug_expiration_input.disabled = true;
318 }
319 constraint = function (e) {
320 if (bug_tracker_input.checked) {
321 bug_expiration_input.disabled = false;
322 bug_expiration_input.checked = true;
323 } else {
324 bug_expiration_input.checked = false;
325 bug_expiration_input.disabled = true;
326 }
327 };
328 var inputs = document.getElementsByTagName('input');
329 for (var i = 0; i < inputs.length; i++) {
330 if (inputs[i].name == 'field.bugtracker' ||
331 inputs[i].name == 'field.official_malone') {
332 inputs[i].onclick = constraint;
333 }
334 }
335}
336
337function collapseRemoteCommentReply(comment_index) {
338 var prefix = 'remote_comment_reply_';
339 $(prefix + 'tree_icon_' + comment_index).src = '/@@/treeCollapsed';
340 $(prefix + 'div_' + comment_index).style.display = 'none';
341}
342
343function expandRemoteCommentReply(comment_index) {
344 var prefix = 'remote_comment_reply_';
345 $(prefix + 'tree_icon_' + comment_index).src = '/@@/treeExpanded';
346 $(prefix + 'div_' + comment_index).style.display = 'block';
347}
348
349function toggleRemoteCommentReply(comment_index) {
350 var imgname = $('remote_comment_reply_tree_icon_' + comment_index)
351 .src.split('/')
352 .pop();
353 var expanded = (imgname == 'treeExpanded');
354 if (expanded) {
355 collapseRemoteCommentReply(comment_index);
356 } else {
357 expandRemoteCommentReply(comment_index);
358 }
359}
360
361function connectRemoteCommentReply(comment_index) {
362 YUI().use('event', function(Y) {
363 var toggleFunc = function() {
364 toggleRemoteCommentReply(comment_index);
365 return false;
366 };
367
368 var prefix = 'remote_comment_reply_expand_link_';
369
370 Y.on('load', function(e) {
371 $(prefix + comment_index).onclick = toggleFunc;
372 }, window);
373 });
374}
375
376function unescapeHTML(unescaped_string) {
377 // Based on prototype's unescapeHTML method.
378 // See launchpad bug #78788 for details.
379 var div = document.createElement('div');
380 div.innerHTML = unescaped_string;
381 return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
382}
383
0384
=== modified file 'lib/canonical/launchpad/javascript/lp/lp.js'
--- lib/canonical/launchpad/javascript/lp/lp.js 2009-12-07 12:26:37 +0000
+++ lib/canonical/launchpad/javascript/lp/lp.js 2010-01-21 23:36:33 +0000
@@ -245,221 +245,6 @@
245 });245 });
246}246}
247247
248function getContentArea() {
249 // to end all doubt on where the content sits. It also felt a bit
250 // silly doing this over and over in every function, even if it is
251 // a tiny operation. Just guarding against someone changing the
252 // names again, in the name of semantics or something.... ;)
253 var node = document.getElementById('maincontent');
254 if (!node) {node = $('content');}
255 if (!node) {node = $('mainarea');}
256 return node;
257}
258
259function toggleCollapsible(e) {
260 // this is the function that collapses/expands fieldsets.
261
262 // "this" is the node that the event is attached to
263 var node = this;
264
265 // walk up the node hierarchy until we find the <legend> element
266 while (node.nodeName.toLowerCase() != 'legend') {
267 node = node.parentNode;
268 if (!node) {
269 return false;
270 }
271 }
272
273 // the expander image is legend -> a -> img
274 var icon = node.firstChild.firstChild;
275 var legend = node;
276
277 if (icon.getAttribute('src').indexOf('/@@/treeCollapsed') != -1) {
278 // that was an ugly check, but IE rewrites image sources to
279 // absolute urls from some sick reason....
280 icon.setAttribute('src','/@@/treeExpanded');
281 swapElementClass(
282 legend.parentNode.lastChild, 'collapsed', 'expanded');
283 swapElementClass(
284 legend.parentNode.childNodes[1], 'expanded', 'collapsed');
285 } else {
286 icon.setAttribute('src','/@@/treeCollapsed');
287 swapElementClass(
288 legend.parentNode.lastChild, 'expanded', 'collapsed');
289 swapElementClass(
290 legend.parentNode.childNodes[1], 'collapsed', 'expanded');
291 }
292 return false;
293}
294
295function activateCollapsibles() {
296 // a script that searches for sections that can be (or are
297 // already) collapsed - and enables the collapse-behavior
298
299 // usage : give the class "collapsible" to a fieldset also, give
300 // it a <legend> with some descriptive text. you can also add the
301 // class "collapsed" amounting to a total of
302 // <fieldset class="collapsible collapsed"> to make the section
303 // pre-collapsed
304
305 // terminate if we hit a non-compliant DOM implementation
306 if (!document.getElementsByTagName) {
307 return false;
308 }
309 if (!document.getElementById) {
310 return false;
311 }
312
313 // only search in the content-area
314 var contentarea = getContentArea();
315 if (!contentarea) {
316 return false;
317 }
318
319 // gather all objects that are to be collapsed
320 // we only do fieldsets for now. perhaps DIVs later...
321 var collapsibles = contentarea.getElementsByTagName('fieldset');
322
323 for (var i = 0; i < collapsibles.length; i++) {
324 if (collapsibles[i].className.indexOf('collapsible') == -1) {
325 continue;
326 }
327
328 var legends = collapsibles[i].getElementsByTagName('LEGEND');
329
330 // get the legend
331 // if there is no legend, we do not touch the fieldset at all.
332 // we assume that if there is a legend, there is only
333 // one. nothing else makes any sense
334 if (!legends.length) {
335 continue;
336 }
337 var legend = legends[0];
338
339 //create an anchor to handle click-events
340 var anchor = document.createElement('a');
341 anchor.href = '#';
342 anchor.onclick = toggleCollapsible;
343
344 // add the icon/button with its functionality to the legend
345 var icon = document.createElement('img');
346 icon.setAttribute('src','/@@/treeExpanded');
347 icon.setAttribute('class','collapseIcon');
348 icon.setAttribute('height','14');
349 icon.setAttribute('width','14');
350
351 // insert the icon icon at the start of the anchor
352 anchor.appendChild(icon);
353
354 // reparent all the legend's children into a span, and the span
355 // into an anchor. The span is used to underline the legend
356 // text; because the img is inside the anchor, we can't
357 // underline the whole anchor.
358 var span = document.createElement('span');
359 while (legend.hasChildNodes()) {
360 var child = legend.firstChild;
361 legend.removeChild(child);
362 span.appendChild(child);
363 }
364 anchor.appendChild(span);
365
366 // add the anchor to the legend
367 legend.appendChild(anchor);
368
369 // wrap the contents inside a div to make turning them on and
370 // off simpler. unless something very strange happens, this
371 // new div should always be the last childnode we'll give it a
372 // class to make sure.
373
374 var hiderWrapper = document.createElement('div');
375 hiderWrapper.setAttribute('class','collapseWrapper');
376
377 // also add a new div describing that the element is collapsed.
378 var collapsedDescription = document.createElement('div');
379 collapsedDescription.setAttribute('class','collapsedText');
380 collapsedDescription.style.display = 'none';
381
382 // if the fieldset has the class of "collapsed", pre-collapse
383 // it. This can be used to preserve valuable UI-space
384 if (collapsibles[i].className.indexOf('collapsed') != -1 ) {
385 icon.setAttribute('src','/@@/treeCollapsed');
386 collapsedDescription.style.display = 'block';
387 setElementClass(hiderWrapper, 'collapsed');
388 // Unhide the fieldset, now that all of its children are hidden:
389 removeElementClass(collapsibles[i], 'collapsed');
390 }
391
392 // now we have the wrapper div.. Stuff all the contents inside it
393 var nl = collapsibles[i].childNodes.length;
394 for (var j = 0; j < nl; j++){
395 var node = collapsibles[i].childNodes[0];
396 if (node.nodeName == 'LEGEND') {
397 if (collapsibles[i].childNodes.length > 1) {
398 hiderWrapper.appendChild(collapsibles[i].childNodes[1]);
399 }
400 } else {
401 hiderWrapper.appendChild(collapsibles[i].childNodes[0]);
402 }
403 }
404 // and add it to the document
405 collapsibles[i].appendChild(hiderWrapper);
406 collapsibles[i].insertBefore(collapsedDescription, hiderWrapper);
407 }
408}
409
410function toggleFoldable(e) {
411 var ELEMENT_NODE = 1;
412 var node = this;
413 while (node.nextSibling) {
414 node = node.nextSibling;
415 if (node.nodeType != ELEMENT_NODE) {
416 continue;
417 }
418 if (node.className.indexOf('foldable') == -1) {
419 continue;
420 }
421 if (node.style.display == 'none') {
422 node.style.display = 'inline';
423 } else {
424 node.style.display = 'none';
425 }
426 }
427}
428
429function activateFoldables() {
430 // Create links to toggle the display of foldable content.
431 var included = getElementsByTagAndClassName(
432 'span', 'foldable', document);
433 var quoted = getElementsByTagAndClassName(
434 'span', 'foldable-quoted', document);
435 var elements = concat(included, quoted);
436 for (var i = 0; i < elements.length; i++) {
437 var span = elements[i];
438 if (span.className == 'foldable-quoted') {
439 var quoted_lines = span.getElementsByTagName('br');
440 if (quoted_lines && quoted_lines.length <= 11) {
441 // We do not hide short quoted passages (12 lines) by default.
442 continue;
443 }
444 }
445
446 var ellipsis = document.createElement('a');
447 ellipsis.style.textDecoration = 'underline';
448 ellipsis.href = VOID_URL;
449 ellipsis.onclick = toggleFoldable;
450 ellipsis.appendChild(document.createTextNode('[...]'));
451
452 span.parentNode.insertBefore(ellipsis, span);
453 span.insertBefore(document.createElement('br'), span.firstChild);
454 span.style.display = 'none';
455 if (span.nextSibling) {
456 // Text lines follows this span.
457 var br = document.createElement('br');
458 span.parentNode.insertBefore(br, span.nextSibling);
459 }
460 }
461}
462
463function toggleExpandableTableRow(element_id) {248function toggleExpandableTableRow(element_id) {
464 var row = document.getElementById(element_id);249 var row = document.getElementById(element_id);
465 var view_icon = document.getElementById(element_id + "-arrow");250 var view_icon = document.getElementById(element_id + "-arrow");
@@ -557,98 +342,6 @@
557 document.getElementById(widget_name).checked = true;342 document.getElementById(widget_name).checked = true;
558}343}
559344
560
561function convertTextInputToTextArea(text_input_id, rows) {
562 var current_text_input = getElement(text_input_id);
563 var new_text_area = document.createElement("textarea");
564 var attributes = {
565 'id': text_input_id,
566 'rows': rows,
567 'name': getNodeAttribute(current_text_input, 'name'),
568 'lang': getNodeAttribute(current_text_input, 'lang'),
569 'dir': getNodeAttribute(current_text_input, 'dir')
570 };
571
572 updateNodeAttributes(new_text_area, attributes);
573
574 // we set the javascript events because updateNodeAttributes gets confused
575 // with those events, because it says that 'event' is not defined. 'event'
576 // is one of the arguments of the javascript call that is being copied.
577 new_text_area.setAttribute(
578 'onKeyPress', getNodeAttribute(current_text_input, 'onkeypress'));
579 new_text_area.setAttribute(
580 'onChange', getNodeAttribute(current_text_input, 'onchange'));
581 new_text_area.value = current_text_input.value;
582 swapDOM(current_text_input, new_text_area);
583 return new_text_area;
584}
585
586
587function upgradeToTextAreaForTranslation(text_input_id) {
588 var rows = 6;
589 var current_text_input = $(text_input_id);
590 var text_area = convertTextInputToTextArea(text_input_id, rows);
591 text_area.focus();
592}
593
594function insertExpansionButton(expandable_field) {
595 var button = createDOM(
596 'button', {
597 'style': 'padding: 0;',
598 'title': 'Makes the field larger, so you can see more text.'
599 }
600 );
601 var icon = createDOM(
602 'img', {
603 'alt': 'Enlarge Field',
604 'src': '/+icing/translations-add-more-lines.gif'
605 }
606 );
607 appendChildNodes(button, icon);
608 function buttonOnClick(e) {
609 upgradeToTextAreaForTranslation(expandable_field.id);
610 e.preventDefault();
611 removeElement(button);
612 return false;
613 }
614 connect(button, 'onclick', buttonOnClick);
615 insertSiblingNodesAfter(expandable_field, button);
616}
617
618function insertAllExpansionButtons() {
619 var expandable_fields = getElementsByTagAndClassName(
620 'input', 'expandable');
621 forEach(expandable_fields, insertExpansionButton);
622}
623
624function unescapeHTML(unescaped_string) {
625 // Based on prototype's unescapeHTML method.
626 // See launchpad bug #78788 for details.
627 var div = document.createElement('div');
628 div.innerHTML = unescaped_string;
629 return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
630}
631
632function copyInnerHTMLById(from_id, to_id) {
633 var from = getElement(from_id);
634 var to = getElement(to_id);
635
636 // The replacement regex strips all tags from the html.
637 to.value = unescapeHTML(from.innerHTML.replace(/<\/?[^>]+>/gi, ""));
638
639}
640
641function writeTextIntoPluralTranslationFields(from_id,
642 to_id_pattern, nplurals) {
643 // skip when x is 0, as that is the singular
644 for (var x = 1; x < nplurals; x++) {
645 var to_id = to_id_pattern + x + "_new";
646 var to_select = to_id_pattern + x + "_new_select";
647 copyInnerHTMLById(from_id, to_id);
648 document.getElementById(to_select).checked = true;
649 }
650}
651
652function switchBugBranchFormAndWhiteboard(id) {345function switchBugBranchFormAndWhiteboard(id) {
653 var div = document.getElementById('bugbranch' + id);346 var div = document.getElementById('bugbranch' + id);
654 var wb = document.getElementById('bugbranch' + id + '-wb');347 var wb = document.getElementById('bugbranch' + id + '-wb');
@@ -703,92 +396,7 @@
703 return false;396 return false;
704}397}
705398
706function activateConstrainBugExpiration() {
707 // Constrain enable_bug_expiration to the Launchpad Bugs radio input.
708 // The Launchpad bug tracker is either the first item in a product's
709 // bugtracker field, or it is a distribution's official_malone field.
710 var bug_tracker_input = getElement('field.bugtracker.0');
711 if (! bug_tracker_input) {
712 bug_tracker_input = getElement('field.official_malone');
713 }
714 var bug_expiration_input = getElement('field.enable_bug_expiration');
715 if (! bug_tracker_input || ! bug_expiration_input) {
716 return;
717 }
718 // Disable enable_bug_expiration onload if Launchpad is not the
719 // bug tracker.
720 if (! bug_tracker_input.checked) {
721 bug_expiration_input.disabled = true;
722 }
723 constraint = function (e) {
724 if (bug_tracker_input.checked) {
725 bug_expiration_input.disabled = false;
726 bug_expiration_input.checked = true;
727 } else {
728 bug_expiration_input.checked = false;
729 bug_expiration_input.disabled = true;
730 }
731 };
732 var inputs = document.getElementsByTagName('input');
733 for (var i = 0; i < inputs.length; i++) {
734 if (inputs[i].name == 'field.bugtracker' ||
735 inputs[i].name == 'field.official_malone') {
736 inputs[i].onclick = constraint;
737 }
738 }
739}
740
741function updateField(field, enabled)399function updateField(field, enabled)
742{400{
743 field.disabled = !enabled;401 field.disabled = !enabled;
744}402}
745
746
747function collapseRemoteCommentReply(comment_index) {
748 var prefix = 'remote_comment_reply_';
749 $(prefix + 'tree_icon_' + comment_index).src = '/@@/treeCollapsed';
750 $(prefix + 'div_' + comment_index).style.display = 'none';
751}
752
753function expandRemoteCommentReply(comment_index) {
754 var prefix = 'remote_comment_reply_';
755 $(prefix + 'tree_icon_' + comment_index).src = '/@@/treeExpanded';
756 $(prefix + 'div_' + comment_index).style.display = 'block';
757}
758
759function toggleRemoteCommentReply(comment_index) {
760 var imgname = $('remote_comment_reply_tree_icon_' + comment_index)
761 .src.split('/')
762 .pop();
763 var expanded = (imgname == 'treeExpanded');
764 if (expanded) {
765 collapseRemoteCommentReply(comment_index);
766 } else {
767 expandRemoteCommentReply(comment_index);
768 }
769}
770
771function connectRemoteCommentReply(comment_index) {
772 YUI().use('event', function(Y) {
773 var toggleFunc = function() {
774 toggleRemoteCommentReply(comment_index);
775 return false;
776 };
777
778 var prefix = 'remote_comment_reply_expand_link_';
779
780 Y.on('load', function(e) {
781 $(prefix + comment_index).onclick = toggleFunc;
782 }, window);
783 });
784}
785
786function switchDisplay(tag_id1, tag_id2) {
787 var tag1 = getElement(tag_id1);
788 var tag2 = getElement(tag_id2);
789 var display = tag1.style.display;
790 tag1.style.display = tag2.style.display;
791 tag2.style.display = display;
792 return false;
793}
794
795403
=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
--- lib/lp/app/templates/base-layout-macros.pt 2010-01-08 21:39:00 +0000
+++ lib/lp/app/templates/base-layout-macros.pt 2010-01-21 23:36:33 +0000
@@ -52,6 +52,16 @@
52 do any initialization.52 do any initialization.
53 </tal:comment>53 </tal:comment>
5454
55 <tal:comment>
56 XXX mars 2010-01-21
57 We have to load MochiKit outside of the main javascript rollup so that the
58 rollup's size does not exceed 512Kb. That magic number triggers a bug
59 somewhere in the automated test system that will prevent Windmill from
60 executing.
61 </tal:comment>
62 <script type="text/javascript"
63 tal:attributes="src string:${icingroot}/MochiKit.js"></script>
64
55 <tal:devmode condition="devmode">65 <tal:devmode condition="devmode">
5666
57 <tal:comment replace="nothing">67 <tal:comment replace="nothing">
@@ -80,7 +90,7 @@
80 <script type="text/javascript"90 <script type="text/javascript"
81 tal:attributes="src string:${yui}/event/event.js"></script>91 tal:attributes="src string:${yui}/event/event.js"></script>
82 <script type="text/javascript"92 <script type="text/javascript"
83 tal:attributes="src string:${yui}/event/event-key.js"></script> 93 tal:attributes="src string:${yui}/event/event-key.js"></script>
84 <script type="text/javascript"94 <script type="text/javascript"
85 tal:attributes="src string:${yui}/event-custom/event-custom.js"></script>95 tal:attributes="src string:${yui}/event-custom/event-custom.js"></script>
86 <script type="text/javascript"96 <script type="text/javascript"
@@ -133,8 +143,6 @@
133 tal:attributes="src string:${yui}/node-menunav/node-menunav.js"></script>143 tal:attributes="src string:${yui}/node-menunav/node-menunav.js"></script>
134144
135 <script type="text/javascript"145 <script type="text/javascript"
136 tal:attributes="src string:${icingroot}/MochiKit.js"></script>
137 <script type="text/javascript"
138 tal:attributes="src string:${lazr_js}/lazr/lazr.js"></script>146 tal:attributes="src string:${lazr_js}/lazr/lazr.js"></script>
139 <script type="text/javascript"147 <script type="text/javascript"
140 tal:attributes="src string:${lazr_js}/anim/anim.js"></script>148 tal:attributes="src string:${lazr_js}/anim/anim.js"></script>
141149
=== modified file 'lib/lp/code/windmill/tests/test_branchmergeproposal_review.py'
--- lib/lp/code/windmill/tests/test_branchmergeproposal_review.py 2010-01-14 07:46:09 +0000
+++ lib/lp/code/windmill/tests/test_branchmergeproposal_review.py 2010-01-21 23:36:33 +0000
@@ -106,6 +106,21 @@
106 xpath='//div[@id="conversation"]//div[@class="boardCommentBody"]'106 xpath='//div[@id="conversation"]//div[@class="boardCommentBody"]'
107 '/pre[contains(., "%s")]' % new_comment_text)107 '/pre[contains(., "%s")]' % new_comment_text)
108108
109 def test_merge_proposal_reviewing(self):
110 """Comment on a merge proposal."""
111 client = WindmillTestClient('Code review commenting')
112 lpuser.NO_PRIV.ensure_login(client)
113
114 proposal = self.factory.makeBranchMergeProposal()
115 self.open_proposal_page(client, proposal)
116 client.waits.forElement(xpath=ADD_COMMENT_BUTTON)
117
118 new_comment_text = generate_uuid()
119 client.type(text=new_comment_text, id="field.comment")
120 client.select(id=u'field.vote', val=u'APPROVE')
121 client.click(xpath=ADD_COMMENT_BUTTON)
122 client.waits.forElement(id=u'review-no-priv', timeout=u'40000')
123
109124
110def test_suite():125def test_suite():
111 return unittest.TestLoader().loadTestsFromName(__name__)126 return unittest.TestLoader().loadTestsFromName(__name__)

Subscribers

People subscribed via source and target branches

to status/vote changes: