Merge pull request #4758 from WoltLab/mailbox-filterAddress
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WCF.Comment.js
1 "use strict";
2
3 /**
4 * Namespace for comments
5 */
6 WCF.Comment = { };
7
8 /**
9 * Comment support for WCF
10 *
11 * @author Alexander Ebert
12 * @copyright 2001-2019 WoltLab GmbH
13 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14 */
15 WCF.Comment.Handler = Class.extend({
16 /**
17 * list of comment buttons per comment
18 * @var object
19 */
20 _commentButtonList: { },
21
22 /**
23 * list of comment objects
24 * @var object
25 */
26 _comments: { },
27
28 /**
29 * comment container object
30 * @var jQuery
31 */
32 _container: null,
33
34 /**
35 * container id
36 * @var string
37 */
38 _containerID: '',
39
40 /**
41 * number of currently displayed comments
42 * @var integer
43 */
44 _displayedComments: 0,
45
46 /**
47 * button to load next comments
48 * @var jQuery
49 */
50 _loadNextComments: null,
51
52 /**
53 * buttons to load next responses per comment
54 * @var object
55 */
56 _loadNextResponses: { },
57
58 /**
59 * action proxy
60 * @var WCF.Action.Proxy
61 */
62 _proxy: null,
63
64 /**
65 * list of response objects
66 * @var object
67 */
68 _responses: { },
69
70 _responseCache: {},
71
72 /**
73 * data of the comment the active guest user is about to create
74 * @var object
75 */
76 _commentData: { },
77
78 /**
79 * guest dialog with username input field and recaptcha
80 * @var jQuery
81 */
82 _guestDialog: null,
83
84 _permalinkComment: null,
85 _permalinkResponse: null,
86 _scrollTarget: null,
87
88 /**
89 * Initializes the WCF.Comment.Handler class.
90 *
91 * @param string containerID
92 */
93 init: function(containerID) {
94 this._commentButtonList = { };
95 this._comments = { };
96 this._containerID = containerID;
97 this._displayedComments = 0;
98 this._loadNextComments = null;
99 this._loadNextResponses = { };
100 this._permalinkComment = null;
101 this._permalinkResponse = null;
102 this._responseAdd = null;
103 this._responseCache = {};
104 this._responseRevert = null;
105 this._responses = {};
106 this._scrollTarget = null;
107 this._onResponsesLoaded = null;
108
109 this._container = $('#' + $.wcfEscapeID(this._containerID));
110 if (!this._container.length) {
111 console.debug("[WCF.Comment.Handler] Unable to find container identified by '" + this._containerID + "'");
112 return;
113 }
114
115 this._proxy = new WCF.Action.Proxy({
116 success: $.proxy(this._success, this)
117 });
118
119 this._initComments();
120 this._initResponses();
121
122 // add new comment
123 if (this._container.data('canAdd')) {
124 if (elBySel('.commentListAddComment .wysiwygTextarea', this._container[0]) === null) {
125 console.error("Missing WYSIWYG implementation, adding comments is not available.");
126 }
127 else {
128 require(['WoltLabSuite/Core/Ui/Comment/Add', 'WoltLabSuite/Core/Ui/Comment/Response/Add'], (function (UiCommentAdd, UiCommentResponseAdd) {
129 new UiCommentAdd(elBySel('.jsCommentAdd', this._container[0]));
130
131 this._responseAdd = new UiCommentResponseAdd(
132 elBySel('.jsCommentResponseAdd', this._container[0]),
133 {
134 callbackInsert: (function () {
135 if (this._responseRevert !== null) {
136 this._responseRevert();
137 this._responseRevert = null;
138 }
139 }).bind(this)
140 });
141 }).bind(this));
142 }
143 }
144
145 require(['WoltLabSuite/Core/Ui/Comment/Edit', 'WoltLabSuite/Core/Ui/Comment/Response/Edit'], (function (UiCommentEdit, UiCommentResponseEdit) {
146 new UiCommentEdit(this._container[0]);
147 new UiCommentResponseEdit(this._container[0]);
148 }).bind(this));
149
150 WCF.DOMNodeInsertedHandler.execute();
151 WCF.DOMNodeInsertedHandler.addCallback('WCF.Comment.Handler', $.proxy(this._domNodeInserted, this));
152
153 WCF.System.ObjectStore.add('WCF.Comment.Handler', this);
154
155 window.addEventListener('hashchange', function () {
156 var hash = window.location.hash;
157 if (hash && hash.match(/.+\/(comment\d+)/)) {
158 var commentId = RegExp.$1;
159
160 // delay scrolling in case a tab menu change was requested
161 window.setTimeout(function () {
162 var comment = elById(commentId);
163 if (comment) comment.scrollIntoView({ behavior: 'smooth' });
164 }, 100);
165 }
166 });
167
168 var hash = window.location.hash;
169 if (hash.match(/^#(?:[^\/]+\/)?comment(\d+)(?:\/response(\d+))?/)) {
170 var comment = elById('comment' + RegExp.$1);
171 if (comment) {
172 var response;
173 if (RegExp.$2) {
174 response = elById('comment' + RegExp.$1 + 'response' + RegExp.$2);
175 if (response) {
176 this._scrollTo(response, true);
177 }
178 else {
179 // load response on-the-fly
180 this._loadResponseSegment(comment, RegExp.$1, RegExp.$2);
181 }
182 }
183 else {
184 this._scrollTo(comment, true);
185 }
186 }
187 else {
188 // load comment on-the-fly
189 this._loadCommentSegment(RegExp.$1, RegExp.$2);
190 }
191 }
192 },
193
194 _scrollTo: function (element, highlight) {
195 if (this._scrollTarget === null) {
196 this._scrollTarget = elCreate('span');
197 this._scrollTarget.className = 'commentScrollTarget';
198
199 document.body.appendChild(this._scrollTarget);
200 }
201
202 this._scrollTarget.style.setProperty('top', (element.getBoundingClientRect().top + window.pageYOffset - 49) + 'px', '');
203
204 require(['Ui/Scroll'], (function (UiScroll) {
205 UiScroll.element(this._scrollTarget, function () {
206 if (highlight) {
207 if (element.classList.contains('commentHighlightTarget')) {
208 element.classList.remove('commentHighlightTarget');
209 //noinspection BadExpressionStatementJS
210 element.offsetTop;
211 }
212
213 element.classList.add('commentHighlightTarget');
214 }
215 })
216 }).bind(this));
217 },
218
219 _loadCommentSegment: function (commentId, responseID) {
220 this._permalinkComment = elCreate('li');
221 this._permalinkComment.className = 'commentPermalinkContainer loading';
222 this._permalinkComment.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
223 this._container[0].insertBefore(this._permalinkComment, this._container[0].firstChild);
224
225 this._proxy.setOption('data', {
226 actionName: 'loadComment',
227 className: 'wcf\\data\\comment\\CommentAction',
228 objectIDs: [commentId],
229 parameters: {
230 data: {
231 objectID: this._container.data('objectID'),
232 objectTypeID: this._container.data('objectTypeID'),
233 responseID: ~~responseID
234 }
235 }
236 });
237 this._proxy.sendRequest();
238 },
239
240 _loadResponseSegment: function (comment, commentId, responseID) {
241 this._permalinkResponse = elCreate('li');
242 this._permalinkResponse.className = 'commentResponsePermalinkContainer loading';
243 this._permalinkResponse.innerHTML = '<span class="icon icon32 fa-spinner"></span>';
244 var responseList = elBySel('.commentResponseList', comment);
245 responseList.insertBefore(this._permalinkResponse, responseList.firstChild);
246
247 this._proxy.setOption('data', {
248 actionName: 'loadResponse',
249 className: 'wcf\\data\\comment\\CommentAction',
250 objectIDs: [commentId],
251 parameters: {
252 data: {
253 objectID: this._container.data('objectID'),
254 objectTypeID: this._container.data('objectTypeID'),
255 responseID: ~~responseID
256 }
257 }
258 });
259 this._proxy.sendRequest();
260 },
261
262 /**
263 * Shows a button to load next comments.
264 */
265 _handleLoadNextComments: function() {
266 if (this._displayedComments < this._container.data('comments')) {
267 if (this._loadNextComments === null) {
268 this._loadNextComments = $('<li class="commentLoadNext showMore"><button class="small">' + WCF.Language.get('wcf.comment.more') + '</button></li>').appendTo(this._container);
269 this._loadNextComments.children('button').click($.proxy(this._loadComments, this));
270 }
271
272 this._loadNextComments.children('button').enable();
273 }
274 else if (this._loadNextComments !== null) {
275 this._loadNextComments.remove();
276 }
277 },
278
279 /**
280 * Shows a button to load next responses per comment.
281 *
282 * @param integer commentID
283 */
284 _handleLoadNextResponses: function(commentID) {
285 var $comment = this._comments[commentID];
286 $comment.data('displayedResponses', $comment.find('ul.commentResponseList > li').length);
287
288 if ($comment.data('displayedResponses') < $comment.data('responses')) {
289 if (this._loadNextResponses[commentID] === undefined) {
290 var $difference = $comment.data('responses') - $comment.data('displayedResponses');
291 this._loadNextResponses[commentID] = $('<li class="jsCommentLoadNextResponses"><a>' + WCF.Language.get('wcf.comment.response.more', { count: $difference }) + '</a></li>').appendTo(this._commentButtonList[commentID]);
292 this._loadNextResponses[commentID].children('a').data('commentID', commentID).click($.proxy(this._loadResponses, this));
293 this._commentButtonList[commentID].parent().show();
294 }
295 else {
296 var $difference = $comment.data('responses') - $comment.data('displayedResponses');
297 this._loadNextResponses[commentID][0].querySelector("a").textContent = WCF.Language.get(WCF.Language.get('wcf.comment.response.more', { count: $difference }));
298 }
299 }
300 else if (this._loadNextResponses[commentID] !== undefined) {
301 this._loadNextResponses[commentID].remove();
302 }
303 },
304
305 /**
306 * Loads next comments.
307 */
308 _loadComments: function() {
309 this._loadNextComments.children('button').disable();
310
311 this._proxy.setOption('data', {
312 actionName: 'loadComments',
313 className: 'wcf\\data\\comment\\CommentAction',
314 parameters: {
315 data: {
316 objectID: this._container.data('objectID'),
317 objectTypeID: this._container.data('objectTypeID'),
318 lastCommentTime: this._container.data('lastCommentTime')
319 }
320 }
321 });
322 this._proxy.sendRequest();
323 },
324
325 /**
326 * Loads next responses for given comment.
327 *
328 * @param object event
329 */
330 _loadResponses: function(event) {
331 this._loadResponsesExecute($(event.currentTarget).disable().data('commentID'), false);
332 },
333
334 /**
335 * Executes loading of comments, optionally fetching all at once.
336 *
337 * @param integer commentID
338 * @param boolean loadAllResponses
339 */
340 _loadResponsesExecute: function(commentID, loadAllResponses) {
341 this._proxy.setOption('data', {
342 actionName: 'loadResponses',
343 className: 'wcf\\data\\comment\\response\\CommentResponseAction',
344 parameters: {
345 data: {
346 commentID: commentID,
347 lastResponseTime: this._comments[commentID].data('lastResponseTime'),
348 loadAllResponses: (loadAllResponses ? 1 : 0)
349 }
350 }
351 });
352 this._proxy.sendRequest();
353 },
354
355 /**
356 * Handles DOMNodeInserted events.
357 */
358 _domNodeInserted: function() {
359 this._initComments();
360 this._initResponses();
361 },
362
363 /**
364 * Initializes available comments.
365 */
366 _initComments: function() {
367 var link = elBySel('link[rel="canonical"]');
368 if (link) {
369 link = link.href;
370 }
371 else {
372 link = window.location.toString().replace(/#.+$/, '');
373 }
374
375 // check if comments are within a tab menu
376 var tab = this._container[0].closest('.tabMenuContent');
377 if (tab) {
378 link += '#' + elData(tab, 'name');
379 }
380
381 var self = this;
382 var $loadedComments = false;
383 this._container.find('.jsComment').each(function(index, comment) {
384 var $comment = $(comment).removeClass('jsComment');
385 var $commentID = $comment.data('commentID');
386 self._comments[$commentID] = $comment;
387
388 // permalink anchor
389 $comment[0].id = 'comment' + $commentID;
390
391 var $insertAfter = $comment.find('ul.commentResponseList');
392 if (!$insertAfter.length) $insertAfter = $comment.find('.commentContent');
393
394 var $container = $('<div class="commentOptionContainer" />').hide().insertAfter($insertAfter);
395 self._commentButtonList[$commentID] = $('<ul class="inlineList dotSeparated" />').appendTo($container);
396
397 self._handleLoadNextResponses($commentID);
398 self._initComment($commentID, $comment);
399 self._initPermalink($comment[0], link);
400 self._displayedComments++;
401
402 $loadedComments = true;
403 });
404
405 if ($loadedComments) {
406 this._handleLoadNextComments();
407 }
408 },
409
410 /**
411 * Initializes a specific comment.
412 *
413 * @param integer commentID
414 * @param jQuery comment
415 */
416 _initComment: function(commentID, comment) {
417 if (this._container.data('canAdd')) {
418 this._initAddResponse(commentID, comment);
419 }
420
421 if (comment.data('canEdit')) {
422 var $editButton = $('<li><a href="#" class="jsCommentEditButton jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 fa-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
423 $editButton.appendTo(comment.find('ul.buttonList:eq(0)'));
424 }
425
426 if (comment.data('canDelete')) {
427 var $deleteButton = $('<li><a href="#" class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 fa-times" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
428 $deleteButton.data('commentID', commentID).appendTo(comment.find('ul.buttonList:eq(0)')).click($.proxy(this._delete, this));
429 }
430
431 var enableComment = elBySel('.jsEnableComment', comment[0]);
432 if (enableComment) {
433 enableComment.addEventListener('click', this._enableComment.bind(this));
434 }
435 },
436
437 _enableComment: function (event) {
438 event.preventDefault();
439
440 var comment = event.currentTarget.closest('.comment');
441
442 this._proxy.setOption('data', {
443 actionName: 'enable',
444 className: 'wcf\\data\\comment\\CommentAction',
445 objectIDs: [elData(comment, 'object-id')]
446 });
447 this._proxy.sendRequest();
448 },
449
450 _initPermalink: function(comment, link) {
451 var anchor = elCreate('a');
452 anchor.href = link + (link.indexOf('#') === -1 ? '#' : '/') + 'comment' + elData(comment, 'object-id');
453
454 var time = elBySel('.commentContent:not(.commentResponseContent) .containerHeadline time', comment);
455 time.parentNode.insertBefore(anchor, time);
456 anchor.appendChild(time);
457 },
458
459 /**
460 * Initializes available responses.
461 */
462 _initResponses: function() {
463 var link = elBySel('link[rel="canonical"]');
464 if (link) {
465 link = link.href;
466 }
467 else {
468 link = window.location.toString().replace(/#.+$/, '');
469 }
470
471 // check if comments are within a tab menu
472 var tab = this._container[0].closest('.tabMenuContent');
473 if (tab) {
474 link += '#' + elData(tab, 'name');
475 }
476
477 for (var commentId in this._comments) {
478 if (this._comments.hasOwnProperty(commentId)) {
479 elBySelAll('.jsCommentResponse', this._comments[commentId][0], (function(response) {
480 var $response = $(response).removeClass('jsCommentResponse');
481 var $responseID = $response.data('responseID');
482 this._responses[$responseID] = $response;
483
484 //noinspection JSReferencingMutableVariableFromClosure
485 response.id = 'comment' + commentId + 'response' + $responseID;
486
487 this._initResponse($responseID, $response);
488
489 //noinspection JSReferencingMutableVariableFromClosure
490 this._initPermalinkResponse(commentId, response, $responseID, link);
491
492 var enableResponse = elBySel('.jsEnableResponse', response);
493 if (enableResponse) {
494 enableResponse.addEventListener('click', this._enableCommentResponse.bind(this));
495 }
496 }).bind(this));
497 }
498 }
499 },
500
501 _enableCommentResponse: function (event) {
502 event.preventDefault();
503
504 var response = event.currentTarget.closest('.commentResponse');
505
506 this._proxy.setOption('data', {
507 actionName: 'enableResponse',
508 className: 'wcf\\data\\comment\\CommentAction',
509 parameters: {
510 data: {
511 responseID: elData(response, 'object-id')
512 }
513 }
514 });
515 this._proxy.sendRequest();
516 },
517
518 _initPermalinkResponse: function (commentId, response, responseId, link) {
519 var anchor = elCreate('a');
520 anchor.href = link + (link.indexOf('#') === -1 ? '#' : '/') + 'comment' + commentId + '/response' + responseId;
521
522 var time = elBySel('.commentResponseContent .containerHeadline time', response);
523 time.parentNode.insertBefore(anchor, time);
524 anchor.appendChild(time);
525 },
526
527 /**
528 * Initializes a specific response.
529 *
530 * @param integer responseID
531 * @param jQuery response
532 */
533 _initResponse: function(responseID, response) {
534 if (response.data('canEdit')) {
535 var $editButton = $('<li><a href="#" class="jsCommentResponseEditButton jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 fa-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
536 $editButton.data('responseID', responseID).appendTo(response.find('ul.buttonList:eq(0)'));
537 }
538
539 if (response.data('canDelete')) {
540 var $deleteButton = $('<li><a href="#" class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 fa-times" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
541
542 var self = this;
543 $deleteButton.data('responseID', responseID).appendTo(response.find('ul.buttonList:eq(0)')).click(function(event) { self._delete(event, true); });
544 }
545 },
546
547 /**
548 * Initializes the UI elements to add a response.
549 *
550 * @param integer commentID
551 * @param jQuery comment
552 */
553 _initAddResponse: function(commentID, comment) {
554 var $placeholder = $('<li class="jsCommentShowAddResponse"><a>' + WCF.Language.get('wcf.comment.button.response.add') + '</a></li>').data('commentID', commentID).click($.proxy(this._showAddResponse, this)).appendTo(this._commentButtonList[commentID]);
555 this._commentButtonList[commentID].parent().show();
556 },
557
558 /**
559 * Displays the UI elements to create a response.
560 *
561 * @param object event
562 */
563 _showAddResponse: function(event) {
564 event.preventDefault();
565
566 // pending request
567 if (this._onResponsesLoaded !== null) {
568 return;
569 }
570
571 // API is missing
572 if (this._responseAdd === null) {
573 console.error("Missing response API.");
574 return;
575 }
576
577 var responseContainer = this._responseAdd.getContainer();
578 if (responseContainer === null) {
579 // instance is busy
580 return;
581 }
582
583 if (this._responseRevert !== null) {
584 this._responseRevert();
585 this._responseRevert = null;
586 }
587
588 var $placeholder = $(event.currentTarget);
589 var $commentID = $placeholder.data('commentID');
590
591 this._onResponsesLoaded = (function() {
592 $placeholder.hide();
593
594 if (responseContainer.parentNode && responseContainer.parentNode.classList.contains('jsCommentResponseAddContainer')) {
595 // strip the parent element, it is used as a work-around
596 elRemove(responseContainer.parentNode);
597 }
598
599 var commentOptionContainer = this._commentButtonList[$commentID][0].closest('.commentOptionContainer');
600 commentOptionContainer.parentNode.insertBefore(responseContainer, commentOptionContainer.nextSibling);
601
602 if (typeof this._responseCache[$commentID] === 'string') {
603 this._responseAdd.setContent(this._responseCache[$commentID]);
604 }
605 else {
606 this._responseAdd.setContent('');
607 }
608
609 this._responseRevert = (function () {
610 this._responseCache[$commentID] = this._responseAdd.getContent();
611
612 elRemove(responseContainer);
613 $placeholder.show();
614 }).bind(this);
615
616 this._onResponsesLoaded = null;
617 }).bind(this);
618
619 if ($placeholder.prev().hasClass('jsCommentLoadNextResponses')) {
620 this._loadResponsesExecute($commentID, true);
621 $placeholder.parent().children('.button').disable();
622 }
623 else {
624 this._onResponsesLoaded();
625 }
626 },
627
628 /**
629 * Shows a confirmation message prior to comment or response deletion.
630 *
631 * @param object event
632 * @param boolean isResponse
633 */
634 _delete: function(event, isResponse) {
635 event.preventDefault();
636 WCF.System.Confirmation.show(WCF.Language.get('wcf.comment.delete.confirmMessage'), $.proxy(function(action) {
637 if (action === 'confirm') {
638 var $data = {
639 objectID: this._container.data('objectID'),
640 objectTypeID: this._container.data('objectTypeID')
641 };
642 if (isResponse !== true) {
643 $data.commentID = $(event.currentTarget).data('commentID');
644 }
645 else {
646 $data.responseID = $(event.currentTarget).data('responseID');
647 }
648
649 this._proxy.setOption('data', {
650 actionName: 'remove',
651 className: 'wcf\\data\\comment\\CommentAction',
652 parameters: {
653 data: $data
654 }
655 });
656 this._proxy.sendRequest();
657 }
658 }, this));
659 },
660
661 /**
662 * Handles successful AJAX requests.
663 *
664 * @param object data
665 * @param string textStatus
666 * @param jQuery jqXHR
667 */
668 _success: function(data, textStatus, jqXHR) {
669 switch (data.actionName) {
670 case 'enable':
671 this._enable(data);
672 break;
673
674 case 'enableResponse':
675 this._enableResponse(data);
676 break;
677
678 case 'loadComment':
679 this._insertComment(data);
680 break;
681
682 case 'loadComments':
683 this._insertComments(data);
684 break;
685
686 case 'loadResponse':
687 this._insertResponse(data);
688 break;
689
690 case 'loadResponses':
691 this._insertResponses(data);
692 break;
693
694 case 'remove':
695 this._remove(data);
696 break;
697 }
698
699 WCF.DOMNodeInsertedHandler.execute();
700 },
701
702 _enable: function(data) {
703 if (data.returnValues.commentID) {
704 var comment = elBySel('.comment[data-object-id="' + data.returnValues.commentID + '"]', this._container[0]);
705 if (comment) {
706 elData(comment, 'is-disabled', 0);
707 var badge = elBySel('.jsIconDisabled', comment);
708 if (badge) elRemove(badge);
709
710 var enableLink = elBySel('.jsEnableComment', comment);
711 if (enableLink) elRemove(enableLink.parentNode);
712 }
713 }
714 },
715
716 _enableResponse: function(data) {
717 if (data.returnValues.responseID) {
718 var response = elBySel('.commentResponse[data-object-id="' + data.returnValues.responseID + '"]', this._container[0]);
719 if (response) {
720 elData(response, 'is-disabled', 0);
721 var badge = elBySel('.jsIconDisabled', response);
722 if (badge) elRemove(badge);
723
724 var enableLink = elBySel('.jsEnableResponse', response);
725 if (enableLink) elRemove(enableLink.parentNode);
726 }
727 }
728 },
729
730 _insertComment: function (data) {
731 if (data.returnValues.template === '') {
732 elRemove(this._permalinkComment);
733
734 // comment id is invalid or there is a mismatch, silently ignore it
735 return;
736 }
737
738 $(data.returnValues.template).insertBefore(this._permalinkComment);
739 var comment = this._permalinkComment.previousElementSibling;
740 comment.classList.add('commentPermalinkContainer');
741
742 elRemove(this._permalinkComment);
743 this._permalinkComment = comment;
744
745 if (data.returnValues.response) {
746 this._permalinkResponse = elCreate('li');
747 this._permalinkResponse.className = 'commentResponsePermalinkContainer loading';
748 this._permalinkResponse.innerHTML = '<span class="icon icon32 fa-spinner"></span>';
749 var responseList = elBySel('.commentResponseList', comment);
750 responseList.insertBefore(this._permalinkResponse, responseList.firstChild);
751
752 this._insertResponse({
753 returnValues: {
754 template: data.returnValues.response
755 }
756 });
757 }
758
759 //noinspection BadExpressionStatementJS
760 comment.offsetTop;
761
762 comment.classList.add('commentHighlightTarget');
763 },
764
765 _insertResponse: function(data) {
766 if (data.returnValues.template === '') {
767 elRemove(this._permalinkResponse);
768
769 // comment id is invalid or there is a mismatch, silently ignore it
770 return;
771 }
772
773 $(data.returnValues.template).insertBefore(this._permalinkResponse);
774 var response = this._permalinkResponse.previousElementSibling;
775 response.classList.add('commentResponsePermalinkContainer');
776
777 elRemove(this._permalinkResponse);
778 this._permalinkResponse = response;
779
780 //noinspection BadExpressionStatementJS
781 response.offsetTop;
782
783 response.classList.add('commentHighlightTarget');
784 },
785
786 /**
787 * Inserts previously loaded comments.
788 *
789 * @param object data
790 */
791 _insertComments: function(data) {
792 // insert comments
793 $(data.returnValues.template).insertBefore(this._loadNextComments);
794
795 // update time of last comment
796 this._container.data('lastCommentTime', data.returnValues.lastCommentTime);
797
798 // check if permalink comment has been loaded and remove it from view
799 if (this._permalinkComment) {
800 var commentId = elData(this._permalinkComment, 'object-id');
801
802 if (elBySel('.comment[data-object-id="' + commentId + '"]:not(.commentPermalinkContainer)', this._container[0]) !== null) {
803 elRemove(this._permalinkComment);
804 this._permalinkComment = null;
805 }
806 }
807
808 this._initComments();
809 },
810
811 /**
812 * Inserts previously loaded responses.
813 *
814 * @param object data
815 */
816 _insertResponses: function(data) {
817 var $comment = this._comments[data.returnValues.commentID];
818
819 // insert responses
820 $(data.returnValues.template).appendTo($comment.find('ul.commentResponseList'));
821
822 // update time of last response
823 $comment.data('lastResponseTime', data.returnValues.lastResponseTime);
824
825 // update button state to load next responses
826 this._handleLoadNextResponses(data.returnValues.commentID);
827
828 // check if permalink response has been loaded and remove it from view
829 if (this._permalinkResponse) {
830 var responseId = elData(this._permalinkResponse, 'object-id');
831
832 if (elBySel('.commentResponse[data-object-id="' + responseId + '"]:not(.commentPermalinkContainer)', this._container[0]) !== null) {
833 elRemove(this._permalinkResponse);
834 this._permalinkResponse = null;
835 }
836 }
837
838 // check if there is a pending reply request
839 if (this._onResponsesLoaded !== null) this._onResponsesLoaded();
840 },
841
842 /**
843 * Removes a comment or response from list.
844 *
845 * @param object data
846 */
847 _remove: function(data) {
848 if (data.returnValues.commentID) {
849 this._comments[data.returnValues.commentID].remove();
850 delete this._comments[data.returnValues.commentID];
851 }
852 else {
853 var $response = this._responses[data.returnValues.responseID];
854 var $comment = this._comments[$response.parents('li.comment:eq(0)').data('commentID')];
855
856 // decrease response counter because a correct response count
857 // is required in _handleLoadNextResponses()
858 $comment.data('responses', parseInt($comment.data('responses')) - 1);
859
860 var $commentResponseList = $response.parent();
861 $response.remove();
862
863 if (!$commentResponseList.children().length) {
864 // make '.commentResponseList' accessible via CSS'
865 // :empty selector
866 $commentResponseList.empty();
867 }
868
869 delete this._responses[data.returnValues.responseID];
870 }
871 },
872
873 _prepareEdit: function() { console.warn("This method is no longer supported."); },
874 _keyUp: function() { console.warn("This method is no longer supported."); },
875 _save: function() { console.warn("This method is no longer supported."); },
876 _failure: function() { console.warn("This method is no longer supported."); },
877 _edit: function() { console.warn("This method is no longer supported."); },
878 _update: function() { console.warn("This method is no longer supported."); },
879 _createGuestDialog: function() { console.warn("This method is no longer supported."); },
880 _keyDown: function() { console.warn("This method is no longer supported."); },
881 _submit: function() { console.warn("This method is no longer supported."); },
882 _keyUpEdit: function() { console.warn("This method is no longer supported."); },
883 _saveEdit: function() { console.warn("This method is no longer supported."); },
884 _cancelEdit: function() { console.warn("This method is no longer supported."); }
885 });
886
887 /**
888 * Namespace for comment responses
889 */
890 WCF.Comment.Response = { };