Odoo18-Base/addons/html_editor/static/src/wysiwyg.js
2025-01-06 10:57:38 +07:00

124 lines
4.7 KiB
JavaScript

import { Component, onMounted, onWillDestroy, useRef, useState, useSubEnv } from "@odoo/owl";
import { Editor } from "./editor";
import { Toolbar } from "./main/toolbar/toolbar";
import { useChildRef, useSpellCheck } from "@web/core/utils/hooks";
import { LocalOverlayContainer } from "./local_overlay_container";
import { uniqueId } from "@web/core/utils/functions";
/**
* @typedef { import("./editor").EditorConfig } EditorConfig
**/
function copyCssRules(sourceDoc, targetDoc) {
for (const sheet of sourceDoc.styleSheets) {
const rules = [];
for (const r of sheet.cssRules) {
rules.push(r.cssText);
}
const cssRules = rules.join(" ");
const styleTag = targetDoc.createElement("style");
styleTag.appendChild(targetDoc.createTextNode(cssRules));
targetDoc.head.appendChild(styleTag);
}
}
export class Wysiwyg extends Component {
static template = "html_editor.Wysiwyg";
static components = { Toolbar, LocalOverlayContainer };
static props = {
config: { type: Object, optional: true },
class: { type: String, optional: true },
contentClass: { type: String, optional: true }, // on editable element
style: { type: String, optional: true },
toolbar: { type: Boolean, optional: true },
iframe: { type: Boolean, optional: true },
copyCss: { type: Boolean, optional: true },
onLoad: { type: Function, optional: true },
onBlur: { type: Function, optional: true },
dynamicPlaceholder: { type: Boolean, optional: true },
};
static defaultProps = {
onLoad: () => {},
onBlur: () => {},
};
setup() {
this.state = useState({
showToolbar: false,
});
this.overlayRef = useChildRef();
useSubEnv({
localOverlayContainerKey: uniqueId("wysiwyg"),
});
const contentRef = useRef("content");
this.editor = this.props.editor;
const config = this.getEditorConfig();
this.editor = new Editor(config, this.env.services);
this.props.onLoad(this.editor);
useSpellCheck({
refName: "content",
});
onMounted(() => {
// now that component is mounted, editor is attached to el, and
// plugins are started, so we can allow the toolbar to be displayed
this.state.showToolbar = true;
/** @type { any } **/
const el = contentRef.el;
if (el.tagName === "IFRAME") {
// grab the inner body instead
const attachEditor = () => {
if (!this.editor.isDestroyed) {
if (this.props.copyCss) {
copyCssRules(document, el.contentDocument);
}
const additionalClasses = el.dataset.class?.trim().split(" ");
if (additionalClasses) {
for (const c of additionalClasses) {
el.contentDocument.body.classList.add(c);
}
}
this.editor.attachTo(el.contentDocument.body);
}
};
if (el.contentDocument.readyState === "complete") {
attachEditor();
} else {
// in firefox, iframe is not immediately available. we need to wait
// for it to be ready before mounting editor
el.addEventListener(
"load",
() => {
attachEditor();
this.render();
},
{ once: true }
);
}
} else {
this.editor.attachTo(el);
}
});
onWillDestroy(() => this.editor.destroy(true));
}
getEditorConfig() {
return {
...this.props.config,
// TODO ABD TODO @phoenix: check if there is too much info in the wysiwyg env.
// i.e.: env has X because of parent component,
// embedded component descendant sometimes uses X from env which is set conditionally:
// -> it will override the one one from the parent => OK.
// -> it will not => the embedded component still has X in env because of its ancestors => Issue.
embeddedComponentInfo: { app: this.__owl__.app, env: this.env },
localOverlayContainers: {
key: this.env.localOverlayContainerKey,
ref: this.overlayRef,
},
disableFloatingToolbar: this.props.toolbar,
};
}
}