From 7b83aa87865486836bbb5e77a8137084afb8427d Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 14 Jun 2016 21:57:40 +0200 Subject: [PATCH] Overhauled code implementation for Redactor --- com.woltlab.wcf/templates/wysiwyg.tpl | 13 +- .../3rdParty/redactor2/plugins/WoltLabCode.js | 21 +- .../install/files/js/WoltLab/WCF/Event/Key.js | 10 + .../files/js/WoltLab/WCF/Ui/Redactor/Code.js | 243 ++++++++++++++++++ wcfsetup/install/files/style/bbcode/code.scss | 1 + .../install/files/style/layout/global.scss | 4 + wcfsetup/install/lang/de.xml | 52 ++-- wcfsetup/install/lang/en.xml | 52 ++-- 8 files changed, 324 insertions(+), 72 deletions(-) create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Code.js diff --git a/com.woltlab.wcf/templates/wysiwyg.tpl b/com.woltlab.wcf/templates/wysiwyg.tpl index 52bc64755c..d27fdea03c 100644 --- a/com.woltlab.wcf/templates/wysiwyg.tpl +++ b/com.woltlab.wcf/templates/wysiwyg.tpl @@ -40,6 +40,14 @@ ], function () { require(['Language', 'WoltLab/WCF/Ui/Redactor/Metacode'], function(Language, UiRedactorMetacode) { Language.addObject({ + 'wcf.editor.code.edit': '{lang}wcf.editor.code.edit{/lang}', + 'wcf.editor.code.file': '{lang}wcf.editor.code.file{/lang}', + 'wcf.editor.code.file.description': '{lang}wcf.editor.code.file.description{/lang}', + 'wcf.editor.code.highlighter': '{lang}wcf.editor.code.highlighter{/lang}', + 'wcf.editor.code.highlighter.description': '{lang}wcf.editor.code.highlighter.description{/lang}', + '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}', @@ -54,6 +62,8 @@ var buttons = [], buttonOptions = [], customButtons = []; {include file='wysiwygToolbar'} + var highlighters = { {implode from=$__wcf->getBBCodeHandler()->getHighlighters() item=__highlighter}'{$__highlighter}': '{lang}wcf.bbcode.code.{@$__highlighter}.title{/lang}'{/implode} }; + // TODO: Should the media stuff be here? {include file='mediaJavaScript'} @@ -90,7 +100,8 @@ woltlab: { autosave: autosave, buttons: buttonOptions, - customButtons: customButtons + customButtons: customButtons, + highlighters: highlighters } }; diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js index a39af3d664..63d1a2e37f 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabCode.js @@ -3,25 +3,8 @@ $.Redactor.prototype.WoltLabCode = function() { return { init: function() { - this.opts.activeButtonsStates.pre = 'code'; - - require(['EventHandler'], (function (EventHandler) { - EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_code_' + this.$element[0].id, (function(data) { - data.cancel = true; - - this.button.toggle({}, 'pre', 'func', 'block.format'); - - var pre = this.selection.block(); - if (pre && pre.nodeName === 'PRE') { - if (pre.textContent === '') { - pre.textContent = '\u200B'; - } - - if (elData(pre, 'display-value') === '') { - elData(pre, 'display-value', 'TODO: source code'); - } - } - }).bind(this)); + require(['WoltLab/WCF/Ui/Redactor/Code'], (function (UiRedactorCode) { + new UiRedactorCode(this); }).bind(this)); } }; diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Event/Key.js b/wcfsetup/install/files/js/WoltLab/WCF/Event/Key.js index 3467e48e29..a35a5da746 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/Event/Key.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/Event/Key.js @@ -80,6 +80,16 @@ define([], function() { */ Escape: function(event) { return _isKey(event, 'Escape', 27); + }, + + /** + * Returns true if pressed key equals 'Tab'. + * + * @param {Event} event event object + * @return {boolean} + */ + Tab: function(event) { + return _isKey(event, 'Tab', 9); } }; }); diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Code.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Code.js new file mode 100644 index 0000000000..ebdf43455e --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Code.js @@ -0,0 +1,243 @@ +/** + * Provides helper functions to work with DOM nodes. + * + * @author Alexander Ebert + * @copyright 2001-2016 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLab/WCF/Ui/Redactor/Code + */ +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 UiRedactorCode(editor) { this.init(editor); } + UiRedactorCode.prototype = { + /** + * Initializes the source code management. + * + * @param {Object} editor editor instance + */ + init: function(editor) { + this._editor = editor; + this._elementId = this._editor.$element[0].id; + this._pre = null; + + EventHandler.add('com.woltlab.wcf.redactor2', 'bbcode_code_' + this._elementId, this._bbcodeCode.bind(this)); + EventHandler.add('com.woltlab.wcf.redactor2', 'observe_load_' + this._elementId, this._observeLoad.bind(this)); + + // support for active button marking + this._editor.opts.activeButtonsStates.pre = 'code'; + + // static bind to ensure that removing works + this._callbackEdit = this._edit.bind(this); + + // bind listeners on init + this._observeLoad(); + }, + + /** + * Intercepts the insertion of `[code]` tags and uses a native `
` instead.
+		 * 
+		 * @param       {Object}        data    event data
+		 * @protected
+		 */
+		_bbcodeCode: function(data) {
+			data.cancel = true;
+			
+			this._editor.button.toggle({}, 'pre', 'func', 'block.format');
+			
+			var pre = this._editor.selection.block();
+			if (pre && pre.nodeName === 'PRE') {
+				if (pre.textContent === '') {
+					pre.textContent = '\u200B';
+				}
+				
+				this._setTitle(pre);
+				
+				pre.removeEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+				pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+				
+				// there must be some kind of element after the 
+				if (pre.nextElementSibling === null) {
+					var p = elCreate('p');
+					p.textContent = '\u200B';
+					pre.parentNode.appendChild(p);
+				}
+				
+				this._editor.caret.after(pre);
+			}
+		},
+		
+		/**
+		 * Binds event listeners and sets quote title on both editor
+		 * initialization and when switching back from code view.
+		 * 
+		 * @protected
+		 */
+		_observeLoad: function() {
+			elBySelAll('pre', this._editor.$editor[0], (function(pre) {
+				pre.addEventListener(WCF_CLICK_EVENT, this._callbackEdit);
+				this._setTitle(pre);
+			}).bind(this));
+		},
+		
+		/**
+		 * Opens the dialog overlay to edit the code's properties.
+		 * 
+		 * @param       {Event}         event           event object
+		 * @protected
+		 */
+		_edit: function(event) {
+			var pre = event.currentTarget;
+			
+			if (_headerHeight === 0) {
+				_headerHeight = ~~window.getComputedStyle(pre).paddingTop.replace(/px$/, '');
+				
+				var styles = window.getComputedStyle(pre, '::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(pre);
+			if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
+				event.preventDefault();
+				
+				this._pre = pre;
+				
+				UiDialog.open(this);
+			}
+		},
+		
+		/**
+		 * Saves the changes to the code's properties.
+		 * 
+		 * @param       {Event}         event           event object
+		 * @protected
+		 */
+		_save: function(event) {
+			event.preventDefault();
+			
+			var id = 'redactor-code-' + this._elementId;
+			
+			['file', 'highlighter', 'line'].forEach((function (attr) {
+				elData(this._pre, attr, elById(id + '-' + attr).value);
+			}).bind(this));
+			
+			this._setTitle(this._pre);
+			this._editor.caret.after(this._pre);
+			
+			UiDialog.close(this);
+		},
+		
+		/**
+		 * Sets or updates the code's header title.
+		 * 
+		 * @param       {Element}       pre     code element
+		 * @protected
+		 */
+		_setTitle: function(pre) {
+			var file = elData(pre, 'file'),
+			    highlighter = elData(pre, 'highlighter');
+			
+			//noinspection JSUnresolvedVariable
+			highlighter = (this._editor.opts.woltlab.highlighters.hasOwnProperty(highlighter)) ? this._editor.opts.woltlab.highlighters[highlighter] : '';
+			
+			elData(pre, 'display-value', Language.get('wcf.editor.code.title', {
+				file: file,
+				highlighter: highlighter
+			}));
+		},
+		
+		_dialogSetup: function() {
+			var id = 'redactor-code-' + this._elementId,
+			    idButtonSave = id + '-button-save',
+			    idFile = id + '-file',
+			    idHighlighter = id + '-highlighter',
+			    idLine = id + '-line';
+			
+			return {
+				id: id,
+				options: {
+					onSetup: (function() {
+						elById(idButtonSave).addEventListener(WCF_CLICK_EVENT, this._save.bind(this));
+						
+						// set highlighters
+						var highlighters = '';
+						
+						var value, values = [];
+						//noinspection JSUnresolvedVariable
+						for (var highlighter in this._editor.opts.woltlab.highlighters) {
+							//noinspection JSUnresolvedVariable
+							if (this._editor.opts.woltlab.highlighters.hasOwnProperty(highlighter)) {
+								//noinspection JSUnresolvedVariable
+								values.push([highlighter, this._editor.opts.woltlab.highlighters[highlighter]]);
+							}
+						}
+						
+						// sort by label
+						values.sort(function(a, b) {
+							if (a[1] < b[1]) {
+								return  -1;
+							}
+							else if (a[1] > b[1]) {
+								return 1;
+							}
+							
+							return 0;
+						});
+						
+						values.forEach((function(value) {
+							highlighters += '';
+						}).bind(this));
+						
+						elById(idHighlighter).innerHTML = highlighters;
+					}).bind(this),
+					
+					onShow: (function() {
+						elById(idHighlighter).value = elData(this._pre, 'highlighter');
+						var line = elData(this._pre, 'line');
+						elById(idLine).value = (line === '') ? 1 : ~~line;
+						elById(idFile).value = elData(this._pre, 'file');
+					}).bind(this),
+					
+					title: Language.get('wcf.editor.code.edit')
+				},
+				source: '
' + + '
' + + '
' + + '
' + + '' + + '' + Language.get('wcf.editor.code.highlighter.description') + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + Language.get('wcf.editor.code.line.description') + '' + + '
' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + Language.get('wcf.editor.code.file.description') + '' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + }; + } + }; + + return UiRedactorCode; +}); \ No newline at end of file diff --git a/wcfsetup/install/files/style/bbcode/code.scss b/wcfsetup/install/files/style/bbcode/code.scss index 92e47e73a0..eedcfa9d83 100644 --- a/wcfsetup/install/files/style/bbcode/code.scss +++ b/wcfsetup/install/files/style/bbcode/code.scss @@ -11,6 +11,7 @@ &::before { content: attr(data-display-value); + cursor: pointer; display: block; font-family: $wcfFontFamily; margin-bottom: 20px; diff --git a/wcfsetup/install/files/style/layout/global.scss b/wcfsetup/install/files/style/layout/global.scss index 58c3807523..c1ba6fc38d 100644 --- a/wcfsetup/install/files/style/layout/global.scss +++ b/wcfsetup/install/files/style/layout/global.scss @@ -27,6 +27,10 @@ -webkit-filter: grayscale(1); } +.monospace { + font-family: Consolas, 'Courier New', monospace !important; +} + /* boxes with an image */ .box16 { @include box(16px, 5px); } .box24 { @include box(24px, 8px); } diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index e2ea991c07..b3ee9edaa9 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -1776,34 +1776,24 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - + + + + @@ -2127,6 +2117,7 @@ Fehler sind beispielsweise: + @@ -2142,6 +2133,15 @@ Fehler sind beispielsweise: + + + + + + + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index d9536e50b5..2bc5cc9b10 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -1783,34 +1783,24 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + @@ -2138,6 +2128,7 @@ Errors are: + @@ -2153,6 +2144,15 @@ Errors are: + + + + + + + + + -- 2.20.1