// add new comment
if (this._container.data('canAdd')) {
- this._initAddComment();
+ if (elBySel('.commentListAddComment .wysiwygTextarea', this._container[0]) === null) {
+ 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]), {
+
+ });
+ }).bind(this));
+ }
}
WCF.DOMNodeInsertedHandler.execute();
}
},
- /**
- * Initializes the UI components to add a comment.
- */
- _initAddComment: function() {
- var button = elBySel('.jsCommentAdd .formSubmit button[data-type="save"]', this._commentAdd[0]);
- if (button) button.addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
- },
-
/**
* Initializes the UI elements to add a response.
*
* @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());
return;
}
- var $actionName = 'addComment';
var $data = {
+ commentID: $input.data('commentID'),
message: $value,
objectID: this._container.data('objectID'),
objectTypeID: this._container.data('objectTypeID')
};
- if (isResponse === true) {
- $actionName = 'addResponse';
- $data.commentID = $input.data('commentID');
- }
if (!WCF.User.userID) {
this._commentData = $data;
new WCF.Action.Proxy({
autoSend: true,
data: {
- actionName: $actionName,
+ actionName: 'addResponse',
className: 'wcf\\data\\comment\\CommentAction',
parameters: {
data: $data
*/
_success: function(data, textStatus, jqXHR) {
switch (data.actionName) {
- case 'addComment':
- if (data.returnValues.guestDialog) {
- this._createGuestDialog(data.returnValues.guestDialog, data.returnValues.useCaptcha);
- }
- else {
- this._commentAdd.find('textarea').val('').blur().trigger('updateHeight');
- $(data.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn();
-
- if (!WCF.User.userID) {
- this._guestDialog.wcfDialog('close');
- }
- }
- break;
-
case 'addResponse':
if (data.returnValues.guestDialog) {
this._createGuestDialog(data.returnValues.guestDialog, data.returnValues.useCaptcha);
*/
_submit: function(event) {
var $requestData = {
- actionName: this._commentData.commentID ? 'addResponse' : 'addComment',
+ actionName: 'addResponse',
className: 'wcf\\data\\comment\\CommentAction'
};
--- /dev/null
+/**
+ * Handles the comment 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(['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";
+
+ /**
+ * @constructor
+ */
+ function UiCommentAdd(container, options) { this.init(container, options); }
+ 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);
+
+ this._container = container;
+ this._content = elBySel('.commentListAddComment', this._container);
+ this._textarea = elBySel('.wysiwygTextarea', this._container);
+ this._editor = null;
+ this._loadingOverlay = null;
+
+ // handle submit button
+ var submitButton = elBySel('button[data-type="save"]', this._container);
+ submitButton.addEventListener(WCF_CLICK_EVENT, this._submit.bind(this));
+ },
+
+ /**
+ * Submits the guest dialog.
+ *
+ * @param {Event} event
+ * @protected
+ */
+ _submitGuestDialog: function(event) {
+ // only submit when enter key is pressed
+ if (event.type === 'keypress' && !EventKey.Enter(event)) {
+ return;
+ }
+
+ var usernameInput = elBySel('input[name=username]', event.currentTarget.closest('.dialogContent'));
+ if (usernameInput.value === '') {
+ var error = DomTraverse.nextByClass(usernameInput, 'innerError');
+ if (!error) {
+ error = elCreate('small');
+ error.className = 'innerError';
+ error.innerText = Language.get('wcf.global.form.error.empty');
+
+ DomUtil.insertAfter(error, usernameInput);
+
+ usernameInput.closest('dl').classList.add('formError');
+ }
+
+ return;
+ }
+
+ var parameters = {
+ parameters: {
+ data: {
+ username: usernameInput.value
+ }
+ }
+ };
+
+ //noinspection JSCheckFunctionSignatures
+ var captchaId = elData(event.currentTarget, 'captcha-id');
+ if (ControllerCaptcha.has(captchaId)) {
+ parameters = Core.extend(parameters, ControllerCaptcha.getData(captchaId));
+ }
+
+ this._submit(undefined, parameters);
+ },
+
+ /**
+ * Validates the message and submits it to the server.
+ *
+ * @param {Event?} event event object
+ * @param {Object?} additionalParameters additional parameters sent to the server
+ * @protected
+ */
+ _submit: function(event, additionalParameters) {
+ if (event) {
+ event.preventDefault();
+ }
+
+ if (!this._validate()) {
+ // validation failed, bail out
+ return;
+ }
+
+ 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')
+ }
+ };
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters.data);
+
+ if (!User.userId && !additionalParameters) {
+ parameters.requireGuestDialog = true;
+ }
+
+ Ajax.api(this, Core.extend({
+ parameters: parameters
+ }, additionalParameters));
+ },
+
+ /**
+ * Validates the message and invokes listeners to perform additional validation.
+ *
+ * @return {boolean} validation result
+ * @protected
+ */
+ _validate: function() {
+ // remove all existing error elements
+ var errorMessages = elByClass('innerError', this._container);
+ while (errorMessages.length) {
+ elRemove(errorMessages[0]);
+ }
+
+ // check if editor contains actual content
+ if (this._getEditor().utils.isEmpty()) {
+ this.throwError(this._textarea, Language.get('wcf.global.form.error.empty'));
+ return false;
+ }
+
+ var data = {
+ api: this,
+ editor: this._getEditor(),
+ message: this._getEditor().code.get(),
+ valid: true
+ };
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'validate_text', 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 === 'empty' ? Language.get('wcf.global.form.error.empty') : message);
+
+ DomUtil.insertAfter(error, element);
+ },
+
+ /**
+ * Displays a loading spinner while the request is processed by the server.
+ *
+ * @protected
+ */
+ _showLoadingOverlay: function() {
+ if (this._loadingOverlay === null) {
+ this._loadingOverlay = elCreate('div');
+ this._loadingOverlay.className = 'commentLoadingOverlay';
+ this._loadingOverlay.innerHTML = '<span class="icon icon96 fa-spinner"></span>';
+ }
+
+ this._content.classList.add('loading');
+ this._content.appendChild(this._loadingOverlay);
+ },
+
+ /**
+ * Hides the loading spinner.
+ *
+ * @protected
+ */
+ _hideLoadingOverlay: function() {
+ this._content.classList.remove('loading');
+
+ var loadingOverlay = elBySel('.commentLoadingOverlay', this._content);
+ if (loadingOverlay !== null) {
+ loadingOverlay.parentNode.removeChild(loadingOverlay);
+ }
+ },
+
+ /**
+ * Resets the editor contents and notifies event listeners.
+ *
+ * @protected
+ */
+ _reset: function() {
+ this._getEditor().code.set('<p>\u200b</p>');
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'reset_text');
+ },
+
+ /**
+ * Handles errors occured during server processing.
+ *
+ * @param {Object} data response data
+ * @protected
+ */
+ _handleError: function(data) {
+ //noinspection JSUnresolvedVariable
+ this.throwError(this._textarea, data.returnValues.errorType);
+ },
+
+ /**
+ * Returns the current editor instance.
+ *
+ * @return {Object} editor instance
+ * @protected
+ */
+ _getEditor: function() {
+ if (this._editor === null) {
+ if (typeof window.jQuery === 'function') {
+ this._editor = window.jQuery(this._textarea).data('redactor');
+ }
+ else {
+ throw new Error("Unable to access editor, jQuery has not been loaded yet.");
+ }
+ }
+
+ return this._editor;
+ },
+
+ /**
+ * 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.
+ *
+ * @param {Object} data response data
+ * @protected
+ */
+ _insertMessage: function(data) {
+ // insert HTML
+ //noinspection JSCheckFunctionSignatures
+ DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
+
+ UiNotification.show(Language.get(this._options.successMessage));
+
+ DomChangeListener.trigger();
+ },
+
+ /**
+ * @param {{returnValues:{guestDialog:string,guestDialogID:string}}} data
+ * @protected
+ */
+ _ajaxSuccess: function(data) {
+ if (!User.userId && !data.returnValues.guestDialogID) {
+ throw new Error("Missing 'guestDialogID' return value for guest.");
+ }
+
+ if (!User.userId && data.returnValues.guestDialog) {
+ UiDialog.openStatic(data.returnValues.guestDialogID, data.returnValues.guestDialog, {
+ closable: false,
+ title: Language.get('wcf.global.confirmation.title')
+ });
+
+ var dialog = UiDialog.getDialog(data.returnValues.guestDialogID);
+ elBySel('input[type=submit]', dialog.content).addEventListener(WCF_CLICK_EVENT, this._submitGuestDialog.bind(this));
+ elBySel('input[type=text]', dialog.content).addEventListener('keypress', this._submitGuestDialog.bind(this));
+ }
+ else {
+ this._insertMessage(data);
+
+ if (!User.userId) {
+ UiDialog.close(data.returnValues.guestDialogID);
+ }
+
+ this._reset();
+
+ this._hideLoadingOverlay();
+ }
+ },
+
+ _ajaxFailure: function(data) {
+ this._hideLoadingOverlay();
+
+ //noinspection JSUnresolvedVariable
+ if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+ return true;
+ }
+
+ this._handleError(data);
+
+ return false;
+ },
+
+ _ajaxSetup: function() {
+ return {
+ data: {
+ actionName: 'addComment',
+ className: 'wcf\\data\\comment\\CommentAction'
+ },
+ silent: true
+ };
+ }
+ };
+
+ return UiCommentAdd;
+});
use wcf\data\object\type\ObjectType;
use wcf\data\object\type\ObjectTypeCache;
use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\bbcode\BBCodeHandler;
use wcf\system\captcha\CaptchaHandler;
use wcf\system\comment\manager\ICommentManager;
use wcf\system\comment\CommentHandler;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\SystemException;
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\object\type\ICommentUserNotificationObjectType;
*/
protected $commentProcessor = null;
+ /**
+ * @var HtmlInputProcessor
+ */
+ protected $htmlInputProcessor;
+
/**
* response object
* @var CommentResponse
$this->validateUsername();
$this->validateCaptcha();
- $this->validateMessage();
+ $this->validateMessage(true);
$objectType = $this->validateObjectType();
// validate object id and permissions
];
}
+ /** @var HtmlInputProcessor $htmlInputProcessor */
+ $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
+
// create comment
$this->createdComment = CommentEditor::create([
'objectTypeID' => $this->parameters['data']['objectTypeID'],
'time' => TIME_NOW,
'userID' => WCF::getUser()->userID ?: null,
'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
- 'message' => $this->parameters['data']['message'],
+ 'message' => $htmlInputProcessor->getHtml(),
'responses' => 0,
'responseIDs' => serialize([])
]);
public function validateEdit() {
$this->validatePrepareEdit();
- $this->validateMessage();
+ $this->validateMessage($this->comment !== null);
}
/**
/**
* Validates message parameter.
*/
- protected function validateMessage() {
+ protected function validateMessage($isComment = false) {
$this->readString('message', false, 'data');
$this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
}
CommentHandler::enforceCensorship($this->parameters['data']['message']);
+
+ if ($isComment) {
+ $this->setDisallowedBBCodes();
+ $htmlInputProcessor = $this->getHtmlInputProcessor($this->parameters['data']['message']);
+
+ // 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;
+ }
+ else {
+ unset($this->parameters['htmlInputProcessor']);
+ }
}
/**
}
}
+ /**
+ * Sets the list of disallowed bbcodes for comments.
+ */
+ protected function setDisallowedBBCodes() {
+ BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.comment.disallowedBBCodes')));
+ }
+
+ 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;
+ }
+
/**
* Returns the comment object.
*