devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
470 lines (468 loc) • 18.7 kB
JavaScript
/**
* DevExtreme (ui/html_editor/ui.html_editor.js)
* Version: 18.2.18
* Build date: Tue Oct 18 2022
*
* Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var _renderer = require("../../core/renderer");
var _renderer2 = _interopRequireDefault(_renderer);
var _extend = require("../../core/utils/extend");
var _type = require("../../core/utils/type");
var _dom = require("../../core/utils/dom");
var _common = require("../../core/utils/common");
var _component_registrator = require("../../core/component_registrator");
var _component_registrator2 = _interopRequireDefault(_component_registrator);
var _empty_template = require("../widget/empty_template");
var _empty_template2 = _interopRequireDefault(_empty_template);
var _editor = require("../editor/editor");
var _editor2 = _interopRequireDefault(_editor);
var _ui = require("../widget/ui.errors");
var _ui2 = _interopRequireDefault(_ui);
var _callbacks = require("../../core/utils/callbacks");
var _callbacks2 = _interopRequireDefault(_callbacks);
var _deferred = require("../../core/utils/deferred");
var _quill_registrator = require("./quill_registrator");
var _quill_registrator2 = _interopRequireDefault(_quill_registrator);
require("./converters/delta");
var _converterController = require("./converterController");
var _converterController2 = _interopRequireDefault(_converterController);
var _wordLists = require("./matchers/wordLists");
var _wordLists2 = _interopRequireDefault(_wordLists);
var _textDecoration = require("./matchers/textDecoration");
var _textDecoration2 = _interopRequireDefault(_textDecoration);
var _formDialog = require("./ui/formDialog");
var _formDialog2 = _interopRequireDefault(_formDialog);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
"default": obj
}
}
var HTML_EDITOR_CLASS = "dx-htmleditor";
var QUILL_CONTAINER_CLASS = "dx-quill-container";
var QUILL_CLIPBOARD_CLASS = "ql-clipboard";
var HTML_EDITOR_SUBMIT_ELEMENT_CLASS = "dx-htmleditor-submit-element";
var HTML_EDITOR_CONTENT_CLASS = "dx-htmleditor-content";
var MARKDOWN_VALUE_TYPE = "markdown";
var ANONYMOUS_TEMPLATE_NAME = "htmlContent";
var HtmlEditor = _editor2.default.inherit({
_getDefaultOptions: function() {
return (0, _extend.extend)(this.callBase(), {
focusStateEnabled: true,
valueType: "html",
placeholder: "",
toolbar: null,
variables: null,
formDialogOptions: null
})
},
_init: function() {
this.callBase();
this._cleanCallback = (0, _callbacks2.default)();
this._contentInitializedCallback = (0, _callbacks2.default)()
},
_getAnonymousTemplateName: function() {
return ANONYMOUS_TEMPLATE_NAME
},
_initTemplates: function() {
this.callBase();
this._defaultTemplates[ANONYMOUS_TEMPLATE_NAME] = new _empty_template2.default(this)
},
_focusTarget: function() {
return this.$element().find("." + HTML_EDITOR_CONTENT_CLASS)
},
_focusInHandler: function(_ref) {
var relatedTarget = _ref.relatedTarget;
if (this._shouldSkipFocusEvent(relatedTarget)) {
return
}
this._toggleFocusClass(true, this.$element());
this.callBase.apply(this, arguments)
},
_focusOutHandler: function(_ref2) {
var relatedTarget = _ref2.relatedTarget;
if (this._shouldSkipFocusEvent(relatedTarget)) {
return
}
this._toggleFocusClass(false, this.$element());
this.callBase.apply(this, arguments)
},
_shouldSkipFocusEvent: function(relatedTarget) {
return (0, _renderer2.default)(relatedTarget).hasClass(QUILL_CLIPBOARD_CLASS)
},
_initMarkup: function() {
this._$htmlContainer = (0, _renderer2.default)("<div>").addClass(QUILL_CONTAINER_CLASS);
this.$element().addClass(HTML_EDITOR_CLASS).wrapInner(this._$htmlContainer);
var template = this._getTemplate(ANONYMOUS_TEMPLATE_NAME);
var transclude = true;
this._$templateResult = template && template.render({
container: (0, _dom.getPublicElement)(this._$htmlContainer),
noModel: true,
transclude: transclude
});
this._renderSubmitElement();
this.callBase();
this._updateContainerMarkup()
},
_renderSubmitElement: function() {
this._$submitElement = (0, _renderer2.default)("<textarea>").addClass(HTML_EDITOR_SUBMIT_ELEMENT_CLASS).attr("hidden", true).appendTo(this.$element());
this._setSubmitValue(this.option("value"))
},
_setSubmitValue: function(value) {
this._getSubmitElement().val(value)
},
_getSubmitElement: function() {
return this._$submitElement
},
_removeXSSVulnerableHtml: function(value) {
var $frame = (0, _renderer2.default)("<iframe>").css("display", "none").attr({
id: "xss-frame",
sandbox: "allow-same-origin"
}).appendTo("body");
var frame = $frame.get(0);
var frameWindow = frame.contentWindow;
var frameDocument = frameWindow.document;
var frameDocumentBody = frameDocument.body;
frameDocumentBody.innerHTML = value;
var removeInlineHandlers = function removeInlineHandlers(element) {
if (element.attributes) {
for (var i = 0; i < element.attributes.length; i++) {
var name = element.attributes[i].name;
if (0 === name.indexOf("on")) {
element.removeAttribute(name)
}
}
}
if (element.childNodes) {
for (var _i = 0; _i < element.childNodes.length; _i++) {
removeInlineHandlers(element.childNodes[_i])
}
}
};
removeInlineHandlers(frameDocumentBody);
(0, _renderer2.default)(frameDocumentBody).find("script").remove();
var sanitizedHtml = frameDocumentBody.innerHTML;
$frame.remove();
return sanitizedHtml
},
_updateContainerMarkup: function() {
var markup = this.option("value");
if (this._isMarkdownValue()) {
this._prepareMarkdownConverter();
markup = this._markdownConverter.toHtml(markup)
}
if (markup) {
var sanitizedMarkup = this._removeXSSVulnerableHtml(markup);
this._$htmlContainer.html(sanitizedMarkup)
}
},
_prepareMarkdownConverter: function() {
var MarkdownConverter = _converterController2.default.getConverter("markdown");
if (MarkdownConverter) {
this._markdownConverter = new MarkdownConverter
} else {
throw _ui2.default.Error("E1051", "markdown")
}
},
_render: function() {
this._prepareConverters();
this.callBase()
},
_prepareQuillRegistrator: function() {
if (!this._quillRegistrator) {
this._quillRegistrator = new _quill_registrator2.default
}
},
_getRegistrator: function() {
this._prepareQuillRegistrator();
return this._quillRegistrator
},
_prepareConverters: function() {
if (!this._deltaConverter) {
var DeltaConverter = _converterController2.default.getConverter("delta");
if (DeltaConverter) {
this._deltaConverter = new DeltaConverter
}
}
if (this.option("valueType") === MARKDOWN_VALUE_TYPE && !this._markdownConverter) {
this._prepareMarkdownConverter()
}
},
_renderContentImpl: function() {
this._contentRenderedDeferred = new _deferred.Deferred;
var renderContentPromise = this._contentRenderedDeferred.promise();
this.callBase();
this._renderHtmlEditor();
this._renderFormDialog();
return renderContentPromise
},
_renderHtmlEditor: function() {
var _this = this;
var modulesConfig = this._getModulesConfig();
this._quillInstance = this._getRegistrator().createEditor(this._$htmlContainer[0], {
placeholder: this.option("placeholder"),
readOnly: this.option("readOnly") || this.option("disabled"),
modules: modulesConfig,
theme: "basic"
});
this._deltaConverter.setQuillInstance(this._quillInstance);
this._textChangeHandlerWithContext = this._textChangeHandler.bind(this);
this._quillInstance.on("text-change", this._textChangeHandlerWithContext);
if (this._hasTranscludedContent()) {
this._updateContentTask = (0, _common.executeAsync)(function() {
_this._applyTranscludedContent()
})
} else {
this._finalizeContentRendering()
}
},
_applyTranscludedContent: function() {
var markup = this._deltaConverter.toHtml();
var newDelta = this._quillInstance.clipboard.convert(markup);
if (newDelta.ops.length) {
this._quillInstance.setContents(newDelta)
} else {
this._finalizeContentRendering()
}
},
_hasTranscludedContent: function() {
return this._$templateResult && this._$templateResult.length
},
_getModulesConfig: function() {
var quill = this._getRegistrator().getQuill();
var wordListMatcher = (0, _wordLists2.default)(quill);
var modulesConfig = (0, _extend.extend)({
toolbar: this._getModuleConfigByOption("toolbar"),
variables: this._getModuleConfigByOption("variables"),
dropImage: this._getBaseModuleConfig(),
clipboard: {
matchVisual: false,
matchers: [
["p.MsoListParagraphCxSpFirst", wordListMatcher],
["p.MsoListParagraphCxSpMiddle", wordListMatcher],
["p.MsoListParagraphCxSpLast", wordListMatcher],
[Node.ELEMENT_NODE, (0, _textDecoration2.default)(quill)]
]
}
}, this._getCustomModules());
return modulesConfig
},
_getModuleConfigByOption: function(userOptionName) {
var userConfig = this.option(userOptionName);
if (!(0, _type.isDefined)(userConfig)) {
return
}
return (0, _extend.extend)(this._getBaseModuleConfig(), userConfig)
},
_getBaseModuleConfig: function() {
return {
editorInstance: this
}
},
_getCustomModules: function() {
var _this2 = this;
var modules = {};
var moduleNames = this._getRegistrator().getRegisteredModuleNames();
moduleNames.forEach(function(modulePath) {
modules[modulePath] = _this2._getBaseModuleConfig()
});
return modules
},
_textChangeHandler: function(newDelta, oldDelta, source) {
var htmlMarkup = this._deltaConverter.toHtml();
var value = this._isMarkdownValue() ? this._updateValueByType(MARKDOWN_VALUE_TYPE, htmlMarkup) : htmlMarkup;
if (this.option("value") !== value) {
this._isEditorUpdating = true;
this.option("value", value)
}
this._finalizeContentRendering()
},
_finalizeContentRendering: function() {
if (this._contentRenderedDeferred) {
this.clearHistory();
this._contentInitializedCallback.fire();
this._contentRenderedDeferred.resolve();
this._contentRenderedDeferred = void 0
}
},
_updateValueByType: function(valueType, value) {
var converter = this._markdownConverter;
if (!(0, _type.isDefined)(converter)) {
return
}
var currentValue = value || this.option("value");
return valueType === MARKDOWN_VALUE_TYPE ? converter.toMarkdown(currentValue) : converter.toHtml(currentValue)
},
_isMarkdownValue: function() {
return this.option("valueType") === MARKDOWN_VALUE_TYPE
},
_resetEnabledState: function() {
if (this._quillInstance) {
var isEnabled = !(this.option("readOnly") || this.option("disabled"));
this._quillInstance.enable(isEnabled)
}
},
_renderFormDialog: function() {
var userOptions = (0, _extend.extend)(true, {
width: "auto",
height: "auto",
closeOnOutsideClick: true
}, this.option("formDialogOptions"));
this._formDialog = new _formDialog2.default(this, userOptions)
},
_optionChanged: function(args) {
switch (args.name) {
case "value":
var sanitizedValue = args.value ? this._removeXSSVulnerableHtml(args.value) : args.value;
if (this._quillInstance) {
if (this._isEditorUpdating) {
this._isEditorUpdating = false
} else {
var updatedValue = this._isMarkdownValue() ? this._updateValueByType("HTML", sanitizedValue) : sanitizedValue;
this._updateHtmlContent(updatedValue)
}
} else {
this._$htmlContainer.html(sanitizedValue)
}
this._setSubmitValue(sanitizedValue);
this.callBase(args);
break;
case "placeholder":
case "variables":
case "toolbar":
this._invalidate();
break;
case "valueType":
this._prepareConverters();
var newValue = this._updateValueByType(args.value);
if ("html" === args.value && this._quillInstance) {
this._updateHtmlContent(newValue)
} else {
this.option("value", newValue)
}
break;
case "readOnly":
case "disabled":
this.callBase(args);
this._resetEnabledState();
break;
case "formDialogOptions":
this._renderFormDialog();
break;
case "width":
this.callBase(args);
this._repaintToolbar();
break;
default:
this.callBase(args)
}
},
_repaintToolbar: function() {
var toolbar = this._quillInstance.getModule("toolbar");
toolbar && toolbar.repaint()
},
_updateHtmlContent: function(newMarkup) {
var newDelta = this._quillInstance.clipboard.convert(newMarkup);
this._quillInstance.setContents(newDelta)
},
_clean: function() {
if (this._quillInstance) {
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: function(methodName, args) {
if (this._quillInstance) {
return this._quillInstance[methodName].apply(this._quillInstance, args)
}
},
_applyQuillHistoryMethod: function(methodName) {
if (this._quillInstance && this._quillInstance.history) {
this._quillInstance.history[methodName]()
}
},
addCleanCallback: function(callback) {
this._cleanCallback.add(callback)
},
addContentInitializedCallback: function(callback) {
this._contentInitializedCallback.add(callback)
},
registerModules: function(modules) {
this._getRegistrator().registerModules(modules);
if (this._quillInstance) {
this.repaint()
}
},
getModule: function(modulePath) {
return this._getRegistrator().getQuill().import(modulePath)
},
getQuillInstance: function() {
return this._quillInstance
},
getSelection: function() {
return this._applyQuillMethod("getSelection")
},
setSelection: function(index, length) {
this._applyQuillMethod("setSelection", 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")
},
undo: function() {
this._applyQuillHistoryMethod("undo")
},
redo: function() {
this._applyQuillHistoryMethod("redo")
},
getLength: function() {
return this._applyQuillMethod("getLength")
},
"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")
}
});
(0, _component_registrator2.default)("dxHtmlEditor", HtmlEditor);
module.exports = HtmlEditor;