From a9b6cdfcb59f320eca5e7ccfa468fba7ed771c45 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Thu, 16 Jun 2016 11:40:49 +0200 Subject: [PATCH] Added support for spoilers in WYSIWYG --- com.woltlab.wcf/templates/wysiwyg.tpl | 15 +- .../redactor2/plugins/WoltLabBlock.js | 47 +++++ .../redactor2/plugins/WoltLabSpoiler.js | 11 ++ .../js/WoltLab/WCF/Ui/Redactor/Spoiler.js | 183 ++++++++++++++++++ .../install/files/style/bbcode/spoiler.scss | 21 ++ wcfsetup/install/lang/de.xml | 5 + wcfsetup/install/lang/en.xml | 5 + 7 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabBlock.js create mode 100644 wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSpoiler.js create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Spoiler.js diff --git a/com.woltlab.wcf/templates/wysiwyg.tpl b/com.woltlab.wcf/templates/wysiwyg.tpl index 065a7f06c3..26768abafc 100644 --- a/com.woltlab.wcf/templates/wysiwyg.tpl +++ b/com.woltlab.wcf/templates/wysiwyg.tpl @@ -19,6 +19,7 @@ {* WoltLab *} '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabAttachment.js?v={@LAST_UPDATE_TIME}', + '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabBlock.js?v={@LAST_UPDATE_TIME}', '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabButton.js?v={@LAST_UPDATE_TIME}', '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabCode.js?v={@LAST_UPDATE_TIME}', '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabColor.js?v={@LAST_UPDATE_TIME}', @@ -32,7 +33,8 @@ '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabModal.js?v={@LAST_UPDATE_TIME}', '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabQuote.js?v={@LAST_UPDATE_TIME}', '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabSize.js?v={@LAST_UPDATE_TIME}', - '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabSmiley.js?v={@LAST_UPDATE_TIME}' + '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabSmiley.js?v={@LAST_UPDATE_TIME}', + '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabSpoiler.js?v={@LAST_UPDATE_TIME}' {else} '{@$__wcf->getPath()}js/3rdParty/redactor2/redactor.min.js?v={@LAST_UPDATE_TIME}', '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/combined.min.js?v={@LAST_UPDATE_TIME}' @@ -64,7 +66,12 @@ '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}' + 'wcf.editor.quote.url.description': '{lang}wcf.editor.quote.url.description{/lang}', + + 'wcf.editor.spoiler.label': '{lang}wcf.editor.spoiler.label{/lang}', + 'wcf.editor.spoiler.label.description': '{lang}wcf.editor.spoiler.label.description{/lang}', + 'wcf.editor.spoiler.edit': '{lang}wcf.editor.spoiler.edit{/lang}', + 'wcf.editor.spoiler.title': '{lang __literal=true}wcf.editor.spoiler.title{/lang}' }); var buttons = [], buttonOptions = [], customButtons = []; @@ -92,6 +99,7 @@ 'source', 'table', 'WoltLabAttachment', + 'WoltLabBlock', 'WoltLabCode', 'WoltLabColor', 'WoltLabDropdown', @@ -102,7 +110,8 @@ 'WoltLabModal', 'WoltLabQuote', 'WoltLabSize', - 'WoltLabSmiley' + 'WoltLabSmiley', + 'WoltLabSpoiler' ], toolbarFixed: false, woltlab: { diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabBlock.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabBlock.js new file mode 100644 index 0000000000..f64d35a13e --- /dev/null +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabBlock.js @@ -0,0 +1,47 @@ +$.Redactor.prototype.WoltLabBlock = function() { + "use strict"; + + return { + init: function() { + this.block.tags = ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'figure']; + + this.block.format = (function(tag, attr, value, type) { + tag = (tag === 'quote') ? 'blockquote' : tag; + + // WoltLab modification: move list of allowed elements + // outside this method to allow extending it + // + //this.block.tags = ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'figure']; + if ($.inArray(tag, this.block.tags) === -1) + { + return; + } + + if (tag === 'p' && typeof attr === 'undefined') + { + // remove all + attr = 'class'; + } + + this.placeholder.hide(); + this.buffer.set(); + + return (this.utils.isCollapsed()) ? this.block.formatCollapsed(tag, attr, value, type) : this.block.formatUncollapsed(tag, attr, value, type); + }).bind(this); + }, + + register: function(tag) { + if (this.block.tags.indexOf(tag) !== -1) { + return; + } + + this.block.tags.push(tag); + + if (this.opts.blockTags.indexOf(tag) === -1) { + this.opts.blockTags.push(tag); + + this.reIsBlock = new RegExp('^(' + this.opts.blockTags.join('|').toUpperCase() + ')$', 'i'); + } + } + } +}; diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSpoiler.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSpoiler.js new file mode 100644 index 0000000000..a9295967f3 --- /dev/null +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabSpoiler.js @@ -0,0 +1,11 @@ +$.Redactor.prototype.WoltLabSpoiler = function() { + "use strict"; + + return { + init: function() { + require(['WoltLab/WCF/Ui/Redactor/Spoiler'], (function (UiRedactorSpoiler) { + new UiRedactorSpoiler(this); + }).bind(this)); + } + }; +}; diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Spoiler.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Spoiler.js new file mode 100644 index 0000000000..08c264863e --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Spoiler.js @@ -0,0 +1,183 @@ +/** + * Manages spoilers. + * + * @author Alexander Ebert + * @copyright 2001-2016 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLab/WCF/Ui/Redactor/Spoiler + */ +define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog) { + "use strict"; + + var _headerHeight = 0; + + /** + * @param {Object} editor editor instance + * @constructor + */ + function UiRedactorSpoiler(editor) { this.init(editor); } + UiRedactorSpoiler.prototype = { + /** + * Initializes the spoiler management. + * + * @param {Object} editor editor instance + */ + init: function(editor) { + this._editor = editor; + this._elementId = this._editor.$element[0].id; + this._spoiler = null; + + EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_spoiler_' + this._elementId, this._bbcodeSpoiler.bind(this)); + EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this)); + + // register custom block element + this._editor.WoltLabBlock.register('woltlab-spoiler'); + this._editor.block.tags.push('woltlab-spoiler'); + + // support for active button marking + this._editor.opts.activeButtonsStates['woltlab-spoiler'] = 'woltlabSpoiler'; + + // static bind to ensure that removing works + this._callbackEdit = this._edit.bind(this); + + // bind listeners on init + this._observeLoad(); + }, + + /** + * Intercepts the insertion of `[spoiler]` tags and uses + * the custom `` element instead. + * + * @param {Object} data event data + * @protected + */ + _bbcodeSpoiler: function(data) { + data.cancel = true; + + this._editor.button.toggle({}, 'woltlab-spoiler', 'func', 'block.format'); + + var spoiler = this._editor.selection.block(); + if (spoiler && spoiler.nodeName === 'WOLTLAB-SPOILER') { + this._setTitle(spoiler); + + spoiler.addEventListener(WCF_CLICK_EVENT, this._callbackEdit); + } + }, + + /** + * Binds event listeners and sets quote title on both editor + * initialization and when switching back from code view. + * + * @protected + */ + _observeLoad: function() { + this._editor.events.stopDetectChanges(); + + elBySelAll('woltlab-spoiler', this._editor.$editor[0], (function(spoiler) { + spoiler.addEventListener(WCF_CLICK_EVENT, this._callbackEdit); + this._setTitle(spoiler); + }).bind(this)); + + this._editor.events.startDetectChanges(); + }, + + /** + * Opens the dialog overlay to edit the spoiler's properties. + * + * @param {Event} event event object + * @protected + */ + _edit: function(event) { + var spoiler = event.currentTarget; + + if (_headerHeight === 0) { + _headerHeight = ~~window.getComputedStyle(spoiler).paddingTop.replace(/px$/, ''); + + var styles = window.getComputedStyle(spoiler, '::before'); + _headerHeight += ~~styles.paddingTop.replace(/px$/, ''); + _headerHeight += ~~styles.height.replace(/px$/, ''); + _headerHeight += ~~styles.paddingBottom.replace(/px$/, ''); + } + + // check if the click hit the header + var offset = DomUtil.offset(spoiler); + if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) { + event.preventDefault(); + + this._spoiler = spoiler; + + UiDialog.open(this); + } + }, + + /** + * Saves the changes to the spoiler's properties. + * + * @param {Event} event event object + * @protected + */ + _save: function(event) { + event.preventDefault(); + + this._editor.events.stopDetectChanges(); + + elData(this._spoiler, 'label', elById('redactor-spoiler-' + this._elementId + '-label').value); + + this._setTitle(this._spoiler); + this._editor.caret.after(this._spoiler); + + this._editor.events.startDetectChanges(); + + UiDialog.close(this); + }, + + /** + * Sets or updates the spoiler's header title. + * + * @param {Element} spoiler spoiler element + * @protected + */ + _setTitle: function(spoiler) { + var title = Language.get('wcf.editor.spoiler.title', { label: elData(spoiler, 'label') }); + + if (elData(spoiler, 'title') !== title) { + elData(spoiler, 'title', title); + } + }, + + _dialogSetup: function() { + var id = 'redactor-spoiler-' + this._elementId, + idButtonSave = id + '-button-save', + idLabel = id + '-label'; + + return { + id: id, + options: { + onSetup: (function() { + elById(idButtonSave).addEventListener(WCF_CLICK_EVENT, this._save.bind(this)); + }).bind(this), + + onShow: (function() { + elById(idLabel).value = elData(this._spoiler, 'label'); + }).bind(this), + + title: Language.get('wcf.editor.spoiler.edit') + }, + source: '
' + + '
' + + '
' + + '
' + + '' + + '' + Language.get('wcf.editor.spoiler.label.description') + '' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + }; + } + }; + + return UiRedactorSpoiler; +}); \ No newline at end of file diff --git a/wcfsetup/install/files/style/bbcode/spoiler.scss b/wcfsetup/install/files/style/bbcode/spoiler.scss index dcf26b8221..6da045d143 100644 --- a/wcfsetup/install/files/style/bbcode/spoiler.scss +++ b/wcfsetup/install/files/style/bbcode/spoiler.scss @@ -1,3 +1,24 @@ +.redactor-editor woltlab-spoiler { + background-color: rgb(255, 255, 255) !important; + border: 1px solid rgb(196, 196, 196) !important; + border-radius: 2px; + display: block; + margin: 1em 0; + padding: 10px 20px; + position: relative; + white-space: pre; + + &::before { + content: attr(data-title); + cursor: pointer; + display: block; + margin-bottom: 20px; + + @include wcfFontHeadline; + } +} + + .spoilerBox > div { border-left: 5px solid $wcfContentBorderInner; margin-top: 10px; diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index ecb42b8c2d..06137a2886 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -2157,6 +2157,11 @@ Fehler sind beispielsweise: + + + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 9f7dfecdb1..f6c0fba2c2 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -2168,6 +2168,11 @@ Errors are: + + + + + -- 2.20.1