|
1 // globals used: gConf, gPrefs, gLayout, gSync |
|
2 |
|
3 |
|
4 // IComment == IHM comment class |
|
5 IComment = function() { |
|
6 |
|
7 this.commentId = null ; |
|
8 |
|
9 var iCommentWidth = gLayout.getTopICommentsWidth() ; |
|
10 var iCommentLeft = gConf['iCommentLeftPadding'] ; |
|
11 |
|
12 var changeToPending = gettext("change comment state to pending") ; |
|
13 var changeToApprove = gettext("change comment state to approved") ; |
|
14 var changeToUnapprove= gettext("change comment state to unapproved") ; |
|
15 var cancelChange = gettext("cancel changing the state of this comment") ; |
|
16 var pending = gettext("pending") ; |
|
17 var approved = gettext("approved") ; |
|
18 var unapproved = gettext("unapproved") ; |
|
19 var cancel = gettext("cancel") ; |
|
20 var showReplies = gettext("show replies") ; |
|
21 var changeTo= gettext("change to:") ; |
|
22 var reply = gettext("reply") ; |
|
23 var editComment = gettext("edit comment") ; |
|
24 var deleteComment = gettext("delete comment") ; |
|
25 var edit = gettext("edit") ; |
|
26 var del = gettext("delete") ; |
|
27 var close = gettext("close") ; |
|
28 |
|
29 // no header, no body yet |
|
30 this.overlay = new CY.Overlay( { |
|
31 zIndex :3, |
|
32 shim :false, /* until we really need it, no shim */ |
|
33 visible :false, |
|
34 width : iCommentWidth, |
|
35 xy : [ iCommentLeft, 0 ], |
|
36 headerContent : '<div class="icomment-header">' + |
|
37 '<div class="c-iactions">' + |
|
38 '<a class="c-moderate c-action" title="">'+ "vis" +'</a>' + " " + |
|
39 '<a class="c-edit c-action" title="'+ editComment +'" alt="' + editComment + '">' + edit + '</a>' + " " + |
|
40 '<a class="c-delete c-action" title="'+ deleteComment +'" alt="' + deleteComment + '">' + del + '</a>' + " " + |
|
41 '</div>' + |
|
42 '<div class="c-state-actions displaynone">' + |
|
43 changeTo + ' ' + |
|
44 '<a class="c-state-pending c-action" title="' + changeToPending + '" alt="' + changeToPending + '">'+ pending +'</a>' + " " + |
|
45 '<a class="c-state-approved c-action" title="' + changeToApprove + '" alt="' + changeToApprove + '">'+ approved +'</a>' + " " + |
|
46 '<a class="c-state-unapproved c-action" title="' + changeToUnapprove + '" alt="' + changeToUnapprove + '">'+ unapproved +'</a>' + " " + |
|
47 '<a class="c-state-cancel c-action" title="' + cancelChange + '" alt="' + cancelChange + '">' + cancel +'</a>' + " " + |
|
48 '</div>' + |
|
49 '<a class="c-close c-iclose c-action" title="'+ close + '" alt="' + close + '"><em>X</em></a>' + |
|
50 '</div>', |
|
51 bodyContent : '<div class="icomment-body">' + |
|
52 '<span class="c-content"></span>' + |
|
53 '<span class="c-ireplyactions">' + |
|
54 '<a class="c-readreplies c-action" title="'+ showReplies +'" alt="' + showReplies + '">' + showReplies +'</a>' + " " + |
|
55 '<a class="c-reply c-action" title="'+ reply +'" alt="' + reply + '">' + reply +'</a>' + " " + |
|
56 '</span>' + |
|
57 '</div>' |
|
58 }); |
|
59 |
|
60 this.overlay.get('contentBox').addClass("c-comment") ; |
|
61 |
|
62 // attach to DOM |
|
63 this.overlay.render('#leftcolumn'); |
|
64 |
|
65 this.animation = new CY.Anim({ |
|
66 node: this.overlay.get('boundingBox'), |
|
67 duration: gPrefs.get('general','animduration'), |
|
68 easing: CY.Easing.easeOut |
|
69 }); |
|
70 |
|
71 // CY.on won't work |
|
72 this.overlay.get('contentBox').query(".c-close").on("click", this.onCloseCommentClick, this); |
|
73 this.overlay.get('contentBox').query(".c-moderate").on("click", this.onModerateCommentClick, this); |
|
74 this.overlay.get('contentBox').query(".c-state-pending").on("click", this.onPendingCommentClick, this); |
|
75 this.overlay.get('contentBox').query(".c-state-approved").on("click", this.onApprovedCommentClick, this); |
|
76 this.overlay.get('contentBox').query(".c-state-unapproved").on("click", this.onUnapprovedCommentClick, this); |
|
77 this.overlay.get('contentBox').query(".c-state-cancel").on("click", this.onCancelStateChangeClick, this); |
|
78 this.overlay.get('contentBox').query(".c-edit").on("click", this.onEditCommentClick, this); |
|
79 this.overlay.get('contentBox').query(".c-delete").on("click", this.onDeleteCommentClick, this); |
|
80 this.overlay.get('contentBox').query(".c-reply").on("click", this.onReplyCommentClick, this); |
|
81 this.overlay.get('contentBox').query(".c-readreplies").on("click", this.onReadRepliesCommentClick, this); |
|
82 |
|
83 this.overlay.get('contentBox').query(".icomment-header").on("mouseenter", this.onMouseEnterHeader, this); |
|
84 this.overlay.get('contentBox').query(".icomment-header").on("mouseleave", this.onMouseLeaveHeader, this); |
|
85 |
|
86 this.overlay.get('contentBox').on("click", this.onCommentClick, this); |
|
87 } |
|
88 |
|
89 IComment.prototype = { |
|
90 // checking readyForAction is not enough because handler could be called after animation end in the case : |
|
91 // close btn is clicked before animation end (so before overlay gets hidden) but handler is called after so preventClickOn is false and close is called on an invisible overlay. |
|
92 // (whan clicking very fast on the close button while close animation is taking place). |
|
93 // SO : SHOULD ALWAYS CHECK FOR VISIBLE OVERLAY IN HANDLERS THAT COULD BE CALLED FROM AN ANIMATED OVERLAY |
|
94 onCloseCommentClick : function (e) { |
|
95 e.halt() ; // prevent click triggered on content box |
|
96 if (readyForAction() && this.isVisible()) |
|
97 gSync.closeComment(this) ; |
|
98 }, |
|
99 onModerateCommentClick : function (e) { |
|
100 e.halt() ; // prevent click triggered on content box |
|
101 if (readyForAction() && this.isVisible()) { |
|
102 this.overlay.get('contentBox').query(".c-iactions").addClass("displaynone") ; |
|
103 this.overlay.get('contentBox').query(".c-state-actions").removeClass("displaynone") ; |
|
104 } |
|
105 }, |
|
106 onPendingCommentClick : function (e) { |
|
107 e.halt() ; // prevent click triggered on content box |
|
108 if (readyForAction() && this.isVisible()) { |
|
109 gSync.moderateComment(this, 'pending') ; |
|
110 } |
|
111 }, |
|
112 onApprovedCommentClick : function (e) { |
|
113 e.halt() ; // prevent click triggered on content box |
|
114 if (readyForAction() && this.isVisible()) { |
|
115 gSync.moderateComment(this, 'approved') ; |
|
116 } |
|
117 }, |
|
118 onUnapprovedCommentClick : function (e) { |
|
119 e.halt() ; // prevent click triggered on content box |
|
120 if (readyForAction() && this.isVisible()) { |
|
121 gSync.moderateComment(this, 'unapproved') ; |
|
122 } |
|
123 }, |
|
124 onCancelStateChangeClick : function (e) { |
|
125 e.halt() ; // prevent click triggered on content box |
|
126 if (readyForAction() && this.isVisible()) { |
|
127 this.overlay.get('contentBox').query(".c-iactions").removeClass("displaynone") ; |
|
128 this.overlay.get('contentBox').query(".c-state-actions").addClass("displaynone") ; |
|
129 } |
|
130 }, |
|
131 onDeleteCommentClick : function (e) { |
|
132 e.halt() ; // prevent click triggered on content box |
|
133 if (readyForAction() && this.isVisible()) { |
|
134 gSync.removeComment(this) ; |
|
135 } |
|
136 }, |
|
137 onEditCommentClick : function (e) { |
|
138 e.halt() ; // prevent click triggered on content box |
|
139 if (readyForAction() && this.isVisible()) |
|
140 gSync.showEditForm(this) ; |
|
141 }, |
|
142 onReplyCommentClick : function (e) { |
|
143 e.halt() ; // prevent click triggered on content box |
|
144 if (readyForAction() && this.isVisible()) |
|
145 gSync.showReplyForm(this) ; |
|
146 }, |
|
147 onReadRepliesCommentClick : function (e) { |
|
148 e.halt() ; // prevent click triggered on content box |
|
149 if (readyForAction() && this.isVisible()) |
|
150 gSync.openComment(this) ; |
|
151 }, |
|
152 onCommentClick : function (e) { |
|
153 if (readyForAction() && this.isVisible()) { |
|
154 // first condition here is checking it's not clicked via a 'show comments' link |
|
155 if (e.target.get("target") == "_blank") { |
|
156 var link = e.target ; |
|
157 var showCommentUrl = sv_site_url + sv_text_view_show_comment_url ; |
|
158 if (link.get('href').indexOf(showCommentUrl) == 0) { |
|
159 var res = (new RegExp('comment_key=([^&]*)', "g")).exec(link.get('href')) ; |
|
160 if (res != null) { |
|
161 // open new comment .... we'll suppose it satisfies the filter for now |
|
162 // TODO : should we reset the filter in this case ? instead of having the link open in a new window |
|
163 var key = res[1] ; |
|
164 var comment = gDb.getCommentByKey(key) ; |
|
165 if (comment != null) { |
|
166 e.halt() ; |
|
167 if (!link.hasClass("c-permalink")) {// clicking on the permalink itself should do anything |
|
168 checkForOpenedDialog(null, function() { |
|
169 gSync.showSingleComment(comment) ; |
|
170 }) ; |
|
171 } |
|
172 } |
|
173 } |
|
174 } |
|
175 } |
|
176 else { |
|
177 if (gShowingAllComments) { |
|
178 // next special dirty case test explained : when editing/replying to a comment with gShowingAllComments a click in the edit/reply form also is a click on the iComment, in this case we don't want to showSingleComment .... |
|
179 if (!this._isHostingAForm()) { |
|
180 var comment = gDb.getComment(this.commentId) ; |
|
181 checkForOpenedDialog(null, function() { |
|
182 if (comment != null) |
|
183 gSync.showSingleComment(comment) ; |
|
184 }) |
|
185 } |
|
186 } |
|
187 else |
|
188 gSync.activate(this) ; |
|
189 } |
|
190 } |
|
191 }, |
|
192 |
|
193 onMouseEnterHeader : function () { |
|
194 if (readyForAction() && this.isVisible()) { |
|
195 this.overlay.get('contentBox').query(".c-permalink").removeClass('displaynone'); |
|
196 } |
|
197 }, |
|
198 |
|
199 onMouseLeaveHeader : function () { |
|
200 if (readyForAction() && this.isVisible()) { |
|
201 this.overlay.get('contentBox').query(".c-permalink").addClass('displaynone'); |
|
202 } |
|
203 }, |
|
204 |
|
205 setWidth : function(width) { |
|
206 this.overlay.get('boundingBox').setStyle("width", width + 'px'); |
|
207 }, |
|
208 |
|
209 activate:function() { |
|
210 // debug !! |
|
211 // CY.log('activate' + this.commentId) ; |
|
212 this.overlay.get('boundingBox').addClass('c-focus-comment') ; |
|
213 this.overlay.get('contentBox').query(".c-close").addClass('c-iclose-focus') ; |
|
214 this.overlay.get('contentBox').query(".c-close").removeClass('c-iclose') ; |
|
215 |
|
216 }, |
|
217 |
|
218 deactivate:function() { |
|
219 // debug !! |
|
220 // CY.log('deactivate' + this.commentId) ; |
|
221 this.overlay.get('boundingBox').removeClass('c-focus-comment') ; |
|
222 this.overlay.get('contentBox').query(".c-close").addClass('c-iclose') ; |
|
223 this.overlay.get('contentBox').query(".c-close").removeClass('c-iclose-focus') ; |
|
224 |
|
225 }, |
|
226 hide:function() { |
|
227 // is IComment the top active one ? |
|
228 if (gIComments.isTopActive(this.commentId)) { // then try to activate next in displayed list |
|
229 if (!gIComments.activateVisibleNext()) |
|
230 gIComments.deactivate() ; |
|
231 } |
|
232 |
|
233 if (this.isVisible()) { |
|
234 this.overlay.hide(); |
|
235 this.overlay.blur() ; |
|
236 } |
|
237 }, |
|
238 hideContent:function() { |
|
239 this.overlay.get('contentBox').query(".icomment-header").addClass('displaynone') ; |
|
240 this.overlay.get('contentBox').query(".icomment-body").addClass('displaynone') ; |
|
241 }, |
|
242 showContent:function() { |
|
243 this.overlay.get('contentBox').query(".icomment-header").removeClass('displaynone') ; |
|
244 this.overlay.get('contentBox').query(".icomment-body").removeClass('displaynone') ; |
|
245 }, |
|
246 isVisible:function() { |
|
247 return this.overlay.get('visible') ; |
|
248 }, |
|
249 show:function() { |
|
250 this.hideReadRepliesLnk() ; // for now |
|
251 return this.overlay.show() ; |
|
252 }, |
|
253 showReadRepliesLnk:function() { |
|
254 this.overlay.get('contentBox').query(".c-readreplies").removeClass('displaynone') ; |
|
255 }, |
|
256 hideReadRepliesLnk:function() { |
|
257 this.overlay.get('contentBox').query(".c-readreplies").addClass('displaynone') ; |
|
258 }, |
|
259 changeModeration:function(comment) { |
|
260 var moderationLnk = this.overlay.get('contentBox').query(".c-moderate") ; |
|
261 moderationLnk.set("innerHTML", gettext(comment.state)) ; |
|
262 |
|
263 moderationLnk.removeClass("c-state-approved") ; |
|
264 moderationLnk.removeClass("c-state-pending") ; |
|
265 moderationLnk.removeClass("c-state-unapproved") ; |
|
266 moderationLnk.addClass("c-state-" + comment.state) ; |
|
267 |
|
268 this.overlay.get('contentBox').query(".c-iactions").removeClass("displaynone") ; |
|
269 this.overlay.get('contentBox').query(".c-state-actions").addClass("displaynone") ; |
|
270 }, |
|
271 isfetched : function() { |
|
272 return (this.commentId != null) ; |
|
273 }, |
|
274 unfetch : function() { |
|
275 this.commentId = null ; |
|
276 }, |
|
277 fetch : function(comment) { |
|
278 this.commentId = comment.id ; |
|
279 var boundingBoxNode = this.overlay.get('boundingBox') ; |
|
280 |
|
281 // TITLE |
|
282 var titleInfos = interpolate(gettext('last modified on %(date)s'),{'date':comment.modified_user_str}, true) ; |
|
283 |
|
284 var modifDateTooltip = (comment.modified == comment.created) ? '' : '<a title="' + titleInfos + '"> * </a>' ; |
|
285 var permaTitle = gettext('Permalink to this comment') ; |
|
286 var permalink = '<a class="c-permalink displaynone c-action" target="_blank" title="'+ permaTitle +'" href="" >ΒΆ </a>' ; |
|
287 |
|
288 var infos = interpolate(gettext('by %(name)s, created on %(date)s'),{'name':comment.name, 'date':comment.created_user_str}, true) ; |
|
289 |
|
290 var newTitleContent = '<span class="c-header"><div class="c-header-title">' + comment.title + permalink + '</div><div class="c-infos">' + infos + '</div></span>' ; |
|
291 |
|
292 var newTitleNode = CY.Node.create(newTitleContent) ; |
|
293 var prevTitleNode = boundingBoxNode.query(".c-header") ; |
|
294 if (prevTitleNode == null) // first time, no title yet |
|
295 boundingBoxNode.query('.icomment-header').insertBefore(newTitleNode, boundingBoxNode.one('.c-iactions')) ; |
|
296 else |
|
297 prevTitleNode.get('parentNode').replaceChild(newTitleNode, prevTitleNode) ; |
|
298 |
|
299 // TAG |
|
300 var newTagNode = CY.Node.create('<div class="c-tags"><span class="c-tags-infos">' + 'tags:' + '</span>' + comment.tags + '</div>') ; |
|
301 var prevTagNode = boundingBoxNode.query(".c-tags") ; |
|
302 if (prevTagNode == null) |
|
303 boundingBoxNode.query('.icomment-header').appendChild(newTagNode) ; |
|
304 else |
|
305 prevTagNode.get('parentNode').replaceChild(newTagNode, prevTagNode) ; |
|
306 // NO TAG ? |
|
307 if (comment.tags == "") |
|
308 newTagNode.addClass('displaynone') ; |
|
309 |
|
310 // CONTENT |
|
311 var newContentNode = CY.Node.create('<span class="c-content">' + comment.content_html + '</span>') ; |
|
312 var prevContentNode = boundingBoxNode.query(".c-content") ; |
|
313 if (prevContentNode == null) |
|
314 boundingBoxNode.query('.icomment-body').appendChild(newContentNode) ; |
|
315 else |
|
316 prevContentNode.get('parentNode').replaceChild(newContentNode, prevContentNode) ; |
|
317 |
|
318 // PERMALINK |
|
319 boundingBoxNode.query(".c-permalink").set("href",sv_site_url + comment.permalink) ; |
|
320 |
|
321 // MODERATION |
|
322 this.changeModeration(comment) ; |
|
323 // also change link title to give users the possibility to know comment id (to be able to reference this exact comment in GET arguments) |
|
324 var moderationLnk = this.overlay.get('contentBox').query(".c-moderate") ; |
|
325 //var cid = (comment.reply_to_id == null) ? this.commentId : "" ; |
|
326 moderationLnk.set("title", "click to change comment ID visibility".replace(/ID/, this.commentId).replace(/ /, " ")) ; |
|
327 |
|
328 // open links in new window : |
|
329 var links = boundingBoxNode.queryAll(".c-content a") ; |
|
330 if (links != null) |
|
331 links.setAttribute( "target" , "_blank" ) ; |
|
332 links = boundingBoxNode.queryAll(".c-header-title a") ; |
|
333 if (links != null) |
|
334 links.setAttribute( "target" , "_blank" ) ; |
|
335 |
|
336 this.permAdapt(comment) ; |
|
337 }, |
|
338 |
|
339 permAdapt : function(comment) { |
|
340 // this comment permissions |
|
341 var delLnk = this.overlay.get('contentBox').query(".c-delete") ; |
|
342 if (delLnk) { // there will be a server side check anyway |
|
343 if (!comment.can_delete) |
|
344 delLnk.addClass('displaynone') ; |
|
345 else |
|
346 delLnk.removeClass('displaynone') ; |
|
347 } |
|
348 |
|
349 var editLnk = this.overlay.get('contentBox').query(".c-edit") ; |
|
350 if (editLnk) { |
|
351 if (!comment.can_edit) |
|
352 editLnk.addClass('displaynone') ; |
|
353 else |
|
354 editLnk.removeClass('displaynone') ; |
|
355 } |
|
356 |
|
357 var replyLnk = this.overlay.get('contentBox').query(".c-reply") ; |
|
358 if (replyLnk) { |
|
359 if (!hasPerm("can_create_comment")) |
|
360 replyLnk.addClass('displaynone') ; |
|
361 else |
|
362 replyLnk.removeClass('displaynone') ; |
|
363 } |
|
364 |
|
365 var moderateLnk = this.overlay.get('contentBox').query(".c-moderate") ; |
|
366 if (moderateLnk) { |
|
367 if (!comment.can_moderate) |
|
368 moderateLnk.addClass('displaynone') ; |
|
369 else |
|
370 moderateLnk.removeClass('displaynone') ; |
|
371 } |
|
372 }, |
|
373 setThreadPad : function(pad) { // TODO review ... |
|
374 this.overlay.get('contentBox').query('.yui-widget-hd').setStyle('paddingLeft', pad + 'px') ; |
|
375 this.overlay.get('contentBox').query('.yui-widget-bd').setStyle('paddingLeft', pad + 'px') ; |
|
376 |
|
377 }, |
|
378 setPosition : function(xy) { |
|
379 var boundingBoxNode = this.overlay.get('boundingBox') ; |
|
380 |
|
381 boundingBoxNode.setStyle("opacity", 1); // TODO check this is still usefull |
|
382 boundingBoxNode.setXY(xy) ; |
|
383 }, |
|
384 getPosition : function(xy) { |
|
385 var boundingBoxNode = this.overlay.get('boundingBox') ; |
|
386 |
|
387 return boundingBoxNode.getXY() ; |
|
388 }, |
|
389 onAnimationEnd : function() { |
|
390 if (!CY.Lang.isUndefined(this['animation-handle']) && !CY.Lang.isNull(this['animation-handle'])) { |
|
391 this['animation-handle'].detach() ; |
|
392 this['animation-handle'] = null ; |
|
393 // CY.log('detached...') ; |
|
394 } |
|
395 gIComments.signalAnimationEnd() ; |
|
396 if (gIComments.animationsEnded()) |
|
397 gIComments.whenAnimationsEnd() ; |
|
398 }, |
|
399 |
|
400 //aa = new CY.Anim({node:gIComments._c[0].overlay.get('boundingBox'), to:{xy : [0,0]}, duration:2.0}) |
|
401 setAnimationToPosition : function(toXY) { |
|
402 var boundingBoxNode = this.overlay.get('boundingBox') ; |
|
403 |
|
404 // ANIMATION |
|
405 // 2 lines of optim that could be removed (0.011) |
|
406 if (gPrefs.get('general','animduration') < 0.011) |
|
407 boundingBoxNode.setXY(toXY) ; |
|
408 |
|
409 this.animation.set('to', { xy: toXY}); |
|
410 this.animation.set('duration', gPrefs.get('general','animduration')) ; // shouldn't be here really ... |
|
411 this['animation-handle'] = this.animation.on('end', this.onAnimationEnd, this); |
|
412 |
|
413 return this.animation ; |
|
414 }, |
|
415 setWidth : function(width) { |
|
416 var boundingBoxNode = this.overlay.get('boundingBox') ; |
|
417 |
|
418 // POSITION |
|
419 boundingBoxNode.setStyle("width", width + 'px'); |
|
420 }, |
|
421 getHeight : function() { |
|
422 return this.overlay.get('boundingBox').get('offsetHeight') ; |
|
423 }, |
|
424 scrollIntoView : function() { |
|
425 //this.isVisible() && |
|
426 if (!this.overlay.get('contentBox').inViewportRegion()) |
|
427 this.overlay.get('contentBox').scrollIntoView(true) ; |
|
428 }, |
|
429 _isHostingAForm : function() { |
|
430 return (this.isVisible() && ((gNewReplyHost != null && gNewReplyHost == this) || (gEditICommentHost != null && gEditICommentHost == this))); |
|
431 } |
|
432 |
|
433 } |