Added basic redactor plugins (WIP)
authorAlexander Ebert <ebert@woltlab.com>
Sat, 21 May 2016 11:34:06 +0000 (13:34 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 21 May 2016 11:34:13 +0000 (13:34 +0200)
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor2/plugins/alignment.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor2/plugins/source.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor2/plugins/table.js [new file with mode: 0644]
wcfsetup/install/files/style/ui/redactor.scss

index 5991bba5fa62d06d153c188619c4ce1a7bf64c49..0f98454c9b802cb615853461074d5354fc992336 100644 (file)
@@ -27,7 +27,7 @@
                var config = {
                        buttons: buttons,
                        minHeight: 200,
-                       plugins: ['WoltLabButton', 'WoltLabColor', 'WoltLabDropdown', 'WoltLabEvent', 'WoltLabLink', 'WoltLabQuote'],
+                       plugins: ['alignment', 'source', 'table', 'WoltLabButton', 'WoltLabColor', 'WoltLabDropdown', 'WoltLabEvent', 'WoltLabLink', 'WoltLabQuote'],
                        toolbarFixed: false,
                        woltlab: {
                                autosave: autosave
        });
                
        head.load([
+               {* Imperavi *}
                '{@$__wcf->getPath()}js/3rdParty/redactor2/redactor.js?v={@LAST_UPDATE_TIME}',
+               '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/alignment.js?v={@LAST_UPDATE_TIME}',
+               '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/source.js?v={@LAST_UPDATE_TIME}',
+               '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/table.js?v={@LAST_UPDATE_TIME}',
                
                {* WoltLab *}
                '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabButton.js?v={@LAST_UPDATE_TIME}',
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/alignment.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/alignment.js
new file mode 100644 (file)
index 0000000..19e3318
--- /dev/null
@@ -0,0 +1,50 @@
+(function($)
+{
+       $.Redactor.prototype.alignment = function()
+       {
+               return {
+                       langs: {
+                               en: {
+                               "align": "Align",
+                                       "align-left": "Align Left",
+                                       "align-center": "Align Center",
+                                       "align-right": "Align Right"
+                               }
+                       },
+                       init: function()
+                       {
+                               var that = this;
+                               var dropdown = {};
+
+                               dropdown.left = { title: that.lang.get('align-left'), func: that.alignment.setLeft };
+                               dropdown.center = { title: that.lang.get('align-center'), func: that.alignment.setCenter };
+                               dropdown.right = { title: that.lang.get('align-right'), func: that.alignment.setRight };
+
+                               var button = this.button.add('alignment', this.lang.get('align'));
+                               this.button.addDropdown(button, dropdown);
+                       },
+                       removeAlign: function()
+                       {
+                   this.block.removeClass('text-center');
+                   this.block.removeClass('text-right');
+                       },
+                       setLeft: function()
+                       {
+                               this.buffer.set();
+                               this.alignment.removeAlign();
+                       },
+                       setCenter: function()
+                       {
+                               this.buffer.set();
+                               this.alignment.removeAlign();
+                               this.block.addClass('text-center');
+                       },
+                       setRight: function()
+                       {
+                               this.buffer.set();
+                               this.alignment.removeAlign();
+                               this.block.addClass('text-right');
+                       }
+               };
+       };
+})(jQuery);
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/source.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/source.js
new file mode 100644 (file)
index 0000000..d8fc60b
--- /dev/null
@@ -0,0 +1,195 @@
+(function($)
+{
+       $.Redactor.prototype.source = function()
+       {
+               return {
+                       init: function()
+                       {
+                               var button = this.button.addFirst('html', 'HTML');
+                               this.button.addCallback(button, this.source.toggle);
+
+                               var style = {
+                                       'width': '100%',
+                                       'margin': '0',
+                                       'background': '#111',
+                                       'box-sizing': 'border-box',
+                                       'color': 'rgba(255, 255, 255, .8)',
+                                       'font-size': '14px',
+                                       'outline': 'none',
+                                       'padding': '16px',
+                                       'line-height': '22px',
+                                       'font-family': 'Menlo, Monaco, Consolas, "Courier New", monospace'
+                               };
+
+                               this.source.$textarea = $('<textarea />');
+                               this.source.$textarea.css(style).hide();
+
+                               if (this.opts.type === 'textarea')
+                               {
+                                       this.core.box().append(this.source.$textarea);
+                               }
+                               else
+                               {
+                                       this.core.box().after(this.source.$textarea);
+                               }
+
+                               this.core.element().on('destroy.callback.redactor', $.proxy(function()
+                               {
+                                       this.source.$textarea.remove();
+
+                               }, this));
+
+                       },
+                       toggle: function()
+                       {
+                               return (this.source.$textarea.hasClass('open')) ? this.source.hide() : this.source.show();
+                       },
+                       setCaretOnShow: function()
+                       {
+                               this.source.offset = this.offset.get();
+                               var scroll = $(window).scrollTop();
+
+                               var     width = this.core.editor().innerWidth();
+                               var height = this.core.editor().innerHeight();
+
+                               // caret position sync
+                               this.source.start = 0;
+                               this.source.end = 0;
+                               var $editorDiv = $("<div/>").append($.parseHTML(this.core.editor().html(), document, true));
+                               var $selectionMarkers = $editorDiv.find("span.redactor-selection-marker");
+
+                               if ($selectionMarkers.length > 0)
+                               {
+                                       var editorHtml = $editorDiv.html().replace(/&amp;/g, '&');
+
+                                       if ($selectionMarkers.length === 1)
+                                       {
+                                               this.source.start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
+                                               this.source.end = this.source.start;
+                                       }
+                                       else if ($selectionMarkers.length === 2)
+                                       {
+                                               this.source.start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
+                                               this.source.end = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-2").prop("outerHTML")) - $editorDiv.find("#selection-marker-1").prop("outerHTML").toString().length;
+                                       }
+                               }
+
+                       },
+                       setCaretOnHide: function(html)
+                       {
+                               this.source.start = this.source.$textarea.get(0).selectionStart;
+                               this.source.end = this.source.$textarea.get(0).selectionEnd;
+
+                               // if selection starts from end
+                               if (this.source.start > this.source.end && this.source.end > 0)
+                               {
+                                       var tempStart = this.source.end;
+                                       var tempEnd = this.source.start;
+
+                                       this.source.start = tempStart;
+                                       this.source.end = tempEnd;
+                               }
+
+                               this.source.start = this.source.enlargeOffset(html, this.source.start);
+                               this.source.end = this.source.enlargeOffset(html, this.source.end);
+
+                               html = html.substr(0, this.source.start) + this.marker.html(1) + html.substr(this.source.start);
+
+                               if (this.source.end > this.source.start)
+                               {
+                                       var markerLength = this.marker.html(1).toString().length;
+
+                                       html = html.substr(0, this.source.end + markerLength) + this.marker.html(2) + html.substr(this.source.end + markerLength);
+                               }
+
+                               return html;
+
+                       },
+                       hide: function()
+                       {
+                               this.source.$textarea.removeClass('open').hide();
+                               this.source.$textarea.off('.redactor-source');
+
+                               var code = this.source.$textarea.val();
+
+                               code = this.paragraphize.load(code);
+                               code = this.source.setCaretOnHide(code);
+
+                               this.code.start(code);
+                               this.button.enableAll();
+                               this.core.editor().show().focus();
+                               this.selection.restore();
+                               this.code.sync();
+                       },
+                       show: function()
+                       {
+                               this.selection.save();
+                               this.source.setCaretOnShow();
+
+                               var height = this.core.editor().innerHeight();
+                               var code = this.code.get();
+
+                               code = code.replace(/\n\n\n/g, "\n");
+                               code = code.replace(/\n\n/g, "\n");
+
+                               this.core.editor().hide();
+                               this.button.disableAll('html');
+                               this.source.$textarea.val(code).height(height).addClass('open').show();
+                               this.source.$textarea.on('keyup.redactor-source', $.proxy(function()
+                               {
+                                       if (this.opts.type === 'textarea')
+                                       {
+                                               this.core.textarea().val(this.source.$textarea.val());
+                                       }
+
+                               }, this));
+
+                               this.marker.remove();
+
+                               $(window).scrollTop(scroll);
+
+                               if (this.source.$textarea[0].setSelectionRange)
+                               {
+                                       this.source.$textarea[0].setSelectionRange(this.source.start, this.source.end);
+                               }
+
+                               this.source.$textarea[0].scrollTop = 0;
+
+                               setTimeout($.proxy(function()
+                               {
+                                       this.source.$textarea.focus();
+
+                               }, this), 0);
+                       },
+                       enlargeOffset: function(html, offset)
+                       {
+                               var htmlLength = html.length;
+                               var c = 0;
+
+                               if (html[offset] === '>')
+                               {
+                                       c++;
+                               }
+                               else
+                               {
+                                       for(var i = offset; i <= htmlLength; i++)
+                                       {
+                                               c++;
+
+                                               if (html[i] === '>')
+                                               {
+                                                       break;
+                                               }
+                                               else if (html[i] === '<' || i === htmlLength)
+                                               {
+                                                       c = 0;
+                                                       break;
+                                               }
+                                       }
+                               }
+
+                               return offset + c;
+                       }
+               };
+       };
+})(jQuery);
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/table.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/table.js
new file mode 100644 (file)
index 0000000..1c91a4f
--- /dev/null
@@ -0,0 +1,471 @@
+(function($)
+{
+       $.Redactor.prototype.table = function()
+       {
+               return {
+                       langs: {
+                               en: {
+                                       "table": "Table",
+                                       "insert-table": "Insert table",
+                                       "insert-row-above": "Insert row above",
+                                       "insert-row-below": "Insert row below",
+                                       "insert-column-left": "Insert column left",
+                                       "insert-column-right": "Insert column right",
+                                       "add-head": "Add head",
+                                       "delete-head": "Delete head",
+                                       "delete-column": "Delete column",
+                                       "delete-row": "Delete row",
+                                       "delete-table": "Delete table"
+                               }
+                       },
+                       init: function()
+                       {
+                               var dropdown = {};
+
+                               dropdown.insert_table = {
+                                                                       title: this.lang.get('insert-table'),
+                                                                       func: this.table.insert,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               in: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.insert_row_above = {
+                                                                       title: this.lang.get('insert-row-above'),
+                                                                       func: this.table.addRowAbove,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.insert_row_below = {
+                                                                       title: this.lang.get('insert-row-below'),
+                                                                       func: this.table.addRowBelow,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.insert_column_left = {
+                                                                       title: this.lang.get('insert-column-left'),
+                                                                       func: this.table.addColumnLeft,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.insert_column_right = {
+                                                                       title: this.lang.get('insert-column-right'),
+                                                                       func: this.table.addColumnRight,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.add_head = {
+                                                                       title: this.lang.get('add-head'),
+                                                                       func: this.table.addHead,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.delete_head = {
+                                                                       title: this.lang.get('delete-head'),
+                                                                       func: this.table.deleteHead,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.delete_column = {
+                                                                       title: this.lang.get('delete-column'),
+                                                                       func: this.table.deleteColumn,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.delete_row = {
+                                                                       title: this.lang.get('delete-row'),
+                                                                       func: this.table.deleteRow,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+                               dropdown.delete_table = {
+                                                                       title: this.lang.get('delete-table'),
+                                                                       func: this.table.deleteTable,
+                                                                       observe: {
+                                                                               element: 'table',
+                                                                               out: {
+                                                                                       attr: {
+                                                                                               'class': 'redactor-dropdown-link-inactive',
+                                                                                               'aria-disabled': true,
+                                                                                       }
+                                                                               }
+                                                                       }
+                                                               };
+
+
+                               var button = this.button.addBefore('link', 'table', this.lang.get('table'));
+                               this.button.addDropdown(button, dropdown);
+                       },
+                       insert: function()
+                       {
+                               if (this.table.getTable())
+                               {
+                                       return;
+                               }
+
+                               this.placeholder.hide();
+
+                               var rows = 2;
+                               var columns = 3;
+                               var $tableBox = $('<div>');
+                               var $table = $('<table />');
+
+
+                               for (var i = 0; i < rows; i++)
+                               {
+                                       var $row = $('<tr>');
+
+                                       for (var z = 0; z < columns; z++)
+                                       {
+                                               var $column = $('<td>' + this.opts.invisibleSpace + '</td>');
+
+                                               // set the focus to the first td
+                                               if (i === 0 && z === 0)
+                                               {
+                                                       $column.append(this.marker.get());
+                                               }
+
+                                               $($row).append($column);
+                                       }
+
+                                       $table.append($row);
+                               }
+
+                               $tableBox.append($table);
+                               var html = $tableBox.html();
+
+                               this.buffer.set();
+
+                               var current = this.selection.current();
+                               if ($(current).closest('li').length !== 0)
+                               {
+                                       $(current).closest('ul, ol').first().after(html);
+                               }
+                               else
+                               {
+                                       this.air.collapsed();
+                                       this.insert.html(html);
+                               }
+
+                               this.selection.restore();
+                               this.core.callback('insertedTable', $table);
+                       },
+                       getTable: function()
+                       {
+                               var $table = $(this.selection.current()).closest('table');
+
+                               if (!this.utils.isRedactorParent($table))
+                               {
+                                       return false;
+                               }
+
+                               if ($table.size() === 0)
+                               {
+                                       return false;
+                               }
+
+                               return $table;
+                       },
+                       restoreAfterDelete: function($table)
+                       {
+                               this.selection.restore();
+                               $table.find('span.redactor-selection-marker').remove();
+
+                       },
+                       deleteTable: function()
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               this.buffer.set();
+
+
+                               var $next = $table.next();
+                               if (!this.opts.linebreaks && $next.length !== 0)
+                               {
+                                       this.caret.start($next);
+                               }
+                               else
+                               {
+                                       this.caret.after($table);
+                               }
+
+
+                               $table.remove();
+
+
+                       },
+                       deleteRow: function()
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               var $current = $(this.selection.current());
+
+                               this.buffer.set();
+
+                               var $current_tr = $current.closest('tr');
+                               var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
+                               if ($focus_tr.length)
+                               {
+                                       var $focus_td = $focus_tr.children('td, th').first();
+                                       if ($focus_td.length)
+                                       {
+                                               $focus_td.prepend(this.marker.get());
+                                       }
+                               }
+
+                               $current_tr.remove();
+                               this.table.restoreAfterDelete($table);
+                       },
+                       deleteColumn: function()
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               this.buffer.set();
+
+                               var $current = $(this.selection.current());
+                               var $current_td = $current.closest('td, th');
+                               var index = $current_td[0].cellIndex;
+
+                               $table.find('tr').each($.proxy(function(i, elem)
+                               {
+                                       var $elem = $(elem);
+                                       var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
+                                       if (i === 0)
+                                       {
+                                               $elem.find('td, th').eq(focusIndex).prepend(this.marker.get());
+                                       }
+
+                                       $elem.find('td, th').eq(index).remove();
+
+                               }, this));
+
+                               this.table.restoreAfterDelete($table);
+                       },
+                       addHead: function()
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               this.buffer.set();
+
+                               if ($table.find('thead').size() !== 0)
+                               {
+                                       this.table.deleteHead();
+                                       return;
+                               }
+
+                               var tr = $table.find('tr').first().clone();
+                               tr.find('td').replaceWith($.proxy(function()
+                               {
+                                       return $('<th>').html(this.opts.invisibleSpace);
+                               }, this));
+
+                               $thead = $('<thead></thead>').append(tr);
+                               $table.prepend($thead);
+
+
+
+                       },
+                       deleteHead: function()
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               var $thead = $table.find('thead');
+                               if ($thead.size() === 0)
+                               {
+                                       return;
+                               }
+
+                               this.buffer.set();
+
+                               $thead.remove();
+
+                       },
+                       addRowAbove: function()
+                       {
+                               this.table.addRow('before');
+                       },
+                       addRowBelow: function()
+                       {
+                               this.table.addRow('after');
+                       },
+                       addColumnLeft: function()
+                       {
+                               this.table.addColumn('before');
+                       },
+                       addColumnRight: function()
+                       {
+                               this.table.addColumn('after');
+                       },
+                       addRow: function(type)
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               this.buffer.set();
+
+                               var $current = $(this.selection.current());
+                               var $current_tr = $current.closest('tr');
+                               var new_tr = $current_tr.clone();
+
+                               new_tr.find('th').replaceWith(function()
+                               {
+                                       var $td = $('<td>');
+                                       $td[0].attributes = this.attributes;
+
+                                       return $td.append($(this).contents());
+                               });
+
+                               new_tr.find('td').html(this.opts.invisibleSpace);
+
+                               if (type === 'after')
+                               {
+                                       $current_tr.after(new_tr);
+                               }
+                               else
+                               {
+                                       $current_tr.before(new_tr);
+                               }
+
+
+                       },
+                       addColumn: function (type)
+                       {
+                               var $table = this.table.getTable();
+                               if (!$table)
+                               {
+                                       return;
+                               }
+
+                               var index = 0;
+                               var current = $(this.selection.current());
+
+                               this.buffer.set();
+
+                               var $current_tr = current.closest('tr');
+                               var $current_td = current.closest('td, th');
+
+                               $current_tr.find('td, th').each($.proxy(function(i, elem)
+                               {
+                                       if ($(elem)[0] === $current_td[0])
+                                       {
+                                               index = i;
+                                       }
+
+                               }, this));
+
+                               $table.find('tr').each($.proxy(function(i, elem)
+                               {
+                                       var $current = $(elem).find('td, th').eq(index);
+
+                                       var td = $current.clone();
+                                       td.html(this.opts.invisibleSpace);
+
+                                       if (type === 'after')
+                                       {
+                                               $current.after(td);
+                                       }
+                                       else
+                                       {
+                                               $current.before(td);
+                                       }
+
+                               }, this));
+
+
+                       }
+               };
+       };
+})(jQuery);
\ No newline at end of file
index 84323e18b483ab308dd9d5eefc1b73c88920fe4c..87f4474ca4b234ce0b1c8e7bc8f05fe7ce111123 100644 (file)
 .redactor-voice-label {
        display: none;
 }
+
+/* alignment plugin */
+.text-center {
+       text-align: center;
+}
+
+.text-right {
+       text-align: right;
+}