UNPKG

@progress/kendo-react-editor

Version:

React Editor enables users to create rich text content through a WYSIWYG interface. KendoReact Editor package

332 lines (331 loc) 11.9 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the package root for more information *------------------------------------------------------------------------------------------- */ import * as a from "react"; import i from "prop-types"; import { ToolbarSeparator as L, ButtonGroup as B, Toolbar as F, toolbarButtons as I } from "@progress/kendo-react-buttons"; import { validatePackage as M, getLicenseMessage as W, classNames as x, WatermarkOverlay as z } from "@progress/kendo-react-common"; import { Plugin as b, PluginKey as _, spacesFix as H, caretColor as j, history as A, dropCursor as V, gapCursor as K, tableEditing as U, cspFix as q, getMark as G, Schema as R, nodes as J, marks as Q, EditorState as X, keymap as S, baseKeymap as Y, EditorView as Z } from "@progress/kendo-editor-common"; import { defaultStyle as $, tablesStyles as tt, rtlStyles as et } from "./config/defaultStyles.mjs"; import { EditorToolsSettings as it } from "./config/toolsSettings.mjs"; import { EditorDialogs as st } from "./dialogs/index.mjs"; import { EditorUtils as w } from "./utils/index.mjs"; import { editorPropsKey as T } from "./utils/props-key.mjs"; import { updateEditorValue as ot } from "./utils/controlled-value.mjs"; import { firefox as O } from "./utils/browser-detection.mjs"; import { packageMetadata as P } from "./package-metadata.mjs"; import { keys as D, messages as nt } from "./messages/index.mjs"; import { provideLocalizationService as rt, registerForLocalization as at } from "@progress/kendo-react-intl"; const { link: N, bold: lt, italic: ht, underline: ct } = it, k = class k extends a.Component { constructor(l) { super(l), this.state = { view: void 0, linkDialog: !1 }, this._element = null, this._contentElement = null, this.iframe = null, this.trOnChange = null, this.htmlOnChange = null, this.showLicenseWatermark = !1, this.focus = () => { this.view && this.view.focus(); }, this.renderDialog = (t, e, s) => this.state[s] && /* @__PURE__ */ a.createElement( t, { view: this.view, settings: e, dir: this.props.dir, onClose: () => this.setState({ [s]: !1 }) } ), this.renderTool = (t, e, s = !1) => { const c = /* @__PURE__ */ a.createElement( t, { view: this.view, dir: this.props.dir, key: e, className: s ? "k-toolbar-button" : void 0 } ); return c.type === L ? /* @__PURE__ */ a.createElement(t, { key: e }) : c; }, this.updateTools = (t, e) => { this.setState({ view: t }); }, this.filterTransaction = (t, e) => { const s = { target: this, transaction: t, state: e }; return (this.props.onExecute && this.props.onExecute.call(void 0, s)) !== !1; }, this.onPasteHtml = (t) => { if (this.props.onPasteHtml && this.pasteEvent) { const e = { target: this, pastedHtml: t, nativeEvent: this.pasteEvent }, s = this.props.onPasteHtml.call(void 0, e); if (this.pasteEvent = void 0, typeof s == "string") return s; } return t; }, this.dispatchTransaction = (t) => { const e = t.docChanged; if (this.props.onChange && e) { this.trOnChange = t; const s = t.doc, c = t.doc.type.schema, d = { target: this, value: s, html: "", transaction: t, schema: c }; Object.defineProperty(d, "html", { get: () => (this.htmlOnChange = w.getHtml({ doc: s, schema: c }), this.htmlOnChange) }), this.props.onChange.call(void 0, d); } this.view && (this.props.value === void 0 || !e) && this.view.updateState(this.view.state.apply(t)); }, this.onFocus = (t, e) => { if (this.props.onFocus) { const s = { target: this, nativeEvent: e }; this.props.onFocus.call(void 0, s); } return !1; }, this.onBlur = (t, e) => { if (this.props.onBlur) { const s = { target: this, nativeEvent: e }; this.props.onBlur.call(void 0, s); } return !1; }, this.onPaste = (t, e) => (this.props.onPasteHtml && (this.pasteEvent = e), !1), this.onIFrameInit = (t) => { if (this.props.onIFrameInit) { const e = { target: this, iframe: t }; this.props.onIFrameInit.call(void 0, e); } return !1; }, this.showLicenseWatermark = !M(P, { component: "Editor" }), this.licenseMessage = W(P); } /** * The value of the Editor. */ get value() { return this.trOnChange !== null ? this.trOnChange.doc : this.props.value !== void 0 ? this.props.value : this.view ? this.view.state.doc : this.props.defaultContent || ""; } /** * Returns the DOM element of the Editor. */ get element() { return this._element; } /** * Returns the content-editable DOM element of the Editor. */ get contentElement() { return this._contentElement; } /** * Returns the `view` object of the Editor. */ get view() { return this._view; } /** * @hidden */ componentDidMount() { (!this.iframe || !O) && this.initialize(); } /** * @hidden */ componentDidUpdate(l) { const { value: t } = this.props, e = this.view; t === void 0 || !e || (ot(e, t, l.value, this.trOnChange, this.htmlOnChange), this.trOnChange = null, this.htmlOnChange = null); } /** * @hidden */ componentWillUnmount() { var t; this.view && this.view.destroy(), this._view = void 0; const l = (t = this.iframe) == null ? void 0 : t.contentWindow; if (l) { this._contentElement && this._contentElement.parentNode && this._contentElement.parentNode.removeChild(this._contentElement); const e = l.document.head; for (; e && e.firstChild; ) e.removeChild(e.firstChild); } } /** * @hidden */ render() { const { tools: l = [], defaultEditMode: t = "iframe", preserveWhitespace: e = "full", style: s, className: c } = this.props, d = rt(this); if (this.view) { const n = T.getState(this.view.state); n.preserveWhitespace = e; } let p = this.props.contentStyle; p === void 0 && (s || {}).height === void 0 && (p = { height: "300px" }); const m = l.map( (n, u) => Array.isArray(n) ? /* @__PURE__ */ a.createElement(B, { key: u, className: "k-toolbar-button-group k-button-group-solid" }, n.map((g, y) => this.renderTool(g, y))) : this.renderTool(n, u, !0) ); return /* @__PURE__ */ a.createElement( "div", { ref: (n) => { this._element = n; }, className: x("k-editor", c, { "k-editor-resizable": this.props.resizable }), dir: this.props.dir, style: s }, m.length > 0 && /* @__PURE__ */ a.createElement(F, { className: "k-editor-toolbar", keyboardNavigation: this.props.keyboardNavigation }, m), t === "iframe" ? /* @__PURE__ */ a.createElement("div", { className: "k-editor-content" }, /* @__PURE__ */ a.createElement( "iframe", { onLoad: O ? () => { this.initialize(); } : void 0, ref: (n) => { this.iframe = n; }, frameBorder: "0", title: d.toLanguageString(D.iframeTitle, nt[D.iframeTitle]), style: p, className: "k-iframe" } )) : /* @__PURE__ */ a.createElement("div", { style: p, className: "k-editor-content" }, /* @__PURE__ */ a.createElement( "div", { ref: (n) => { this._contentElement = n; }, suppressContentEditableWarning: !0, role: "textbox", "aria-labelledby": this.props.ariaLabelledBy, "aria-describedby": this.props.ariaDescribedBy, "aria-label": this.props.ariaLabel } )), this.renderDialog(st.InsertLinkDialog, N, "linkDialog"), this.showLicenseWatermark && /* @__PURE__ */ a.createElement(z, { message: this.licenseMessage }) ); } initialize() { const l = this.iframe && this.iframe.contentWindow; if (l) { const o = l.document, h = o.createElement("meta"); h.setAttribute("charset", "utf-8"), o.head.appendChild(h), this._contentElement = o.createElement("div"), o.body.appendChild(this._contentElement), this._contentElement.classList.add("k-content"), this.iframe && this.props.onIFrameInit ? this.onIFrameInit(this.iframe) : [$, tt, this.props.dir === "rtl" ? et : void 0].forEach((r) => { if (r) { const f = o.createElement("style"); f.appendChild(o.createTextNode(r)), o.head.appendChild(f); } }); } const t = this._contentElement; if (!t) return; const { preserveWhitespace: e = "full" } = this.props, s = [ // https://prosemirror.net/docs/ref/#state.PluginSpec new b({ view: () => ({ update: this.updateTools }), key: new _("toolbar-tools-update-plugin") }), new b({ filterTransaction: this.filterTransaction, key: new _("onExecute-event-plugin") }), new b({ key: T, state: { init: () => ({ preserveWhitespace: e }), apply: (o, h) => h } }), H(), j(), A(), V(), K(), U(), q() ], c = { ...w.getShortcuts({ types: { listItem: "list_item", hardBreak: "hard_break" }, toolsSettings: { bold: lt, italic: ht, underline: ct } }), "Mod-k": () => { const { linkDialog: o } = this.state, h = this.view; if (h) { const r = h.state, f = r.selection.empty, v = G(r, r.schema.marks[N.mark]); !o && !(f && !v) && this.setState({ linkDialog: !0 }); } return !o; }, "Alt-F10": () => { var h; const o = (h = this.element) == null ? void 0 : h.querySelector(".k-toolbar"); if (o) { const r = o.querySelector(I.join(",")); if (r) return r.focus(), !0; } return !1; } }, { defaultContent: d = "", value: p, onMount: m } = this.props, n = p && typeof p != "string" ? p : w.createDocument(new R({ nodes: J, marks: Q }), p || d, { preserveWhitespace: e }), u = { state: X.create({ plugins: [...s, S(c), S(Y)], doc: n }), transformPastedHTML: this.onPasteHtml, dispatchTransaction: this.dispatchTransaction, handleDOMEvents: { focus: this.onFocus, blur: this.onBlur, paste: this.onPaste }, handleDrop: (o, h, r, f) => { let v = !1; return r.content.nodesBetween(0, r.content.size, (C) => { v = v || C.type.name === "table_caption_external"; }), v; } }, g = { plugins: s, shortcuts: c, target: this, viewProps: u, dom: t }, y = this._view = m && m.call(void 0, g) || new Z({ mount: t }, u); this.setState({ view: y }); } }; k.propTypes = { defaultContent: i.string, value: i.oneOfType([i.object, i.string]), defaultEditMode: i.oneOf(["iframe", "div"]), contentStyle: i.object, dir: i.string, className: i.string, ariaDescribedBy: i.string, ariaLabelledBy: i.string, ariaLabel: i.string, style: i.object, tools: i.arrayOf(i.any), keyboardNavigation: i.bool, resizable: i.bool, preserveWhitespace: i.oneOf([!0, !1, "full"]), onMount: i.func, onFocus: i.func, onBlur: i.func, onChange: i.func, onPasteHtml: i.func, onExecute: i.func, onIFrameInit: i.func }; let E = k; at(E); export { E as Editor };