alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
357 lines (354 loc) • 10.9 kB
JavaScript
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
};