"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",
"@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",
* Manages code blocks.
*
* @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
+ * @copyright 2001-2019 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @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 `<pre>` 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 = '<option value="">' + Language.get('wcf.editor.code.highlighter.detect') + '</option>';
- highlighters += '<option value="plain">' + Language.get('wcf.editor.code.highlighter.plain') + '</option>';
- //noinspection JSUnresolvedVariable
- var values = this._editor.opts.woltlab.highlighters.map(function (highlighter) {
+ let highlighters = `<option value="">${Language.get("wcf.editor.code.highlighter.detect")}</option>
+ <option value="plain">${Language.get("wcf.editor.code.highlighter.plain")}</option>`;
+ 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;
}
}
return 0;
});
- values.forEach((function (value) {
- highlighters += '<option value="' + value[0] + '">' + StringUtil.escapeHTML(value[1]) + '</option>';
- }).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 += `<option value="${value[0]}">${StringUtil.escapeHTML(value[1])}</option>`;
+ });
+ 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: '<div class="section">'
- + '<dl>'
- + '<dt><label for="' + idHighlighter + '">' + Language.get('wcf.editor.code.highlighter') + '</label></dt>'
- + '<dd>'
- + '<select id="' + idHighlighter + '"></select>'
- + '<small>' + Language.get('wcf.editor.code.highlighter.description') + '</small>'
- + '</dd>'
- + '</dl>'
- + '<dl>'
- + '<dt><label for="' + idLine + '">' + Language.get('wcf.editor.code.line') + '</label></dt>'
- + '<dd>'
- + '<input type="number" id="' + idLine + '" min="0" value="1" class="long" data-dialog-submit-on-enter="true">'
- + '<small>' + Language.get('wcf.editor.code.line.description') + '</small>'
- + '</dd>'
- + '</dl>'
- + '<dl>'
- + '<dt><label for="' + idFile + '">' + Language.get('wcf.editor.code.file') + '</label></dt>'
- + '<dd>'
- + '<input type="text" id="' + idFile + '" class="long" data-dialog-submit-on-enter="true">'
- + '<small>' + Language.get('wcf.editor.code.file.description') + '</small>'
- + '</dd>'
- + '</dl>'
- + '</div>'
- + '<div class="formSubmit">'
- + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
- + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
- + '</div>'
+ source: `<div class="section">
+ <dl>
+ <dt>
+ <label for="${idHighlighter}">${Language.get("wcf.editor.code.highlighter")}</label>
+ </dt>
+ <dd>
+ <select id="${idHighlighter}"></select>
+ <small>${Language.get("wcf.editor.code.highlighter.description")}</small>
+ </dd>
+ </dl>
+ <dl>
+ <dt>
+ <label for="${idLine}">${Language.get("wcf.editor.code.line")}</label>
+ </dt>
+ <dd>
+ <input type="number" id="${idLine}" min="0" value="1" class="long" data-dialog-submit-on-enter="true">
+ <small>${Language.get("wcf.editor.code.line.description")}</small>
+ </dd>
+ </dl>
+ <dl>
+ <dt>
+ <label for="${idFile}">${Language.get("wcf.editor.code.file")}</label>
+ </dt>
+ <dd>
+ <input type="text" id="${idFile}" class="long" data-dialog-submit-on-enter="true">
+ <small>${Language.get("wcf.editor.code.file.description")}</small>
+ </dd>
+ </dl>
+ </div>
+ <div class="formSubmit">
+ <button id="${idButtonSave}" class="buttonPrimary" data-type="submit">${Language.get("wcf.global.button.save")}</button>
+ <button id="${idButtonDelete}">${Language.get("wcf.global.button.delete")}</button>
+ </div>`,
};
}
- };
+ }
+ Core.enableLegacyInheritance(UiRedactorCode);
return UiRedactorCode;
});
+++ /dev/null
-/**
- * Manages code blocks.
- *
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @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 `<pre>` 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 = '<option value="">' + Language.get('wcf.editor.code.highlighter.detect') + '</option>';
- highlighters += '<option value="plain">' + Language.get('wcf.editor.code.highlighter.plain') + '</option>';
-
- //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 += '<option value="' + value[0] + '">' + StringUtil.escapeHTML(value[1]) + '</option>';
- }).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: '<div class="section">'
- + '<dl>'
- + '<dt><label for="' + idHighlighter + '">' + Language.get('wcf.editor.code.highlighter') + '</label></dt>'
- + '<dd>'
- + '<select id="' + idHighlighter + '"></select>'
- + '<small>' + Language.get('wcf.editor.code.highlighter.description') + '</small>'
- + '</dd>'
- + '</dl>'
- + '<dl>'
- + '<dt><label for="' + idLine + '">' + Language.get('wcf.editor.code.line') + '</label></dt>'
- + '<dd>'
- + '<input type="number" id="' + idLine + '" min="0" value="1" class="long" data-dialog-submit-on-enter="true">'
- + '<small>' + Language.get('wcf.editor.code.line.description') + '</small>'
- + '</dd>'
- + '</dl>'
- + '<dl>'
- + '<dt><label for="' + idFile + '">' + Language.get('wcf.editor.code.file') + '</label></dt>'
- + '<dd>'
- + '<input type="text" id="' + idFile + '" class="long" data-dialog-submit-on-enter="true">'
- + '<small>' + Language.get('wcf.editor.code.file.description') + '</small>'
- + '</dd>'
- + '</dl>'
- + '</div>'
- + '<div class="formSubmit">'
- + '<button id="' + idButtonSave + '" class="buttonPrimary" data-type="submit">' + Language.get('wcf.global.button.save') + '</button>'
- + '<button id="' + idButtonDelete + '">' + Language.get('wcf.global.button.delete') + '</button>'
- + '</div>'
- };
- }
- };
-
- return UiRedactorCode;
-});
--- /dev/null
+/**
+ * Manages code blocks.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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<Identifier, PrismLang>;
+
+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 `<pre>` 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<DialogCallbackSetup> {
+ 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 = `<option value="">${Language.get("wcf.editor.code.highlighter.detect")}</option>
+ <option value="plain">${Language.get("wcf.editor.code.highlighter.plain")}</option>`;
+
+ 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 += `<option value="${value[0]}">${StringUtil.escapeHTML(value[1])}</option>`;
+ });
+
+ 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: `<div class="section">
+ <dl>
+ <dt>
+ <label for="${idHighlighter}">${Language.get("wcf.editor.code.highlighter")}</label>
+ </dt>
+ <dd>
+ <select id="${idHighlighter}"></select>
+ <small>${Language.get("wcf.editor.code.highlighter.description")}</small>
+ </dd>
+ </dl>
+ <dl>
+ <dt>
+ <label for="${idLine}">${Language.get("wcf.editor.code.line")}</label>
+ </dt>
+ <dd>
+ <input type="number" id="${idLine}" min="0" value="1" class="long" data-dialog-submit-on-enter="true">
+ <small>${Language.get("wcf.editor.code.line.description")}</small>
+ </dd>
+ </dl>
+ <dl>
+ <dt>
+ <label for="${idFile}">${Language.get("wcf.editor.code.file")}</label>
+ </dt>
+ <dd>
+ <input type="text" id="${idFile}" class="long" data-dialog-submit-on-enter="true">
+ <small>${Language.get("wcf.editor.code.file.description")}</small>
+ </dd>
+ </dl>
+ </div>
+ <div class="formSubmit">
+ <button id="${idButtonSave}" class="buttonPrimary" data-type="submit">${Language.get(
+ "wcf.global.button.save",
+ )}</button>
+ <button id="${idButtonDelete}">${Language.get("wcf.global.button.delete")}</button>
+ </div>`,
+ };
+ }
+}
+
+Core.enableLegacyInheritance(UiRedactorCode);
+
+export = UiRedactorCode;
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;
};
}