Streamlined quote handling
authorAlexander Ebert <ebert@woltlab.com>
Wed, 15 Jun 2016 14:50:07 +0000 (16:50 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 15 Jun 2016 14:50:13 +0000 (16:50 +0200)
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabQuote.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Code.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Quote.js
wcfsetup/install/files/style/bbcode/quote.scss
wcfsetup/install/files/style/ui/redactor.scss
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 94e517484e5dd1e396e1f5324355c8b3ddcde21e..065a7f06c3dedae7dfeba683b40da63857a60c96 100644 (file)
@@ -49,6 +49,7 @@
                                'wcf.editor.code.line': '{lang}wcf.editor.code.line{/lang}',
                                'wcf.editor.code.line.description': '{lang}wcf.editor.code.line.description{/lang}',
                                'wcf.editor.code.title': '{lang __literal=true}wcf.editor.code.title{/lang}',
+                               
                                'wcf.editor.image.edit': '{lang}wcf.editor.image.edit{/lang}',
                                'wcf.editor.image.insert': '{lang}wcf.editor.image.insert{/lang}',
                                'wcf.editor.image.link': '{lang}wcf.editor.image.link{/lang}',
                                'wcf.editor.image.float.left': '{lang}wcf.editor.image.float.left{/lang}',
                                'wcf.editor.image.float.right': '{lang}wcf.editor.image.float.right{/lang}',
                                'wcf.editor.image.source': '{lang}wcf.editor.image.source{/lang}',
-                               'wcf.editor.image.source.error.invalid': '{lang}wcf.editor.image.source.error.invalid{/lang}'
+                               'wcf.editor.image.source.error.invalid': '{lang}wcf.editor.image.source.error.invalid{/lang}',
+                               
+                               'wcf.editor.quote.author': '{lang}wcf.editor.quote.author{/lang}',
+                               'wcf.editor.quote.edit': '{lang}wcf.editor.quote.edit{/lang}',
+                               'wcf.editor.quote.title': '{lang __literal=true}wcf.editor.quote.title{/lang}',
+                               'wcf.editor.quote.url': '{lang}wcf.editor.quote.url{/lang}',
+                               'wcf.editor.quote.url.description': '{lang}wcf.editor.quote.url.description{/lang}'
                        });
                        
                        var buttons = [], buttonOptions = [], customButtons = [];
index bf5d08fb668ec610f6f8190f3184d5fb45404d51..79b71c469ddbc596105e0baaed4828b5d9b01778 100644 (file)
@@ -3,31 +3,10 @@ $.Redactor.prototype.WoltLabQuote = function() {
        
        return {
                init: function() {
-                       // TODO: this should be somewhere else
                        var button = this.button.add('woltlabQuote', '');
-                       this.button.addCallback(button, this.WoltLabQuote.insert);
                        
-                       require(['WoltLab/WCF/Ui/Redactor/Quote'], (function(UiRedactorQuote) {
-                               UiRedactorQuote.initEditor(this.$element[0].id, this.$editor[0]);
-                       }).bind(this));
-               },
-               
-               insert: function() {
-                       require(['Dom/Traverse', 'WoltLab/WCF/Ui/Redactor/Quote'], (function(DomTraverse, UiRedactorQuote) {
-                               var current = this.selection.current();
-                               if (current) {
-                                       if (current.nodeType === Node.TEXT_NODE) current = current.parentNode;
-                                       
-                                       if (current.nodeName === 'BLOCKQUOTE' || DomTraverse.parentByTag(current, 'BLOCKQUOTE', this.$editor[0])) {
-                                               return;
-                                       }
-                               }
-                               
-                               UiRedactorQuote.insert((function(element) {
-                                       element.innerHTML = this.opts.invisibleSpace + this.selection.markerHtml();
-                                       
-                                       this.insert.html(element.outerHTML);
-                               }).bind(this));
+                       require(['WoltLab/WCF/Ui/Redactor/Quote'], (function (UiRedactorQuote) {
+                               new UiRedactorQuote(this, button);
                        }).bind(this));
                }
        };
index 45e04ec966f01728e544a509bf761dc29fac8b44..b92ec60e797c390bc448472c582e2675f0229966 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Provides helper functions to work with DOM nodes.
+ * Manages code blocks.
  *
  * @author      Alexander Ebert
  * @copyright   2001-2016 WoltLab GmbH
@@ -53,22 +53,9 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                        
                        var pre = this._editor.selection.block();
                        if (pre && pre.nodeName === 'PRE') {
-                               if (pre.textContent === '') {
-                                       pre.textContent = '\u200B';
-                               }
-                               
                                this._setTitle(pre);
                                
                                pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
-                               
-                               // there must be some kind of element after the <pre>
-                               if (pre.nextElementSibling === null) {
-                                       var p = elCreate('p');
-                                       p.textContent = '\u200B';
-                                       pre.parentNode.appendChild(p);
-                               }
-                               
-                               this._editor.caret.after(pre);
                        }
                },
                
index f88a4272a981869c93c90449f730ebaf4f1622b2..37e979686c01d781d48bbb1b3afe75b5dc2d3713 100644 (file)
 /**
- * Manages insertation and editing of quotes.
- * 
- * @author     Alexander Ebert
- * @copyright  2001-2015 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module     WoltLab/WCF/Ui/Redactor/Quote
+ * Manages quotes.
+ *
+ * @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/Redactor/Quote
  */
-define(['EventHandler', 'Language', 'Dom/Util', 'Ui/Dialog'], function(EventHandler, Language, DomUtil, UiDialog) {
+define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog) {
        "use strict";
        
-       var _callbackEdit = null;
-       var _element = null;
-       var _insertCallback = null;
-       var _quotePaddingTop = 0;
-       var _titleHeight = 0;
-       var _wysiwygQuoteButton = null;
-       var _wysiwygQuoteTitle = null;
-       var _wysiwygQuoteUrl = null;
+       var _headerHeight = 0;
        
-       return {
+       /**
+        * @param       {Object}        editor  editor instance
+        * @param       {jQuery}        button  toolbar button
+        * @constructor
+        */
+       function UiRedactorQuote(editor, button) { this.init(editor, button); }
+       UiRedactorQuote.prototype = {
                /**
-                * Registers an editor instance.
+                * Initializes the quote management.
                 * 
-                * @param       {string}        editorId        textarea identifier
-                * @param       {object}        editor          editor element
+                * @param       {Object}        editor  editor instance
+                * @param       {jQuery}        button  toolbar button
                 */
-               initEditor: function(editorId, editor) {
-                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + editorId, (function(data) {
-                               this.observeAll(data.editor);
-                       }).bind(this));
+               init: function(editor, button) {
+                       this._blockquote = null;
+                       this._editor = editor;
+                       this._elementId = this._editor.$element[0].id;
                        
-                       this.observeAll(editor);
-               },
-               
-               /**
-                * Opens a dialog to insert a quote at caret position.
-                * 
-                * @param       {function}      callback        callback invoked with the <blockquote> element as parameter
-                */
-               insert: function(callback) {
-                       _insertCallback = callback;
+                       EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this));
                        
-                       UiDialog.open(this);
-                       UiDialog.setTitle(this, Language.get('wcf.wysiwyg.quote.insert'));
+                       this._editor.button.addCallback(button, this._click.bind(this));
                        
-                       _wysiwygQuoteButton.textContent = Language.get('wcf.global.button.submit');
-                       _wysiwygQuoteTitle.value = '';
-                       _wysiwygQuoteUrl.value = '';
+                       // support for active button marking
+                       this._editor.opts.activeButtonsStates.blockquote = 'woltlabQuote';
+                       
+                       // static bind to ensure that removing works
+                       this._callbackEdit = this._edit.bind(this);
+                       
+                       // bind listeners on init
+                       this._observeLoad();
                },
                
                /**
-                * Edits a <blockquote> element.
+                * Toggles the quote block on button click.
                 * 
-                * @param       {Event?}        event           event object
-                * @param       {Element=}      element         <blockquote> element
+                * @protected
                 */
-               edit: function(event, element) {
-                       if (event instanceof Event) {
-                               element = event.currentTarget;
-                       }
-                       
-                       if (_titleHeight === 0) {
-                               var styles = window.getComputedStyle(element, '::before');
-                               _titleHeight = DomUtil.styleAsInt(styles, 'height');
-                               
-                               styles = window.getComputedStyle(element);
-                               _quotePaddingTop = DomUtil.styleAsInt(styles, 'padding-top');
-                       }
+               _click: function() {
+                       this._editor.button.toggle({}, 'blockquote', 'func', 'block.format');
                        
-                       if (event instanceof Event) {
-                               // check if click occured within the ::before pseudo element
-                               var rect = DomUtil.offset(element);
-                               if ((event.clientY + window.scrollY) > (rect.top + _quotePaddingTop + _titleHeight)) {
-                                       return;
-                               }
+                       var blockquote = this._editor.selection.block();
+                       if (blockquote && blockquote.nodeName === 'BLOCKQUOTE') {
+                               this._setTitle(blockquote);
                                
-                               event.preventDefault();
-                       }
-                       
-                       _element = element;
-                       
-                       UiDialog.open(this);
-                       UiDialog.setTitle(this, Language.get('wcf.wysiwyg.quote.edit'));
-                       
-                       // set values
-                       _wysiwygQuoteButton.textContent = Language.get('wcf.global.button.save');
-                       _wysiwygQuoteTitle.value = elData(_element, 'quote-title');
-                       _wysiwygQuoteUrl.value = elData(_element, 'quote-url');
-               },
-               
-               /**
-                * Observes all <blockquote> elements for clicks on the editable headline
-                * @param       {Element}       editorElement           editor element
-                */
-               observeAll: function(editorElement) {
-                       var elements = elByTag('BLOCKQUOTE', editorElement);
-                       for (var i = 0, length = elements.length; i < length; i++) {
-                               this._observe(elements[i], true);
+                               blockquote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
                        }
                },
                
                /**
-                * Observes clicks on a <blockquote> element and updates the headline.
+                * Binds event listeners and sets quote title on both editor
+                * initialization and when switching back from code view.
                 * 
-                * @param       {Element}       element         <blockquote> element
-                * @param       {boolean}       updateHeader    update quote header
+                * @protected
                 */
-               _observe: function(element, updateHeader) {
-                       if (_callbackEdit === null) _callbackEdit = this.edit.bind(this);
+               _observeLoad: function() {
+                       this._editor.events.stopDetectChanges();
                        
-                       element.addEventListener(WCF_CLICK_EVENT, _callbackEdit);
+                       elBySelAll('blockquote', this._editor.$editor[0], (function(blockquote) {
+                               blockquote.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+                               this._setTitle(blockquote);
+                       }).bind(this));
                        
-                       if (updateHeader) this._updateHeader(element);
+                       this._editor.events.startDetectChanges();
                },
                
                /**
-                * Updates the headline of target <blockquote> element.
+                * Opens the dialog overlay to edit the quote's properties.
                 * 
-                * @param       {Element}       element         <blockquote> element
+                * @param       {Event}         event           event object
+                * @protected
                 */
-               _updateHeader: function(element) {
-                       var value = Language.get('wcf.wysiwyg.quote.header', {
-                               title: elData(element, 'quote-title') || elData(element, 'quote-url') || ''
-                       });
+               _edit: function(event) {
+                       var blockquote = event.currentTarget;
+                       
+                       if (_headerHeight === 0) {
+                               _headerHeight = ~~window.getComputedStyle(blockquote).paddingTop.replace(/px$/, '');
+                               
+                               var styles = window.getComputedStyle(blockquote, '::before');
+                               _headerHeight += ~~styles.paddingTop.replace(/px$/, '');
+                               _headerHeight += ~~styles.height.replace(/px$/, '');
+                               _headerHeight += ~~styles.paddingBottom.replace(/px$/, '');
+                       }
                        
-                       if (elData(element, 'quote-header') !== value) {
-                               elData(element, 'quote-header', value);
+                       // check if the click hit the header
+                       var offset = DomUtil.offset(blockquote);
+                       if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
+                               event.preventDefault();
+                               
+                               this._blockquote = blockquote;
+                               
+                               UiDialog.open(this);
                        }
                },
                
                /**
-                * Adds or edits a <blockquote> element on dialog submit.
+                * Saves the changes to the quote's properties.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
                 */
-               _dialogSubmit: function() {
-                       if (_insertCallback !== null) {
-                               // insert a new <blockquote> element
-                               var element = elCreate('blockquote');
-                               element.className = 'quoteBox';
-                               element.id = 'quote-' + DomUtil.getUniqueId();
-                               
-                               _insertCallback(element);
-                               
-                               _element = elById(element.id);
-                               _element.id = '';
-                               
-                               this._observe(_element, false);
-                       }
+               _save: function(event) {
+                       event.preventDefault();
                        
-                       // edit an existing <blockquote> element
-                       elData(_element, 'quote-title', _wysiwygQuoteTitle.value.trim());
-                       elData(_element, 'quote-url', _wysiwygQuoteUrl.value.trim());
+                       this._editor.events.stopDetectChanges();
                        
-                       this._updateHeader(_element);
+                       var id = 'redactor-quote-' + this._elementId;
                        
-                       UiDialog.close(this);
-               },
-               
-               _dialogOnSetup: function() {
-                       _wysiwygQuoteTitle = elById('wysiwygQuoteTitle');
-                       _wysiwygQuoteUrl = elById('wysiwygQuoteUrl');
+                       ['author', 'url'].forEach((function (attr) {
+                               elData(this._blockquote, attr, elById(id + '-' + attr).value);
+                       }).bind(this));
                        
-                       var _keyupCallback = (function(event) {
-                               if (event.which === 13) {
-                                       this._dialogSubmit(event);
-                               }
-                       }).bind(this);
+                       this._setTitle(this._blockquote);
+                       this._editor.caret.after(this._blockquote);
                        
-                       _wysiwygQuoteTitle.addEventListener('keyup', _keyupCallback);
-                       _wysiwygQuoteUrl.addEventListener('keyup', _keyupCallback);
+                       this._editor.events.startDetectChanges();
                        
-                       _wysiwygQuoteButton = elById('wysiwygQuoteSubmit');
-                       _wysiwygQuoteButton.addEventListener(WCF_CLICK_EVENT, this._dialogSubmit.bind(this));
+                       UiDialog.close(this);
                },
                
-               _dialogOnClose: function() {
-                       _element = null;
-                       _insertCallback = null;
+               /**
+                * Sets or updates the quote's header title.
+                * 
+                * @param       {Element}       blockquote     quote element
+                * @protected
+                */
+               _setTitle: function(blockquote) {
+                       var title = Language.get('wcf.editor.quote.title', {
+                               author: elData(blockquote, 'author'),
+                               url: elData(blockquote, 'url')
+                       });
+                       
+                       if (elData(blockquote, 'title') !== title) {
+                               elData(blockquote, 'title', title);
+                       }
                },
                
                _dialogSetup: function() {
+                       var id = 'redactor-quote-' + this._elementId,
+                           idAuthor = id + '-author',
+                           idButtonSave = id + '-button-save',
+                           idUrl = id + '-url';
+                       
                        return {
-                               id: 'wysiwygQuoteDialog',
+                               id: id,
                                options: {
-                                       onClose: this._dialogOnClose.bind(this),
-                                       onSetup: this._dialogOnSetup.bind(this)
+                                       onSetup: (function() {
+                                               elById(idButtonSave).addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
+                                       }).bind(this),
+                                       
+                                       onShow: (function() {
+                                               elById(idAuthor).value = elData(this._blockquote, 'author');
+                                               elById(idUrl).value = elData(this._blockquote, 'url');
+                                       }).bind(this),
+                                       
+                                       title: Language.get('wcf.editor.quote.edit')
                                },
-                               source: '<dl>'
-                                               + '<dt><label for="wysiwygQuoteTitle">' + Language.get('wcf.wysiwyg.quote.title') + '</label></dt>'
-                                               + '<dd><input type="text" id="wysiwygQuoteTitle" class="long"></dd>'
+                               source: '<div class="section">'
+                                       + '<dl>'
+                                               + '<dt><label for="' + idAuthor + '">' + Language.get('wcf.editor.quote.author') + '</label></dt>'
+                                               + '<dd>'
+                                                       + '<input type="text" id="' + idAuthor + '" class="long">'
+                                               + '</dd>'
                                        + '</dl>'
                                        + '<dl>'
-                                               + '<dt><label for="wysiwygQuoteUrl">' + Language.get('wcf.wysiwyg.quote.url') + '</label></dt>'
-                                               + '<dd><input type="text" id="wysiwygQuoteUrl" class="long"></dd>'
+                                               + '<dt><label for="' + idUrl + '">' + Language.get('wcf.editor.quote.url') + '</label></dt>'
+                                               + '<dd>'
+                                                       + '<input type="text" id="' + idUrl + '" class="long">'
+                                                       + '<small>' + Language.get('wcf.editor.quote.url.description') + '</small>'
+                                               + '</dd>'
                                        + '</dl>'
-                                       + '<div class="formSubmit">'
-                                               + '<button class="buttonPrimary" id="wysiwygQuoteSubmit"></button>'
-                                       + '</div>'
+                               + '</div>'
+                               + '<div class="formSubmit">'
+                                       + '<button id="' + idButtonSave + '" class="buttonPrimary">' + Language.get('wcf.global.button.save') + '</button>'
+                               + '</div>'
                        };
                }
        };
-});
+       
+       return UiRedactorQuote;
+});
\ No newline at end of file
index 871a69944ded419d717110da77b516b53a2c5dc9..cc537f81cbb45221dc761226cedd5a851fd1e3df 100644 (file)
@@ -1,3 +1,4 @@
+.redactor-editor blockquote,
 .quoteBox {
        border: 1px solid $wcfContentBorderInner;
        font-style: italic;
        }
 }
 
+.redactor-editor blockquote::before {
+       content: attr(data-title);
+       cursor: pointer;
+       display: block;
+       font-style: normal;
+       margin-bottom: 20px;
+       
+       @include wcfFontHeadline;
+}
+
 .quoteBoxHeader {
        align-items: center;
        display: flex;
index b17a341735f55052e9ad23852bfd301e5fdc3316..ba88eb5706747e192c3b77ac2a890fedac2c3748 100644 (file)
                }
        }
        
-       .quoteBox {
-               position: relative;
-               
-               &::before {
-                       content: attr(data-quote-header);
-                       cursor: pointer;
-                       display: block;
-                       font-style: normal;
-                       margin-bottom: 20px;
-                       padding-right: 25px;
-                       
-                       @include wcfFontHeadline;
-               }
-               
-               &::after {
-                       content: $fa-var-pencil;
-                       cursor: pointer;
-                       font-family: FontAwesome;
-                       position: absolute;
-                       right: 24px;
-                       top: 10px;
-                       
-                       @include wcfFontHeadline;
-               }
-       }
-       
        .TODO_codeBox {
                overflow: hidden;
                position: relative;
index abd8fb0e01c6477cd96fc20fe0ff2b2769c52bc2..24ff34d29b12f938dbd13f17af02a28f677ccf0b 100644 (file)
@@ -2152,6 +2152,12 @@ Fehler sind beispielsweise:
                <item name="wcf.editor.image.float.right"><![CDATA[Rechts]]></item>
                <item name="wcf.editor.image.source"><![CDATA[Quelle]]></item>
                <item name="wcf.editor.image.source.error.invalid"><![CDATA[Der eingegebene Link ist ungültig.]]></item>
+               
+               <item name="wcf.editor.quote.author"><![CDATA[Quelle]]></item>
+               <item name="wcf.editor.quote.edit"><![CDATA[Zitat bearbeiten]]></item>
+               <item name="wcf.editor.quote.title"><![CDATA[{if $author}Zitat von {$author}{else}Zitat{/if}]]></item>
+               <item name="wcf.editor.quote.url"><![CDATA[Link]]></item>
+               <item name="wcf.editor.quote.url.description"><![CDATA[Optional: Geben Sie einen Link zu der Quelle an.]]></item>
        </category>
        
        <category name="wcf.global">
index cda152f34a6d37014d441130228427c3e0abfa32..d913f3eb429d6445936d1a5e7cce568d8fd3dcf7 100644 (file)
@@ -2163,6 +2163,12 @@ Errors are:
                <item name="wcf.editor.image.float.right"><![CDATA[Right]]></item>
                <item name="wcf.editor.image.source"><![CDATA[Source]]></item>
                <item name="wcf.editor.image.source.error.invalid"><![CDATA[You have entered an invalid link.]]></item>
+               
+               <item name="wcf.editor.quote.author"><![CDATA[Source]]></item>
+               <item name="wcf.editor.quote.edit"><![CDATA[Edit Quote]]></item>
+               <item name="wcf.editor.quote.title"><![CDATA[{if $author}Quote from {$author}{else}Quote{/if}]]></item>
+               <item name="wcf.editor.quote.url"><![CDATA[Link]]></item>
+               <item name="wcf.editor.quote.url.description"><![CDATA[Optional: Specify the link to the source.]]></item>
        </category>
        
        <category name="wcf.global">