Merge ~pappacena/launchpad:comment-editing-ui-list-revisions into launchpad:master
- Git
- lp:~pappacena/launchpad
- comment-editing-ui-list-revisions
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged |
---|---|
Approved by: | Thiago F. Pappacena |
Approved revision: | 9e068e5c903b1409bc54fb6c58f3b88e7b8ecb97 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:comment-editing-ui-list-revisions |
Merge into: | launchpad:master |
Diff against target: |
540 lines (+318/-27) 8 files modified
lib/canonical/launchpad/icing/css/base.scss (+55/-0) lib/lp/answers/browser/question.py (+1/-3) lib/lp/answers/stories/question-workflow.txt (+1/-1) lib/lp/answers/templates/questionmessage-display.pt (+22/-2) lib/lp/services/messages/interfaces/messagerevision.py (+2/-1) lib/lp/services/messages/javascript/messages.edit.js (+103/-18) lib/lp/services/messages/javascript/tests/test_messages.edit.html (+58/-1) lib/lp/services/messages/javascript/tests/test_messages.edit.js (+76/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+403213@code.launchpad.net |
Commit message
Showing message revision list in the UI
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/canonical/launchpad/icing/css/base.scss b/lib/canonical/launchpad/icing/css/base.scss | |||
2 | index 6ee2779..1e33ca7 100644 | |||
3 | --- a/lib/canonical/launchpad/icing/css/base.scss | |||
4 | +++ b/lib/canonical/launchpad/icing/css/base.scss | |||
5 | @@ -579,6 +579,61 @@ body { | |||
6 | 579 | margin: 5px; | 579 | margin: 5px; |
7 | 580 | } | 580 | } |
8 | 581 | } | 581 | } |
9 | 582 | |||
10 | 583 | .message-revision-container { | ||
11 | 584 | position: absolute; | ||
12 | 585 | background-color: white; | ||
13 | 586 | margin-top: 20px; | ||
14 | 587 | display: none; | ||
15 | 588 | border: 1px solid #ddd; | ||
16 | 589 | width: 45em; | ||
17 | 590 | z-index: 100; | ||
18 | 591 | max-height: 200px; | ||
19 | 592 | overflow-y: auto; | ||
20 | 593 | overflow-x: hidden; | ||
21 | 594 | |||
22 | 595 | .message-revision-container-header { | ||
23 | 596 | background-color: #eee; | ||
24 | 597 | border-bottom: 1px solid #ddd; | ||
25 | 598 | padding: 10px; | ||
26 | 599 | |||
27 | 600 | span { | ||
28 | 601 | text-align: left; | ||
29 | 602 | display: inline-block; | ||
30 | 603 | font-size: 110%; | ||
31 | 604 | font-weight: bold; | ||
32 | 605 | } | ||
33 | 606 | |||
34 | 607 | img { | ||
35 | 608 | float: right; | ||
36 | 609 | cursor: pointer; | ||
37 | 610 | } | ||
38 | 611 | } | ||
39 | 612 | |||
40 | 613 | .message-revision-item { | ||
41 | 614 | border-bottom: 1px solid #ddd; | ||
42 | 615 | padding: 2px; | ||
43 | 616 | |||
44 | 617 | .message-revision-title { | ||
45 | 618 | padding: 5px; | ||
46 | 619 | cursor: pointer; | ||
47 | 620 | font-weight: 300; | ||
48 | 621 | } | ||
49 | 622 | |||
50 | 623 | .message-revision-body { | ||
51 | 624 | display: none; | ||
52 | 625 | padding-left: 20px; | ||
53 | 626 | padding-bottom: 10px; | ||
54 | 627 | } | ||
55 | 628 | } | ||
56 | 629 | |||
57 | 630 | .active { | ||
58 | 631 | background-color: #eee; | ||
59 | 632 | .message-revision-title { | ||
60 | 633 | font-weight: bold; | ||
61 | 634 | } | ||
62 | 635 | } | ||
63 | 636 | } | ||
64 | 582 | } | 637 | } |
65 | 583 | 638 | ||
66 | 584 | @import 'typography', | 639 | @import 'typography', |
67 | diff --git a/lib/lp/answers/browser/question.py b/lib/lp/answers/browser/question.py | |||
68 | index 9d4986d..52cff40 100644 | |||
69 | --- a/lib/lp/answers/browser/question.py | |||
70 | +++ b/lib/lp/answers/browser/question.py | |||
71 | @@ -1195,13 +1195,11 @@ class QuestionMessageDisplayView(LaunchpadView): | |||
72 | 1195 | return check_permission('launchpad.Moderate', self.context) | 1195 | return check_permission('launchpad.Moderate', self.context) |
73 | 1196 | 1196 | ||
74 | 1197 | def getBoardCommentCSSClass(self): | 1197 | def getBoardCommentCSSClass(self): |
76 | 1198 | css_classes = ["boardComment"] | 1198 | css_classes = ["boardComment", "editable-message"] |
77 | 1199 | if not self.context.visible: | 1199 | if not self.context.visible: |
78 | 1200 | # If a comment that isn't visible is being rendered, it's being | 1200 | # If a comment that isn't visible is being rendered, it's being |
79 | 1201 | # rendered for an admin or registry_expert. | 1201 | # rendered for an admin or registry_expert. |
80 | 1202 | css_classes.append("adminHiddenComment") | 1202 | css_classes.append("adminHiddenComment") |
81 | 1203 | if self.can_edit: | ||
82 | 1204 | css_classes.append("editable-message") | ||
83 | 1205 | return " ".join(css_classes) | 1203 | return " ".join(css_classes) |
84 | 1206 | 1204 | ||
85 | 1207 | @property | 1205 | @property |
86 | diff --git a/lib/lp/answers/stories/question-workflow.txt b/lib/lp/answers/stories/question-workflow.txt | |||
87 | index 5e39acd..1a0b67a 100755 | |||
88 | --- a/lib/lp/answers/stories/question-workflow.txt | |||
89 | +++ b/lib/lp/answers/stories/question-workflow.txt | |||
90 | @@ -215,7 +215,7 @@ The confirmed answer is also highlighted. | |||
91 | 215 | 215 | ||
92 | 216 | >>> soup = find_main_content(owner_browser.contents) | 216 | >>> soup = find_main_content(owner_browser.contents) |
93 | 217 | >>> bestAnswer = soup.find_all('div', 'boardComment')[-2] | 217 | >>> bestAnswer = soup.find_all('div', 'boardComment')[-2] |
95 | 218 | >>> print(bestAnswer.find('img')) | 218 | >>> print(bestAnswer.find_all('img')[1]) |
96 | 219 | <img ... src="/@@/favourite-yes" ... title="Marked as best answer"/> | 219 | <img ... src="/@@/favourite-yes" ... title="Marked as best answer"/> |
97 | 220 | 220 | ||
98 | 221 | >>> print(soup.find( | 221 | >>> print(soup.find( |
99 | diff --git a/lib/lp/answers/templates/questionmessage-display.pt b/lib/lp/answers/templates/questionmessage-display.pt | |||
100 | index fc3651c..c1ed29a 100644 | |||
101 | --- a/lib/lp/answers/templates/questionmessage-display.pt | |||
102 | +++ b/lib/lp/answers/templates/questionmessage-display.pt | |||
103 | @@ -14,6 +14,25 @@ | |||
104 | 14 | <tbody> | 14 | <tbody> |
105 | 15 | <tr> | 15 | <tr> |
106 | 16 | <td> | 16 | <td> |
107 | 17 | <div class="message-revision-container"> | ||
108 | 18 | <div class="message-revision-container-header"> | ||
109 | 19 | <span>Revision history for this message</span> | ||
110 | 20 | <img src="/+icing/build/overlay/assets/skins/sam/images/close.gif" | ||
111 | 21 | class="message-revision-close"/> | ||
112 | 22 | </div> | ||
113 | 23 | <script type="text/template"> | ||
114 | 24 | <div class='message-revision-item'> | ||
115 | 25 | <div class='message-revision-title'> | ||
116 | 26 | <a class="js-action"> | ||
117 | 27 | Revision #{revision}, created at {date_created} | ||
118 | 28 | </a> | ||
119 | 29 | </div> | ||
120 | 30 | <div class='message-revision-body'>{content}</div> | ||
121 | 31 | </div> | ||
122 | 32 | </script> | ||
123 | 33 | |||
124 | 34 | <div class="message-revision-list"></div> | ||
125 | 35 | </div> | ||
126 | 17 | <tal:bestanswer condition="view/isBestAnswer"> | 36 | <tal:bestanswer condition="view/isBestAnswer"> |
127 | 18 | <img src="/@@/favourite-yes" style="float:right;" alt="Best" | 37 | <img src="/@@/favourite-yes" style="float:right;" alt="Best" |
128 | 19 | title="Marked as best answer" /> | 38 | title="Marked as best answer" /> |
129 | @@ -28,11 +47,12 @@ | |||
130 | 28 | datetime context/datecreated/fmt:isodate" | 47 | datetime context/datecreated/fmt:isodate" |
131 | 29 | tal:content="context/datecreated/fmt:displaydate">Thursday 13:21 | 48 | tal:content="context/datecreated/fmt:displaydate">Thursday 13:21 |
132 | 30 | </time><span class="editable-message-last-edit-date"><tal:last-edit condition="context/date_last_edited"> | 49 | </time><span class="editable-message-last-edit-date"><tal:last-edit condition="context/date_last_edited"> |
134 | 31 | (last edit <time | 50 | <a href="#" class="editable-message-last-edit-link" |
135 | 51 | tal:condition="context/date_last_edited">(last edit <time | ||
136 | 32 | itemprop="editTime" | 52 | itemprop="editTime" |
137 | 33 | tal:attributes="title context/date_last_edited/fmt:datetime; | 53 | tal:attributes="title context/date_last_edited/fmt:datetime; |
138 | 34 | datetime context/date_last_edited/fmt:isodate" | 54 | datetime context/date_last_edited/fmt:isodate" |
140 | 35 | tal:content="context/date_last_edited/fmt:displaydate" />)</tal:last-edit>: | 55 | tal:content="context/date_last_edited/fmt:displaydate" />)</a></tal:last-edit>: |
141 | 36 | </span> | 56 | </span> |
142 | 37 | </td> | 57 | </td> |
143 | 38 | <td> | 58 | <td> |
144 | diff --git a/lib/lp/services/messages/interfaces/messagerevision.py b/lib/lp/services/messages/interfaces/messagerevision.py | |||
145 | index 3ee5a90..aa0ff0a 100644 | |||
146 | --- a/lib/lp/services/messages/interfaces/messagerevision.py | |||
147 | +++ b/lib/lp/services/messages/interfaces/messagerevision.py | |||
148 | @@ -35,7 +35,8 @@ class IMessageRevisionView(Interface): | |||
149 | 35 | """IMessageRevision readable attributes.""" | 35 | """IMessageRevision readable attributes.""" |
150 | 36 | id = Int(title=_("ID"), required=True, readonly=True) | 36 | id = Int(title=_("ID"), required=True, readonly=True) |
151 | 37 | 37 | ||
153 | 38 | revision = Int(title=_("Revision number"), required=True, readonly=True) | 38 | revision = exported( |
154 | 39 | Int(title=_("Revision number"), required=True, readonly=True)) | ||
155 | 39 | 40 | ||
156 | 40 | content = exported(Text( | 41 | content = exported(Text( |
157 | 41 | title=_("The message at the given revision"), | 42 | title=_("The message at the given revision"), |
158 | diff --git a/lib/lp/services/messages/javascript/messages.edit.js b/lib/lp/services/messages/javascript/messages.edit.js | |||
159 | index a1e6806..f84bf72 100644 | |||
160 | --- a/lib/lp/services/messages/javascript/messages.edit.js | |||
161 | +++ b/lib/lp/services/messages/javascript/messages.edit.js | |||
162 | @@ -2,7 +2,8 @@ | |||
163 | 2 | * GNU Affero General Public License version 3 (see the file LICENSE). | 2 | * GNU Affero General Public License version 3 (see the file LICENSE). |
164 | 3 | * | 3 | * |
165 | 4 | * This modules controls HTML comments in order to make them editable. To do | 4 | * This modules controls HTML comments in order to make them editable. To do |
167 | 5 | * so, it requires: | 5 | * so, it requires some definitions (see test_messages.edit.html file for the |
168 | 6 | * complete structure reference): | ||
169 | 6 | * - A div container with the class .editable-message containing everything | 7 | * - A div container with the class .editable-message containing everything |
170 | 7 | * else related to the message | 8 | * else related to the message |
171 | 8 | * - A data-baseurl="/path/to/msg" on the .editable-message container | 9 | * - A data-baseurl="/path/to/msg" on the .editable-message container |
172 | @@ -14,6 +15,16 @@ | |||
173 | 14 | * - A .editable-message-last-edit-date span, where we update the date of the | 15 | * - A .editable-message-last-edit-date span, where we update the date of the |
174 | 15 | * last message editing. | 16 | * last message editing. |
175 | 16 | * | 17 | * |
176 | 18 | * For the message revision history, you should define: | ||
177 | 19 | * - A .editable-message-last-edit-link, that will show the revisions pop-up | ||
178 | 20 | * once clicked. | ||
179 | 21 | * - A .message-revision-container, holding the header and the body of the | ||
180 | 22 | * revision list, and the template for each revision item. | ||
181 | 23 | * - A .message-revision-container-header. | ||
182 | 24 | * - A .message-revision-list, where the revisions will be placed. | ||
183 | 25 | * - A <script type="text/template">, with the definition of how each item | ||
184 | 26 | * should look like. | ||
185 | 27 | * | ||
186 | 17 | * Once those HTML elements are available in the page, this module should be | 28 | * Once those HTML elements are available in the page, this module should be |
187 | 18 | * initialized with `lp.services.messages.edit.setup()`. | 29 | * initialized with `lp.services.messages.edit.setup()`. |
188 | 19 | * | 30 | * |
189 | @@ -23,15 +34,6 @@ | |||
190 | 23 | YUI.add('lp.services.messages.edit', function(Y) { | 34 | YUI.add('lp.services.messages.edit', function(Y) { |
191 | 24 | var module = Y.namespace('lp.services.messages.edit'); | 35 | var module = Y.namespace('lp.services.messages.edit'); |
192 | 25 | 36 | ||
193 | 26 | // XXX pappacena 2021-05-21: We should drop this message once we have a | ||
194 | 27 | // way to list the old message revisions in the web UI. | ||
195 | 28 | module.msg_edit_success_notification = ( | ||
196 | 29 | "Message edited, but the original content may still be publicly " + | ||
197 | 30 | "visible using the API.<br />Please " + | ||
198 | 31 | "<a href='https://launchpad.net/+apidoc/devel.html#message'>" + | ||
199 | 32 | "check the API documentation</a> in case you " + | ||
200 | 33 | "need to remove old message revisions." | ||
201 | 34 | ); | ||
202 | 35 | module.msg_edit_error_notification = ( | 37 | module.msg_edit_error_notification = ( |
203 | 36 | "There was an error updating the comment. " + | 38 | "There was an error updating the comment. " + |
204 | 37 | "Please try again in a few minutes." | 39 | "Please try again in a few minutes." |
205 | @@ -115,7 +117,7 @@ YUI.add('lp.services.messages.edit', function(Y) { | |||
206 | 115 | // text area. | 117 | // text area. |
207 | 116 | module.showEditMessageField(elements.msg_body, elements.msg_form); | 118 | module.showEditMessageField(elements.msg_body, elements.msg_form); |
208 | 117 | elements.msg_form.one('textarea').getDOMNode().focus(); | 119 | elements.msg_form.one('textarea').getDOMNode().focus(); |
210 | 118 | } | 120 | }; |
211 | 119 | 121 | ||
212 | 120 | // What to do when a user clicks "cancel edit" button. | 122 | // What to do when a user clicks "cancel edit" button. |
213 | 121 | module.onEditCancelClick = function(elements) { | 123 | module.onEditCancelClick = function(elements) { |
214 | @@ -134,13 +136,15 @@ YUI.add('lp.services.messages.edit', function(Y) { | |||
215 | 134 | 136 | ||
216 | 135 | module.saveMessageContent( | 137 | module.saveMessageContent( |
217 | 136 | baseurl, new_content, | 138 | baseurl, new_content, |
219 | 137 | function(err) { module.onMessageSaved(elements, new_content); }, | 139 | function(err) { |
220 | 140 | module.onMessageSaved(elements, new_content, baseurl); | ||
221 | 141 | }, | ||
222 | 138 | function(err) { module.onMessageSaveError(elements, err); } | 142 | function(err) { module.onMessageSaveError(elements, err); } |
223 | 139 | ); | 143 | ); |
224 | 140 | }; | 144 | }; |
225 | 141 | 145 | ||
226 | 142 | // What to do when a message is saved in the backend. | 146 | // What to do when a message is saved in the backend. |
228 | 143 | module.onMessageSaved = function(elements, new_content) { | 147 | module.onMessageSaved = function(elements, new_content, baseurl) { |
229 | 144 | // When finished updating at the backend, re-enable UI | 148 | // When finished updating at the backend, re-enable UI |
230 | 145 | // elements and display the new message. | 149 | // elements and display the new message. |
231 | 146 | var html_msg = module.htmlify_msg(new_content); | 150 | var html_msg = module.htmlify_msg(new_content); |
232 | @@ -150,11 +154,18 @@ YUI.add('lp.services.messages.edit', function(Y) { | |||
233 | 150 | elements.textarea.getDOMNode().disabled = false; | 154 | elements.textarea.getDOMNode().disabled = false; |
234 | 151 | elements.update_btn.getDOMNode().disabled = false; | 155 | elements.update_btn.getDOMNode().disabled = false; |
235 | 152 | module.hideLoading(elements.container); | 156 | module.hideLoading(elements.container); |
236 | 153 | module.showNotification( | ||
237 | 154 | elements.container, | ||
238 | 155 | module.msg_edit_success_notification, true); | ||
239 | 156 | elements.last_edit.getDOMNode().innerHTML = ( | 157 | elements.last_edit.getDOMNode().innerHTML = ( |
241 | 157 | ' (last edit a moment ago): '); | 158 | '<a href="#" class="editable-message-last-edit-link">' + |
242 | 159 | '(last edit a moment ago):' + | ||
243 | 160 | '</a>'); | ||
244 | 161 | |||
245 | 162 | // Wire click handler to the newly created "last edit" button. | ||
246 | 163 | var last_edit_btn = elements.container.one( | ||
247 | 164 | '.editable-message-last-edit-link'); | ||
248 | 165 | last_edit_btn.on('click', function(e) { | ||
249 | 166 | e.preventDefault(); | ||
250 | 167 | module.onLastEditClick(elements, baseurl); | ||
251 | 168 | }); | ||
252 | 158 | }; | 169 | }; |
253 | 159 | 170 | ||
254 | 160 | // What to do when a message fails to update on the backend. | 171 | // What to do when a message fails to update on the backend. |
255 | @@ -168,6 +179,72 @@ YUI.add('lp.services.messages.edit', function(Y) { | |||
256 | 168 | elements.update_btn.getDOMNode().disabled = false; | 179 | elements.update_btn.getDOMNode().disabled = false; |
257 | 169 | }; | 180 | }; |
258 | 170 | 181 | ||
259 | 182 | module.fillMessageRevisions = function(elements, revisions) { | ||
260 | 183 | // Position the message revision list element. | ||
261 | 184 | revisions = revisions.reverse(); | ||
262 | 185 | var revisions_container = elements.container.one( | ||
263 | 186 | ".message-revision-container"); | ||
264 | 187 | var last_edit_el = elements.last_edit.getDOMNode(); | ||
265 | 188 | var target_position = last_edit_el.getBoundingClientRect(); | ||
266 | 189 | var nodes_holder = revisions_container.one(".message-revision-list"); | ||
267 | 190 | var template = revisions_container.one( | ||
268 | 191 | "script[type='text/template']").getDOMNode().innerHTML; | ||
269 | 192 | |||
270 | 193 | revisions_container.setStyle('left', target_position.left); | ||
271 | 194 | revisions_container.setStyle('display', 'block'); | ||
272 | 195 | revisions_container.one(".message-revision-close").on( | ||
273 | 196 | "click", function() { | ||
274 | 197 | nodes_holder.getDOMNode().innerHTML = ''; | ||
275 | 198 | revisions_container.setStyle('display', 'none'); | ||
276 | 199 | }); | ||
277 | 200 | |||
278 | 201 | var content = ""; | ||
279 | 202 | revisions.forEach(function(rev) { | ||
280 | 203 | var attrs = rev.getAttrs(); | ||
281 | 204 | var date_created = new Date(attrs.date_created); | ||
282 | 205 | attrs.date_created = date_created.toLocaleString(); | ||
283 | 206 | attrs.content = module.htmlify_msg(attrs.content); | ||
284 | 207 | content += Y.Lang.sub(template, attrs); | ||
285 | 208 | }); | ||
286 | 209 | |||
287 | 210 | nodes_holder.getDOMNode().innerHTML = content; | ||
288 | 211 | nodes_holder.all(".message-revision-item").each(function(rev_item) { | ||
289 | 212 | rev_item.one(".message-revision-title").on('click', function() { | ||
290 | 213 | nodes_holder.all('.message-revision-body').setStyle( | ||
291 | 214 | 'display', 'none'); | ||
292 | 215 | var body = rev_item.one('.message-revision-body'); | ||
293 | 216 | var current_display = body.getStyle('display'); | ||
294 | 217 | body.setStyle( | ||
295 | 218 | 'display', current_display === 'block'? 'none' : 'block'); | ||
296 | 219 | nodes_holder.all(".message-revision-item").removeClass( | ||
297 | 220 | 'active'); | ||
298 | 221 | rev_item.addClass('active'); | ||
299 | 222 | }); | ||
300 | 223 | }); | ||
301 | 224 | }; | ||
302 | 225 | |||
303 | 226 | module.onLastEditClick = function(elements, baseurl) { | ||
304 | 227 | // Hide all open revision containers. | ||
305 | 228 | Y.all('.message-revision-container').each(function(container) { | ||
306 | 229 | container.setStyle('display', 'none'); | ||
307 | 230 | }); | ||
308 | 231 | |||
309 | 232 | var url = "/api/devel" + baseurl + "/revisions"; | ||
310 | 233 | var config = { | ||
311 | 234 | on: { | ||
312 | 235 | success: function(response) { | ||
313 | 236 | module.fillMessageRevisions(elements, response.entries); | ||
314 | 237 | }, | ||
315 | 238 | failure: function(err) { | ||
316 | 239 | alert("Error fetching revisions."); | ||
317 | 240 | } | ||
318 | 241 | }, | ||
319 | 242 | // XXX pappacena 2021-05-19: Pagination will be needed here. | ||
320 | 243 | size: 100 | ||
321 | 244 | }; | ||
322 | 245 | this.lp_client.get(url, config); | ||
323 | 246 | }; | ||
324 | 247 | |||
325 | 171 | module.wireEventHandlers = function(container) { | 248 | module.wireEventHandlers = function(container) { |
326 | 172 | var node = container.getDOMNode(); | 249 | var node = container.getDOMNode(); |
327 | 173 | var baseurl = node.dataset.baseurl; | 250 | var baseurl = node.dataset.baseurl; |
328 | @@ -179,7 +256,8 @@ YUI.add('lp.services.messages.edit', function(Y) { | |||
329 | 179 | "edit_btn": container.one('.editable-message-edit-btn'), | 256 | "edit_btn": container.one('.editable-message-edit-btn'), |
330 | 180 | "update_btn": container.one('.editable-message-update-btn'), | 257 | "update_btn": container.one('.editable-message-update-btn'), |
331 | 181 | "cancel_btn": container.one('.editable-message-cancel-btn'), | 258 | "cancel_btn": container.one('.editable-message-cancel-btn'), |
333 | 182 | "last_edit": container.one('.editable-message-last-edit-date') | 259 | "last_edit": container.one('.editable-message-last-edit-date'), |
334 | 260 | "last_edit_btn": container.one('.editable-message-last-edit-link') | ||
335 | 183 | }; | 261 | }; |
336 | 184 | // If the msg body or the msg form are not defined, don't try to do | 262 | // If the msg body or the msg form are not defined, don't try to do |
337 | 185 | // anything else. | 263 | // anything else. |
338 | @@ -190,6 +268,13 @@ YUI.add('lp.services.messages.edit', function(Y) { | |||
339 | 190 | 268 | ||
340 | 191 | module.hideEditMessageField(elements.msg_body, elements.msg_form); | 269 | module.hideEditMessageField(elements.msg_body, elements.msg_form); |
341 | 192 | 270 | ||
342 | 271 | if (elements.last_edit_btn) { | ||
343 | 272 | elements.last_edit_btn.on('click', function(e) { | ||
344 | 273 | e.preventDefault(); | ||
345 | 274 | module.onLastEditClick(elements, baseurl); | ||
346 | 275 | }); | ||
347 | 276 | } | ||
348 | 277 | |||
349 | 193 | // If the edit button is not present, do not try to bind the | 278 | // If the edit button is not present, do not try to bind the |
350 | 194 | // handlers. | 279 | // handlers. |
351 | 195 | if (!elements.edit_btn || !baseurl) { | 280 | if (!elements.edit_btn || !baseurl) { |
352 | diff --git a/lib/lp/services/messages/javascript/tests/test_messages.edit.html b/lib/lp/services/messages/javascript/tests/test_messages.edit.html | |||
353 | index 3036d88..e6a27c2 100644 | |||
354 | --- a/lib/lp/services/messages/javascript/tests/test_messages.edit.html | |||
355 | +++ b/lib/lp/services/messages/javascript/tests/test_messages.edit.html | |||
356 | @@ -52,10 +52,45 @@ GNU Affero General Public License version 3 (see the file LICENSE). | |||
357 | 52 | <li>lp.services.messages.edit.test</li> | 52 | <li>lp.services.messages.edit.test</li> |
358 | 53 | </ul> | 53 | </ul> |
359 | 54 | 54 | ||
360 | 55 | <!-- First editable message --> | ||
361 | 55 | <div class="editable-message" id="first-message" | 56 | <div class="editable-message" id="first-message" |
362 | 56 | data-baseurl="/message/1"> | 57 | data-baseurl="/message/1"> |
363 | 58 | <!-- Revision list container and its template --> | ||
364 | 59 | <div class="message-revision-container"> | ||
365 | 60 | <!-- The revision list pop-up header, with the image that closes | ||
366 | 61 | the pop-up when clicked. --> | ||
367 | 62 | <div class="message-revision-container-header"> | ||
368 | 63 | <span>Revision history for this message</span> | ||
369 | 64 | <img src="/+icing/build/overlay/assets/skins/sam/images/close.gif" | ||
370 | 65 | class="message-revision-close"/> | ||
371 | 66 | </div> | ||
372 | 67 | <!-- The template for each revision item in the list. The | ||
373 | 68 | content of this tag will be repeated multiple times, replacing | ||
374 | 69 | the {variable} with each MessageRevision entry returned by | ||
375 | 70 | the API. | ||
376 | 71 | --> | ||
377 | 72 | <script type="text/template"> | ||
378 | 73 | <div class='message-revision-item'> | ||
379 | 74 | <!-- The description of the revision, that will expand the | ||
380 | 75 | revision content once clicked. --> | ||
381 | 76 | <div class='message-revision-title'> | ||
382 | 77 | <a class="js-action">Revision #{revision}, created at {date_created}</a> | ||
383 | 78 | </div> | ||
384 | 79 | <!-- The revision content itself --> | ||
385 | 80 | <div class='message-revision-body'>{content}</div> | ||
386 | 81 | </div> | ||
387 | 82 | </script> | ||
388 | 83 | <!-- This is the place where all the revisions will be placed, | ||
389 | 84 | by applying the above template to each MessageRevision entry | ||
390 | 85 | returned by the API. | ||
391 | 86 | --> | ||
392 | 87 | <div class="message-revision-list"></div> | ||
393 | 88 | </div> | ||
394 | 89 | |||
395 | 90 | <!-- Message body --> | ||
396 | 57 | <div> | 91 | <div> |
397 | 58 | Comment from @some-user a while ago | 92 | Comment from @some-user a while ago |
398 | 93 | Comment from @some-user a while ago | ||
399 | 59 | <span class="editable-message-last-edit-date">:</span> | 94 | <span class="editable-message-last-edit-date">:</span> |
400 | 60 | </div> | 95 | </div> |
401 | 61 | <div class="editable-message-body"> | 96 | <div class="editable-message-body"> |
402 | @@ -71,12 +106,34 @@ GNU Affero General Public License version 3 (see the file LICENSE). | |||
403 | 71 | </div> | 106 | </div> |
404 | 72 | </div> | 107 | </div> |
405 | 73 | 108 | ||
406 | 109 | <!-- Second editable message --> | ||
407 | 74 | <div class="editable-message" id="second-message" | 110 | <div class="editable-message" id="second-message" |
408 | 75 | data-baseurl="/message/2"> | 111 | data-baseurl="/message/2"> |
409 | 112 | <!-- Revision list container and its template --> | ||
410 | 113 | <div class="message-revision-container"> | ||
411 | 114 | <div class="message-revision-container-header"> | ||
412 | 115 | <span>Revision history for this message</span> | ||
413 | 116 | <img src="/+icing/build/overlay/assets/skins/sam/images/close.gif" | ||
414 | 117 | class="message-revision-close"/> | ||
415 | 118 | </div> | ||
416 | 119 | <script type="text/template"> | ||
417 | 120 | <div class='message-revision-item'> | ||
418 | 121 | <div class='message-revision-title'> | ||
419 | 122 | <a class="js-action">Revision #{revision}, created at {date_created}</a> | ||
420 | 123 | </div> | ||
421 | 124 | <div class='message-revision-body'>{content}</div> | ||
422 | 125 | </div> | ||
423 | 126 | </script> | ||
424 | 127 | <div class="message-revision-list"></div> | ||
425 | 128 | </div> | ||
426 | 129 | |||
427 | 130 | <!-- Message body --> | ||
428 | 76 | <div> | 131 | <div> |
429 | 77 | Comment from @some-user a while ago | 132 | Comment from @some-user a while ago |
430 | 78 | <span class="editable-message-last-edit-date"> | 133 | <span class="editable-message-last-edit-date"> |
432 | 79 | (last edit 5 minutes ago): | 134 | <a href="#" class="editable-message-last-edit-link"> |
433 | 135 | (last edit 5 minutes ago): | ||
434 | 136 | </a> | ||
435 | 80 | </span> | 137 | </span> |
436 | 81 | </div> | 138 | </div> |
437 | 82 | <div class="editable-message-body"> | 139 | <div class="editable-message-body"> |
438 | diff --git a/lib/lp/services/messages/javascript/tests/test_messages.edit.js b/lib/lp/services/messages/javascript/tests/test_messages.edit.js | |||
439 | index a8a982c..5d12dab 100644 | |||
440 | --- a/lib/lp/services/messages/javascript/tests/test_messages.edit.js | |||
441 | +++ b/lib/lp/services/messages/javascript/tests/test_messages.edit.js | |||
442 | @@ -35,6 +35,14 @@ YUI.add('lp.services.messages.edit.test', function(Y) { | |||
443 | 35 | this.containers[0].one(".editable-message-last-edit-date"), | 35 | this.containers[0].one(".editable-message-last-edit-date"), |
444 | 36 | this.containers[1].one(".editable-message-last-edit-date") | 36 | this.containers[1].one(".editable-message-last-edit-date") |
445 | 37 | ]; | 37 | ]; |
446 | 38 | this.revision_history_link = [ | ||
447 | 39 | this.containers[0].one(".editable-message-last-edit-link"), | ||
448 | 40 | this.containers[1].one(".editable-message-last-edit-link") | ||
449 | 41 | ]; | ||
450 | 42 | this.revision_history_lists = [ | ||
451 | 43 | this.containers[0].one(".message-revision-list"), | ||
452 | 44 | this.containers[1].one(".message-revision-list") | ||
453 | 45 | ]; | ||
454 | 38 | this.msg_bodies = [ | 46 | this.msg_bodies = [ |
455 | 39 | this.containers[0].one(".editable-message-body"), | 47 | this.containers[0].one(".editable-message-body"), |
456 | 40 | this.containers[1].one(".editable-message-body") | 48 | this.containers[1].one(".editable-message-body") |
457 | @@ -73,6 +81,7 @@ YUI.add('lp.services.messages.edit.test', function(Y) { | |||
458 | 73 | this.last_edit[0].getDOMNode().innerHTML = ':'; | 81 | this.last_edit[0].getDOMNode().innerHTML = ':'; |
459 | 74 | this.last_edit[1].getDOMNode().innerHTML = ( | 82 | this.last_edit[1].getDOMNode().innerHTML = ( |
460 | 75 | '(last edit 5 minutes ago):'); | 83 | '(last edit 5 minutes ago):'); |
461 | 84 | this.revision_history_lists[i].getDOMNode().innerHTML = ''; | ||
462 | 76 | } | 85 | } |
463 | 77 | }, | 86 | }, |
464 | 78 | 87 | ||
465 | @@ -160,8 +169,74 @@ YUI.add('lp.services.messages.edit.test', function(Y) { | |||
466 | 160 | // Check that the "last edit" header changed. | 169 | // Check that the "last edit" header changed. |
467 | 161 | Y.Assert.areSame(":", this.last_edit[0].getDOMNode().innerHTML); | 170 | Y.Assert.areSame(":", this.last_edit[0].getDOMNode().innerHTML); |
468 | 162 | Y.Assert.areSame( | 171 | Y.Assert.areSame( |
470 | 163 | " (last edit a moment ago): ", | 172 | '<a href="#" class="editable-message-last-edit-link">' + |
471 | 173 | '(last edit a moment ago):</a>', | ||
472 | 164 | this.last_edit[1].getDOMNode().innerHTML); | 174 | this.last_edit[1].getDOMNode().innerHTML); |
473 | 175 | }, | ||
474 | 176 | |||
475 | 177 | test_shows_revision_history: function() { | ||
476 | 178 | module.setup(); | ||
477 | 179 | module.lp_client.io_provider = new Y.lp.testing.mockio.MockIo(); | ||
478 | 180 | var revisions_container = this.containers[1].one( | ||
479 | 181 | '.message-revision-container'); | ||
480 | 182 | // Revisions container should not be shown before it's clicked. | ||
481 | 183 | Y.Assert.areSame('none', revisions_container.getStyle('display')); | ||
482 | 184 | |||
483 | 185 | // Simulates the click and the request. | ||
484 | 186 | this.revision_history_link[1].simulate('click'); | ||
485 | 187 | |||
486 | 188 | var response = { | ||
487 | 189 | "total_size": 1, "start": 0, | ||
488 | 190 | "entries": [{ | ||
489 | 191 | "revision": 1, | ||
490 | 192 | "content": "content 1", | ||
491 | 193 | "date_created": "2021-05-10T19:41:36.102749+00:00" | ||
492 | 194 | }, { | ||
493 | 195 | "revision": 2, | ||
494 | 196 | "content": "content 2", | ||
495 | 197 | "date_created": "2021-05-11T22:17:11.000123+00:00" | ||
496 | 198 | }] | ||
497 | 199 | }; | ||
498 | 200 | module.lp_client.io_provider.success({ | ||
499 | 201 | responseText: Y.JSON.stringify(response), | ||
500 | 202 | responseHeaders: {'Content-Type': 'application/json'} | ||
501 | 203 | }); | ||
502 | 204 | |||
503 | 205 | // Make sure it didn't fill the first container. | ||
504 | 206 | Y.Assert.areSame( | ||
505 | 207 | "", this.revision_history_lists[0].getDOMNode().innerHTML); | ||
506 | 208 | |||
507 | 209 | // Check that revision list pop-up is shown | ||
508 | 210 | Y.Assert.areSame('block', revisions_container.getStyle('display')); | ||
509 | 211 | |||
510 | 212 | // Check the items in the pop-up revisions list. | ||
511 | 213 | var revisions = this.revision_history_lists[1].all( | ||
512 | 214 | ".message-revision-item"); | ||
513 | 215 | Y.Assert.areSame(2, revisions.size()); | ||
514 | 216 | revisions.each(function(rev, i) { | ||
515 | 217 | // Entries are shown in reverse order. | ||
516 | 218 | var entry = response.entries[response.entries.length - i -1]; | ||
517 | 219 | var date_created = new Date(entry.date_created); | ||
518 | 220 | entry['formatted_date'] = date_created.toLocaleString(); | ||
519 | 221 | var title = rev.one('.message-revision-title'); | ||
520 | 222 | var body = rev.one('.message-revision-body'); | ||
521 | 223 | var expected_title = Y.Lang.sub( | ||
522 | 224 | '<a class="js-action">' + | ||
523 | 225 | 'Revision #{revision}, created at {formatted_date}' + | ||
524 | 226 | '</a>', | ||
525 | 227 | entry); | ||
526 | 228 | Y.Assert.areSame( | ||
527 | 229 | expected_title, title.getDOMNode().innerHTML.trim()); | ||
528 | 230 | Y.Assert.areSame( | ||
529 | 231 | module.htmlify_msg(entry.content), | ||
530 | 232 | body.getDOMNode().innerHTML); | ||
531 | 233 | }); | ||
532 | 234 | |||
533 | 235 | // Lets make sure that a click on the "close" button hides the | ||
534 | 236 | // revisions list. | ||
535 | 237 | revisions_container.one( | ||
536 | 238 | '.message-revision-close').simulate('click'); | ||
537 | 239 | Y.Assert.areSame('none', revisions_container.getStyle('display')); | ||
538 | 165 | } | 240 | } |
539 | 166 | 241 | ||
540 | 167 | }; | 242 | }; |
Pushed the requested changes. This should be good to go now.