'wcf.comment.more': '{lang}wcf.comment.more{/lang}',
'wcf.comment.response.add': '{lang}wcf.comment.response.add{/lang}',
'wcf.comment.response.more': '{lang}wcf.comment.response.more{/lang}',
+ 'wcf.message.error.editorAlreadyInUse': '{lang}wcf.message.error.editorAlreadyInUse{/lang}',
'wcf.moderation.report.reportContent': '{lang}wcf.moderation.report.reportContent{/lang}',
'wcf.moderation.report.success': '{lang}wcf.moderation.report.success{/lang}'
});
--- /dev/null
+{capture assign='wysiwygSelector'}commentEditor{@$comment->commentID}{/capture}
+<textarea id="{$wysiwygSelector}" class="wysiwygTextarea"
+ data-disable-attachments="true"
+ data-disable-media="true"
+ data-support-mention="true"
+>{$comment->message}</textarea>
+{*include file='messageFormTabsInline'*}
+
+<div class="formSubmit">
+ <button class="buttonPrimary" data-type="save">{lang}wcf.global.button.submit{/lang}</button>
+
+ {include file='messageFormPreviewButton' previewMessageFieldID=$wysiwygSelector previewButtonID=$wysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment' previewMessageObjectID=$comment->commentID}
+
+ <button data-type="cancel">{lang}wcf.global.button.cancel{/lang}</button>
+</div>
+
+{include file='wysiwyg'}
{@$comment->getUserProfile()->getAvatar()->getImageTag(48)}
{/if}
- <div itemprop="comment" itemscope itemtype="http://schema.org/Comment">
+ <div class="commentContentContainer" itemprop="comment" itemscope itemtype="http://schema.org/Comment">
<div class="commentContent">
<meta itemprop="dateCreated" content="{@$comment->time|date:'c'}">
{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(48)}
<div class="commentListAddComment collapsed" data-placeholder="{lang}wcf.comment.add{/lang}">
<div class="commentListAddCommentEditorContainer">
- <textarea id="text" name="text" class="wysiwygTextarea"
+ <textarea id="{$wysiwygSelector}" name="text" class="wysiwygTextarea"
data-disable-attachments="true"
data-disable-media="true"
data-support-mention="true"
></textarea>
- {include file='wysiwyg' userProfileCommentList=$commentListContainerID}
+ {include file='wysiwyg'}
<div class="formSubmit">
<button class="buttonPrimary" data-type="save" accesskey="s">{lang}wcf.global.button.submit{/lang}</button>
- {include file='messageFormPreviewButton' previewMessageObjectType='com.woltlab.wcf.comment' previewMessageObjectID=0}
+ {include file='messageFormPreviewButton' previewMessageFieldID=$wysiwygSelector previewButtonID=$wysiwygSelector|concat:'_PreviewButton' previewMessageObjectType='com.woltlab.wcf.comment' previewMessageObjectID=0}
</div>
</div>
</div>
{if $commentCanAdd}
<ul id="userProfileCommentList" class="commentList containerList" data-can-add="true" data-object-id="{@$userID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
- {include file='commentListAddComment' commentListContainerID='userProfileCommentList'}
+ {include file='commentListAddComment' wysiwygSelector='userProfileCommentListAddComment'}
{include file='commentList'}
</ul>
{else}
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
*/
WCF.Comment.Handler = Class.extend({
- /**
- * input element to add a comment
- * @var jQuery
- */
- _commentAdd: null,
-
/**
* list of comment buttons per comment
* @var object
* @param string userAvatarSmall
*/
init: function(containerID, userAvatar, userAvatarSmall) {
- this._commentAdd = null;
this._commentButtonList = { };
this._comments = { };
this._containerID = containerID;
console.debug("Missing WYSIWYG implementation, adding comments is not available.");
}
else {
- require(['WoltLabSuite/Core/Ui/Comment/Add'], (function (UICommentAdd) {
- new UICommentAdd(elBySel('.jsCommentAdd', this._container[0]), {
-
- });
+ require(['WoltLabSuite/Core/Ui/Comment/Add'], (function (UiCommentAdd) {
+ new UiCommentAdd(elBySel('.jsCommentAdd', this._container[0]));
}).bind(this));
}
}
+ require(['WoltLabSuite/Core/Ui/Comment/Edit'], (function (UiCommentEdit) {
+ new UiCommentEdit(this._container[0]);
+ }).bind(this));
+
WCF.DOMNodeInsertedHandler.execute();
WCF.DOMNodeInsertedHandler.addCallback('WCF.Comment.Handler', $.proxy(this._domNodeInserted, this));
}
if (comment.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>');
- $editButton.data('commentID', commentID).appendTo(comment.find('ul.buttonList:eq(0)')).click($.proxy(this._prepareEdit, this));
+ 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>');
+ $editButton.appendTo(comment.find('ul.buttonList:eq(0)'));
}
if (comment.data('canDelete')) {
* @param boolean isResponse
*/
_prepareEdit: function(event, isResponse) {
+ if (!isResponse) {
+ throw new Error("Editing comments is no longer supported through this method.");
+ }
+
event.preventDefault();
var $button = $(event.currentTarget);
var $data = {
objectID: this._container.data('objectID'),
- objectTypeID: this._container.data('objectTypeID')
+ objectTypeID: this._container.data('objectTypeID'),
+ responseID: $button.data('responseID')
};
- if (isResponse === true) {
- $data.responseID = $button.data('responseID');
- }
- else {
- $data.commentID = $button.data('commentID');
- }
-
this._proxy.setOption('data', {
actionName: 'prepareEdit',
className: 'wcf\\data\\comment\\CommentAction',
/**
* @constructor
*/
- function UiCommentAdd(container, options) { this.init(container, options); }
+ function UiCommentAdd(container) { this.init(container); }
UiCommentAdd.prototype = {
/**
* Initializes a new quick reply field.
*
* @param {Element} container container element
- * @param {Object} options configuration options
*/
- init: function(container, options) {
- this._options = Core.extend({
- ajax: {
- className: ''
- },
- successMessage: 'wcf.global.success.add'
- }, options);
-
+ init: function(container) {
this._container = container;
this._content = elBySel('.commentListAddComment', this._container);
this._textarea = elBySel('.wysiwygTextarea', this._container);
//noinspection JSCheckFunctionSignatures
DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
- UiNotification.show(Language.get(this._options.successMessage));
+ UiNotification.show(Language.get('wcf.global.success.add'));
DomChangeListener.trigger();
},
--- /dev/null
+/**
+ * Provides editing support for comments.
+ *
+ * @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/Edit
+ */
+define(
+ [
+ 'Ajax', 'Core', 'Dictionary', 'Environment',
+ 'EventHandler', 'Language', 'List', 'Dom/ChangeListener', 'Dom/Traverse',
+ 'Dom/Util', 'Ui/Notification', 'Ui/ReusableDropdown', 'WoltLabSuite/Core/Ui/Scroll'
+ ],
+ function(
+ Ajax, Core, Dictionary, Environment,
+ EventHandler, Language, List, DomChangeListener, DomTraverse,
+ DomUtil, UiNotification, UiReusableDropdown, UiScroll
+ )
+{
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function UiCommentEdit(container) { this.init(container); }
+ UiCommentEdit.prototype = {
+ /**
+ * Initializes the comment edit manager.
+ *
+ * @param {Element} container container element
+ */
+ init: function(container) {
+ this._activeElement = null;
+ this._callbackClick = null;
+ this._comments = new List();
+ this._container = container;
+ this._editorContainer = null;
+
+ this.rebuild();
+
+ DomChangeListener.add('Ui/Comment/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('.comment', this._container, (function (comment) {
+ if (this._comments.has(comment)) {
+ return;
+ }
+
+ if (elDataBool(comment, 'can-edit')) {
+ var button = elBySel('.jsCommentEditButton', comment);
+ if (button !== null) {
+ if (this._callbackClick === null) {
+ this._callbackClick = this._click.bind(this);
+ }
+
+ button.addEventListener(WCF_CLICK_EVENT, this._callbackClick);
+ }
+ }
+
+ this._comments.add(comment);
+ }).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('.comment');
+
+ 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('.commentContentContainer', this._activeElement);
+ content.insertBefore(this._editorContainer, content.firstChild);
+ },
+
+ /**
+ * Shows the message editor.
+ *
+ * @param {Object} data ajax response data
+ * @protected
+ */
+ _showEditor: function(data) {
+ var id = this._getEditorId();
+
+ var icon = elBySel('.icon', this._editorContainer);
+ elRemove(icon);
+
+ var editor = elCreate('div');
+ editor.className = 'editorContainer';
+ //noinspection JSUnresolvedVariable
+ DomUtil.setInnerHtml(editor, data.returnValues.template);
+ this._editorContainer.appendChild(editor);
+
+ // bind buttons
+ var formSubmit = elBySel('.formSubmit', editor);
+
+ var buttonSave = elBySel('button[data-type="save"]', formSubmit);
+ buttonSave.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
+
+ var buttonCancel = elBySel('button[data-type="cancel"]', formSubmit);
+ buttonCancel.addEventListener(WCF_CLICK_EVENT, this._restoreMessage.bind(this));
+
+ EventHandler.add('com.woltlab.wcf.redactor', 'submitEditor_' + id, (function(data) {
+ data.cancel = true;
+
+ this._save();
+ }).bind(this));
+
+ var editorElement = elById(id);
+ if (Environment.editor() === 'redactor') {
+ window.setTimeout((function() {
+ UiScroll.element(this._activeElement);
+ }).bind(this), 250);
+ }
+ else {
+ editorElement.focus();
+ }
+ },
+
+ /**
+ * Restores the message view.
+ *
+ * @protected
+ */
+ _restoreMessage: function() {
+ this._destroyEditor();
+
+ elRemove(this._editorContainer);
+
+ this._activeElement = null;
+ },
+
+ /**
+ * Saves the editor message.
+ *
+ * @protected
+ */
+ _save: function() {
+ var parameters = {
+ data: {
+ message: ''
+ }
+ };
+
+ var id = this._getEditorId();
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'getText_' + id, parameters.data);
+
+ if (!this._validate(parameters)) {
+ // validation failed
+ return;
+ }
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_' + id, parameters);
+
+ Ajax.api(this, {
+ actionName: 'save',
+ objectIDs: [this._getObjectId(this._activeElement)],
+ parameters: parameters
+ });
+
+ this._hideEditor();
+ },
+
+ /**
+ * Validates the message and invokes listeners to perform additional validation.
+ *
+ * @param {Object} parameters request parameters
+ * @return {boolean} validation result
+ * @protected
+ */
+ _validate: function(parameters) {
+ // remove all existing error elements
+ var errorMessages = elByClass('innerError', this._activeElement);
+ while (errorMessages.length) {
+ elRemove(errorMessages[0]);
+ }
+
+ var data = {
+ api: this,
+ parameters: parameters,
+ valid: true
+ };
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_' + this._getEditorId(), data);
+
+ return (data.valid !== false);
+ },
+
+ /**
+ * Throws an error by adding an inline error to target element.
+ *
+ * @param {Element} element erroneous element
+ * @param {string} message error message
+ */
+ throwError: function(element, message) {
+ var error = elCreate('small');
+ error.className = 'innerError';
+ error.textContent = message;
+
+ DomUtil.insertAfter(error, element);
+ },
+
+ /**
+ * Shows the update message.
+ *
+ * @param {Object} data ajax response data
+ * @protected
+ */
+ _showMessage: function(data) {
+ // set new content
+ //noinspection JSCheckFunctionSignatures
+ DomUtil.setInnerHtml(elBySel('.commentContent .userMessage', this._editorContainer.parentNode), data.returnValues.message);
+
+ this._restoreMessage();
+
+ UiNotification.show();
+ },
+
+ /**
+ * Hides the editor from view.
+ *
+ * @protected
+ */
+ _hideEditor: function() {
+ elHide(elBySel('.editorContainer', this._editorContainer));
+
+ var icon = elCreate('span');
+ icon.className = 'icon icon48 fa-spinner';
+ this._editorContainer.appendChild(icon);
+ },
+
+ /**
+ * Restores the previously hidden editor.
+ *
+ * @protected
+ */
+ _restoreEditor: function() {
+ var icon = elBySel('.fa-spinner', this._editorContainer);
+ elRemove(icon);
+
+ var editorContainer = elBySel('.editorContainer', this._editorContainer);
+ if (editorContainer !== null) elShow(editorContainer);
+ },
+
+ /**
+ * Destroys the editor instance.
+ *
+ * @protected
+ */
+ _destroyEditor: function() {
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'autosaveDestroy_' + this._getEditorId());
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'destroy_' + this._getEditorId());
+ },
+
+ /**
+ * Returns the unique editor id.
+ *
+ * @return {string} editor id
+ * @protected
+ */
+ _getEditorId: function() {
+ return 'commentEditor' + this._getObjectId(this._activeElement);
+ },
+
+ /**
+ * Returns the element's `data-object-id` value.
+ *
+ * @param {Element} element target element
+ * @return {int}
+ * @protected
+ */
+ _getObjectId: function(element) {
+ return ~~elData(element, 'object-id');
+ },
+
+ _ajaxFailure: function(data) {
+ var editor = elBySel('.redactor-layer', this._editorContainer);
+
+ // handle errors occurring on editor load
+ if (editor === null) {
+ this._restoreMessage();
+
+ return true;
+ }
+
+ this._restoreEditor();
+
+ //noinspection JSUnresolvedVariable
+ if (!data || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+ return true;
+ }
+
+ var innerError = elBySel('.innerError', this._editorContainer);
+ if (innerError === null) {
+ innerError = elCreate('small');
+ innerError.className = 'innerError';
+
+ DomUtil.insertAfter(innerError, editor);
+ }
+
+ //noinspection JSUnresolvedVariable
+ innerError.textContent = data.returnValues.errorType;
+
+ return false;
+ },
+
+ _ajaxSuccess: function(data) {
+ switch (data.actionName) {
+ case 'beginEdit':
+ this._showEditor(data);
+ break;
+
+ case 'save':
+ this._showMessage(data);
+ break;
+ }
+ },
+
+ _ajaxSetup: function() {
+ var objectTypeId = ~~elData(this._container, 'object-type-id');
+
+ return {
+ data: {
+ className: 'wcf\\data\\comment\\CommentAction',
+ parameters: {
+ data: {
+ objectTypeID: objectTypeId
+ }
+ }
+ },
+ silent: true
+ };
+ }
+ };
+
+ return UiCommentEdit;
+});
'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
'message' => $htmlInputProcessor->getHtml(),
'responses' => 0,
- 'responseIDs' => serialize([])
+ 'responseIDs' => serialize([]),
+ 'enableHtml' => 1
]);
// update counter
* Validates parameters to edit a comment or a response.
*/
public function validatePrepareEdit() {
- // validate comment id or response id
+ // validate response id
try {
- $this->validateCommentID();
+ $this->validateResponseID();
}
catch (UserInputException $e) {
- try {
- $this->validateResponseID();
- }
- catch (UserInputException $e) {
- throw new UserInputException('objectIDs');
- }
+ throw new UserInputException('objectIDs');
}
// validate object type id
// validate object id and permissions
$this->commentProcessor = $objectType->getProcessor();
- if ($this->comment !== null) {
- if (!$this->commentProcessor->canEditComment($this->comment)) {
- throw new PermissionDeniedException();
- }
- }
- else {
- if (!$this->commentProcessor->canEditResponse($this->response)) {
- throw new PermissionDeniedException();
- }
+ if (!$this->commentProcessor->canEditResponse($this->response)) {
+ throw new PermissionDeniedException();
}
}
* @return array
*/
public function prepareEdit() {
- if ($this->comment !== null) {
- $message = $this->comment->message;
- }
- else {
- $message = $this->response->message;
- }
-
- $returnValues = [
+ return [
'action' => 'prepare',
- 'message' => $message
+ 'message' => $this->response->message,
+ 'responseID' => $this->response->responseID
];
- if ($this->comment !== null) {
- $returnValues['commentID'] = $this->comment->commentID;
- }
- else {
- $returnValues['responseID'] = $this->response->responseID;
- }
-
- return $returnValues;
}
/**
* @return array
*/
public function edit() {
- $returnValues = ['action' => 'saved'];
+ $editor = new CommentResponseEditor($this->response);
+ $editor->update([
+ 'message' => $this->parameters['data']['message']
+ ]);
+ $response = new CommentResponse($this->response->responseID);
- if ($this->response === null) {
- $editor = new CommentEditor($this->comment);
- $editor->update([
- 'message' => $this->parameters['data']['message']
- ]);
- $comment = new Comment($this->comment->commentID);
- $returnValues['commentID'] = $this->comment->commentID;
- $returnValues['message'] = $comment->getFormattedMessage();
- }
- else {
- $editor = new CommentResponseEditor($this->response);
- $editor->update([
- 'message' => $this->parameters['data']['message']
- ]);
- $response = new CommentResponse($this->response->responseID);
- $returnValues['responseID'] = $this->response->responseID;
- $returnValues['message'] = $response->getFormattedMessage();
+ return [
+ 'action' => 'saved',
+ 'message' => $response->getFormattedMessage(),
+ 'responseID' => $this->response->responseID
+ ];
+ }
+
+ public function validateBeginEdit() {
+ $this->comment = $this->getSingleObject();
+
+ // validate object type id
+ $objectType = $this->validateObjectType();
+
+ // validate object id and permissions
+ $this->commentProcessor = $objectType->getProcessor();
+ if (!$this->commentProcessor->canEditComment($this->comment->getDecoratedObject())) {
+ throw new PermissionDeniedException();
}
+ }
+
+ public function beginEdit() {
+ WCF::getTPL()->assign([
+ 'comment' => $this->comment,
+ 'wysiwygSelector' => 'commentEditor'.$this->comment->commentID
+ ]);
- return $returnValues;
+ return [
+ 'actionName' => 'beginEdit',
+ 'template' => WCF::getTPL()->fetch('commentEditor', 'wcf')
+ ];
+ }
+
+ public function validateSave() {
+ $this->validateBeginEdit();
+
+ $this->validateMessage(true);
+ }
+
+ public function save() {
+ /** @var HtmlInputProcessor $htmlInputProcessor */
+ $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+
+ $action = new CommentAction([$this->comment], 'update', [
+ 'data' => [
+ 'message' => $htmlInputProcessor->getHtml()
+ ]
+ ]);
+ $action->executeAction();
+
+ return [
+ 'actionName' => 'save',
+ 'message' => (new Comment($this->comment->commentID))->getFormattedMessage()
+ ];
}
/**
/**
* Validates message parameter.
+ *
+ * @param bool $isComment
+ * @throws UserInputException
*/
protected function validateMessage($isComment = false) {
$this->readString('message', false, 'data');
}
}
}
+
+.commentEditorContainer {
+ > .icon {
+ left: calc(50% - 24px);
+ position: relative;
+ }
+
+ ~ .commentContent,
+ ~ .commentOptionContainer {
+ display: none;
+ }
+}
+
+.commentListAddComment,
+.commentEditorContainer {
+ .formSubmit {
+ /* reduced margin, now in sync with the container padding */
+ margin-top: 20px;
+ }
+}