UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

688 lines (685 loc) • 26.5 kB
/** * DevExtreme (cjs/__internal/ui/html_editor/html_editor.js) * Version: 25.1.3 * Build date: Wed Jun 25 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; require("../../ui/html_editor/converters/m_delta"); var _events = require("../../../common/core/events"); var _events_engine = _interopRequireDefault(require("../../../common/core/events/core/events_engine")); var _emitterGesture = _interopRequireDefault(require("../../../common/core/events/gesture/emitter.gesture.scroll")); var _pointer = _interopRequireDefault(require("../../../common/core/events/pointer")); var _index = require("../../../common/core/events/utils/index"); var _component_registrator = _interopRequireDefault(require("../../../core/component_registrator")); var _config = _interopRequireDefault(require("../../../core/config")); var _devices = _interopRequireDefault(require("../../../core/devices")); var _element = require("../../../core/element"); var _renderer = _interopRequireDefault(require("../../../core/renderer")); var _empty_template = require("../../../core/templates/empty_template"); var _callbacks = _interopRequireDefault(require("../../../core/utils/callbacks")); var _common = require("../../../core/utils/common"); var _deferred = require("../../../core/utils/deferred"); var _extend = require("../../../core/utils/extend"); var _type = require("../../../core/utils/type"); var _editor = _interopRequireDefault(require("../../ui/editor/editor")); var _m_converterController = _interopRequireDefault(require("../../ui/html_editor/m_converterController")); var _m_quill_importer = require("../../ui/html_editor/m_quill_importer"); var _m_quill_registrator = _interopRequireDefault(require("../../ui/html_editor/m_quill_registrator")); var _m_wordLists = _interopRequireDefault(require("../../ui/html_editor/matchers/m_wordLists")); var _formDialog = _interopRequireDefault(require("../../ui/html_editor/ui/formDialog")); var _html_sanitizer = require("../../ui/html_editor/utils/html_sanitizer"); var _m_utils = require("../../ui/text_box/m_utils.scroll"); var _aiDialog = _interopRequireDefault(require("./ui/aiDialog")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function(n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]) } } return n }, _extends.apply(null, arguments) } const QUILL_CONTAINER_CLASS = "dx-quill-container"; const QUILL_CLIPBOARD_CLASS = "ql-clipboard"; const HTML_EDITOR_CLASS = "dx-htmleditor"; const HTML_EDITOR_SUBMIT_ELEMENT_CLASS = "dx-htmleditor-submit-element"; const HTML_EDITOR_CONTENT_CLASS = "dx-htmleditor-content"; const ANONYMOUS_TEMPLATE_NAME = "htmlContent"; const isIos = "ios" === _devices.default.current().platform; let editorsCount = 0; class HtmlEditor extends _editor.default { _getDefaultOptions() { const { editorStylingMode: editorStylingMode } = (0, _config.default)(); const stylingMode = editorStylingMode || "outlined"; return _extends({}, super._getDefaultOptions(), { aiIntegration: null, allowSoftLineBreak: false, converter: null, customizeModules: null, focusStateEnabled: true, imageUpload: null, mediaResizing: null, mentions: null, placeholder: "", stylingMode: stylingMode, tableContextMenu: null, tableResizing: null, toolbar: null, variables: null }) } _init() { this._mentionKeyInTemplateStorage = editorsCount; editorsCount += 1; super._init(); this._cleanCallback = (0, _callbacks.default)(); this._contentInitializedCallback = (0, _callbacks.default)(); this._prepareHtmlConverter() } _prepareHtmlConverter() { const { converter: converter } = this.option(); if (converter) { this._htmlConverter = converter } } _getAnonymousTemplateName() { return "htmlContent" } _initTemplates() { this._templateManager.addDefaultTemplates({ [ANONYMOUS_TEMPLATE_NAME]: new _empty_template.EmptyTemplate }); super._initTemplates() } _focusTarget() { return this._getContent() } _getContent() { return this.$element().find(".dx-htmleditor-content") } _focusInHandler(e) { const { relatedTarget: relatedTarget } = e; if (this._shouldSkipFocusEvent(relatedTarget)) { return } this._toggleFocusClass(true, this.$element()); super._focusInHandler(e) } _focusOutHandler(e) { const { relatedTarget: relatedTarget } = e; if (this._shouldSkipFocusEvent(relatedTarget)) { return } this._toggleFocusClass(false, this.$element()); super._focusOutHandler(e) } _shouldSkipFocusEvent(relatedTarget) { return (0, _renderer.default)(relatedTarget).hasClass("ql-clipboard") } _initMarkup() { 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 = null === template || void 0 === template ? void 0 : template.render({ container: (0, _element.getPublicElement)(this._$htmlContainer), noModel: true, transclude: true }); this._renderSubmitElement(); super._initMarkup(); this._updateContainerMarkup() } _renderValidationState() { const $content = this._getContent(); if (1 === $content.length) { super._renderValidationState() } } _renderSubmitElement() { this._$submitElement = (0, _renderer.default)("<textarea>").addClass("dx-htmleditor-submit-element").attr("hidden", true).appendTo(this.$element()); const { value: value } = this.option(); this._setSubmitValue(value) } _setSubmitValue(value) { this._getSubmitElement().val(value) } _getSubmitElement() { return this._$submitElement } _convertToHtml(raw) { var _this$_htmlConverter, _this$_htmlConverter2; const value = raw ?? ""; const result = (0, _type.isFunction)(null === (_this$_htmlConverter = this._htmlConverter) || void 0 === _this$_htmlConverter ? void 0 : _this$_htmlConverter.toHtml) ? String((null === (_this$_htmlConverter2 = this._htmlConverter) || void 0 === _this$_htmlConverter2 ? void 0 : _this$_htmlConverter2.toHtml(value)) ?? "") : value; return result } _convertFromHtml(raw) { var _this$_htmlConverter3, _this$_htmlConverter4; const value = raw ?? ""; const result = (0, _type.isFunction)(null === (_this$_htmlConverter3 = this._htmlConverter) || void 0 === _this$_htmlConverter3 ? void 0 : _this$_htmlConverter3.fromHtml) ? String((null === (_this$_htmlConverter4 = this._htmlConverter) || void 0 === _this$_htmlConverter4 ? void 0 : _this$_htmlConverter4.fromHtml(value)) ?? "") : value; return result } _updateContainerMarkup() { const { value: value } = this.option(); const html = this._convertToHtml(value); if (!html) { return } const quill = (0, _m_quill_importer.getQuill)(); const sanitizedHtml = (0, _html_sanitizer.sanitizeHtml)(quill, html); this._$htmlContainer.html(sanitizedHtml) } _render() { this._prepareConverters(); super._render(); this._toggleReadOnlyState() } _prepareQuillRegistrator() { if (!this._quillRegistrator) { this._quillRegistrator = new _m_quill_registrator.default } } _getRegistrator() { this._prepareQuillRegistrator(); return this._quillRegistrator } _prepareConverters() { if (!this._deltaConverter) { const DeltaConverter = _m_converterController.default.getConverter("delta"); if (DeltaConverter) { this._deltaConverter = new DeltaConverter } } } _renderContentImpl() { this._contentRenderedDeferred = (0, _deferred.Deferred)(); const renderContentPromise = this._contentRenderedDeferred.promise(); super._renderContentImpl(); this._renderHtmlEditor(); this._renderFormDialog(); this._renderAIDialog(); this._addKeyPressHandler(); return renderContentPromise } _pointerMoveHandler(e) { if (isIos) { e.stopPropagation() } } _attachFocusEvents() { (0, _common.deferRender)(super._attachFocusEvents.bind(this)) } _addKeyPressHandler() { const keyDownEvent = (0, _index.addNamespace)("keydown", `${this.NAME}TextChange`); _events_engine.default.on(this._$htmlContainer, keyDownEvent, this._keyDownHandler.bind(this)) } _keyDownHandler(e) { this._saveValueChangeEvent(e) } _renderHtmlEditor() { const { customizeModules: customizeModules } = this.option(); 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() { const $scrollContainer = this._getContent(); const initScrollData = (0, _m_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() { const { value: value } = this.option(); if (!(0, _type.isDefined)(value)) { 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() { var _this$_$templateResul; return Boolean(null === (_this$_$templateResul = this._$templateResult) || void 0 === _this$_$templateResul ? void 0 : _this$_$templateResul.length) } _getModulesConfig() { const modulesConfig = (0, _extend.extend)({}, { clipboard: this._getClipboardConfig(), imageCursor: this._getBaseModuleConfig(), imageUpload: this._getModuleConfigByOption("imageUpload"), keyboard: this._getKeyboardModuleConfig(), mentions: this._getModuleConfigByOption("mentions"), multiline: Boolean(this.option("allowSoftLineBreak")), resizing: this._getModuleConfigByOption("mediaResizing"), table: true, tableContextMenu: this._getModuleConfigByOption("tableContextMenu"), tableResizing: this._getModuleConfigByOption("tableResizing"), toolbar: this._getModuleConfigByOption("toolbar"), uploader: this._getUploaderModuleConfig(), variables: this._getModuleConfigByOption("variables") }, this._getCustomModules()); return modulesConfig } _getUploaderModuleConfig() { return { onDrop: e => this._saveValueChangeEvent((0, _events.Event)(e)), imageBlot: "extendedImage" } } _getKeyboardModuleConfig() { return { onKeydown: e => this._saveValueChangeEvent((0, _events.Event)(e)) } } _getClipboardConfig() { const quill = this._getRegistrator().getQuill(); const wordListMatcher = (0, _m_wordLists.default)(quill); return { onPaste: e => this._saveValueChangeEvent((0, _events.Event)(e)), onCut: e => this._saveValueChangeEvent((0, _events.Event)(e)), matchers: [ ["p.MsoListParagraphCxSpFirst", wordListMatcher], ["p.MsoListParagraphCxSpMiddle", wordListMatcher], ["p.MsoListParagraphCxSpLast", wordListMatcher] ] } } _getModuleConfigByOption(optionName) { const optionValue = this.option(optionName); if (!(0, _type.isDefined)(optionValue)) { return } const configuration = Array.isArray(optionValue) ? { [optionName]: optionValue } : optionValue; const finalConfiguration = (0, _extend.extend)(this._getBaseModuleConfig(), configuration); return finalConfiguration } _getBaseModuleConfig() { return { editorInstance: this } } _getCustomModules() { const modules = {}; const moduleNames = this._getRegistrator().getRegisteredModuleNames(); moduleNames.forEach((modulePath => { modules[modulePath] = this._getBaseModuleConfig() })); return modules } _textChangeHandler() { const { value: value } = this.option(); const html = this._deltaConverter.toHtml(); const convertedValue = this._convertFromHtml(html); if (value !== convertedValue && !this._isNullValueConverted(value, convertedValue)) { this._isEditorUpdating = true; this.option({ value: convertedValue }) } this._finalizeContentRendering() } _isNullValueConverted(value, convertedValue) { return null === value && "" === convertedValue } _finalizeContentRendering() { if (this._contentRenderedDeferred) { this.clearHistory(); this._contentInitializedCallback.fire(); this._contentRenderedDeferred.resolve(); this._contentRenderedDeferred = void 0 } } _resetEnabledState() { if (this._quillInstance) { const isEnabled = !(this.option("readOnly") || this.option("disabled")); this._quillInstance.enable(isEnabled) } } _renderFormDialog() { this._formDialog = new _formDialog.default(this.$element(), { width: "auto", height: "auto", hideOnOutsideClick: true }) } _shouldRenderAIDialog() { const { aiIntegration: aiIntegration, toolbar: toolbar } = this.option(); if (!(aiIntegration && null !== toolbar && void 0 !== toolbar && toolbar.items)) { return false } return toolbar.items.some((item => "string" === typeof item ? "ai" === item : "ai" === item.name)) } _renderAIDialog() { const shouldRenderAIDialog = this._shouldRenderAIDialog(); if (shouldRenderAIDialog) { const { aiIntegration: aiIntegration } = this.option(); this._aiDialog = new _aiDialog.default(this.$element(), aiIntegration) } } _getStylingModePrefix() { return "dx-htmleditor-" } _getQuillContainer() { return this._$htmlContainer } _prepareModuleOptions(args) { let { value: value } = args; const { fullName: fullName, name: name } = args; const optionData = null === fullName || void 0 === fullName ? void 0 : fullName.split("."); const optionName = optionData.length >= 2 ? optionData[1] : name; if (3 === optionData.length) { value = { [optionData[2]]: value } } return [optionName, value] } _moduleOptionChanged(moduleName, args) { const moduleInstance = this.getModule(moduleName); const shouldPassOptionsToModule = Boolean(moduleInstance); if (shouldPassOptionsToModule) { moduleInstance.option(...this._prepareModuleOptions(args)) } else { this._invalidate() } } _processHtmlContentUpdating(value) { if (this._quillInstance) { if (this._isEditorUpdating) { this._isEditorUpdating = false } else { const html = this._convertToHtml(value); this._suppressValueChangeAction(); this._updateHtmlContent(html); this._resumeValueChangeAction() } } else { this._$htmlContainer.html(value) } } _processAIIntegrationUpdate() { if ((0, _type.isDefined)(this._aiDialog)) { const { aiIntegration: aiIntegration } = this.option(); this._aiDialog.updateAIIntegration(aiIntegration); return } this._renderAIDialog() } _optionChanged(args) { const { name: name, value: value, previousValue: previousValue } = args; switch (name) { case "aiIntegration": this._processAIIntegrationUpdate(); break; case "converter": { this._htmlConverter = value; const { value: currentValue } = this.option(); this._processHtmlContentUpdating(currentValue); break } case "value": { this._processHtmlContentUpdating(value); const { value: currentValue } = this.option(); if (currentValue !== previousValue) { this._setSubmitValue(currentValue); super._optionChanged(_extends({}, args, { [name]: currentValue })) } break } case "placeholder": case "variables": case "toolbar": case "mentions": case "customizeModules": case "allowSoftLineBreak": this._invalidate(); break; case "tableResizing": this._moduleOptionChanged("tableResizing", args); break; case "stylingMode": this._renderStylingMode(); break; case "readOnly": case "disabled": super._optionChanged(args); this._resetEnabledState(); break; case "tableContextMenu": this._moduleOptionChanged("tableContextMenu", args); break; case "mediaResizing": this._moduleOptionChanged("resizing", args); break; case "width": super._optionChanged(args); this._repaintToolbar(); break; case "imageUpload": this._moduleOptionChanged("imageUpload", args); break; default: super._optionChanged(args) } } _repaintToolbar() { this._applyToolbarMethod("repaint") } _updateHtmlContent(html) { const newDelta = this._quillInstance.clipboard.convert({ html: html }); this._quillInstance.setContents(newDelta) } _clean() { if (this._quillInstance) { _events_engine.default.off(this._getContent(), `.${this.NAME}`); this._quillInstance.off("text-change", this._textChangeHandlerWithContext); this._cleanCallback.fire() } this._abortUpdateContentTask(); this._cleanCallback.empty(); this._contentInitializedCallback.empty(); super._clean() } _abortUpdateContentTask() { if (this._updateContentTask) { this._updateContentTask.abort(); this._updateContentTask = void 0 } } _applyQuillMethod(methodName) { if (!this._quillInstance) { return } for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key] } return this._quillInstance[methodName].apply(this._quillInstance, args) } _applyQuillHistoryMethod(methodName) { var _this$_quillInstance; if (null !== (_this$_quillInstance = this._quillInstance) && void 0 !== _this$_quillInstance && _this$_quillInstance.history) { this._quillInstance.history[methodName]() } } _applyToolbarMethod(methodName) { var _this$getModule; null === (_this$getModule = this.getModule("toolbar")) || void 0 === _this$getModule || _this$getModule[methodName]() } addCleanCallback(callback) { this._cleanCallback.add(callback) } addContentInitializedCallback(callback) { this._contentInitializedCallback.add(callback) } register(components) { this._getRegistrator().registerModules(components); if (this._quillInstance) { this.repaint() } } get(componentPath) { return this._getRegistrator().getQuill().import(componentPath) } getModule(moduleName) { return this._applyQuillMethod("getModule", moduleName) } getQuillInstance() { return this._quillInstance } getSelection(focus) { const selection = this._applyQuillMethod("getSelection", focus); return selection } setSelection(index, length) { this._applyQuillMethod("setSelection", index, length) } getText(index, length) { const text = this._applyQuillMethod("getText", index, length); return text } format(formatName, formatValue) { this._applyQuillMethod("format", formatName, formatValue) } formatText(index, length, formatName, formatValue) { this._applyQuillMethod("formatText", index, length, formatName, formatValue) } formatLine(index, length, formatName, formatValue) { this._applyQuillMethod("formatLine", index, length, formatName, formatValue) } getFormat(index, length) { const formats = this._applyQuillMethod("getFormat", index, length); return formats } removeFormat(index, length) { this._applyQuillMethod("removeFormat", index, length) } clearHistory() { this._applyQuillHistoryMethod("clear"); this._applyToolbarMethod("updateHistoryWidgets") } undo() { this._applyQuillHistoryMethod("undo") } redo() { this._applyQuillHistoryMethod("redo") } getLength() { const length = this._applyQuillMethod("getLength"); return length } getBounds(index, length) { const bounds = this._applyQuillMethod("getBounds", index, length); return bounds } delete(index, length) { this._applyQuillMethod("deleteText", index, length) } insertText(index, text, formatName, formatValue) { this._applyQuillMethod("insertText", index, text, formatName, formatValue) } insertEmbed(index, type, options) { this._applyQuillMethod("insertEmbed", index, type, options) } showFormDialog(formConfig) { return this._formDialog.show(formConfig) } showAIDialog(payload) { var _this$_aiDialog; return null === (_this$_aiDialog = this._aiDialog) || void 0 === _this$_aiDialog ? void 0 : _this$_aiDialog.show(payload) } formDialogOption(optionName, optionValue) { return this._formDialog.popupOption.apply(this._formDialog, [optionName, optionValue]) } focus() { super.focus(); this._applyQuillMethod("focus") } blur() { this._applyQuillMethod("blur") } getMentionKeyInTemplateStorage() { return this._mentionKeyInTemplateStorage } }(0, _component_registrator.default)("dxHtmlEditor", HtmlEditor); var _default = exports.default = HtmlEditor;