Improved Redactor integration and resolved a few issues
authorAlexander Ebert <ebert@woltlab.com>
Fri, 4 Jul 2014 11:34:08 +0000 (13:34 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 4 Jul 2014 11:34:08 +0000 (13:34 +0200)
wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wmonkeypatch.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/woptions.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WCF.User.js
wcfsetup/install/files/js/WCF.js

index 6d8aa998b136a45d5bf8de90f9f25ac95805a0e1..4745e72e20adeeb2bb8d588154b962920be8d6f4 100644 (file)
@@ -36,6 +36,11 @@ RedactorPlugins.wbbcode = {
                        html = html.replace(/<p><br([^>]+)?><\/p>/g, '<p>@@@wcf_empty_line@@@</p>');
                        return $mpSyncClean.call(self, html);
                };
+               
+               if (this.getOption('wAutosaveOnce')) {
+                       this._saveTextToStorage();
+                       delete this.opts.wAutosaveOnce;
+               }
        },
        
        /**
@@ -143,7 +148,7 @@ RedactorPlugins.wbbcode = {
                var $parts = html.split(/(<\/?p>)/);
                var $tmp = '';
                var $buffer = '';
-               for (var $i = 1; $i < $parts.length; $i++) {
+               for (var $i = 0; $i < $parts.length; $i++) {
                        var $part = $parts[$i];
                        if ($part == '<p>') {
                                continue;
@@ -158,7 +163,12 @@ RedactorPlugins.wbbcode = {
                                $buffer = '';
                        }
                        else {
-                               $buffer += $part;
+                               if ($i == 0) {
+                                       $tmp += $part;
+                               }
+                               else {
+                                       $buffer += $part;
+                               }
                        }
                }
                html = $tmp;
@@ -306,8 +316,8 @@ RedactorPlugins.wbbcode = {
                html = html.replace(/<\/(ul|ol)>/gi, '[/list]');
                
                // [table]
-               html = html.replace(/<table[^>]*>/gi, '[table]');
-               html = html.replace(/<\/table>/gi, '[/table]');
+               html = html.replace(/<table[^>]*>/gi, '[table]\n');
+               html = html.replace(/<\/table>/gi, '[/table]\n');
                
                // remove <tbody>
                html = html.replace(/<tbody>([\s\S]*?)<\/tbody>/, function(match, p1) {
@@ -317,15 +327,15 @@ RedactorPlugins.wbbcode = {
                // remove empty <tr>s
                html = html.replace(/<tr><\/tr>/gi, '');
                // [tr]
-               html = html.replace(/<tr>/gi, '[tr]');
-               html = html.replace(/<\/tr>/gi, '[/tr]');
+               html = html.replace(/<tr>/gi, '[tr]\n');
+               html = html.replace(/<\/tr>/gi, '[/tr]\n');
                
                // [td]+[align]
                html = html.replace(/<td style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]");
                
                // [td]
-               html = html.replace(/<td>/gi, '[td]');
-               html = html.replace(/<\/td>/gi, '[/td]');
+               html = html.replace(/(\t)*<td>/gi, '[td]');
+               html = html.replace(/(\t)*<\/td>/gi, '[/td]\n');
                
                // cache redactor's selection markers
                var $cachedMarkers = { };
index b77f4782db2d6425cf6705516142830beb03a8ef..a0dc2820631e9e62936b6e5cdfcab56d5b4671df 100644 (file)
@@ -249,6 +249,82 @@ RedactorPlugins.wmonkeypatch = {
                this.$modal.children('.dialogContent').removeClass('dialogForm');
        },
        
+       tableInsert: function()
+       {
+               this.bufferSet(false);
+
+               var rows = $('#redactor_table_rows').val(),
+                       columns = $('#redactor_table_columns').val(),
+                       $table_box = $('<div></div>'),
+                       tableId = Math.floor(Math.random() * 99999),
+                       $table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
+                       i, $row, z, $column;
+
+               for (i = 0; i < rows; i++)
+               {
+                       $row = $('<tr></tr>');
+
+                       for (z = 0; z < columns; z++)
+                       {
+                               $column = $('<td>' + this.opts.invisibleSpace + '</td>');
+
+                               // set the focus to the first td
+                               if (i === 0 && z === 0)
+                               {
+                                       $column.append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
+                               }
+
+                               $($row).append($column);
+                       }
+
+                       $table.append($row);
+               }
+
+               $table_box.append($table);
+               var html = $table_box.html();
+
+               if (this.opts.linebreaks === false && this.browser('mozilla'))
+               {
+                       html += '<p>' + this.opts.invisibleSpace + '</p>';
+               }
+
+               this.modalClose();
+               this.selectionRestore();
+
+               var current = this.getBlock() || this.getCurrent();
+
+               if (current && current.tagName != 'BODY')
+               {
+                       // WoltLab fix for nested tables
+                       if (current.tagName == 'TD') {
+                               $(current).append(html);
+                       }
+                       else {
+                               if (current.tagName == 'LI')
+                               {
+                                       var current = $(current).closest('ul, ol');
+                               }
+       
+                               $(current).after(html);
+                       }
+                       // WoltLab fix for nested tables
+               }
+               else
+               {
+                       this.insertHtmlAdvanced(html, false);
+               }
+
+               this.selectionRestore();
+
+               var table = this.$editor.find('#table' + tableId);
+               this.buttonActiveObserver();
+
+               table.find('span#selection-marker-1, inline#selection-marker-1').remove();
+               table.removeAttr('id');
+
+               this.sync();
+       },
+       
        modalOpenedCallback: function() {
                // handle positioning of form submit controls
                var $heightDifference = 0;
diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/woptions.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/woptions.js
new file mode 100644 (file)
index 0000000..6dc7571
--- /dev/null
@@ -0,0 +1,117 @@
+if (!RedactorPlugins) var RedactorPlugins = {};
+
+/**
+ * Provides message option tabs for Redactor.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+RedactorPlugins.woptions = {
+       /**
+        * list of message option elements
+        * @var object<object>
+        */
+       _messageOptions: { },
+       
+       /**
+        * message option container
+        * @var jQuery
+        */
+       _messageOptionContainer: null,
+       
+       /**
+        * navigation container
+        * @var jQuery
+        */
+       _messageOptionNavigation: null,
+       
+       /**
+        * Initializes the RedactorPlugins.woptions plugin.
+        */
+       init: function() {
+               var $options = this.getOption('wMessageOptions');
+               if (!$options.length) {
+                       return;
+               }
+               
+               this._messageOptionContainer = $('<div id="redactorMessageOptions" class="redactorMessageOptions" />').appendTo(this.$box);
+               this._messageOptionNavigation = $('<nav><ul /></nav>').appendTo(this._messageOptionContainer).children('ul');
+               
+               for (var $i = 0; $i < $options.length; $i++) {
+                       var $container = $options[$i];
+                       
+                       var $listItem = $('<li><a>' + $container.title + '</a></li>').appendTo(this._messageOptionNavigation);
+                       $listItem.data('containerID', $container.containerID).click($.proxy(this._showMessageOptionContainer, this));
+                       
+                       var $tabContainer = $('<div class="redactorMessageOptionContainer" id="redactorMessageOptions_' + $container.containerID + '" />').hide().appendTo(this._messageOptionContainer);
+                       
+                       for (var $j = 0; $j < $container.items.length; $j++) {
+                               $($container.items[$j]).appendTo($tabContainer);
+                       }
+                       
+                       this._messageOptions[$container.containerID] = {
+                               container: $tabContainer,
+                               listItem: $listItem
+                       };
+               }
+               
+               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'updateMessageOptions', this._messageOptions);
+               
+               WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'reset', $.proxy(this._wOptionsListener, this));
+       },
+       
+       /**
+        * Toggles the specified message option container.
+        * 
+        * @param       object          event
+        * @param       string          containerID
+        */
+       _showMessageOptionContainer: function(event, containerID) {
+               var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
+               if (this._messageOptions[$containerID].listItem.hasClass('active')) {
+                       this._messageOptions[$containerID].listItem.removeClass('active');
+                       this._messageOptions[$containerID].container.hide();
+                       
+                       return;
+               }
+               
+               $.each(this._messageOptions, function(containerID, elements) {
+                       if (containerID == $containerID) {
+                               elements.listItem.addClass('active');
+                               elements.container.show();
+                       }
+                       else {
+                               elements.listItem.removeClass('active');
+                               elements.container.hide();
+                       }
+               });
+       },
+       
+       /**
+        * Collapses all message option containers.
+        * 
+        * @param       object          data
+        */
+       _wOptionsListener: function(data) {
+               $.each(this._messageOptions, function(containerID, elements) {
+                       elements.listItem.removeClass('active');
+                       elements.container.hide();
+                       
+                       elements.container.find('input, select, textarea').each(function(index, element) {
+                               var $element = $(element);
+                               switch ($element.getTagName()) {
+                                       case 'input':
+                                               $element.prop('checked', false);
+                                       break;
+                                       
+                                       default:
+                                               $element.val('');
+                                       break;
+                               }
+                       });
+               });
+               
+               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'updateMessageOptions', this._messageOptions);
+       }
+};
index efb570b1e1e8a5e503f2e53f3e872903b9cd4078..c4b0c88d64bac05f00e98352a1e535da4f3f873b 100644 (file)
@@ -23,13 +23,15 @@ RedactorPlugins.wupload = {
                this.buttonAwesome('upload', 'fa-upload');
                
                this._initAttachments();
+               
+               WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'reset', $.proxy(this._wUploadListener, this));
        },
        
        /**
         * Initializes the attachments user interface.
         */
        _initAttachments: function() {
-               this._attachmentsContainer = $('<div class="redactorAttachmentContainer" />').hide().appendTo(this.$box);
+               this._attachmentsContainer = $('#redactorMessageOptions_attachments');
                var $attachmentList = $('<ul class="formAttachmentList clearfix" />').hide().appendTo(this._attachmentsContainer);
                $('<dl class="wide"><dt></dt><dd><div data-max-size="{@$attachmentHandler->getMaxSize()}"></div><small>' + WCF.String.unescapeHTML(WCF.Language.get('wcf.attachment.upload.limits')) + '</small></dd></dl>').appendTo(this._attachmentsContainer);
                
@@ -53,12 +55,18 @@ RedactorPlugins.wupload = {
                                
                                $listItem.appendTo($attachmentList);
                                
-                               this._attachmentsContainer.show();
                                $attachmentList.show();
                        }
+                       
+                       this._showMessageOptionContainer(null, 'attachments');
                }
                
                new WCF.Attachment.Upload(this._attachmentsContainer.find('> dl > dd > div'), this._attachmentsContainer.children('ul'), $options.objectType, $options.objectID, $options.tmpHash, $options.parentObjectID, $options.maxCount, this.$source.wcfIdentify());
                new WCF.Action.Delete('wcf\\data\\attachment\\AttachmentAction', '.formAttachmentList > li');
+       },
+       
+       _wUploadListener: function(data) {
+               this._attachmentsContainer.children('.formAttachmentList').hide().empty();
+               this._attachmentsContainer.find('.jsButtonAttachmentInsertAll').hide();
        }
 };
index 8e8b9f5d28e980d867149280d844a2d22e90f2bd..a480aa7dd524f0285b9b483e6c345681d22e1e48 100644 (file)
@@ -25,7 +25,7 @@ RedactorPlugins.wutil = {
                        this.autosaveEnable();
                        
                        if (this.getOption('wautosave').saveOnInit || this.$source.data('saveOnInit')) {
-                               this._saveTextToStorage();
+                               this.setOption('wAutosaveOnce', true);
                        }
                        else {
                                this.autosaveRestore();
@@ -176,6 +176,8 @@ RedactorPlugins.wutil = {
                else {
                        this.$source.val('');
                }
+               
+               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'reset');
        },
        
        /**
index 5c4c5f68f6ff30ea1b441766aaa24e07e1909296..526c420bcac26617db14a57a9cd424c0dbb4623c 100644 (file)
@@ -64,7 +64,7 @@ WCF.Attachment.Upload = WCF.Upload.extend({
                
                this._makeSortable();
                
-               this._insertAllButton = $('<p class="button">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
+               this._insertAllButton = $('<p class="button jsButtonAttachmentInsertAll">' + WCF.Language.get('wcf.attachment.insertAll') + '</p>').hide().appendTo(this._buttonSelector);
                this._insertAllButton.click($.proxy(this._insertAll, this));
                
                if (this._fileListSelector.children('li:not(.uploadFailed)').length) {
index e9817577eb573b2e18a4b66a26b82e0cd78e5e17..d4de8dd23b62bce264ef031fe2c2c580d0a13ed5 100644 (file)
@@ -787,6 +787,38 @@ WCF.Message.QuickReply = Class.extend({
                        $parameters.anchor = this._container.data('anchor');
                }
                
+               // check for additional settings
+               var $container = $('#redactorMessageOptions_settings');
+               if ($container.length) {
+                       $parameters.settings = { };
+                       $container.find('input, textarea, select').each(function(index, element) {
+                               var $element = $(element);
+                               switch ($element.getTagName()) {
+                                       case 'input':
+                                               switch ($element.prop('type')) {
+                                                       case 'checkbox':
+                                                       case 'radio':
+                                                               if ($element.is(':checked')) {
+                                                                       $parameters.settings[$element.prop('name')] = $element.val();
+                                                               }
+                                                               else if ($element.data('submitEmpty')) {
+                                                                       $parameters.settings[$element.prop('name')] = 0;
+                                                               }
+                                                       break;
+                                                       
+                                                       default:
+                                                               $parameters.settings[$element.prop('name')] = $element.val();
+                                                       break;
+                                               }
+                                       break;
+                                       
+                                       default:
+                                               $parameters.settings[$element.prop('name')] = $element.val();
+                                       break;
+                               }
+                       });
+               }
+               
                return $parameters;
        },
        
index b5b13467a04ccbcc95c9e9eb00b1bf63935a9bc2..b0fc3ad9b69d7ea1e734d596d5449bbf7354d6d0 100644 (file)
@@ -2694,6 +2694,8 @@ WCF.User.ObjectWatch.Subscribe = Class.extend({
                        var $objectID = $button.data('objectID');
                        this._buttons[$objectID] = $button.click($.proxy(this._click, this));
                }, this));
+               
+               WCF.System.Event.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus, this));
        },
        
        /**
@@ -2759,14 +2761,11 @@ WCF.User.ObjectWatch.Subscribe = Class.extend({
                else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
                        this._dialog.wcfDialog('close');
                        
-                       // update icon
-                       var $icon = $(this._buttonSelector + '[data-object-id=' + data.returnValues.objectID + '] > .icon');
-                       if (data.returnValues.subscribe) {
-                               $icon.removeClass('icon-bookmark-empty').addClass('icon-bookmark');
-                       }
-                       else {
-                               $icon.removeClass('icon-bookmark').addClass('icon-bookmark-empty');
-                       }
+                       this._updateSubscriptionStatus({
+                               isSubscribed: data.returnValues.subscribe,
+                               objectID: data.returnValues.objectID
+                       });
+                       
                        
                        // show notification
                        if (this._notification === null) {
@@ -2798,6 +2797,26 @@ WCF.User.ObjectWatch.Subscribe = Class.extend({
                        }
                });
                this._proxy.sendRequest();
+       },
+       
+       /**
+        * Updates subscription status and icon.
+        * 
+        * @param       object          data
+        */
+       _updateSubscriptionStatus: function(data) {
+               var $button = $(this._buttonSelector + '[data-object-id=' + data.objectID + ']');
+               var $icon = $button.children('.icon');
+               if (data.isSubscribed) {
+                       $icon.removeClass('icon-bookmark-empty').addClass('icon-bookmark');
+                       $button.data('isSubscribed', true);
+               }
+               else {
+                       $icon.removeClass('icon-bookmark').addClass('icon-bookmark-empty');
+                       $button.data('isSubscribed', false);
+               }
+               
+               WCF.System.Event.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data);
        }
 });
 
index 1cfb4203a430cd1afe818015c0dd3202cabdb6e6..a3f55a1e06db9e29e6de25f3e7e4449504a0ce9c 100755 (executable)
@@ -7650,7 +7650,54 @@ WCF.System.PushNotification = {
                        }
                }
        }
-}
+};
+
+/**
+ * System-wide event system.
+ */
+WCF.System.Event = {
+       /**
+        * list of event listeners grouped by identifier and action.
+        * @var object<object>
+        */
+       _listeners: { },
+       
+       /**
+        * Registers a new event listener.
+        * 
+        * @param       string          identifier
+        * @param       string          action
+        * @param       object          listener
+        */
+       addListener: function(identifier, action, listener) {
+               if (typeof this._listeners[identifier] === 'undefined') {
+                       this._listeners[identifier] = { };
+               }
+               
+               if (typeof this._listeners[identifier][action] === 'undefined') {
+                       this._listeners[identifier][action] = [ ];
+               }
+               
+               this._listeners[identifier][action].push(listener);
+       },
+       
+       /**
+        * Fires a new event and notifies all registered event listeners.
+        * 
+        * @param       string          identifier
+        * @param       string          action
+        * @param       object          data
+        */
+       fireEvent: function(identifier, action, data) {
+               data = data || { };
+               
+               if (this._listeners[identifier] && this._listeners[identifier][action]) {
+                       for (var $i = 0; $i < this._listeners[identifier][action].length; $i++) {
+                               this._listeners[identifier][action][$i](data);
+                       }
+               }
+       }
+};
 
 /**
  * Worker support for frontend based upon DatabaseObjectActions.