Implemented dynamic comment loading
authorAlexander Ebert <ebert@woltlab.com>
Fri, 3 Mar 2017 13:53:31 +0000 (14:53 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 3 Mar 2017 13:53:37 +0000 (14:53 +0100)
See #2223

wcfsetup/install/files/js/WCF.Comment.js
wcfsetup/install/files/lib/data/comment/CommentAction.class.php
wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php
wcfsetup/install/files/style/ui/comment.scss

index 5006bb6c6b0c2ca8cac33ef3b1b34946ec9e85e5..18fac6b5921168190daedecc4b0acb23b8ffbfb8 100644 (file)
@@ -91,6 +91,8 @@ WCF.Comment.Handler = Class.extend({
         */
        _guestDialog: null,
        
+       _permalinkComment: null,
+       
        /**
         * Initializes the WCF.Comment.Handler class.
         * 
@@ -105,6 +107,7 @@ WCF.Comment.Handler = Class.extend({
                this._displayedComments = 0;
                this._loadNextComments = null;
                this._loadNextResponses = { };
+               this._permalinkComment = null;
                this._responses = { };
                this._userAvatar = userAvatar;
                this._userAvatarSmall = userAvatarSmall;
@@ -155,6 +158,38 @@ WCF.Comment.Handler = Class.extend({
                                }, 100);
                        }
                });
+               
+               var hash = window.location.hash;
+               if (hash.match(/^(?:[^\/]+\/)?comment(\d+)/)) {
+                       var comment = elById('comment' + RegExp.$1);
+                       if (comment) {
+                               comment.scrollIntoView({ behavior: 'smooth' });
+                       }
+                       else {
+                               // load comment on-the-fly
+                               this._loadCommentSegment(RegExp.$1);
+                       }
+               }
+       },
+       
+       _loadCommentSegment: function (commentId) {
+               this._permalinkComment = elCreate('li');
+               this._permalinkComment.className = 'commentPermalinkContainer loading';
+               this._permalinkComment.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
+               this._container[0].insertBefore(this._permalinkComment, this._container[0].firstChild);
+               
+               this._proxy.setOption('data', {
+                       actionName: 'loadComment',
+                       className: 'wcf\\data\\comment\\CommentAction',
+                       objectIDs: [commentId],
+                       parameters: {
+                               data: {
+                                       objectID: this._container.data('objectID'),
+                                       objectTypeID: this._container.data('objectTypeID')
+                               }
+                       }
+               });
+               this._proxy.sendRequest();
        },
        
        /**
@@ -617,6 +652,10 @@ WCF.Comment.Handler = Class.extend({
                                this._update(data);
                        break;
                        
+                       case 'loadComment':
+                               this._insertComment(data);
+                               break;
+                       
                        case 'loadComments':
                                this._insertComments(data);
                        break;
@@ -641,6 +680,25 @@ WCF.Comment.Handler = Class.extend({
                WCF.DOMNodeInsertedHandler.execute();
        },
        
+       _insertComment: function (data) {
+               if (data.returnValues.template === '') {
+                       // comment id is invalid or there is a mismatch, silently ignore it
+                       return;
+               }       
+               
+               $(data.returnValues.template).insertBefore(this._permalinkComment);
+               var comment = this._permalinkComment.previousElementSibling;
+               comment.classList.add('commentPermalinkContainer');
+               
+               elRemove(this._permalinkComment);
+               this._permalinkComment = comment;
+               
+               //noinspection BadExpressionStatementJS
+               comment.offsetTop;
+               
+               comment.classList.add('fadeIn');
+       },
+       
        /**
         * Inserts previously loaded comments.
         * 
@@ -652,6 +710,16 @@ WCF.Comment.Handler = Class.extend({
                
                // update time of last comment
                this._container.data('lastCommentTime', data.returnValues.lastCommentTime);
+               
+               // check if permalink comment has been loaded and remove it from view
+               if (this._permalinkComment) {
+                       var commentId = elData(this._permalinkComment, 'object-id');
+                       
+                       if (elBySel('.comment[data-object-id="' + commentId + '"]:not(.commentPermalinkContainer)', this._container[0]) !== null) {
+                               elRemove(this._permalinkComment);
+                               this._permalinkComment = null;
+                       }
+               }
        },
        
        /**
index d4a2341dbae8fed5ed84468346f888d007a9ff28..df7ef31aaf494154e994669524498182578b2948 100644 (file)
@@ -200,6 +200,36 @@ class CommentAction extends AbstractDatabaseObjectAction implements IMessageInli
                ];
        }
        
+       public function validateLoadComment() {
+               $this->readInteger('objectID', false, 'data');
+               
+               try {
+                       $this->comment = $this->getSingleObject()->getDecoratedObject();
+               }
+               catch (UserInputException $e) {
+                       /* unknown comment id, error handling takes place in `loadComment()` */
+               }
+               
+               $objectType = $this->validateObjectType();
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       public function loadComment() {
+               if ($this->comment === null) {
+                       return ['template' => ''];
+               }
+               else if ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
+                       return ['template' => ''];
+               }
+               
+               return [
+                       'template' => $this->renderComment($this->comment)
+               ];
+       }
+       
        /**
         * Validates parameters to add a comment.
         */
index fae20b38d44fcc776bf5c6d7d23ddaabdc55a2a4..3b97e3ef43b1a7fd75f9c2c61614102fcc791e10 100644 (file)
@@ -89,6 +89,9 @@ class StructuredCommentList extends CommentList {
                $this->getConditionBuilder()->add("comment.objectTypeID = ?", [$objectTypeID]);
                $this->getConditionBuilder()->add("comment.objectID = ?", [$objectID]);
                $this->sqlLimit = $this->commentManager->getCommentsPerPage();
+               
+               // TODO: DEBUG ONLY
+               $this->sqlLimit = 5;
        }
        
        /**
index 2bee7467ca7d1e321aeb0b56c6626c3ac079080d..118c06b3deb5e9a7e0ef45ca52d6518ff2c24cd8 100644 (file)
                margin-top: 20px;
        }
 }
+
+.commentPermalinkContainer {
+       border-bottom-color: $wcfContentBorder !important;
+       
+       &.loading > .icon {
+               left: calc(50% - 24px);
+               position: relative;
+       }
+       
+       &:not(.loading) {
+               opacity: 0;
+       }
+       
+       &.fadeIn {
+               opacity: 1;
+               transition: opacity .24s linear;
+       }
+}