From: Alexander Ebert Date: Wed, 4 Nov 2020 12:06:47 +0000 (+0100) Subject: Convert `Ui/Redactor/Code` to TypeScript X-Git-Tag: 5.4.0_Alpha_1~636^2~17 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=4dd8e8cfda3083e230a64835bdfc0ce0d7cd3f01;p=GitHub%2FWoltLab%2FWCF.git Convert `Ui/Redactor/Code` to TypeScript --- diff --git a/package-lock.json b/package-lock.json index 922feececf..4b08d05163 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,12 @@ "integrity": "sha512-KJPg2vGt1l03VaDK1SPKDcm/I5RVexO5Jyo/kGPlaS7SqVOkY83O3f1iyff981UnSzbF3Tg1Zw0r6vX6vB6JxA==", "dev": true }, + "@types/prismjs": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.16.2.tgz", + "integrity": "sha512-1M/j21xgTde7RPtpJVQebW5rzrquj7S+wnqt4x9uWrIPpr0Ya/uXypcqC2aUQL5gtLXFCKSH7GnjfAijMdfbuA==", + "dev": true + }, "@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", diff --git a/package.json b/package.json index cae4a51704..a141704ed3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@types/facebook-js-sdk": "^3.3.1", "@types/jquery": "^3.5.4", "@types/pica": "^5.1.2", + "@types/prismjs": "^1.16.2", "@typescript-eslint/eslint-plugin": "^4.6.0", "@typescript-eslint/parser": "^4.6.0", "@woltlab/zxcvbn": "git+https://github.com/WoltLab/zxcvbn.git#master", diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Code.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Code.js index dd8038f2e5..11588f12bf 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Code.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Redactor/Code.js @@ -2,176 +2,157 @@ * Manages code blocks. * * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH + * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * @module WoltLabSuite/Core/Ui/Redactor/Code */ -define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader', 'prism/prism-meta'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader, PrismMeta) { +define(["require", "exports", "tslib", "../../Core", "../../Dom/Util", "../../Event/Handler", "../../Language", "../../StringUtil", "../Dialog", "./PseudoHeader"], function (require, exports, tslib_1, Core, Util_1, EventHandler, Language, StringUtil, Dialog_1, UiRedactorPseudoHeader) { "use strict"; - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function () { }; - Fake.prototype = { - init: function () { }, - _bbcodeCode: function () { }, - _observeLoad: function () { }, - _edit: function () { }, - _setTitle: function () { }, - _delete: function () { }, - _dialogSetup: function () { }, - _dialogSubmit: function () { } - }; - return Fake; - } - var _headerHeight = 0; - /** - * @param {Object} editor editor instance - * @constructor - */ - function UiRedactorCode(editor) { this.init(editor); } - UiRedactorCode.prototype = { + Core = tslib_1.__importStar(Core); + Util_1 = tslib_1.__importDefault(Util_1); + EventHandler = tslib_1.__importStar(EventHandler); + Language = tslib_1.__importStar(Language); + StringUtil = tslib_1.__importStar(StringUtil); + Dialog_1 = tslib_1.__importDefault(Dialog_1); + UiRedactorPseudoHeader = tslib_1.__importStar(UiRedactorPseudoHeader); + let _headerHeight = 0; + class UiRedactorCode { /** * Initializes the source code management. - * - * @param {Object} editor editor instance */ - init: function (editor) { + constructor(editor) { + this._pre = null; 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)); + EventHandler.add("com.woltlab.wcf.redactor2", `bbcode_code_${this._elementId}`, (data) => this._bbcodeCode(data)); + EventHandler.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad()); // support for active button marking - this._editor.opts.activeButtonsStates.pre = 'code'; + 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) {
+        _bbcodeCode(data) {
             data.cancel = true;
-            var pre = this._editor.selection.block();
-            if (pre && pre.nodeName === 'PRE' && pre.classList.contains('woltlabHtml')) {
+            let pre = this._editor.selection.block();
+            if (pre && pre.nodeName === "PRE" && pre.classList.contains("woltlabHtml")) {
                 return;
             }
-            this._editor.button.toggle({}, 'pre', 'func', 'block.format');
+            this._editor.button.toggle({}, "pre", "func", "block.format");
             pre = this._editor.selection.block();
-            if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
-                if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
+            if (pre && pre.nodeName === "PRE" && !pre.classList.contains("woltlabHtml")) {
+                if (pre.childElementCount === 1 && pre.children[0].nodeName === "BR") {
                     // drop superfluous linebreak
                     pre.removeChild(pre.children[0]);
                 }
                 this._setTitle(pre);
-                pre.addEventListener('click', this._callbackEdit);
+                pre.addEventListener("click", this._callbackEdit);
                 // work-around for Safari
                 this._editor.caret.end(pre);
             }
-        },
+        }
         /**
          * Binds event listeners and sets quote title on both editor
          * initialization and when switching back from code view.
-         *
-         * @protected
          */
-        _observeLoad: function () {
-            elBySelAll('pre:not(.woltlabHtml)', this._editor.$editor[0], (function (pre) {
-                pre.addEventListener('mousedown', this._callbackEdit);
+        _observeLoad() {
+            this._editor.$editor[0].querySelectorAll("pre:not(.woltlabHtml)").forEach((pre) => {
+                pre.addEventListener("mousedown", 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;
+        _edit(event) {
+            const pre = event.currentTarget;
             if (_headerHeight === 0) {
                 _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
             }
             // check if the click hit the header
-            var offset = DomUtil.offset(pre);
-            if (event.pageY > offset.top && event.pageY < (offset.top + _headerHeight)) {
+            const offset = Util_1.default.offset(pre);
+            if (event.pageY > offset.top && event.pageY < offset.top + _headerHeight) {
                 event.preventDefault();
                 this._editor.selection.save();
                 this._pre = pre;
-                UiDialog.open(this);
+                Dialog_1.default.open(this);
             }
-        },
+        }
         /**
          * Saves the changes to the code's properties.
-         *
-         * @protected
          */
-        _dialogSubmit: function () {
-            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);
-        },
+        _dialogSubmit() {
+            const id = "redactor-code-" + this._elementId;
+            const pre = this._pre;
+            ["file", "highlighter", "line"].forEach((attr) => {
+                const input = document.getElementById(`${id}-${attr}`);
+                pre.dataset[attr] = input.value;
+            });
+            this._setTitle(pre);
+            this._editor.caret.after(pre);
+            Dialog_1.default.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.indexOf(highlighter) !== -1) ? PrismMeta[highlighter].title : '';
-            var title = Language.get('wcf.editor.code.title', {
-                file: file,
-                highlighter: highlighter
+        _setTitle(pre) {
+            const file = pre.dataset.file;
+            let highlighter = pre.dataset.highlighter;
+            highlighter =
+                this._editor.opts.woltlab.highlighters.indexOf(highlighter) !== -1 ? PrismMeta[highlighter].title : "";
+            const title = Language.get("wcf.editor.code.title", {
+                file,
+                highlighter,
             });
-            if (elData(pre, 'title') !== title) {
-                elData(pre, 'title', title);
+            if (pre.dataset.title !== title) {
+                pre.dataset.title = title;
             }
-        },
-        _delete: function (event) {
+        }
+        _delete(event) {
             event.preventDefault();
-            var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
-            if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
-                caretEnd = this._pre.parentNode;
+            const pre = this._pre;
+            let caretEnd = pre.nextElementSibling || pre.previousElementSibling;
+            if (caretEnd === null && pre.parentElement !== this._editor.core.editor()[0]) {
+                caretEnd = pre.parentElement;
             }
             if (caretEnd === null) {
-                this._editor.code.set('');
+                this._editor.code.set("");
                 this._editor.focus.end();
             }
             else {
-                elRemove(this._pre);
+                pre.remove();
                 this._editor.caret.end(caretEnd);
             }
-            UiDialog.close(this);
-        },
-        _dialogSetup: function () {
-            var id = 'redactor-code-' + this._elementId, idButtonDelete = id + '-button-delete', idButtonSave = id + '-button-save', idFile = id + '-file', idHighlighter = id + '-highlighter', idLine = id + '-line';
+            Dialog_1.default.close(this);
+        }
+        _dialogSetup() {
+            const id = `redactor-code-${this._elementId}`;
+            const idButtonDelete = `${id}-button-delete`;
+            const idButtonSave = `${id}-button-save`;
+            const idFile = `${id}-file`;
+            const idHighlighter = `${id}-highlighter`;
+            const idLine = `${id}-line`;
             return {
                 id: id,
                 options: {
-                    onClose: (function () {
+                    onClose: () => {
                         this._editor.selection.restore();
-                        UiDialog.destroy(this);
-                    }).bind(this),
-                    onSetup: (function () {
-                        elById(idButtonDelete).addEventListener('click', this._delete.bind(this));
+                        Dialog_1.default.destroy(this);
+                    },
+                    onSetup: () => {
+                        document.getElementById(idButtonDelete).addEventListener("click", (ev) => this._delete(ev));
                         // set highlighters
-                        var highlighters = '';
-                        highlighters += '';
-                        //noinspection JSUnresolvedVariable
-                        var values = this._editor.opts.woltlab.highlighters.map(function (highlighter) {
+                        let highlighters = `
+            `;
+                        const values = this._editor.opts.woltlab.highlighters.map((highlighter) => {
                             return [highlighter, PrismMeta[highlighter].title];
                         });
                         // sort by label
-                        values.sort(function (a, b) {
+                        values.sort((a, b) => {
                             if (a[1] < b[1]) {
                                 return -1;
                             }
@@ -180,48 +161,59 @@ define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Di
                             }
                             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')
+                        values.forEach((value) => {
+                            highlighters += ``;
+                        });
+                        document.getElementById(idHighlighter).innerHTML = highlighters;
+                    },
+                    onShow: () => {
+                        const pre = this._pre;
+                        const highlighter = document.getElementById(idHighlighter);
+                        highlighter.value = pre.dataset.highlighter;
+                        const line = ~~(pre.dataset.line || 1);
+                        const lineInput = document.getElementById(idLine);
+                        lineInput.value = line.toString();
+                        const filename = document.getElementById(idFile);
+                        filename.value = pre.dataset.file;
+                    },
+                    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') + '' - + '
' - + '
' - + '
' - + '
' - + '' - + '' - + '
' + source: `
+
+
+ +
+
+ + ${Language.get("wcf.editor.code.highlighter.description")} +
+
+
+
+ +
+
+ + ${Language.get("wcf.editor.code.line.description")} +
+
+
+
+ +
+
+ + ${Language.get("wcf.editor.code.file.description")} +
+
+
+
+ + +
`, }; } - }; + } + Core.enableLegacyInheritance(UiRedactorCode); return UiRedactorCode; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.js deleted file mode 100644 index 2799276690..0000000000 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Manages code blocks. - * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Redactor/Code - */ -define(['EventHandler', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog', './PseudoHeader', 'prism/prism-meta'], function (EventHandler, EventKey, Language, StringUtil, DomUtil, UiDialog, UiRedactorPseudoHeader, PrismMeta) { - "use strict"; - - if (!COMPILER_TARGET_DEFAULT) { - var Fake = function() {}; - Fake.prototype = { - init: function() {}, - _bbcodeCode: function() {}, - _observeLoad: function() {}, - _edit: function() {}, - _setTitle: function() {}, - _delete: function() {}, - _dialogSetup: function() {}, - _dialogSubmit: function() {} - }; - return Fake; - } - - 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;
-			
-			var pre = this._editor.selection.block();
-			if (pre && pre.nodeName === 'PRE' && pre.classList.contains('woltlabHtml')) {
-				return;
-			}
-			
-			this._editor.button.toggle({}, 'pre', 'func', 'block.format');
-			
-			pre = this._editor.selection.block();
-			if (pre && pre.nodeName === 'PRE' && !pre.classList.contains('woltlabHtml')) {
-				if (pre.childElementCount === 1 && pre.children[0].nodeName === 'BR') {
-					// drop superfluous linebreak
-					pre.removeChild(pre.children[0]);
-				}
-				
-				this._setTitle(pre);
-				
-				pre.addEventListener('click', this._callbackEdit);
-				
-				// work-around for Safari
-				this._editor.caret.end(pre);
-			}
-		},
-		
-		/**
-		 * Binds event listeners and sets quote title on both editor
-		 * initialization and when switching back from code view.
-		 * 
-		 * @protected
-		 */
-		_observeLoad: function() {
-			elBySelAll('pre:not(.woltlabHtml)', this._editor.$editor[0], (function(pre) {
-				pre.addEventListener('mousedown', 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 = UiRedactorPseudoHeader.getHeight(pre);
-			}
-			
-			// 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._editor.selection.save();
-				this._pre = pre;
-				
-				UiDialog.open(this);
-			}
-		},
-		
-		/**
-		 * Saves the changes to the code's properties.
-		 * 
-		 * @protected
-		 */
-		_dialogSubmit: function() {
-			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.indexOf(highlighter) !== -1) ? PrismMeta[highlighter].title : '';
-			
-			var title = Language.get('wcf.editor.code.title', {
-				file: file,
-				highlighter: highlighter
-			});
-			
-			if (elData(pre, 'title') !== title) {
-				elData(pre, 'title', title);
-			}
-		},
-		
-		_delete: function (event) {
-			event.preventDefault();
-			
-			var caretEnd = this._pre.nextElementSibling || this._pre.previousElementSibling;
-			if (caretEnd === null && this._pre.parentNode !== this._editor.core.editor()[0]) {
-				caretEnd = this._pre.parentNode;
-			}
-			
-			if (caretEnd === null) {
-				this._editor.code.set('');
-				this._editor.focus.end();
-			}
-			else {
-				elRemove(this._pre);
-				this._editor.caret.end(caretEnd);
-			}
-			
-			UiDialog.close(this);
-		},
-		
-		_dialogSetup: function() {
-			var id = 'redactor-code-' + this._elementId,
-			    idButtonDelete = id + '-button-delete',
-			    idButtonSave = id + '-button-save',
-			    idFile = id + '-file',
-			    idHighlighter = id + '-highlighter',
-			    idLine = id + '-line';
-			
-			return {
-				id: id,
-				options: {
-					onClose: (function () {
-						this._editor.selection.restore();
-						
-						UiDialog.destroy(this);
-					}).bind(this),
-					
-					onSetup: (function() {
-						elById(idButtonDelete).addEventListener('click', this._delete.bind(this));
-						
-						// set highlighters
-						var highlighters = '';
-						highlighters += '';
-						
-						//noinspection JSUnresolvedVariable
-						var values = this._editor.opts.woltlab.highlighters.map(function (highlighter) {
-							return [highlighter, PrismMeta[highlighter].title];
-						});
-						
-						// 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; -}); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.ts new file mode 100644 index 0000000000..5b3f807546 --- /dev/null +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Code.ts @@ -0,0 +1,288 @@ +/** + * Manages code blocks. + * + * @author Alexander Ebert + * @copyright 2001-2019 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Redactor/Code + */ + +import * as Core from "../../Core"; +import DomUtil from "../../Dom/Util"; +import * as EventHandler from "../../Event/Handler"; +import * as Language from "../../Language"; +import * as StringUtil from "../../StringUtil"; +import UiDialog from "../Dialog"; +import { DialogCallbackObject, DialogCallbackSetup } from "../Dialog/Data"; +import { RedactorEditor } from "./Editor"; +import * as UiRedactorPseudoHeader from "./PseudoHeader"; + +// TODO: "prism/prism-meta" is missing -- START + +interface PrismLang { + title: string; + file: string; +} + +type Identifier = string; + +type PrismMetaData = Record; + +declare const PrismMeta: PrismMetaData; + +// TODO: "prism/prism-meta" is missing -- END + +type Highlighter = [string, string]; + +let _headerHeight = 0; + +class UiRedactorCode implements DialogCallbackObject { + protected readonly _callbackEdit: (ev: MouseEvent) => void; + protected readonly _editor: RedactorEditor; + protected readonly _elementId: string; + protected _pre: HTMLElement | null = null; + + /** + * Initializes the source code management. + */ + constructor(editor: RedactorEditor) { + this._editor = editor; + this._elementId = this._editor.$element[0].id; + + EventHandler.add("com.woltlab.wcf.redactor2", `bbcode_code_${this._elementId}`, (data) => this._bbcodeCode(data)); + EventHandler.add("com.woltlab.wcf.redactor2", `observe_load_${this._elementId}`, () => this._observeLoad()); + + // 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.
+   */
+  protected _bbcodeCode(data: { cancel: boolean }): void {
+    data.cancel = true;
+
+    let pre = this._editor.selection.block();
+    if (pre && pre.nodeName === "PRE" && pre.classList.contains("woltlabHtml")) {
+      return;
+    }
+
+    this._editor.button.toggle({}, "pre", "func", "block.format");
+
+    pre = this._editor.selection.block();
+    if (pre && pre.nodeName === "PRE" && !pre.classList.contains("woltlabHtml")) {
+      if (pre.childElementCount === 1 && pre.children[0].nodeName === "BR") {
+        // drop superfluous linebreak
+        pre.removeChild(pre.children[0]);
+      }
+
+      this._setTitle(pre);
+
+      pre.addEventListener("click", this._callbackEdit);
+
+      // work-around for Safari
+      this._editor.caret.end(pre);
+    }
+  }
+
+  /**
+   * Binds event listeners and sets quote title on both editor
+   * initialization and when switching back from code view.
+   */
+  protected _observeLoad(): void {
+    this._editor.$editor[0].querySelectorAll("pre:not(.woltlabHtml)").forEach((pre: HTMLElement) => {
+      pre.addEventListener("mousedown", this._callbackEdit);
+      this._setTitle(pre);
+    });
+  }
+
+  /**
+   * Opens the dialog overlay to edit the code's properties.
+   */
+  protected _edit(event: MouseEvent): void {
+    const pre = event.currentTarget as HTMLPreElement;
+
+    if (_headerHeight === 0) {
+      _headerHeight = UiRedactorPseudoHeader.getHeight(pre);
+    }
+
+    // check if the click hit the header
+    const offset = DomUtil.offset(pre);
+    if (event.pageY > offset.top && event.pageY < offset.top + _headerHeight) {
+      event.preventDefault();
+
+      this._editor.selection.save();
+      this._pre = pre;
+
+      UiDialog.open(this);
+    }
+  }
+
+  /**
+   * Saves the changes to the code's properties.
+   */
+  _dialogSubmit(): void {
+    const id = "redactor-code-" + this._elementId;
+    const pre = this._pre!;
+
+    ["file", "highlighter", "line"].forEach((attr) => {
+      const input = document.getElementById(`${id}-${attr}`) as HTMLInputElement;
+      pre.dataset[attr] = input.value;
+    });
+
+    this._setTitle(pre);
+    this._editor.caret.after(pre);
+
+    UiDialog.close(this);
+  }
+
+  /**
+   * Sets or updates the code's header title.
+   */
+  protected _setTitle(pre: HTMLElement): void {
+    const file = pre.dataset.file!;
+    let highlighter = pre.dataset.highlighter!;
+
+    highlighter =
+      this._editor.opts.woltlab.highlighters.indexOf(highlighter) !== -1 ? PrismMeta[highlighter].title : "";
+
+    const title = Language.get("wcf.editor.code.title", {
+      file,
+      highlighter,
+    });
+
+    if (pre.dataset.title !== title) {
+      pre.dataset.title = title;
+    }
+  }
+
+  protected _delete(event: MouseEvent): void {
+    event.preventDefault();
+
+    const pre = this._pre!;
+    let caretEnd = pre.nextElementSibling || pre.previousElementSibling;
+    if (caretEnd === null && pre.parentElement !== this._editor.core.editor()[0]) {
+      caretEnd = pre.parentElement;
+    }
+
+    if (caretEnd === null) {
+      this._editor.code.set("");
+      this._editor.focus.end();
+    } else {
+      pre.remove();
+      this._editor.caret.end(caretEnd);
+    }
+
+    UiDialog.close(this);
+  }
+
+  _dialogSetup(): ReturnType {
+    const id = `redactor-code-${this._elementId}`;
+    const idButtonDelete = `${id}-button-delete`;
+    const idButtonSave = `${id}-button-save`;
+    const idFile = `${id}-file`;
+    const idHighlighter = `${id}-highlighter`;
+    const idLine = `${id}-line`;
+
+    return {
+      id: id,
+      options: {
+        onClose: () => {
+          this._editor.selection.restore();
+
+          UiDialog.destroy(this);
+        },
+
+        onSetup: () => {
+          document.getElementById(idButtonDelete)!.addEventListener("click", (ev) => this._delete(ev));
+
+          // set highlighters
+          let highlighters = `
+            `;
+
+          const values: Highlighter[] = this._editor.opts.woltlab.highlighters.map((highlighter) => {
+            return [highlighter, PrismMeta[highlighter].title];
+          });
+
+          // sort by label
+          values.sort((a, b) => {
+            if (a[1] < b[1]) {
+              return -1;
+            } else if (a[1] > b[1]) {
+              return 1;
+            }
+
+            return 0;
+          });
+
+          values.forEach((value) => {
+            highlighters += ``;
+          });
+
+          document.getElementById(idHighlighter)!.innerHTML = highlighters;
+        },
+
+        onShow: () => {
+          const pre = this._pre!;
+
+          const highlighter = document.getElementById(idHighlighter) as HTMLSelectElement;
+          highlighter.value = pre.dataset.highlighter!;
+          const line = ~~(pre.dataset.line || 1);
+
+          const lineInput = document.getElementById(idLine) as HTMLInputElement;
+          lineInput.value = line.toString();
+
+          const filename = document.getElementById(idFile) as HTMLInputElement;
+          filename.value = pre.dataset.file!;
+        },
+
+        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")} +
+
+
+
+ + +
`, + }; + } +} + +Core.enableLegacyInheritance(UiRedactorCode); + +export = UiRedactorCode; diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts index 338f40c154..757f4d958e 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Ui/Redactor/Editor.ts @@ -1,26 +1,47 @@ export interface RedactorEditor { $editor: JQuery; + $element: JQuery; + + opts: { + [key: string]: any; + }; buffer: { - set: () => void; + set(): void; + }; + button: { + toggle(event: MouseEvent | object, btnName: string, type: string, callback: string, args?: object): void; + }; + caret: { + after(node: Node): void; + end(node: Node): void; }; clean: { - onSync: (html: string) => string; + onSync(html: string): string; }; code: { - get: () => string; - start: (html: string) => void; + get(): string; + set(html: string): void; + start(html: string): void; }; core: { - box: () => JQuery; - editor: () => JQuery; - element: () => JQuery; - textarea: () => JQuery; + box(): JQuery; + editor(): JQuery; + element(): JQuery; + textarea(): JQuery; + }; + focus: { + end(): void; }; insert: { - text: (text: string) => void; + text(text: string): void; + }; + selection: { + block(): HTMLElement | false; + restore(): void; + save(): void; }; utils: { - isEmpty: (html: string) => boolean; + isEmpty(html: string): boolean; }; }