* Copyright 2013, Dustan Kasten
* Released under the MIT license.
* https://github.com/iamdustan/smoothscroll/blob/master/LICENSE
+ *
+ * Version below includes the fix of PR
*/
-!function(e,t,r){"use strict";function n(){return e.performance!==r&&e.performance.now!==r?e.performance.now():Date.now()}function o(e){return.5*(1-Math.cos(Math.PI*e))}function a(e){if("object"!=typeof e||e.behavior===r||"auto"===e.behavior||"instant"===e.behavior)return!0;if("smooth"===e.behavior)return!1;throw new TypeError(e.behavior+" is not a valid value for enumeration ScrollBehavior")}function l(e,t,r){e.scrollTop=r,e.scrollLeft=t}function i(e){return e.clientHeight<e.scrollHeight||e.clientWidth<e.scrollWidth?e:e.parentNode.parentNode?i(e.parentNode):void 0}function s(t,r){function a(){var u,p,g,v=n(),d=(v-s)/f;return d=d>1?1:d,u=o(d),p=l+(t-l)*u,g=i+(r-i)*u,m(p,g),p===t&&g===r?(l=i=s=null,void e.cancelAnimationFrame(c)):void(c=e.requestAnimationFrame(a))}var l=e.scrollX||e.pageXOffset,i=e.scrollY||e.pageYOffset,s=n();c&&e.cancelAnimationFrame(c),c=e.requestAnimationFrame(a)}function u(t,r){function a(){var r,g,v,d=n(),h=(d-p)/f;return h=h>1?1:h,r=o(h),g=i+(u-i)*r,v=s+(m-s)*r,l(t,g,v),g===u&&v===m?(i=s=p=null,void e.cancelAnimationFrame(c)):void(c=e.requestAnimationFrame(a))}var i=t.scrollLeft,s=t.scrollTop,u=r.left,m=r.top,p=n();c&&e.cancelAnimationFrame(c),c=e.requestAnimationFrame(a)}if(!("scrollBehavior"in t.documentElement.style)){var c,f=768,m=e.scrollTo,p=e.scrollBy,g=e.Element.prototype.scrollIntoView;e.scroll=e.scrollTo=function(){return a(arguments[0])?m.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]):s.call(e,~~arguments[0].left,~~arguments[0].top)},e.scrollBy=function(){if(a(arguments[0]))return p.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]);var t=e.scrollX||e.pageXOffset,r=e.scrollY||e.pageYOffset;return s(~~arguments[0].left+t,~~arguments[0].top+r)},Element.prototype.scrollIntoView=function(){var t,r,n,o,l,s;return a(arguments[0])?g.call(this,arguments[0]||!0):(l=i(this),l&&(s=e.getComputedStyle(l,null),r=parseInt(s.getPropertyValue("padding-left"),10),n=parseInt(s.getPropertyValue("padding-top"),10),t={top:this.offsetTop-2*n,left:this.offsetLeft-2*r},o=u(l,t)),o)}}}(window,document);
+!function(e,t,r){"use strict";function n(){return e.performance!==r&&e.performance.now!==r?e.performance.now():Date.now()}function o(e){return.5*(1-Math.cos(Math.PI*e))}function a(e){if("object"!=typeof e||e.behavior===r||"auto"===e.behavior||"instant"===e.behavior)return!0;if("smooth"===e.behavior)return!1;throw new TypeError(e.behavior+" is not a valid value for enumeration ScrollBehavior")}function l(e,t,r){e.scrollTop=r,e.scrollLeft=t}function i(t,a){function l(){var p,g,v,h=n(),d=(h-m)/c;return d=d>1?1:d,p=o(d),g=i+(t-i)*p,v=u+(a-u)*p,f(g,v),g===t&&v===a?(i=u=m=null,e.cancelAnimationFrame(s),r):(s=e.requestAnimationFrame(l),r)}var i=e.scrollX||e.pageXOffset,u=e.scrollY||e.pageYOffset,m=n();s&&e.cancelAnimationFrame(s),s=e.requestAnimationFrame(l)}function u(a,u){function f(){var t,i,u,d=n(),y=(d-h)/c;return y=y>1?1:y,t=o(y),i=m+(g-m)*t,u=p+(v-p)*t,l(a,i,u),i===g&&u===v?(m=p=h=null,e.cancelAnimationFrame(s),r):(s=e.requestAnimationFrame(f),r)}if(a===t.documentElement||a===t.body)return i(u.left,u.top),r;var m=a.scrollLeft,p=a.scrollTop,g=u.left,v=u.top,h=n();s&&e.cancelAnimationFrame(s),s=e.requestAnimationFrame(f)}if(!("scrollBehavior"in t.documentElement.style)){var s,c=768,f=e.scrollTo,m=e.scrollBy,p=e.Element.prototype.scrollIntoView;e.scroll=e.scrollTo=function(){return a(arguments[0])?f.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]):i.call(e,~~arguments[0].left,~~arguments[0].top)},e.scrollBy=function(){if(a(arguments[0]))return m.call(e,arguments[0].left||arguments[0],arguments[0].top||arguments[1]);var t=e.scrollX||e.pageXOffset,r=e.scrollY||e.pageYOffset;return i(~~arguments[0].left+t,~~arguments[0].top+r)},Element.prototype.scrollIntoView=function(){var r,n,o,l;return a(arguments[0])?p.call(this,arguments[0]||!0):(l=e.getComputedStyle(t.body,null),n=parseInt(l.getPropertyValue("padding-left"),10),o=parseInt(l.getPropertyValue("padding-top"),10),r={top:this.offsetTop-2*o,left:this.offsetLeft-2*n},u(t.body,r))}}}(window,document);
// validate message field
this._messageFieldID = $.wcfEscapeID(messageFieldID);
- this._messageField = $('#' + this._messageFieldID);
- if (!this._messageField.length) {
+ this._textarea = $('#' + this._messageFieldID);
+ if (!this._textarea.length) {
console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
return;
}
*/
_getMessage: function() {
if (!$.browser.redactor) {
- return $.trim(this._messageField.val());
+ return $.trim(this._textarea.val());
}
- else if (this._messageField.data('redactor')) {
- return this._messageField.redactor('wutil.getText');
+ else if (this._textarea.data('redactor')) {
+ return this._textarea.redactor('wutil.getText');
}
return null;
this._previewButton.html(this._previewButtonLabel).enable();
// remove error message
- this._messageField.parent().children('small.innerError').remove();
+ this._textarea.parent().children('small.innerError').remove();
// evaluate message
this._handleResponse(data);
// restore preview button
this._previewButton.html(this._previewButtonLabel).enable();
- var $innerError = this._messageField.next('small.innerError').empty();
+ var $innerError = this._textarea.next('small.innerError').empty();
if (!$innerError.length) {
- $innerError = $('<small class="innerError" />').appendTo(this._messageField.parent());
+ $innerError = $('<small class="innerError" />').appendTo(this._textarea.parent());
}
$innerError.html(data.returnValues.errorType);
}
});
-/**
- * Provides an AJAX-based quick reply for messages.
- */
-WCF.Message.QuickReply = Class.extend({
- /**
- * quick reply container
- * @var jQuery
- */
- _container: null,
-
- /**
- * message field
- * @var jQuery
- */
- _messageField: null,
-
- /**
- * notification object
- * @var WCF.System.Notification
- */
- _notification: null,
-
- /**
- * true, if a request to save the message is pending
- * @var boolean
- */
- _pendingSave: false,
-
- /**
- * action proxy
- * @var WCF.Action.Proxy
- */
- _proxy: null,
-
- /**
- * collection of quick reply buttons
- * @var jQuery
- */
- _quickReplyButtons: null,
-
- /**
- * quote manager object
- * @var WCF.Message.Quote.Manager
- */
- _quoteManager: null,
-
- /**
- * scroll handler
- * @var WCF.Effect.Scroll
- */
- _scrollHandler: null,
-
- /**
- * success message for created but invisible messages
- * @var string
- */
- _successMessageNonVisible: '',
-
- /**
- * Initializes a new WCF.Message.QuickReply object.
- *
- * @param boolean supportExtendedForm
- * @param WCF.Message.Quote.Manager quoteManager
- */
- init: function(supportExtendedForm, quoteManager) {
- this._container = $('#messageQuickReply');
- this._container.children('.message').addClass('jsInvalidQuoteTarget');
- this._messageField = $('#text');
- this._pendingSave = false;
- if (!this._container || !this._messageField) {
- return;
- }
-
- // button actions
- var $formSubmit = this._container.find('.formSubmit');
- var $saveButton = $formSubmit.find('button[data-type=save]').removeAttr('accesskey').click($.proxy(this._save, this));
- if (supportExtendedForm) $formSubmit.find('button[data-type=extended]').click($.proxy(this._prepareExtended, this));
- $formSubmit.find('button[data-type=cancel]').click($.proxy(this._cancel, this));
-
- if (quoteManager) this._quoteManager = quoteManager;
-
- this._quickReplyButtons = $('.jsQuickReply').data('__api', this).click($.proxy(this.click, this));
-
- this._proxy = new WCF.Action.Proxy({
- failure: $.proxy(this._failure, this),
- showLoadingOverlay: false,
- success: $.proxy(this._success, this)
- });
- this._scroll = new WCF.Effect.Scroll();
- this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.add'));
- this._successMessageNonVisible = '';
-
- WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'submitEditor_text', function(data) {
- data.cancel = true;
-
- $saveButton.trigger('click');
- });
- },
-
- /**
- * Handles clicks on reply button.
- *
- * @param object event
- */
- click: function(event) {
- this._container.toggle();
-
- if (this._container.is(':visible')) {
- this._quickReplyButtons.each(function() {
- var $button = $(this);
- if ($button.parent()[0].tagName === 'LI') {
- $button.parent().hide();
- }
- else {
- $button.hide();
- }
- });
-
- setTimeout((function() {
- $(document).trigger('resize');
- if (!$.browser.mobile || !$.browser.chrome) {
- // Chrome on Android scrolls to the caret position, manually scrolling breaks the position
- this._scroll.scrollTo(this._container, true);
- }
- }).bind(this), 100);
-
- WCF.Message.Submit.registerButton('text', this._container.find('.formSubmit button[data-type=save]'));
-
- if (this._quoteManager) {
- // check if message field is empty
- var $empty = true;
- if ($.browser.redactor) {
- if (this._messageField.data('redactor')) {
- this._editorCallback(this._messageField.redactor('wutil.isEmptyEditor'));
- }
- }
- else {
- $empty = (!this._messageField.val().length);
- this._editorCallback($empty);
- }
- }
- }
-
- // discard event
- if (event !== null) {
- event.stopPropagation();
- return false;
- }
- },
-
- /**
- * Inserts quotes and focuses the editor.
- */
- _editorCallback: function(isEmpty) {
- if (isEmpty) {
- this._quoteManager.insertQuotes(this._getClassName(), this._getObjectID(), $.proxy(this._insertQuotes, this));
- }
-
- if ($.browser.redactor) {
- this._messageField.redactor('wutil.selectionEndOfEditor');
- }
- else {
- this._messageField.focus();
- }
- },
-
- /**
- * Returns container element.
- *
- * @return jQuery
- */
- getContainer: function() {
- return this._container;
- },
-
- /**
- * Insertes quotes into the quick reply editor.
- *
- * @param object data
- */
- _insertQuotes: function(data) {
- if (!data.returnValues.template) {
- return;
- }
-
- if ($.browser.redactor) {
- var $html = WCF.String.unescapeHTML(data.returnValues.template);
- $html = this._messageField.redactor('wbbcode.convertToHtml', $html);
- $html = $html.replace(/<p><blockquote/, '<blockquote');
- $html = $html.replace(/blockquote><\/p>/, 'blockquote>');
-
- this._messageField.redactor('focus.setEnd');
- this._messageField.redactor('wutil.insertDynamic', $html, data.returnValues.template);
- this._messageField.redactor('wutil.selectionEndOfEditor');
- this._messageField.redactor('wbbcode.observeQuotes');
- }
- else {
- this._messageField.val(data.returnValues.template);
- }
- },
-
- /**
- * Saves message.
- */
- _save: function() {
- if (this._pendingSave) {
- return;
- }
-
- var $message = '';
- if ($.browser.redactor) {
- $message = this._messageField.redactor('wutil.getText');
- }
- else {
- $message = $.trim(this._messageField.val());
- }
-
- // check if message is empty
- var $innerError = this._messageField.parent().find('small.innerError');
- if ($message === '' || $message === '0') {
- if (!$innerError.length) {
- $innerError = $('<small class="innerError" />').appendTo(this._messageField.parent());
- }
-
- $innerError.html(WCF.Language.get('wcf.global.form.error.empty'));
- return;
- }
- else {
- $innerError.remove();
- }
-
- this._pendingSave = true;
-
- this._proxy.setOption('data', {
- actionName: 'quickReply',
- className: this._getClassName(),
- interfaceName: 'wcf\\data\\IMessageQuickReplyAction',
- parameters: this._getParameters($message)
- });
- this._proxy.sendRequest();
-
- // show spinner and hide Redactor
- var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
- $('<span class="icon icon48 fa-spinner" />').appendTo($messageBody);
- var $redactorBox = $messageBody.children('.redactor-box').hide();
-
- // hide message tabs
- $redactorBox.next().hide();
-
- // hide form submit
- $messageBody.next().hide();
- },
-
- /**
- * Returns the parameters for the save request.
- *
- * @param string message
- * @return object
- */
- _getParameters: function(message) {
- var $parameters = {
- objectID: this._getObjectID(),
- data: {
- message: message
- },
- lastPostTime: this._container.data('lastPostTime'),
- pageNo: this._container.data('pageNo'),
- removeQuoteIDs: (this._quoteManager === null ? [ ] : this._quoteManager.getQuotesMarkedForRemoval())
- };
- if (this._container.data('anchor')) {
- $parameters.anchor = this._container.data('anchor');
- }
-
- WCF.System.Event.fireEvent('com.woltlab.wcf.messageOptionsInline', 'submit_' + this._messageField.wcfIdentify(), $parameters.data);
-
- return $parameters;
- },
-
- /**
- * Cancels quick reply.
- */
- _cancel: function() {
- this._revertQuickReply(true);
-
- if ($.browser.redactor) {
- this._messageField.redactor('wutil.reset');
- }
- else {
- this._messageField.val('');
- }
- },
-
- /**
- * Reverts quick reply to original state and optionally hiding it.
- *
- * @param boolean hide
- */
- _revertQuickReply: function(hide) {
- var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
-
- if (hide) {
- this._container.hide();
-
- // remove previous error messages
- $messageBody.children('small.innerError').remove();
- }
-
- // display Redactor
- $messageBody.children('.fa-spinner').remove();
- $messageBody.children('.redactor-box').show().next().show();
-
- // display form submit
- $messageBody.next().show();
-
- this._quickReplyButtons.each(function() {
- var $button = $(this);
- if ($button.parent()[0].tagName === 'LI') {
- $button.parent().show();
- }
- else {
- $button.show();
- }
- });
- },
-
- /**
- * Prepares jump to extended message add form.
- */
- _prepareExtended: function() {
- this._pendingSave = true;
-
- // mark quotes for removal
- if (this._quoteManager !== null) {
- this._quoteManager.markQuotesForRemoval();
- }
-
- var $message = '';
- if ($.browser.redactor) {
- $message = this._messageField.redactor('wutil.getText');
-
- if ($message.length) {
- this._messageField.redactor('wutil.saveTextToStorage', true);
- }
- else {
- this._messageField.redactor('wutil.autosavePurge');
- }
- }
- else {
- $message = $.trim(this._messageField.val());
- }
-
- var $parameters = {
- containerID: this._getObjectID(),
- message: $message
- };
-
- WCF.System.Event.fireEvent('com.woltlab.wcf.messageOptionsInline', 'prepareExtended_' + this._messageField.wcfIdentify(), $parameters);
-
- new WCF.Action.Proxy({
- autoSend: true,
- data: {
- actionName: 'jumpToExtended',
- className: this._getClassName(),
- interfaceName: 'wcf\\data\\IExtendedMessageQuickReplyAction',
- parameters: $parameters
- },
- success: (function(data) {
- this._messageField.redactor('wutil.saveTextToStorage');
- window.location = data.returnValues.url;
- }).bind(this)
- });
- },
-
- /**
- * Handles successful AJAX calls.
- *
- * @param object data
- * @param string textStatus
- * @param jQuery jqXHR
- */
- _success: function(data, textStatus, jqXHR) {
- if ($.browser.redactor) {
- this._messageField.redactor('wutil.autosavePause');
- this._messageField.redactor('wutil.autosavePurge');
- }
-
- // redirect to new page
- if (data.returnValues.url) {
- window.location = data.returnValues.url;
- }
- else {
- if (data.returnValues.template) {
- // insert HTML
- var $message = $('' + data.returnValues.template);
- if (this._container.data('sortOrder') == 'DESC') {
- $message.insertAfter(this._container);
- }
- else {
- $message.insertBefore(this._container);
- }
-
- // update last post time
- this._container.data('lastPostTime', data.returnValues.lastPostTime);
-
- // show notification
- this._notification.show(undefined, undefined, WCF.Language.get('wcf.global.success.add'));
-
- this._updateHistory($message.wcfIdentify());
- }
- else {
- // show notification
- var $message = (this._successMessageNonVisible) ? this._successMessageNonVisible : 'wcf.global.success.add';
- this._notification.show(undefined, 5000, WCF.Language.get($message));
- }
-
- if ($.browser.redactor) {
- this._messageField.redactor('wutil.reset');
- this._messageField.redactor('wutil.autosaveResume');
- }
- else {
- this._messageField.val('');
- }
-
- // hide quick reply and revert it
- this._revertQuickReply(true);
-
- // count stored quotes
- if (this._quoteManager !== null) {
- this._quoteManager.countQuotes();
- }
-
- this._pendingSave = false;
- }
- },
-
- /**
- * Reverts quick reply on failure to preserve entered message.
- */
- _failure: function(data) {
- this._pendingSave = false;
- this._revertQuickReply(false);
-
- if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
- return true;
- }
-
- var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
- var $innerError = $messageBody.children('small.innerError').empty();
- if (!$innerError.length) {
- $innerError = $('<small class="innerError" />').appendTo($messageBody);
- }
-
- $innerError.html(data.returnValues.errorType);
-
- return false;
- },
-
- /**
- * Returns action class name.
- *
- * @return string
- */
- _getClassName: function() {
- return '';
- },
-
- /**
- * Returns object id.
- *
- * @return integer
- */
- _getObjectID: function() {
- return 0;
- },
-
- /**
- * Updates the history to avoid old content when going back in the browser
- * history.
- *
- * @param hash
- */
- _updateHistory: function(hash) {
- window.location.hash = hash;
- }
-});
-
/**
* Provides an inline message editor.
*
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLab/WCF/Dom/Util
*/
-define([], function() {
+define(['StringUtil'], function(StringUtil) {
"use strict";
var _matchesSelectorFunction = '';
* Applies a list of CSS properties to an element.
*
* @param {Element} el element
- * @param {Object<string, mixed>} styles list of CSS styles
+ * @param {Object<string, *>} styles list of CSS styles
*/
setStyles: function(el, styles) {
var important = false;
*
* @param {CSSStyleDeclaration} styles result of window.getComputedStyle()
* @param {string} propertyName property name
- * @return {integer} property value as integer
+ * @return {int} property value as integer
*/
styleAsInt: function(styles, propertyName) {
var value = styles.getPropertyValue(propertyName);
}
},
+ /**
+ *
+ * @param html
+ * @param {Element} referenceElement
+ * @param insertMethod
+ */
+ insertHtml: function(html, referenceElement, insertMethod) {
+ var element = elCreate('div');
+ this.setInnerHtml(element, html);
+
+ if (insertMethod === 'append' || insertMethod === 'after') {
+ while (element.childNodes.length) {
+ if (insertMethod === 'append') {
+ referenceElement.appendChild(element.childNodes[0]);
+ }
+ else {
+ this.insertAfter(element.childNodes[0], referenceElement);
+ }
+ }
+ }
+ else if (insertMethod === 'prepend' || insertMethod === 'before') {
+ for (var i = element.childNodes.length - 1; i >= 0; i--) {
+ if (insertMethod === 'prepend') {
+ this.prepend(element.childNodes[i], referenceElement);
+ }
+ else {
+ referenceElement.parentNode.insertBefore(element.childNodes[i], referenceElement);
+ }
+ }
+ }
+ else {
+ throw new Error("Unknown insert method '" + insertMethod + "'.");
+ }
+ },
+
/**
* Returns true if `element` contains the `child` element.
*
}
return false;
+ },
+
+ /**
+ * Retrieves all data attributes from target element, optionally allowing for
+ * a custom prefix that serves two purposes: First it will restrict the results
+ * for items starting with it and second it will remove that prefix.
+ *
+ * @param {Element} element target element
+ * @param {string=} prefix attribute prefix
+ * @param {boolean=} camcelCaseName transform attribute names into camel case using dashes as separators
+ * @param {boolean=} idToUpperCase transform '-id' into 'ID'
+ * @returns {object<string, string>} list of data attributes
+ */
+ getDataAttributes: function(element, prefix, camcelCaseName, idToUpperCase) {
+ prefix = prefix || '';
+ if (!/^data-/.test(prefix)) prefix = 'data-' + prefix;
+ camcelCaseName = (camcelCaseName === true);
+ idToUpperCase = (idToUpperCase === true);
+
+ var attribute, attributes = {}, name, tmp;
+ for (var i = 0, length = element.attributes.length; i < length; i++) {
+ attribute = element.attributes[i];
+
+ if (attribute.name.indexOf(prefix) === 0) {
+ name = attribute.name.replace(new RegExp('^' + prefix), '');
+ if (camcelCaseName) {
+ tmp = name.split('-');
+ name = '';
+ for (var j = 0, innerLength = tmp.length; j < innerLength; j++) {
+ if (name.length) {
+ if (idToUpperCase && tmp[j] === 'id') {
+ tmp[j] = 'ID';
+ }
+ else {
+ tmp[j] = StringUtil.ucfirst(tmp[j]);
+ }
+ }
+
+ name += tmp[j];
+ }
+ }
+
+ attributes[name] = attribute.value;
+ }
+ }
+
+ return attributes;
}
};
/**
* @exports WoltLab/WCF/Event/Handler
*/
- var EventHandler = {
+ return {
/**
* Adds an event listener.
*
}
}
};
-
- return EventHandler;
});
--- /dev/null
+/**
+ * Handles user interaction with the quick reply feature.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Ui/Message/Reply
+ */
+define(['Ajax', 'Core', 'EventHandler', 'Language', 'Dom/Util', 'Ui/Notification', '../Scroll'], function(Ajax, Core, EventHandler, Language, DomUtil, UiNotification, UiScroll) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function UiMessageReply(options) { this.init(options); }
+ UiMessageReply.prototype = {
+ /**
+ * Initializes a new quick reply field.
+ *
+ * @param {Object} options configuration options
+ */
+ init: function(options) {
+ this._options = Core.extend({
+ ajax: {
+ className: ''
+ },
+ successMessage: 'wcf.global.success.add'
+ }, options);
+
+ this._container = elById('messageQuickReply');
+ this._content = elBySel('.messageContent', this._container);
+ this._textarea = elById('text');
+ this._editor = null;
+ this._loadingOverlay = null;
+
+ // prevent marking of text for quoting
+ elBySel('.message', this._container).classList.add('jsInvalidQuoteTarget');
+
+ // handle submit button
+ var submitCallback = this._submit.bind(this);
+ var submitButton = elBySel('button[data-type="save"]');
+ submitButton.addEventListener(WCF_CLICK_EVENT, submitCallback);
+
+ // bind reply button
+ var replyButtons = elBySelAll('.jsQuickReply');
+ for (var i = 0, length = replyButtons.length; i < length; i++) {
+ replyButtons[i].addEventListener(WCF_CLICK_EVENT, (function(event) {
+ event.preventDefault();
+
+ UiScroll.element(this._container, (function() {
+ this._getEditor().focus.end();
+ }).bind(this));
+ }).bind(this));
+ }
+
+ // TODO: add event listener for submit through keyboard in Redactor
+ },
+
+ /**
+ * Validates the message and submits it to the server.
+ *
+ * @param {Event} event event object
+ * @protected
+ */
+ _submit: function(event) {
+ event.preventDefault();
+
+ if (!this._validate()) {
+ // validation failed, bail out
+ return;
+ }
+
+ this._showLoadingOverlay();
+
+ // build parameters
+ var parameters = DomUtil.getDataAttributes(this._container, 'data-', true, true);
+ parameters.data = { message: this._getEditor().code.get() };
+
+ EventHandler.fire('com.woltlab.wcf.redactor2', 'submit_text', parameters);
+
+ Ajax.api(this, {
+ parameters: parameters
+ });
+ },
+
+ /**
+ * 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) {
+ errorMessages[0].parentNode.removeChild(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;
+
+ 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 = 'messageContentLoadingOverlay';
+ 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('.messageContentLoadingOverlay', 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) {
+ 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) {
+ // TODO: clear autosave content and disable it
+
+ // redirect to new page
+ if (data.returnValues.url) {
+ window.location = data.returnValues.url;
+ }
+ else {
+ if (data.returnValues.template) {
+ var elementId;
+
+ // insert HTML
+ if (elData(this._container, 'sort-order') === 'DESC') {
+ DomUtil.insertHtml(data.returnValues.template, this._container, 'after');
+ elementId = DomUtil.identify(this._container.nextElementSibling);
+ }
+ else {
+ DomUtil.insertHtml(data.returnValues.template, this._container, 'before');
+ elementId = DomUtil.identify(this._container.previousElementSibling);
+ }
+
+ // update last post time
+ elData(this._container, 'last-post-time', data.returnValues.lastPostTime);
+
+ window.location.hash = elementId;
+ UiScroll.element(elById(elementId));
+ }
+
+ UiNotification.show(Language.get(this._options.successMessage));
+
+ // TODO: resume autosave
+
+ // TODO: reload quotes
+ }
+ },
+
+ _ajaxSuccess: function(data) {
+ this._insertMessage(data);
+
+ this._reset();
+
+ this._hideLoadingOverlay();
+ },
+
+ _ajaxFailure: function(data) {
+ this._hideLoadingOverlay();
+
+ if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+ return true;
+ }
+
+ this._handleError(data);
+
+ return false;
+ },
+
+ _ajaxSetup: function() {
+ return {
+ data: {
+ actionName: 'quickReply',
+ className: this._options.ajax.className,
+ interfaceName: 'wcf\\data\\IMessageQuickReplyAction'
+ }
+ };
+ }
+ };
+
+ return UiMessageReply;
+});
--- /dev/null
+/**
+ * Smoothly scrolls to an element while accounting for potential sticky headers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Ui/Scroll
+ */
+define(['Dom/Util'], function(DomUtil) {
+ "use strict";
+
+ var _callback = null;
+ var _callbackScroll = null;
+ var _timeoutScroll = null;
+
+ /**
+ * @exports WoltLab/WCF/Ui/Scroll
+ */
+ return {
+ /**
+ * Scrolls to target element, optionally invoking the provided callback once scrolling has ended.
+ *
+ * @param {Element} element target element
+ * @param {function=} callback callback invoked once scrolling has ended
+ */
+ element: function(element, callback) {
+ if (!(element instanceof Element)) {
+ throw new TypeError("Expected a valid DOM element.");
+ }
+ else if (callback !== undefined && typeof callback !== 'function') {
+ throw new TypeError("Expected a valid callback function.");
+ }
+ else if (!document.body.contains(element)) {
+ throw new Error("Element must be part of the visible DOM.");
+ }
+ else if (_callback !== null) {
+ throw new Error("Cannot scroll to element, a concurrent request is running.");
+ }
+
+ if (callback) {
+ _callback = callback;
+
+ if (_callbackScroll === null) {
+ _callbackScroll = this._onScroll.bind(this);
+ }
+
+ window.addEventListener('scroll', _callbackScroll);
+ }
+
+ var y = DomUtil.offset(element).top;
+
+ if (y <= 50) {
+ y = 0;
+ }
+ else {
+ // add an offset of 50 pixel to account for a sticky header
+ y -= 50;
+ }
+
+ window.scrollTo({
+ left: 0,
+ top: y,
+ behavior: 'smooth'
+ });
+ },
+
+ /**
+ * Monitors scroll event to only execute the callback once scrolling has ended.
+ *
+ * @protected
+ */
+ _onScroll: function() {
+ if (_timeoutScroll !== null) window.clearTimeout(_timeoutScroll);
+
+ _timeoutScroll = window.setTimeout(function() {
+ _callback();
+
+ window.removeEventListener('scroll', _callbackScroll);
+ _callback = null;
+ _timeoutScroll = null;
+ }, 100);
+ }
+ }
+});
window.objOwns = function(obj, property) {
return obj.hasOwnProperty(property);
};
+
+ /* assigns a global constant defining the proper 'click' event depending on the browser,
+ enforcing 'touchstart' on mobile devices for a better UX. We're using defineProperty()
+ here because at the time of writing Safari does not support 'const'. Thanks Safari.
+ */
+ var clickEvent = ('touchstart' in document.documentElement) ? 'touchstart' : 'click';
+ Object.defineProperty(window, 'WCF_CLICK_EVENT', {
+ value: clickEvent
+ });
})(window, document);
flex: 1 auto;
flex-direction: column;
margin-left: 30px;
+
+ &.loading {
+ position: relative;
+
+ > .messageContentLoadingOverlay {
+ align-items: center;
+ background-color: $wcfContentBackground;
+ bottom: 0;
+ display: flex;
+ justify-content: center;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+
+ > .icon {
+ flex: 0 0 auto;
+ }
+ }
+ }
}
/* content - header */