UNPKG

alinea

Version:

[![npm](https://img.shields.io/npm/v/alinea.svg)](https://npmjs.org/package/alinea) [![install size](https://packagephobia.com/badge?p=alinea)](https://packagephobia.com/result?p=alinea)

357 lines (354 loc) 10.9 kB
import { FloatingMenuPlugin } from "./chunk-I5C4WAC4.js"; import { Editor, NodeView } from "./chunk-MDIOFKJQ.js"; // node_modules/@tiptap/react/dist/index.js import React, { useState, useRef, useEffect, createContext, useContext } from "react"; import ReactDOM, { flushSync } from "react-dom"; var Portals = ({ renderers }) => { return React.createElement(React.Fragment, null, Object.entries(renderers).map(([key, renderer]) => { return ReactDOM.createPortal(renderer.reactElement, renderer.element, key); })); }; var PureEditorContent = class extends React.Component { constructor(props) { super(props); this.editorContentRef = React.createRef(); this.initialized = false; this.state = { renderers: {} }; } componentDidMount() { this.init(); } componentDidUpdate() { this.init(); } init() { const { editor } = this.props; if (editor && editor.options.element) { if (editor.contentComponent) { return; } const element = this.editorContentRef.current; element.append(...editor.options.element.childNodes); editor.setOptions({ element }); editor.contentComponent = this; editor.createNodeViews(); this.initialized = true; } } maybeFlushSync(fn) { if (this.initialized) { flushSync(fn); } else { fn(); } } setRenderer(id, renderer) { this.maybeFlushSync(() => { this.setState(({ renderers }) => ({ renderers: { ...renderers, [id]: renderer } })); }); } removeRenderer(id) { this.maybeFlushSync(() => { this.setState(({ renderers }) => { const nextRenderers = { ...renderers }; delete nextRenderers[id]; return { renderers: nextRenderers }; }); }); } componentWillUnmount() { const { editor } = this.props; if (!editor) { return; } this.initialized = false; if (!editor.isDestroyed) { editor.view.setProps({ nodeViews: {} }); } editor.contentComponent = null; if (!editor.options.element.firstChild) { return; } const newElement = document.createElement("div"); newElement.append(...editor.options.element.childNodes); editor.setOptions({ element: newElement }); } render() { const { editor, ...rest } = this.props; return React.createElement( React.Fragment, null, React.createElement("div", { ref: this.editorContentRef, ...rest }), React.createElement(Portals, { renderers: this.state.renderers }) ); } }; var EditorContentWithKey = (props) => { const key = React.useMemo(() => { return Math.floor(Math.random() * 4294967295).toString(); }, [props.editor]); return React.createElement(PureEditorContent, { key, ...props }); }; var EditorContent = React.memo(EditorContentWithKey); var Editor2 = class extends Editor { constructor() { super(...arguments); this.contentComponent = null; } }; var EditorContext = createContext({ editor: null }); var EditorConsumer = EditorContext.Consumer; var useCurrentEditor = () => useContext(EditorContext); var FloatingMenu = (props) => { const [element, setElement] = useState(null); const { editor: currentEditor } = useCurrentEditor(); useEffect(() => { var _a; if (!element) { return; } if (((_a = props.editor) === null || _a === void 0 ? void 0 : _a.isDestroyed) || (currentEditor === null || currentEditor === void 0 ? void 0 : currentEditor.isDestroyed)) { return; } const { pluginKey = "floatingMenu", editor, tippyOptions = {}, shouldShow = null } = props; const menuEditor = editor || currentEditor; if (!menuEditor) { console.warn("FloatingMenu component is not rendered inside of an editor component or does not have editor prop."); return; } const plugin = FloatingMenuPlugin({ pluginKey, editor: menuEditor, element, tippyOptions, shouldShow }); menuEditor.registerPlugin(plugin); return () => menuEditor.unregisterPlugin(pluginKey); }, [ props.editor, currentEditor, element ]); return React.createElement("div", { ref: setElement, className: props.className, style: { visibility: "hidden" } }, props.children); }; var ReactNodeViewContext = createContext({ onDragStart: void 0 }); var useReactNodeView = () => useContext(ReactNodeViewContext); var NodeViewWrapper = React.forwardRef((props, ref) => { const { onDragStart } = useReactNodeView(); const Tag = props.as || "div"; return React.createElement(Tag, { ...props, ref, "data-node-view-wrapper": "", onDragStart, style: { whiteSpace: "normal", ...props.style } }); }); function isClassComponent(Component) { return !!(typeof Component === "function" && Component.prototype && Component.prototype.isReactComponent); } function isForwardRefComponent(Component) { var _a; return !!(typeof Component === "object" && ((_a = Component.$$typeof) === null || _a === void 0 ? void 0 : _a.toString()) === "Symbol(react.forward_ref)"); } var ReactRenderer = class { constructor(component, { editor, props = {}, as = "div", className = "", attrs }) { this.ref = null; this.id = Math.floor(Math.random() * 4294967295).toString(); this.component = component; this.editor = editor; this.props = props; this.element = document.createElement(as); this.element.classList.add("react-renderer"); if (className) { this.element.classList.add(...className.split(" ")); } if (attrs) { Object.keys(attrs).forEach((key) => { this.element.setAttribute(key, attrs[key]); }); } this.render(); } render() { var _a, _b; const Component = this.component; const props = this.props; if (isClassComponent(Component) || isForwardRefComponent(Component)) { props.ref = (ref) => { this.ref = ref; }; } this.reactElement = React.createElement(Component, { ...props }); (_b = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) === null || _b === void 0 ? void 0 : _b.setRenderer(this.id, this); } updateProps(props = {}) { this.props = { ...this.props, ...props }; this.render(); } destroy() { var _a, _b; (_b = (_a = this.editor) === null || _a === void 0 ? void 0 : _a.contentComponent) === null || _b === void 0 ? void 0 : _b.removeRenderer(this.id); } }; var ReactNodeView = class extends NodeView { mount() { const props = { editor: this.editor, node: this.node, decorations: this.decorations, selected: false, extension: this.extension, getPos: () => this.getPos(), updateAttributes: (attributes = {}) => this.updateAttributes(attributes), deleteNode: () => this.deleteNode() }; if (!this.component.displayName) { const capitalizeFirstChar = (string) => { return string.charAt(0).toUpperCase() + string.substring(1); }; this.component.displayName = capitalizeFirstChar(this.extension.name); } const ReactNodeViewProvider = (componentProps) => { const Component = this.component; const onDragStart = this.onDragStart.bind(this); const nodeViewContentRef = (element) => { if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) { element.appendChild(this.contentDOMElement); } }; return React.createElement( React.Fragment, null, React.createElement( ReactNodeViewContext.Provider, { value: { onDragStart, nodeViewContentRef } }, React.createElement(Component, { ...componentProps }) ) ); }; ReactNodeViewProvider.displayName = "ReactNodeView"; this.contentDOMElement = this.node.isLeaf ? null : document.createElement(this.node.isInline ? "span" : "div"); if (this.contentDOMElement) { this.contentDOMElement.style.whiteSpace = "inherit"; } let as = this.node.isInline ? "span" : "div"; if (this.options.as) { as = this.options.as; } const { className = "" } = this.options; this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this); this.editor.on("selectionUpdate", this.handleSelectionUpdate); this.renderer = new ReactRenderer(ReactNodeViewProvider, { editor: this.editor, props, as, className: `node-${this.node.type.name} ${className}`.trim(), attrs: this.options.attrs }); } get dom() { var _a; if (this.renderer.element.firstElementChild && !((_a = this.renderer.element.firstElementChild) === null || _a === void 0 ? void 0 : _a.hasAttribute("data-node-view-wrapper"))) { throw Error("Please use the NodeViewWrapper component for your node view."); } return this.renderer.element; } get contentDOM() { if (this.node.isLeaf) { return null; } return this.contentDOMElement; } handleSelectionUpdate() { const { from, to } = this.editor.state.selection; if (from <= this.getPos() && to >= this.getPos() + this.node.nodeSize) { this.selectNode(); } else { this.deselectNode(); } } update(node, decorations) { const updateProps = (props) => { this.renderer.updateProps(props); }; if (node.type !== this.node.type) { return false; } if (typeof this.options.update === "function") { const oldNode = this.node; const oldDecorations = this.decorations; this.node = node; this.decorations = decorations; return this.options.update({ oldNode, oldDecorations, newNode: node, newDecorations: decorations, updateProps: () => updateProps({ node, decorations }) }); } if (node === this.node && this.decorations === decorations) { return true; } this.node = node; this.decorations = decorations; updateProps({ node, decorations }); return true; } selectNode() { this.renderer.updateProps({ selected: true }); this.renderer.element.classList.add("ProseMirror-selectednode"); } deselectNode() { this.renderer.updateProps({ selected: false }); this.renderer.element.classList.remove("ProseMirror-selectednode"); } destroy() { this.renderer.destroy(); this.editor.off("selectionUpdate", this.handleSelectionUpdate); this.contentDOMElement = null; } }; function ReactNodeViewRenderer(component, options) { return (props) => { if (!props.editor.contentComponent) { return {}; } return new ReactNodeView(component, props, options); }; } export { EditorContent, Editor2 as Editor, FloatingMenu, NodeViewWrapper, ReactNodeViewRenderer };