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

Subscribers

People subscribed via source and target branches

to status/vote changes: