Added lightweight WYSIWYG support for comment responses
authorAlexander Ebert <ebert@woltlab.com>
Wed, 1 Nov 2017 15:28:40 +0000 (16:28 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 1 Nov 2017 15:28:47 +0000 (16:28 +0100)
Closes #2461

16 files changed:
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/templates/__commentJavaScript.tpl
com.woltlab.wcf/templates/commentListAddComment.tpl
com.woltlab.wcf/templates/commentResponseEditor.tpl [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Comment.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Add.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Add.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Edit.js [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/CommentAction.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php
wcfsetup/install/files/lib/system/worker/CommentResponseRebuildDataWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/style/ui/comment.scss
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 2d3247e90979b57d34fba429794ec3e2cc89da1a..f2884c29a23c63fb925293885f23c9b3c7158cb2 100644 (file)
                        <name>com.woltlab.wcf.comment</name>
                        <definitionname>com.woltlab.wcf.message</definitionname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.message</definitionname>
+               </type>
                <type>
                        <name>com.woltlab.wcf.paidSubscription</name>
                        <definitionname>com.woltlab.wcf.message</definitionname>
                        <classname>wcf\system\worker\CommentRebuildDataWorker</classname>
                        <nicevalue>120</nicevalue>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\CommentResponseRebuildDataWorker</classname>
+                       <nicevalue>120</nicevalue>
+               </type>
                <type>
                        <name>com.woltlab.wcf.sitemap</name>
                        <definitionname>com.woltlab.wcf.rebuildData</definitionname>
index e8546a49050e6e44c99331309cbb1e98f6672c47..51eeea8ffcbca2e3ea5b7bb986442dc55ab6290f 100644 (file)
@@ -14,7 +14,7 @@
                        'wcf.moderation.report.success': '{lang}wcf.moderation.report.success{/lang}'
                });
                
-               new {if $commentHandlerClass|isset}{@$commentHandlerClass}{else}WCF.Comment.Handler{/if}('{$commentContainerID}', '{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(48)}', '{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(32)}');
+               new {if $commentHandlerClass|isset}{@$commentHandlerClass}{else}WCF.Comment.Handler{/if}('{$commentContainerID}');
                {if MODULE_LIKE && $commentList->getCommentManager()->supportsLike() && $__wcf->getSession()->getPermission('user.like.canViewLike')}
                        require(['WoltLabSuite/Core/Ui/Like/Handler'], function(UiLikeHandler) {
                                var canDislike = {if LIKE_ENABLE_DISLIKE}true{else}false{/if};
index b820c55d08510028b6733ad82830b10dfc45f39f..0534cc8c8a5ed6b69a774b736c5a261205f0a351 100644 (file)
@@ -1,6 +1,6 @@
 <li class="box48 jsCommentAdd jsOnly">
        {@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(48)}
-       <div class="commentListAddComment collapsed" data-placeholder="{lang}wcf.comment.add{/lang}">
+       <div class="commentListAddComment collapsed jsOuterEditorContainer" data-placeholder="{lang}wcf.comment.add{/lang}">
                <div class="commentListAddCommentEditorContainer">
                        {if !$commentList->getCommentManager()->canAddWithoutApproval($commentList->objectID)}
                                <p class="info jsCommentAddRequiresApproval">{lang}wcf.comment.moderation.info{/lang}</p>
        </div>
 </li>
 
+{* comment response, editor instance will be re-used *}
+{capture assign=_commentResponseWysiwygSelector}{$wysiwygSelector}Response{/capture}
+<li class="jsCommentResponseAddContainer" style="display: none">
+       <div class="box32 jsCommentResponseAdd jsOnly">
+               {@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(32)}
+               <div class="commentListAddCommentResponse collapsed jsOuterEditorContainer" data-placeholder="{lang}wcf.comment.response.add{/lang}">
+                       <div class="commentListAddCommentResponseEditorContainer">
+                               {if !$commentList->getCommentManager()->canAddWithoutApproval($commentList->objectID)}
+                                       <p class="info jsCommentAddRequiresApproval">{lang}wcf.comment.moderation.info{/lang}</p>
+                               {/if}
+                               
+                               <textarea id="{$_commentResponseWysiwygSelector}" name="text" class="wysiwygTextarea"
+                                         data-disable-attachments="true"
+                                         data-disable-media="true"
+                               ></textarea>
+                               
+                               {* in-template call for full backwards-compatibility *}
+                               {$commentList->getCommentManager()->setDisallowedBBCodes()}
+                               
+                               {include file='wysiwyg' wysiwygSelector=$_commentResponseWysiwygSelector}
+                               
+                               <div class="formSubmit">
+                                       <button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
+                                       
+                                       {include file='messageFormPreviewButton' previewMessageFieldID=$_commentResponseWysiwygSelector previewButtonID=$_commentResponseWysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment.response' previewMessageObjectID=0}
+                               </div>
+                       </div>
+               </div>
+       </div>
+</li>
diff --git a/com.woltlab.wcf/templates/commentResponseEditor.tpl b/com.woltlab.wcf/templates/commentResponseEditor.tpl
new file mode 100644 (file)
index 0000000..c8a0b0b
--- /dev/null
@@ -0,0 +1,16 @@
+{capture assign='wysiwygSelector'}commentResponseEditor{@$response->responseID}{/capture}
+<textarea id="{$wysiwygSelector}" class="wysiwygTextarea"
+          data-disable-attachments="true"
+          data-disable-media="true"
+>{$response->message}</textarea>
+{*include file='messageFormTabsInline'*}
+
+<div class="formSubmit">
+       <button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
+       
+       {include file='messageFormPreviewButton' previewMessageFieldID=$wysiwygSelector previewButtonID=$wysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment.response' previewMessageObjectID=$response->responseID}
+       
+       <button data-type="cancel">{lang}wcf.global.button.cancel{/lang}</button>
+</div>
+
+{include file='wysiwyg'}
index 0e6bf13afa07951826da76b3238a2f19d43f2c46..79b0f39d732870e03f15cca90d147f47c370bd67 100644 (file)
@@ -67,17 +67,7 @@ WCF.Comment.Handler = Class.extend({
         */
        _responses: { },
        
-       /**
-        * user's avatar (48px version)
-        * @var string
-        */
-       _userAvatar: '',
-       
-       /**
-        * user's avatar (32px version)
-        * @var string
-        */
-       _userAvatarSmall: '',
+       _responseCache: {},
        
        /**
         * data of the comment the active guest user is about to create
@@ -99,10 +89,8 @@ WCF.Comment.Handler = Class.extend({
         * Initializes the WCF.Comment.Handler class.
         * 
         * @param       string          containerID
-        * @param       string          userAvatar
-        * @param       string          userAvatarSmall
         */
-       init: function(containerID, userAvatar, userAvatarSmall) {
+       init: function(containerID) {
                this._commentButtonList = { };
                this._comments = { };
                this._containerID = containerID;
@@ -111,10 +99,11 @@ WCF.Comment.Handler = Class.extend({
                this._loadNextResponses = { };
                this._permalinkComment = null;
                this._permalinkResponse = null;
-               this._responses = { };
+               this._responseAdd = null;
+               this._responseCache = {};
+               this._responseRevert = null;
+               this._responses = {};
                this._scrollTarget = null;
-               this._userAvatar = userAvatar;
-               this._userAvatarSmall = userAvatarSmall;
                
                this._container = $('#' + $.wcfEscapeID(this._containerID));
                if (!this._container.length) {
@@ -123,7 +112,6 @@ WCF.Comment.Handler = Class.extend({
                }
                
                this._proxy = new WCF.Action.Proxy({
-                       failure: $.proxy(this._failure, this),
                        success: $.proxy(this._success, this)
                });
                
@@ -133,17 +121,29 @@ WCF.Comment.Handler = Class.extend({
                // add new comment
                if (this._container.data('canAdd')) {
                        if (elBySel('.commentListAddComment .wysiwygTextarea', this._container[0]) === null) {
-                               console.debug("Missing WYSIWYG implementation, adding comments is not available.");
+                               console.error("Missing WYSIWYG implementation, adding comments is not available.");
                        }
                        else {
-                               require(['WoltLabSuite/Core/Ui/Comment/Add'], (function (UiCommentAdd) {
+                               require(['WoltLabSuite/Core/Ui/Comment/Add', 'WoltLabSuite/Core/Ui/Comment/Response/Add'], (function (UiCommentAdd, UiCommentResponseAdd) {
                                        new UiCommentAdd(elBySel('.jsCommentAdd',  this._container[0]));
+                                       
+                                       this._responseAdd = new UiCommentResponseAdd(
+                                               elBySel('.jsCommentResponseAdd',  this._container[0]),
+                                               {
+                                                       callbackInsert: (function () {
+                                                               if (this._responseRevert !== null) {
+                                                                       this._responseRevert();
+                                                                       this._responseRevert = null;
+                                                               }
+                                                       }).bind(this)
+                                               });
                                }).bind(this));
                        }
                }
                
-               require(['WoltLabSuite/Core/Ui/Comment/Edit'], (function (UiCommentEdit) {
+               require(['WoltLabSuite/Core/Ui/Comment/Edit', 'WoltLabSuite/Core/Ui/Comment/Response/Edit'], (function (UiCommentEdit, UiCommentResponseEdit) {
                        new UiCommentEdit(this._container[0]);
+                       new UiCommentResponseEdit(this._container[0]);
                }).bind(this));
                
                WCF.DOMNodeInsertedHandler.execute();
@@ -531,10 +531,8 @@ WCF.Comment.Handler = Class.extend({
         */
        _initResponse: function(responseID, response) {
                if (response.data('canEdit')) {
-                       var $editButton = $('<li><a href="#" class="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>');
-                       
-                       var self = this;
-                       $editButton.data('responseID', responseID).appendTo(response.find('ul.buttonList:eq(0)')).click(function(event) { self._prepareEdit(event, true); });
+                       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>');
+                       $editButton.data('responseID', responseID).appendTo(response.find('ul.buttonList:eq(0)'));
                }
                
                if (response.data('canDelete')) {
@@ -553,61 +551,34 @@ WCF.Comment.Handler = Class.extend({
         */
        _initAddResponse: function(commentID, comment) {
                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]);
-               
-               var $listItem = $('<div class="box32 commentResponseAdd jsCommentResponseAdd">' + this._userAvatarSmall + '<div /></div>').hide();
-               $listItem.appendTo(this._commentButtonList[commentID].parent().show());
-               
-               var $inputContainer = $listItem.children('div');
-               var $input = $('<textarea placeholder="' + WCF.Language.get('wcf.comment.response.add') + '" maxlength="65535" class="long" />').data('commentID', commentID).appendTo($inputContainer).flexible();
-               $('<button class="small">' + WCF.Language.get('wcf.global.button.submit') + '</button>').click($.proxy(function(event) { this._save(event, true); }, this)).appendTo($inputContainer);
-               
-               var self = this;
-               $input.keyup(function(event) { self._keyUp(event, true); });
-               
-               comment.data('responsePlaceholder', $placeholder).data('responseInput', $listItem);
-               
-               // mirror the moderation notice
-               var commentRequireApproval = elBySel('.commentListAddCommentEditorContainer .jsCommentAddRequiresApproval', this._container[0]);
-               if (commentRequireApproval) {
-                       $inputContainer[0].insertBefore(commentRequireApproval.cloneNode(true), $inputContainer[0].firstChild);
-               }
+               this._commentButtonList[commentID].parent().show();
        },
        
        /**
-        * Prepares editing of a comment or response.
+        * Displays the UI elements to create a response.
         * 
         * @param       object          event
-        * @param       boolean         isResponse
         */
-       _prepareEdit: function(event, isResponse) {
-               if (!isResponse) {
-                       throw new Error("Editing comments is no longer supported through this method.");
+       _showAddResponse: function(event) {
+               event.preventDefault();
+               
+               // API is missing
+               if (this._responseAdd === null) {
+                       console.error("Missing response API.");
+                       return;
                }
                
-               event.preventDefault();
-               var $button = $(event.currentTarget);
-               var $data = {
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID'),
-                       responseID: $button.data('responseID')
-               };
+               var responseContainer = this._responseAdd.getContainer();
+               if (responseContainer === null) {
+                       // instance is busy
+                       return;
+               }
+               
+               if (this._responseRevert !== null) {
+                       this._responseRevert();
+                       this._responseRevert = null;
+               }
                
-               this._proxy.setOption('data', {
-                       actionName: 'prepareEdit',
-                       className: 'wcf\\data\\comment\\CommentAction',
-                       parameters: {
-                               data: $data
-                       }
-               });
-               this._proxy.sendRequest();
-       },
-       
-       /**
-        * Displays the UI elements to create a response.
-        * 
-        * @param       object          event
-        */
-       _showAddResponse: function(event) {
                var $placeholder = $(event.currentTarget);
                var $commentID = $placeholder.data('commentID');
                if ($placeholder.prev().hasClass('jsCommentLoadNextResponses')) {
@@ -617,101 +588,27 @@ WCF.Comment.Handler = Class.extend({
                
                $placeholder.hide();
                
-               var $responseInput = this._comments[$commentID].data('responseInput').show();
-               $responseInput.find('textarea').focus();
-               
-               $responseInput.parents('.commentOptionContainer').addClass('jsAddResponseActive');
-       },
-       
-       /**
-        * Handles the keyup event for comments and responses.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        */
-       _keyUp: function(event, isResponse) {
-               if (event.which === $.ui.keyCode.ESCAPE) {
-                       // cancel input
-                       $(event.currentTarget).val('').trigger('blur', event).trigger('updateHeight');
-                       
-                       return;
-               }
-               else if (event.which === $.ui.keyCode.ENTER && event.ctrlKey) {
-                       this._save(null, isResponse, $(event.currentTarget));
-                       
-                       return false;
-               }
-       },
-       
-       /**
-        * Saves entered comment/response.
-        * 
-        * @param       object          event
-        * @param       boolean         isResponse
-        * @param       jQuery          input
-        */
-       _save: function(event, isResponse, input) {
-               if (!isResponse) {
-                       throw new Error("Adding comments through `_save()` is no longer supported.");
-               }
-               
-               var $input = (event === null) ? input : $(event.currentTarget).parent().children('textarea');
-               $input.next('small.innerError').remove();
-               var $value = $.trim($input.val());
-               
-               // ignore empty comments
-               if ($value == '') {
-                       return;
+               if (responseContainer.parentNode && responseContainer.parentNode.classList.contains('jsCommentResponseAddContainer')) {
+                       // strip the parent element, it is used as a work-around
+                       elRemove(responseContainer.parentNode);
                }
                
-               var $data = {
-                       commentID: $input.data('commentID'),
-                       message: $value,
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
+               var commentOptionContainer = this._commentButtonList[$commentID][0].closest('.commentOptionContainer');
+               commentOptionContainer.parentNode.insertBefore(responseContainer, commentOptionContainer.nextSibling);
                
-               if (!WCF.User.userID) {
-                       this._commentData = $data;
-                       
-                       // check if guest dialog has already been loaded
-                       this._proxy.setOption('data', {
-                               actionName: 'getGuestDialog',
-                               className: 'wcf\\data\\comment\\CommentAction',
-                               parameters: {
-                                       data: {
-                                               message: $value,
-                                               objectID: this._container.data('objectID'),
-                                               objectTypeID: this._container.data('objectTypeID')
-                                       }
-                               }
-                       });
-                       this._proxy.sendRequest();
+               if (typeof this._responseCache[$commentID] === 'string') {
+                       this._responseAdd.setContent(this._responseCache[$commentID]);
                }
                else {
-                       new WCF.Action.Proxy({
-                               autoSend: true,
-                               data: {
-                                       actionName: 'addResponse',
-                                       className: 'wcf\\data\\comment\\CommentAction',
-                                       parameters: {
-                                               data: $data
-                                       }
-                               },
-                               success: $.proxy(this._success, this),
-                               failure: (function(data, jqXHR, textStatus, errorThrown) {
-                                       if (data.returnValues && data.returnValues.fieldName) {
-                                               if (data.returnValues.fieldName === 'text' && data.returnValues.errorType) {
-                                                       $('<small class="innerError">' + data.returnValues.errorType + '</small>').insertAfter($input);
-                                                       
-                                                       return false;
-                                               }
-                                       }
-                                       
-                                       this._failure(data, jqXHR, textStatus, errorThrown);
-                               }).bind(this)
-                       });
+                       this._responseAdd.setContent('');
                }
+               
+               this._responseRevert = (function () {
+                       this._responseCache[$commentID] = this._responseAdd.getContent();
+                       
+                       elRemove(responseContainer);
+                       $placeholder.show();
+               }).bind(this);
        },
        
        /**
@@ -747,24 +644,6 @@ WCF.Comment.Handler = Class.extend({
                }, this));
        },
        
-       /**
-        * Handles a failed AJAX request.
-        * 
-        * @param       object          data
-        * @param       object          jqXHR
-        * @param       string          textStatus
-        * @param       string          errorThrown
-        * @return      boolean
-        */
-       _failure: function(data, jqXHR, textStatus, errorThrown) {
-               if (!WCF.User.userID && this._guestDialog) {
-                       // enable submit button again
-                       this._guestDialog.find('input[type="submit"]').enable();
-               }
-               
-               return true;
-       },
-       
        /**
         * Handles successful AJAX requests.
         * 
@@ -774,33 +653,6 @@ WCF.Comment.Handler = Class.extend({
         */
        _success: function(data, textStatus, jqXHR) {
                switch (data.actionName) {
-                       case 'addResponse':
-                               if (data.returnValues.guestDialog) {
-                                       this._createGuestDialog(data.returnValues.guestDialog, data.returnValues.useCaptcha);
-                               }
-                               else {
-                                       var $comment = this._comments[data.returnValues.commentID];
-                                       $comment.find('.jsCommentResponseAdd textarea').val('').blur().trigger('updateHeight');
-                                       
-                                       // revert response field
-                                       elBySel('.commentOptionContainer', $comment[0]).classList.remove('jsAddResponseActive');
-                                       elHide(elBySel('.jsCommentResponseAdd', $comment[0]));
-                                       elShow(elBySel('.jsCommentShowAddResponse', $comment[0]));
-                                       
-                                       var $responseList = $comment.find('ul.commentResponseList');
-                                       if (!$responseList.length) $responseList = $('<ul class="containerList commentResponseList" />').insertBefore($comment.find('.commentOptionContainer'));
-                                       $(data.returnValues.template).appendTo($responseList).wcfFadeIn();
-                                       
-                                       if (!WCF.User.userID) {
-                                               this._guestDialog.wcfDialog('close');
-                                       }
-                               }
-                       break;
-                       
-                       case 'edit':
-                               this._update(data);
-                       break;
-                       
                        case 'enable':
                                this._enable(data);
                                break;
@@ -825,17 +677,9 @@ WCF.Comment.Handler = Class.extend({
                                this._insertResponses(data);
                        break;
                        
-                       case 'prepareEdit':
-                               this._edit(data);
-                       break;
-                       
                        case 'remove':
                                this._remove(data);
                        break;
-                       
-                       case 'getGuestDialog':
-                               this._createGuestDialog(data.returnValues.template, data.returnValues.useCaptcha);
-                       break;
                }
                
                WCF.DOMNodeInsertedHandler.execute();
@@ -992,7 +836,7 @@ WCF.Comment.Handler = Class.extend({
                        var $response = this._responses[data.returnValues.responseID];
                        var $comment = this._comments[$response.parents('li.comment:eq(0)').data('commentID')];
                        
-                       // decrease response counter as a correct response count
+                       // decrease response counter because a correct response count
                        // is required in _handleLoadNextResponses()
                        $comment.data('responses', parseInt($comment.data('responses')) - 1);
                        
@@ -1009,215 +853,18 @@ WCF.Comment.Handler = Class.extend({
                }
        },
        
-       /**
-        * Prepares editing of a comment or response.
-        * 
-        * @param       object          data
-        */
-       _edit: function(data) {
-               var $content;
-               if (data.returnValues.commentID) {
-                       $content = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0)');
-               }
-               else {
-                       $content = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0)');
-               }
-               
-               // replace content with input field
-               $content.html($.proxy(function(index, oldHTML) {
-                       var $textarea = $('<textarea class="long" maxlength="65535" />').val(data.returnValues.message);
-                       $textarea.data('__html', oldHTML).keyup($.proxy(this._keyUpEdit, this));
-                       
-                       if (data.returnValues.commentID) {
-                               $textarea.data('commentID', data.returnValues.commentID);
-                       }
-                       else {
-                               $textarea.data('responseID', data.returnValues.responseID);
-                       }
-                       
-                       return $textarea;
-               }, this));
-               var $textarea = $content.children('textarea');
-               $('<button class="small">' + WCF.Language.get('wcf.global.button.submit') + '</button>').insertAfter($textarea).click($.proxy(this._saveEdit, this));
-               $textarea.focus().flexible();
-               
-               // hide elements
-               $content.parent().find('.containerHeadline:eq(0)').hide();
-               $content.parent().find('.buttonGroupNavigation:eq(0)').hide();
-       },
-       
-       /**
-        * Updates a comment or response.
-        * 
-        * @param       object          data
-        */
-       _update: function(data) {
-               var $input;
-               if (data.returnValues.commentID) {
-                       $input = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0) > textarea');
-               }
-               else {
-                       $input = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0) > textarea');
-               }
-               
-               $input.data('__html', data.returnValues.message);
-               
-               this._cancelEdit($input);
-       },
-       
-       /**
-        * Creates the guest dialog using the given template.
-        * 
-        * @param       string          template
-        * @param       boolean         useCaptcha
-        */
-       _createGuestDialog: function(template, useCaptcha) {
-               var $refreshGuestDialog = !!this._guestDialog;
-               if (!this._guestDialog) {
-                       this._guestDialog = $('<div id="commentAddGuestDialog" />').hide().appendTo(document.body);
-               }
-               
-               this._guestDialog.html(template);
-               this._guestDialog.data('useCaptcha', useCaptcha);
-               
-               // bind submit event listeners
-               this._guestDialog.find('input[type="submit"]').click($.proxy(this._submit, this));
-               this._guestDialog.find('input[type="text"]').keydown($.proxy(this._keyDown, this));
-               
-               this._guestDialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.comment.guestDialog.title')
-               });
-       },
-       
-       /**
-        * Handles clicking enter in the input fields of the guest dialog by
-        * submitting it.
-        * 
-        * @param       Event           event
-        */
-       _keyDown: function(event) {
-               if (event.which === $.ui.keyCode.ENTER) {
-                       this._submit();
-               }
-       },
-       
-       /**
-        * Handles submitting the guest dialog.
-        * 
-        * @param       Event           event
-        */
-       _submit: function(event) {
-               var $requestData = {
-                       actionName: 'addResponse',
-                       className: 'wcf\\data\\comment\\CommentAction'
-               };
-               
-               var $data = this._commentData;
-               $data.username = this._guestDialog.find('input[name="username"]').val();
-               
-               $requestData.parameters = {
-                       data: $data
-               };
-               
-               var $captchaData = WCF.System.Captcha.getData('commentAdd');
-               if ($captchaData instanceof Promise) {
-                       $captchaData.then($.proxy(function ($captchaData) {
-                               $requestData = $.extend($captchaData, $requestData);
-                               this._proxy.setOption('data', $requestData);
-                               this._proxy.sendRequest();
-                       }, this))
-               }
-               else {
-                       $requestData = $.extend($captchaData, $requestData);
-                       this._proxy.setOption('data', $requestData);
-                       this._proxy.sendRequest();
-               }
-       },
-       
-       /**
-        * Handles the keyup event for comments and responses during edit.
-        * 
-        * @param       object          event
-        */
-       _keyUpEdit: function(event) {
-               if (event.which === $.ui.keyCode.ESCAPE) {
-                       // cancel input
-                       this._cancelEdit($(event.currentTarget));
-                       return;
-               }
-               else if (event.which === $.ui.keyCode.ENTER && event.ctrlKey) {
-                       this._saveEdit(event);
-                       return false;
-               }
-       },
-       
-       /**
-        * Saves editing of a comment or response.
-        * 
-        * @param       object          event
-        */
-       _saveEdit: function(event) {
-               var $input = $(event.currentTarget);
-               if ($input.is('button')) {
-                       $input.parent().children('small.innerError').remove();
-                       $input = $input.parent().children('textarea');
-               }
-               var $message = $.trim($input.val());
-               
-               // ignore empty message
-               if ($message === '') {
-                       return;
-               }
-               
-               var $data = {
-                       message: $message,
-                       objectID: this._container.data('objectID'),
-                       objectTypeID: this._container.data('objectTypeID')
-               };
-               if ($input.data('commentID')) {
-                       $data.commentID = $input.data('commentID');
-               }
-               else {
-                       $data.responseID = $input.data('responseID');
-               }
-               
-               new WCF.Action.Proxy({
-                       autoSend: true,
-                       data: {
-                               actionName: 'edit',
-                               className: 'wcf\\data\\comment\\CommentAction',
-                               parameters: {
-                                       data: $data
-                               }
-                       },
-                       success: $.proxy(this._success, this),
-                       failure: (function(data, jqXHR, textStatus, errorThrown) {
-                               if (data.returnValues && data.returnValues.fieldName) {
-                                       if (data.returnValues.fieldName === 'text' && data.returnValues.errorType) {
-                                               $('<small class="innerError">' + data.returnValues.errorType + '</small>').insertAfter($input);
-                                               
-                                               return false;
-                                       }
-                               }
-                               
-                               this._failure(data, jqXHR, textStatus, errorThrown);
-                       }).bind(this)
-               });
-       },
-       
-       /**
-        * Cancels editing of a comment or response.
-        * 
-        * @param       jQuery          input
-        */
-       _cancelEdit: function(input) {
-               // restore elements
-               input.parent().prev('.containerHeadline:eq(0)').show();
-               input.parent().next('.buttonGroupNavigation:eq(0)').show();
-               
-               // restore HTML
-               input.parent().html(input.data('__html'));
-       }
+       _prepareEdit: function() { console.warn("This method is no longer supported."); },
+       _keyUp: function() { console.warn("This method is no longer supported."); },
+       _save: function() { console.warn("This method is no longer supported."); },
+       _failure: function() { console.warn("This method is no longer supported."); },
+       _edit: function() { console.warn("This method is no longer supported."); },
+       _update: function() { console.warn("This method is no longer supported."); },
+       _createGuestDialog: function() { console.warn("This method is no longer supported."); },
+       _keyDown: function() { console.warn("This method is no longer supported."); },
+       _submit: function() { console.warn("This method is no longer supported."); },
+       _keyUpEdit: function() { console.warn("This method is no longer supported."); },
+       _saveEdit: function() { console.warn("This method is no longer supported."); },
+       _cancelEdit: function() { console.warn("This method is no longer supported."); }
 });
 
 /**
index c167bbb0972f89c6a69e7975cbf3c0fec7595408..5899e72919b18235097d03da860c02a92d3c1962 100644 (file)
@@ -1,13 +1,21 @@
 /**
  * Handles the comment add feature.
  * 
+ * Warning: This implementation is also used for responses, but in a slightly
+ *          modified version. Changes made to this class need to be verified
+ *          against the response implementation.
+ * 
  * @author     Alexander Ebert
  * @copyright  2001-2017 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/Comment/Add
  */
-define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'],
-       function(Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha) {
+define([
+       'Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Ui/Scroll', 'EventKey', 'User', 'WoltLabSuite/Core/Controller/Captcha'
+],
+function(
+       Ajax, Core, EventHandler, Language, DomChangeListener, DomUtil, DomTraverse, UiDialog, UiNotification, UiScroll, EventKey, User, ControllerCaptcha
+) {
        "use strict";
        
        if (!COMPILER_TARGET_DEFAULT) {
@@ -16,6 +24,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        init: function() {},
                        _submitGuestDialog: function() {},
                        _submit: function() {},
+                       _getParameters: function () {},
                        _validate: function() {},
                        throwError: function() {},
                        _showLoadingOverlay: function() {},
@@ -43,7 +52,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                 */
                init: function(container) {
                        this._container = container;
-                       this._content = elBySel('.commentListAddComment', this._container);
+                       this._content = elBySel('.jsOuterEditorContainer', this._container);
                        this._textarea = elBySel('.wysiwygTextarea', this._container);
                        this._editor = null;
                        this._loadingOverlay = null;
@@ -54,7 +63,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                                        
                                        this._content.classList.remove('collapsed');
                                        
-                                       UiScroll.element(this._container, (function() {
+                                       UiScroll.element(this._container, (function () {
                                                window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
                                        }).bind(this));
                                }
@@ -139,14 +148,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        this._showLoadingOverlay();
                        
                        // build parameters
-                       var commentList = this._container.closest('.commentList');
-                       var parameters = {
-                               data: {
-                                       message: this._getEditor().code.get(),
-                                       objectID: elData(commentList, 'object-id'),
-                                       objectTypeID: elData(commentList, 'object-type-id')
-                               }
-                       };
+                       var parameters = this._getParameters();
                        
                        EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
                        
@@ -159,6 +161,24 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        }, additionalParameters));
                },
                
+               /**
+                * Returns the request parameters to add a comment.
+                * 
+                * @return      {{data: {message: string, objectID: number, objectTypeID: number}}}
+                * @protected
+                */
+               _getParameters: function () {
+                       var commentList = this._container.closest('.commentList');
+                       
+                       return {
+                               data: {
+                                       message: this._getEditor().code.get(),
+                                       objectID: ~~elData(commentList, 'object-id'),
+                                       objectTypeID: ~~elData(commentList, 'object-type-id')
+                               }
+                       };
+               },
+               
                /**
                 * Validates the message and invokes listeners to perform additional validation.
                 * 
@@ -282,10 +302,10 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                },
                
                /**
-                * Inserts the rendered message into the post list, unless the post is on the next
-                * page in which case a redirect will be performed instead.
+                * Inserts the rendered message.
                 * 
                 * @param       {Object}        data    response data
+                * @return      {Element}       scroll target
                 * @protected
                 */
                _insertMessage: function(data) {
@@ -296,6 +316,8 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                        UiNotification.show(Language.get('wcf.global.success.add'));
                        
                        DomChangeListener.trigger();
+                       
+                       return this._container.nextElementSibling;
                },
                
                /**
@@ -314,7 +336,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                                elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
                        }
                        else {
-                               this._insertMessage(data);
+                               var scrollTarget = this._insertMessage(data);
                                
                                if (!User.userId) {
                                        UiDialog.close('jsDialogGuestComment');
@@ -325,7 +347,7 @@ define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/ChangeListener', 'Dom/U
                                this._hideLoadingOverlay();
                                
                                window.setTimeout((function () {
-                                       UiScroll.element(this._container.nextElementSibling);
+                                       UiScroll.element(scrollTarget);
                                }).bind(this), 100);
                        }
                },
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Add.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Add.js
new file mode 100644 (file)
index 0000000..674bd9c
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Handles the comment response add feature.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Comment/Add
+ */
+define([
+       'Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Dom/Traverse', 'Ui/Notification',  'WoltLabSuite/Core/Ui/Comment/Add'
+],
+function(
+       Core, Language, DomChangeListener, DomUtil, DomTraverse, UiNotification, UiCommentAdd
+) {
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       getContainer: function() {},
+                       getContent: function() {},
+                       setContent: function() {},
+                       _submitGuestDialog: function() {},
+                       _submit: function() {},
+                       _getParameters: function () {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showLoadingOverlay: function() {},
+                       _hideLoadingOverlay: function() {},
+                       _reset: function() {},
+                       _handleError: function() {},
+                       _getEditor: function() {},
+                       _insertMessage: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       /**
+        * @constructor
+        */
+       function UiCommentResponseAdd(container, options) { this.init(container, options); }
+       Core.inherit(UiCommentResponseAdd, UiCommentAdd, {
+               init: function (container, options) {
+                       UiCommentResponseAdd._super.prototype.init.call(this, container);
+                       
+                       this._options = Core.extend({
+                               callbackInsert: null
+                       }, options);
+               },
+               
+               /**
+                * Returns the editor container for placement or `null` if the editor is busy.
+                * 
+                * @return      {(Element|null)}
+                */
+               getContainer: function() {
+                       return (this._isBusy) ? null : this._container;
+               },
+               
+               /**
+                * Retrieves the current content from the editor.
+                * 
+                * @return      {string}
+                */
+               getContent: function () {
+                       return window.jQuery(this._textarea).redactor('code.get');
+               },
+               
+               /**
+                * Sets the content and places the caret at the end of the editor.
+                * 
+                * @param       {string}        html
+                */
+               setContent: function (html) {
+                       window.jQuery(this._textarea).redactor('code.set', html);
+                       window.jQuery(this._textarea).redactor('WoltLabCaret.endOfEditor');
+                       
+                       var innerError = elBySel('.innerError', this._textarea.parentNode);
+                       if (innerError !== null) elRemove(innerError);
+               },
+               
+               _getParameters: function () {
+                       var parameters = UiCommentResponseAdd._super.prototype._getParameters.call(this);
+                       parameters.data.commentID = ~~elData(this._container.closest('.comment'), 'object-id');
+                       
+                       return parameters;
+               },
+               
+               _insertMessage: function(data) {
+                       var commentContent = DomTraverse.childByClass(this._container.parentNode, 'commentContent');
+                       var responseList = commentContent.nextElementSibling;
+                       if (responseList === null || !responseList.classList.contains('commentResponseList')) {
+                               responseList = elCreate('ul');
+                               responseList.className = 'containerList commentResponseList';
+                               elData(responseList, 'responses', 0);
+                               
+                               commentContent.parentNode.insertBefore(responseList, commentContent.nextSibling);
+                       }
+                       
+                       // insert HTML
+                       //noinspection JSCheckFunctionSignatures
+                       DomUtil.insertHtml(data.returnValues.template, responseList, 'append');
+                       
+                       UiNotification.show(Language.get('wcf.global.success.add'));
+                       
+                       DomChangeListener.trigger();
+                       
+                       // reset editor
+                       window.jQuery(this._textarea).redactor('code.set', '');
+                       
+                       if (this._options.callbackInsert !== null) this._options.callbackInsert();
+                       
+                       // update counter
+                       elData(responseList, 'responses', responseList.children.length);
+                       
+                       return responseList.lastElementChild;
+               },
+               
+               _ajaxSetup: function() {
+                       var data = UiCommentResponseAdd._super.prototype._ajaxSetup.call(this);
+                       data.data.actionName = 'addResponse';
+                       
+                       return data;
+               }
+       });
+       
+       return UiCommentResponseAdd;
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Edit.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Comment/Response/Edit.js
new file mode 100644 (file)
index 0000000..de1fa08
--- /dev/null
@@ -0,0 +1,177 @@
+/**
+ * Provides editing support for comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Comment/Response/Edit
+ */
+define(
+       [
+               'Ajax',         'Core',            'Dictionary',          'Environment',
+               'EventHandler', 'Language',        'List',                'Dom/ChangeListener', 'Dom/Traverse',
+               'Dom/Util',     'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll', 'WoltLabSuite/Core/Ui/Comment/Edit'
+       ],
+       function(
+               Ajax,            Core,              Dictionary,            Environment,
+               EventHandler,    Language,          List,                  DomChangeListener,    DomTraverse,
+               DomUtil,         UiNotification,    UiReusableDropdown,    UiScroll, UiCommentEdit
+       )
+{
+       "use strict";
+       
+       if (!COMPILER_TARGET_DEFAULT) {
+               var Fake = function() {};
+               Fake.prototype = {
+                       init: function() {},
+                       rebuild: function() {},
+                       _click: function() {},
+                       _prepare: function() {},
+                       _showEditor: function() {},
+                       _restoreMessage: function() {},
+                       _save: function() {},
+                       _validate: function() {},
+                       throwError: function() {},
+                       _showMessage: function() {},
+                       _hideEditor: function() {},
+                       _restoreEditor: function() {},
+                       _destroyEditor: function() {},
+                       _getEditorId: function() {},
+                       _getObjectId: function() {},
+                       _ajaxFailure: function() {},
+                       _ajaxSuccess: function() {},
+                       _ajaxSetup: function() {}
+               };
+               return Fake;
+       }
+       
+       /**
+        * @constructor
+        */
+       function UiCommentResponseEdit(container) { this.init(container); }
+       Core.inherit(UiCommentResponseEdit, UiCommentEdit, {
+               /**
+                * Initializes the comment edit manager.
+                * 
+                * @param       {Element}       container       container element
+                */
+               init: function(container) {
+                       this._activeElement = null;
+                       this._callbackClick = null;
+                       this._container = container;
+                       this._editorContainer = null;
+                       this._responses = new List();
+                       
+                       this.rebuild();
+                       
+                       DomChangeListener.add('Ui/Comment/Response/Edit_' + DomUtil.identify(this._container), this.rebuild.bind(this));
+               },
+               
+               /**
+                * Initializes each applicable message, should be called whenever new
+                * messages are being displayed.
+                */
+               rebuild: function() {
+                       elBySelAll('.commentResponse', this._container, (function (response) {
+                               if (this._responses.has(response)) {
+                                       return;
+                               }
+                               
+                               if (elDataBool(response, 'can-edit')) {
+                                       var button = elBySel('.jsCommentResponseEditButton', response);
+                                       if (button !== null) {
+                                               if (this._callbackClick === null) {
+                                                       this._callbackClick = this._click.bind(this);
+                                               }
+                                               
+                                               button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
+                                       }
+                               }
+                               
+                               this._responses.add(response);
+                       }).bind(this));
+               },
+               
+               /**
+                * Handles clicks on the edit button.
+                *
+                * @param       {?Event}        event           event object
+                * @protected
+                */
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       if (this._activeElement === null) {
+                               this._activeElement = event.currentTarget.closest('.commentResponse');
+                               
+                               this._prepare();
+                               
+                               Ajax.api(this, {
+                                       actionName: 'beginEdit',
+                                       objectIDs: [this._getObjectId(this._activeElement)]
+                               });
+                       }
+                       else {
+                               UiNotification.show('wcf.message.error.editorAlreadyInUse', null, 'warning');
+                       }
+               },
+               
+               /**
+                * Prepares the message for editor display.
+                * 
+                * @protected
+                */
+               _prepare: function() {
+                       this._editorContainer = elCreate('div');
+                       this._editorContainer.className = 'commentEditorContainer';
+                       this._editorContainer.innerHTML = '<span class="icon icon48 fa-spinner"></span>';
+                       
+                       var content = elBySel('.commentResponseContent', this._activeElement);
+                       content.insertBefore(this._editorContainer, content.firstChild);
+               },
+               
+               /**
+                * Shows the update message.
+                * 
+                * @param       {Object}        data            ajax response data
+                * @protected
+                */
+               _showMessage: function(data) {
+                       // set new content
+                       //noinspection JSCheckFunctionSignatures
+                       DomUtil.setInnerHtml(elBySel('.commentResponseContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
+                       
+                       this._restoreMessage();
+                       
+                       UiNotification.show();
+               },
+               
+               /**
+                * Returns the unique editor id.
+                * 
+                * @return      {string}        editor id
+                * @protected
+                */
+               _getEditorId: function() {
+                       return 'commentResponseEditor' + this._getObjectId(this._activeElement);
+               },
+               
+               _ajaxSetup: function() {
+                       var objectTypeId = ~~elData(this._container, 'object-type-id');
+                       
+                       return {
+                               data: {
+                                       className: 'wcf\\data\\comment\\response\\CommentResponseAction',
+                                       parameters: {
+                                               data: {
+                                                       objectTypeID: objectTypeId
+                                               }
+                                       }
+                               },
+                               silent: true
+                       };
+               }
+       });
+       
+       return UiCommentResponseEdit;
+});
index d3b1c133100312cf9e78380e033923e80bcbd7d4..d2da056a73556556c5e2bd77e8b25c8ea0a8e280 100644 (file)
@@ -52,7 +52,7 @@ class CommentAction extends AbstractDatabaseObjectAction implements IMessageInli
         * captcha object type used for comments
         * @var ObjectType
         */
-       public $captchaObjectType = null;
+       public $captchaObjectType;
        
        /**
         * @inheritDoc
@@ -63,13 +63,13 @@ class CommentAction extends AbstractDatabaseObjectAction implements IMessageInli
         * comment object
         * @var Comment
         */
-       protected $comment = null;
+       protected $comment;
        
        /**
         * comment processor
         * @var ICommentManager
         */
-       protected $commentProcessor = null;
+       protected $commentProcessor;
        
        /**
         * @var HtmlInputProcessor
@@ -80,19 +80,19 @@ class CommentAction extends AbstractDatabaseObjectAction implements IMessageInli
         * response object
         * @var CommentResponse
         */
-       protected $response = null;
+       protected $response;
        
        /**
         * comment object created by addComment()
         * @var Comment
         */
-       public $createdComment = null;
+       public $createdComment;
        
        /**
         * response object created by addResponse()
         * @var CommentResponse
         */
-       public $createdResponse = null;
+       public $createdResponse;
        
        /**
         * errors occurring through the validation of addComment or addResponse
@@ -456,6 +456,7 @@ class CommentAction extends AbstractDatabaseObjectAction implements IMessageInli
                        'userID' => WCF::getUser()->userID ?: null,
                        'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
                        'message' => $this->parameters['data']['message'],
+                       'enableHtml' => 1,
                        'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1
                ]);
                $this->createdResponse->setComment($this->comment);
index 832075de0a305e6969f597c5cd8b96c99a87d6c0..438fbb958279e54bdcc286b2990b7d76572d3db6 100644 (file)
@@ -7,6 +7,7 @@ use wcf\data\TUserContent;
 use wcf\system\bbcode\SimpleMessageParser;
 use wcf\system\comment\manager\ICommentManager;
 use wcf\system\comment\CommentHandler;
+use wcf\system\html\output\HtmlOutputProcessor;
 use wcf\util\StringUtil;
 
 /**
@@ -23,6 +24,7 @@ use wcf\util\StringUtil;
  * @property-read      integer|null    $userID         id of the user who wrote the comment response or `null` if the user does not exist anymore or if the comment response has been written by a guest
  * @property-read      string          $username       name of the user or guest who wrote the comment response
  * @property-read      string          $message        comment response message
+ * @property-read       integer         $enableHtml     is 1 if HTML will rendered in the comment response, otherwise 0
  * @property-read      integer         $isDisabled     is 1 if the comment response is disabled, otherwise 0
  */
 class CommentResponse extends DatabaseObject implements IMessage {
@@ -38,38 +40,51 @@ class CommentResponse extends DatabaseObject implements IMessage {
         * @inheritDoc
         */
        public function getFormattedMessage() {
-               return SimpleMessageParser::getInstance()->parse($this->message);
+               $processor = new HtmlOutputProcessor();
+               $processor->process($this->message, 'com.woltlab.wcf.comment.response', $this->responseID);
+               
+               return $processor->getHtml();
        }
        
        /**
-        * Returns comment object related to this response.
+        * Returns a simplified version of the formatted message.
         * 
-        * @return      Comment
+        * @return      string
         */
-       public function getComment() {
-               if ($this->comment === null) {
-                       $this->comment = new Comment($this->commentID);
-               }
+       public function getSimplifiedFormattedMessage() {
+               $processor = new HtmlOutputProcessor();
+               $processor->setOutputType('text/simplified-html');
+               $processor->process($this->message, 'com.woltlab.wcf.comment.response', $this->responseID);
                
-               return $this->comment;
+               return $processor->getHtml();
        }
        
        /**
-        * Sets related comment object.
+        * Returns a version of this message optimized for use in emails.
         * 
-        * @param       Comment         $comment
+        * @param       string  $mimeType       Either 'text/plain' or 'text/html'
+        * @return      string
         */
-       public function setComment(Comment $comment) {
-               if ($this->commentID == $comment->commentID) {
-                       $this->comment = $comment;
+       public function getMailText($mimeType = 'text/plain') {
+               switch ($mimeType) {
+                       case 'text/plain':
+                               $processor = new HtmlOutputProcessor();
+                               $processor->setOutputType('text/plain');
+                               $processor->process($this->message, 'com.woltlab.wcf.comment.response', $this->responseID);
+                               
+                               return $processor->getHtml();
+                       case 'text/html':
+                               return $this->getSimplifiedFormattedMessage();
                }
+               
+               throw new \LogicException('Unreachable');
        }
        
        /**
         * @inheritDoc
         */
        public function getExcerpt($maxLength = 255) {
-               return StringUtil::truncateHTML($this->getFormattedMessage(), $maxLength);
+               return StringUtil::truncateHTML($this->getSimplifiedFormattedMessage(), $maxLength);
        }
        
        /**
@@ -79,6 +94,30 @@ class CommentResponse extends DatabaseObject implements IMessage {
                return $this->message;
        }
        
+       /**
+        * Returns comment object related to this response.
+        * 
+        * @return      Comment
+        */
+       public function getComment() {
+               if ($this->comment === null) {
+                       $this->comment = new Comment($this->commentID);
+               }
+               
+               return $this->comment;
+       }
+       
+       /**
+        * Sets related comment object.
+        * 
+        * @param       Comment         $comment
+        */
+       public function setComment(Comment $comment) {
+               if ($this->commentID == $comment->commentID) {
+                       $this->comment = $comment;
+               }
+       }
+       
        /**
         * @inheritDoc
         */
index a2248d35a22199027b780b05c9af8f2a7fc21bca..12dbbbbb6c0693e6b147759642d3a00e65fd192a 100644 (file)
@@ -3,14 +3,20 @@ namespace wcf\data\comment\response;
 use wcf\data\comment\Comment;
 use wcf\data\comment\CommentEditor;
 use wcf\data\comment\CommentList;
+use wcf\data\object\type\ObjectType;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
+use wcf\system\html\input\HtmlInputProcessor;
 use wcf\system\like\LikeHandler;
 use wcf\system\user\activity\event\UserActivityEventHandler;
 use wcf\system\user\notification\UserNotificationHandler;
 use wcf\system\WCF;
+use wcf\util\MessageUtil;
 
 /**
  * Executes comment response-related actions.
@@ -39,13 +45,30 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
         * comment object
         * @var Comment
         */
-       public $comment = null;
+       public $comment;
        
        /**
         * comment manager object
-        * @var \wcf\system\comment\manager\ICommentManager
+        * @var ICommentManager
         */
-       public $commentManager = null;
+       public $commentManager;
+       
+       /**
+        * comment processor
+        * @var ICommentManager
+        */
+       protected $commentProcessor;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /**
+        * response object
+        * @var CommentResponse
+        */
+       protected $response;
        
        /**
         * @inheritDoc
@@ -194,4 +217,142 @@ class CommentResponseAction extends AbstractDatabaseObjectAction {
                        'template' => WCF::getTPL()->fetch('commentResponseList')
                ];
        }
+       
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateBeginEdit() {
+               $this->response = $this->getSingleObject();
+               
+               // validate object type id
+               $objectType = $this->validateObjectType();
+               
+               // validate object id and permissions
+               $this->commentProcessor = $objectType->getProcessor();
+               if (!$this->commentProcessor->canEditResponse($this->response->getDecoratedObject())) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function beginEdit() {
+               WCF::getTPL()->assign([
+                       'response' => $this->response,
+                       'wysiwygSelector' => 'commentResponseEditor'.$this->response->responseID
+               ]);
+               
+               return [
+                       'actionName' => 'beginEdit',
+                       'template' => WCF::getTPL()->fetch('commentResponseEditor', 'wcf')
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateSave() {
+               $this->validateBeginEdit();
+               
+               $this->validateMessage();
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               /** @var HtmlInputProcessor $htmlInputProcessor */
+               $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+               
+               $action = new CommentResponseAction([$this->response], 'update', [
+                       'data' => [
+                               'message' => $htmlInputProcessor->getHtml()
+                       ]
+               ]);
+               $action->executeAction();
+               
+               return [
+                       'actionName' => 'save',
+                       'message' => (new CommentResponse($this->response->responseID))->getFormattedMessage()
+               ];
+       }
+       
+       /**
+        * Validates message parameter.
+        *
+        * @throws      UserInputException
+        */
+       protected function validateMessage() {
+               $this->readString('message', false, 'data');
+               $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
+               
+               if (empty($this->parameters['data']['message'])) {
+                       throw new UserInputException('message');
+               }
+               
+               CommentHandler::enforceCensorship($this->parameters['data']['message']);
+               
+               $this->setDisallowedBBCodes();
+               $htmlInputProcessor = $this->getHtmlInputProcessor($this->parameters['data']['message'], ($this->comment !== null ? $this->comment->commentID : 0));
+               
+               // search for disallowed bbcodes
+               $disallowedBBCodes = $htmlInputProcessor->validate();
+               if (!empty($disallowedBBCodes)) {
+                       throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
+               }
+               
+               if ($htmlInputProcessor->appearsToBeEmpty()) {
+                       throw new UserInputException('message');
+               }
+               
+               $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
+       }
+       
+       /**
+        * Validates object type id parameter.
+        *
+        * @param       integer         $objectTypeID
+        * @return      ObjectType
+        * @throws      UserInputException
+        */
+       protected function validateObjectType($objectTypeID = null) {
+               if ($objectTypeID === null) {
+                       $this->readInteger('objectTypeID', false, 'data');
+                       $objectTypeID = $this->parameters['data']['objectTypeID'];
+               }
+               
+               $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
+               if ($objectType === null) {
+                       throw new UserInputException('objectTypeID');
+               }
+               
+               return $objectType;
+       }
+       
+       /**
+        * Sets the list of disallowed bbcodes for comments.
+        */
+       protected function setDisallowedBBCodes() {
+               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.comment.disallowedBBCodes')));
+       }
+       
+       /**
+        * Returns the current html input processor or a new one if `$message` is not null.
+        *
+        * @param       string|null     $message        source message
+        * @param       integer         $objectID       object id
+        * @return      HtmlInputProcessor
+        */
+       public function getHtmlInputProcessor($message = null, $objectID = 0) {
+               if ($message === null) {
+                       return $this->htmlInputProcessor;
+               }
+               
+               $this->htmlInputProcessor = new HtmlInputProcessor();
+               $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
+               
+               return $this->htmlInputProcessor;
+       }
 }
diff --git a/wcfsetup/install/files/lib/system/worker/CommentResponseRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/CommentResponseRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..8080dbf
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\CommentResponseEditor;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for updating comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ */
+class CommentResponseRebuildDataWorker extends AbstractRebuildDataWorker {
+       /**
+        * @inheritDoc
+        */
+       protected $limit = 500;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function countObjects() {
+               if ($this->count === null) {
+                       $this->count = 0;
+                       $sql = "SELECT  MAX(responseID) AS responseID
+                               FROM    wcf".WCF_N."_comment_response";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute();
+                       $row = $statement->fetchArray();
+                       if ($row !== false) $this->count = $row['responseID'];
+               }
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               $this->objectList = new CommentResponseList();
+               $this->objectList->sqlOrderBy = 'comment_response.responseID';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               $this->objectList->getConditionBuilder()->add('comment_response.responseID BETWEEN ? AND ?', [$this->limit * $this->loopCount + 1, $this->limit * $this->loopCount + $this->limit]);
+               
+               parent::execute();
+               
+               if (!count($this->objectList)) {
+                       return;
+               }
+               
+               WCF::getDB()->beginTransaction();
+               /** @var CommentResponse $response */
+               foreach ($this->objectList as $response) {
+                       $responseEditor = new CommentResponseEditor($response);
+                       
+                       // update message
+                       if (!$response->enableHtml) {
+                               $this->getHtmlInputProcessor()->process($response->message, 'com.woltlab.wcf.comment.response', $response->responseID, true);
+                               
+                               $responseEditor->update([
+                                       'message' => $this->getHtmlInputProcessor()->getHtml(),
+                                       'enableHtml' => 1
+                               ]);
+                       }
+                       else {
+                               $this->getHtmlInputProcessor()->reprocess($response->message, 'com.woltlab.wcf.comment.response', $response->responseID);
+                               $responseEditor->update(['message' => $this->getHtmlInputProcessor()->getHtml()]);
+                       }
+               }
+               WCF::getDB()->commitTransaction();
+       }
+       
+       /**
+        * @return HtmlInputProcessor
+        */
+       protected function getHtmlInputProcessor() {
+               if ($this->htmlInputProcessor === null) {
+                       $this->htmlInputProcessor = new HtmlInputProcessor();
+               }
+               
+               return $this->htmlInputProcessor;
+       }
+}
index 77f0a2fbe6cc60314e2a0936b81b88cfdbba0373..4da735dd955d1186fe0a817e0cbc056bf3f7db02 100644 (file)
        }
 }
 
+.commentResponse .commentEditorContainer {
+       ~ .containerHeadline,
+       ~ .userMessage,
+       ~ .buttonGroupNavigation {
+               display: none;
+       }
+}
+
 .commentListAddComment,
 .commentEditorContainer {
        .formSubmit {
index 588b8f6ebb7675d47777742ecf1349cdf6522120..dd7d48e6256785ed9fd7286c64e230815d38c8e9 100644 (file)
@@ -1733,6 +1733,8 @@ Als Benachrichtigungs-URL in der Konfiguration der sofortigen Zahlungsbestätigu
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding.description"><![CDATA[Warnung: Die Ausführung dieser Aktion kann bei umfangreichen Datenbanken sehr lange dauern.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment"><![CDATA[Kommentare aktualisieren]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.description"><![CDATA[Aktualisiert die Kommentare]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response"><![CDATA[Antworten auf Kommentare aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response.description"><![CDATA[Aktualisiert die Antworten auf Kommentare]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.page"><![CDATA[Seiten aktualisieren]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.page.description"><![CDATA[Aktualisiert den Suchindex für CMS-Seiten]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.sitemap"><![CDATA[Sitemap aktualisieren]]></item>
index 4c04ea1eb2a14a89085647cf306c3c9c6b1eaaf0..f3a261fac358fe7a7e0491e64c98bbcc334654d3 100644 (file)
@@ -1675,6 +1675,8 @@ When prompted for the notification URL for the instant payment notifications, pl
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding.description"><![CDATA[Warning: This action may take a while to complete on large databases.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment"><![CDATA[Rebuild Comments]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.description"><![CDATA[Rebuilds the comments.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response"><![CDATA[Rebuild Comment Responses]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.response.description"><![CDATA[Rebuilds the comment responses.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.page"><![CDATA[Rebuild Pages]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.page.description"><![CDATA[Rebuilds the page search index.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.sitemap"><![CDATA[Rebuild Sitemap]]></item>
index 1c802e8dbda0af1d741dc2d39674854cb5e12381..77c650f29c115f6ec4daad87d98df7b941dc562c 100644 (file)
@@ -416,6 +416,7 @@ CREATE TABLE wcf1_comment_response (
        userID INT(10),
        username VARCHAR(255) NOT NULL,
        message TEXT NOT NULL,
+       enableHtml TINYINT(1) NOT NULL DEFAULT 0,
        isDisabled TINYINT(1) NOT NULL DEFAULT 0,
        
        KEY (commentID, isDisabled, time),