UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

651 lines (648 loc) • 25.9 kB
/** * DevExtreme (cjs/ui/html_editor/ui.html_editor.js) * Version: 23.2.6 * Build date: Wed May 01 2024 * * Copyright (c) 2012 - 2024 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; exports.default = void 0; var _renderer = _interopRequireDefault(require("../../core/renderer")); var _extend = require("../../core/utils/extend"); var _type = require("../../core/utils/type"); var _element = require("../../core/element"); var _common = require("../../core/utils/common"); var _component_registrator = _interopRequireDefault(require("../../core/component_registrator")); var _empty_template = require("../../core/templates/empty_template"); var _editor = _interopRequireDefault(require("../editor/editor")); var _ui = _interopRequireDefault(require("../widget/ui.errors")); var _callbacks = _interopRequireDefault(require("../../core/utils/callbacks")); var _deferred = require("../../core/utils/deferred"); var _events_engine = _interopRequireDefault(require("../../events/core/events_engine")); var _index = require("../../events/utils/index"); var _index2 = require("../../events/index"); var _emitterGesture = _interopRequireDefault(require("../../events/gesture/emitter.gesture.scroll")); var _utils = require("../text_box/utils.scroll"); var _pointer = _interopRequireDefault(require("../../events/pointer")); var _devices = _interopRequireDefault(require("../../core/devices")); var _quill_registrator = _interopRequireDefault(require("./quill_registrator")); require("./converters/delta"); var _converterController = _interopRequireDefault(require("./converterController")); var _wordLists = _interopRequireDefault(require("./matchers/wordLists")); var _formDialog = _interopRequireDefault(require("./ui/formDialog")); var _config = _interopRequireDefault(require("../../core/config")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj } } function _extends() { _extends = Object.assign ? Object.assign.bind() : function(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } } return target }; return _extends.apply(this, arguments) } const HTML_EDITOR_CLASS = "dx-htmleditor"; const QUILL_CONTAINER_CLASS = "dx-quill-container"; const QUILL_CLIPBOARD_CLASS = "ql-clipboard"; const HTML_EDITOR_SUBMIT_ELEMENT_CLASS = "dx-htmleditor-submit-element"; const HTML_EDITOR_CONTENT_CLASS = "dx-htmleditor-content"; const MARKDOWN_VALUE_TYPE = "markdown"; const ANONYMOUS_TEMPLATE_NAME = "htmlContent"; const isIos = "ios" === _devices.default.current().platform; let editorsCount = 0; const HtmlEditor = _editor.default.inherit({ _getDefaultOptions: function() { return (0, _extend.extend)(this.callBase(), { focusStateEnabled: true, valueType: "html", placeholder: "", toolbar: null, variables: null, mediaResizing: null, tableResizing: null, mentions: null, customizeModules: null, tableContextMenu: null, allowSoftLineBreak: false, formDialogOptions: null, imageUpload: null, stylingMode: (0, _config.default)().editorStylingMode || "outlined" }) }, _init: function() { this._mentionKeyInTemplateStorage = editorsCount++; this.callBase(); this._cleanCallback = (0, _callbacks.default)(); this._contentInitializedCallback = (0, _callbacks.default)() }, _getAnonymousTemplateName: function() { return "htmlContent" }, _initTemplates: function() { this._templateManager.addDefaultTemplates({ htmlContent: new _empty_template.EmptyTemplate }); this.callBase() }, _focusTarget: function() { return this._getContent() }, _getContent: function() { return this.$element().find(".".concat("dx-htmleditor-content")) }, _focusInHandler: function(_ref) { let { relatedTarget: relatedTarget } = _ref; if (this._shouldSkipFocusEvent(relatedTarget)) { return } this._toggleFocusClass(true, this.$element()); this.callBase.apply(this, arguments) }, _focusOutHandler: function(_ref2) { let { relatedTarget: relatedTarget } = _ref2; if (this._shouldSkipFocusEvent(relatedTarget)) { return } this._toggleFocusClass(false, this.$element()); this.callBase.apply(this, arguments) }, _shouldSkipFocusEvent: function(relatedTarget) { return (0, _renderer.default)(relatedTarget).hasClass("ql-clipboard") }, _initMarkup: function() { this._$htmlContainer = (0, _renderer.default)("<div>").addClass("dx-quill-container"); this.$element().attr("role", "application").addClass("dx-htmleditor").wrapInner(this._$htmlContainer); this._renderStylingMode(); const template = this._getTemplate("htmlContent"); this._$templateResult = template && template.render({ container: (0, _element.getPublicElement)(this._$htmlContainer), noModel: true, transclude: true }); this._renderSubmitElement(); this.callBase(); this._updateContainerMarkup() }, _renderValidationState() { const $content = this._getContent(); if (1 === $content.length) { this.callBase() } }, _renderSubmitElement: function() { this._$submitElement = (0, _renderer.default)("<textarea>").addClass("dx-htmleditor-submit-element").attr("hidden", true).appendTo(this.$element()); this._setSubmitValue(this.option("value")) }, _setSubmitValue: function(value) { this._getSubmitElement().val(value) }, _getSubmitElement: function() { return this._$submitElement }, _createNoScriptFrame: function() { return (0, _renderer.default)("<iframe>").css("display", "none").attr({ srcdoc: "", id: "xss-frame", sandbox: "allow-same-origin" }) }, _removeXSSVulnerableHtml: function(value) { const $frame = this._createNoScriptFrame().appendTo("body"); const frame = $frame.get(0); const frameWindow = frame.contentWindow; const frameDocument = frameWindow.document; const frameDocumentBody = frameDocument.body; frameDocumentBody.innerHTML = value; const removeInlineHandlers = element => { if (element.attributes) { for (let i = 0; i < element.attributes.length; i++) { const name = element.attributes[i].name; if (name.startsWith("on")) { element.removeAttribute(name) } } } if (element.childNodes) { for (let i = 0; i < element.childNodes.length; i++) { removeInlineHandlers(element.childNodes[i]) } } }; removeInlineHandlers(frameDocumentBody); frameDocumentBody.querySelectorAll("script").forEach(scriptNode => { scriptNode.remove() }); const sanitizedHtml = frameDocumentBody.innerHTML; $frame.remove(); return sanitizedHtml }, _updateContainerMarkup: function() { let markup = this.option("value"); if (this._isMarkdownValue()) { this._prepareMarkdownConverter(); markup = this._markdownConverter.toHtml(markup) } if (markup) { const sanitizedMarkup = this._removeXSSVulnerableHtml(markup); this._$htmlContainer.html(sanitizedMarkup) } }, _prepareMarkdownConverter: function() { const MarkdownConverter = _converterController.default.getConverter("markdown"); if (MarkdownConverter) { this._markdownConverter = new MarkdownConverter } else { throw _ui.default.Error("E1051", "markdown") } }, _render: function() { this._prepareConverters(); this.callBase() }, _prepareQuillRegistrator: function() { if (!this._quillRegistrator) { this._quillRegistrator = new _quill_registrator.default } }, _getRegistrator: function() { this._prepareQuillRegistrator(); return this._quillRegistrator }, _prepareConverters: function() { if (!this._deltaConverter) { const DeltaConverter = _converterController.default.getConverter("delta"); if (DeltaConverter) { this._deltaConverter = new DeltaConverter } } if ("markdown" === this.option("valueType") && !this._markdownConverter) { this._prepareMarkdownConverter() } }, _renderContentImpl: function() { this._contentRenderedDeferred = new _deferred.Deferred; const renderContentPromise = this._contentRenderedDeferred.promise(); this.callBase(); this._renderHtmlEditor(); this._renderFormDialog(); this._addKeyPressHandler(); return renderContentPromise }, _pointerMoveHandler: function(e) { if (isIos) { e.stopPropagation() } }, _attachFocusEvents: function() { (0, _common.deferRender)(this.callBase.bind(this)) }, _addKeyPressHandler: function() { const keyDownEvent = (0, _index.addNamespace)("keydown", "".concat(this.NAME, "TextChange")); _events_engine.default.on(this._$htmlContainer, keyDownEvent, this._keyDownHandler.bind(this)) }, _keyDownHandler: function(e) { this._saveValueChangeEvent(e) }, _renderHtmlEditor: function() { const customizeModules = this.option("customizeModules"); const modulesConfig = this._getModulesConfig(); if ((0, _type.isFunction)(customizeModules)) { customizeModules(modulesConfig) } this._quillInstance = this._getRegistrator().createEditor(this._$htmlContainer[0], { placeholder: this.option("placeholder"), readOnly: this.option("readOnly") || this.option("disabled"), modules: modulesConfig, theme: "basic" }); this._renderValidationState(); this._deltaConverter.setQuillInstance(this._quillInstance); this._textChangeHandlerWithContext = this._textChangeHandler.bind(this); this._quillInstance.on("text-change", this._textChangeHandlerWithContext); this._renderScrollHandler(); if (this._hasTranscludedContent()) { this._updateContentTask = (0, _common.executeAsync)(() => { this._applyTranscludedContent() }) } else { this._finalizeContentRendering() } }, _renderScrollHandler: function() { const $scrollContainer = this._getContent(); const initScrollData = (0, _utils.prepareScrollData)($scrollContainer); _events_engine.default.on($scrollContainer, (0, _index.addNamespace)(_emitterGesture.default.init, this.NAME), initScrollData, _common.noop); _events_engine.default.on($scrollContainer, (0, _index.addNamespace)(_pointer.default.move, this.NAME), this._pointerMoveHandler.bind(this)) }, _applyTranscludedContent: function() { const valueOption = this.option("value"); if (!(0, _type.isDefined)(valueOption)) { const html = this._deltaConverter.toHtml(); const newDelta = this._quillInstance.clipboard.convert({ html: html }); if (newDelta.ops.length) { this._quillInstance.setContents(newDelta); return } } this._finalizeContentRendering() }, _hasTranscludedContent: function() { return this._$templateResult && this._$templateResult.length }, _getModulesConfig: function() { const quill = this._getRegistrator().getQuill(); const wordListMatcher = (0, _wordLists.default)(quill); const modulesConfig = (0, _extend.extend)({}, { table: true, toolbar: this._getModuleConfigByOption("toolbar"), variables: this._getModuleConfigByOption("variables"), resizing: this._getModuleConfigByOption("mediaResizing"), tableResizing: this._getModuleConfigByOption("tableResizing"), tableContextMenu: this._getModuleConfigByOption("tableContextMenu"), imageUpload: this._getModuleConfigByOption("imageUpload"), imageCursor: this._getBaseModuleConfig(), mentions: this._getModuleConfigByOption("mentions"), uploader: { onDrop: e => this._saveValueChangeEvent((0, _index2.Event)(e)), imageBlot: "extendedImage" }, keyboard: { onKeydown: e => this._saveValueChangeEvent((0, _index2.Event)(e)) }, clipboard: { onPaste: e => this._saveValueChangeEvent((0, _index2.Event)(e)), onCut: e => this._saveValueChangeEvent((0, _index2.Event)(e)), matchers: [ ["p.MsoListParagraphCxSpFirst", wordListMatcher], ["p.MsoListParagraphCxSpMiddle", wordListMatcher], ["p.MsoListParagraphCxSpLast", wordListMatcher] ] }, multiline: Boolean(this.option("allowSoftLineBreak")) }, this._getCustomModules()); return modulesConfig }, _getModuleConfigByOption: function(userOptionName) { const optionValue = this.option(userOptionName); let config = {}; if (!(0, _type.isDefined)(optionValue)) { return } if (Array.isArray(optionValue)) { config[userOptionName] = optionValue } else { config = optionValue } return (0, _extend.extend)(this._getBaseModuleConfig(), config) }, _getBaseModuleConfig: function() { return { editorInstance: this } }, _getCustomModules: function() { const modules = {}; const moduleNames = this._getRegistrator().getRegisteredModuleNames(); moduleNames.forEach(modulePath => { modules[modulePath] = this._getBaseModuleConfig() }); return modules }, _textChangeHandler: function(newDelta, oldDelta, source) { const htmlMarkup = this._deltaConverter.toHtml(); const convertedValue = this._isMarkdownValue() ? this._updateValueByType("markdown", htmlMarkup) : htmlMarkup; const currentValue = this.option("value"); if (currentValue !== convertedValue && !this._isNullValueConverted(currentValue, convertedValue)) { this._isEditorUpdating = true; this.option("value", convertedValue) } this._finalizeContentRendering() }, _isNullValueConverted: function(currentValue, convertedValue) { return null === currentValue && "" === convertedValue }, _finalizeContentRendering: function() { if (this._contentRenderedDeferred) { this.clearHistory(); this._contentInitializedCallback.fire(); this._contentRenderedDeferred.resolve(); this._contentRenderedDeferred = void 0 } }, _updateValueByType: function(valueType, value) { const converter = this._markdownConverter; if (!(0, _type.isDefined)(converter)) { return } const currentValue = (0, _common.ensureDefined)(value, this.option("value")); return "markdown" === valueType ? converter.toMarkdown(currentValue) : converter.toHtml(currentValue) }, _isMarkdownValue: function() { return "markdown" === this.option("valueType") }, _resetEnabledState: function() { if (this._quillInstance) { const isEnabled = !(this.option("readOnly") || this.option("disabled")); this._quillInstance.enable(isEnabled) } }, _renderFormDialog: function() { const userOptions = (0, _extend.extend)(true, { width: "auto", height: "auto", hideOnOutsideClick: true }, this.option("formDialogOptions")); this._formDialog = new _formDialog.default(this, userOptions) }, _getStylingModePrefix: function() { return "dx-htmleditor-" }, _getQuillContainer: function() { return this._$htmlContainer }, _prepareModuleOptions(args) { var _args$fullName; const optionData = null === (_args$fullName = args.fullName) || void 0 === _args$fullName ? void 0 : _args$fullName.split("."); let value = args.value; const optionName = optionData.length >= 2 ? optionData[1] : args.name; if (3 === optionData.length) { value = { [optionData[2]]: value } } return [optionName, value] }, _moduleOptionChanged: function(moduleName, args) { const moduleInstance = this.getModule(moduleName); const shouldPassOptionsToModule = Boolean(moduleInstance); if (shouldPassOptionsToModule) { moduleInstance.option(...this._prepareModuleOptions(args)) } else { this._invalidate() } }, _optionChanged: function(args) { switch (args.name) { case "value": { if (this._quillInstance) { if (this._isEditorUpdating) { this._isEditorUpdating = false } else { const updatedValue = this._isMarkdownValue() ? this._updateValueByType("HTML", args.value) : args.value; this._suppressValueChangeAction(); this._updateHtmlContent(updatedValue); this._resumeValueChangeAction() } } else { this._$htmlContainer.html(args.value) } const value = this.option("value"); if (value !== args.previousValue) { this._setSubmitValue(value); this.callBase(_extends({}, args, { value: value })) } break } case "placeholder": case "variables": case "toolbar": case "mentions": case "customizeModules": case "allowSoftLineBreak": this._invalidate(); break; case "tableResizing": this._moduleOptionChanged("tableResizing", args); break; case "valueType": { this._prepareConverters(); const newValue = this._updateValueByType(args.value); if ("html" === args.value && this._quillInstance) { this._updateHtmlContent(newValue) } else { this.option("value", newValue) } break } case "stylingMode": this._renderStylingMode(); break; case "readOnly": case "disabled": this.callBase(args); this._resetEnabledState(); break; case "formDialogOptions": this._renderFormDialog(); break; case "tableContextMenu": this._moduleOptionChanged("tableContextMenu", args); break; case "mediaResizing": if (!args.previousValue || !args.value) { this._invalidate() } else { this.getModule("resizing").option(args.name, args.value) } break; case "width": this.callBase(args); this._repaintToolbar(); break; case "imageUpload": this._moduleOptionChanged("imageUpload", args); break; default: this.callBase(args) } }, _repaintToolbar: function() { this._applyToolbarMethod("repaint") }, _updateHtmlContent: function(html) { const newDelta = this._quillInstance.clipboard.convert({ html: html }); this._quillInstance.setContents(newDelta) }, _clean: function() { if (this._quillInstance) { _events_engine.default.off(this._getContent(), ".".concat(this.NAME)); this._quillInstance.off("text-change", this._textChangeHandlerWithContext); this._cleanCallback.fire() } this._abortUpdateContentTask(); this._cleanCallback.empty(); this._contentInitializedCallback.empty(); this.callBase() }, _abortUpdateContentTask: function() { if (this._updateContentTask) { this._updateContentTask.abort(); this._updateContentTask = void 0 } }, _applyQuillMethod(methodName, args) { if (this._quillInstance) { return this._quillInstance[methodName].apply(this._quillInstance, args) } }, _applyQuillHistoryMethod(methodName) { if (this._quillInstance && this._quillInstance.history) { this._quillInstance.history[methodName]() } }, _applyToolbarMethod(methodName) { var _this$getModule; null === (_this$getModule = this.getModule("toolbar")) || void 0 === _this$getModule ? void 0 : _this$getModule[methodName]() }, addCleanCallback(callback) { this._cleanCallback.add(callback) }, addContentInitializedCallback(callback) { this._contentInitializedCallback.add(callback) }, register: function(components) { this._getRegistrator().registerModules(components); if (this._quillInstance) { this.repaint() } }, get: function(modulePath) { return this._getRegistrator().getQuill().import(modulePath) }, getModule: function(moduleName) { return this._applyQuillMethod("getModule", arguments) }, getQuillInstance: function() { return this._quillInstance }, getSelection: function(focus) { return this._applyQuillMethod("getSelection", arguments) }, setSelection: function(index, length) { this._applyQuillMethod("setSelection", arguments) }, getText: function(index, length) { return this._applyQuillMethod("getText", arguments) }, format: function(formatName, formatValue) { this._applyQuillMethod("format", arguments) }, formatText: function(index, length, formatName, formatValue) { this._applyQuillMethod("formatText", arguments) }, formatLine: function(index, length, formatName, formatValue) { this._applyQuillMethod("formatLine", arguments) }, getFormat: function(index, length) { return this._applyQuillMethod("getFormat", arguments) }, removeFormat: function(index, length) { return this._applyQuillMethod("removeFormat", arguments) }, clearHistory: function() { this._applyQuillHistoryMethod("clear"); this._applyToolbarMethod("updateHistoryWidgets") }, undo: function() { this._applyQuillHistoryMethod("undo") }, redo: function() { this._applyQuillHistoryMethod("redo") }, getLength: function() { return this._applyQuillMethod("getLength") }, getBounds: function(index, length) { return this._applyQuillMethod("getBounds", arguments) }, delete: function(index, length) { this._applyQuillMethod("deleteText", arguments) }, insertText: function(index, text, formats) { this._applyQuillMethod("insertText", arguments) }, insertEmbed: function(index, type, config) { this._applyQuillMethod("insertEmbed", arguments) }, showFormDialog: function(formConfig) { return this._formDialog.show(formConfig) }, formDialogOption: function(optionName, optionValue) { return this._formDialog.popupOption.apply(this._formDialog, arguments) }, focus: function() { this.callBase(); this._applyQuillMethod("focus") }, blur: function() { this._applyQuillMethod("blur") }, getMentionKeyInTemplateStorage() { return this._mentionKeyInTemplateStorage } }); (0, _component_registrator.default)("dxHtmlEditor", HtmlEditor); var _default = HtmlEditor; exports.default = _default; module.exports = exports.default; module.exports.default = exports.default;