| import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu'; | |
| import React, { forwardRef, useState, useDebugValue, useLayoutEffect, useEffect, useRef, createContext, useContext, version, createRef, memo, createElement } from 'react'; | |
| import ReactDOM, { flushSync } from 'react-dom'; | |
| import { Editor, NodeView, getRenderedAttributes } from '@tiptap/core'; | |
| export * from '@tiptap/core'; | |
| import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu'; | |
| function getDefaultExportFromCjs (x) { | |
| return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; | |
| } | |
| var shim = {exports: {}}; | |
| var useSyncExternalStoreShim_production_min = {}; | |
| /** | |
| * @license React | |
| * use-sync-external-store-shim.production.min.js | |
| * | |
| * Copyright (c) Facebook, Inc. and its affiliates. | |
| * | |
| * This source code is licensed under the MIT license found in the | |
| * LICENSE file in the root directory of this source tree. | |
| */ | |
| var hasRequiredUseSyncExternalStoreShim_production_min; | |
| function requireUseSyncExternalStoreShim_production_min () { | |
| if (hasRequiredUseSyncExternalStoreShim_production_min) return useSyncExternalStoreShim_production_min; | |
| hasRequiredUseSyncExternalStoreShim_production_min = 1; | |
| var e=React;function h(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}var k="function"===typeof Object.is?Object.is:h,l=e.useState,m=e.useEffect,n=e.useLayoutEffect,p=e.useDebugValue;function q(a,b){var d=b(),f=l({inst:{value:d,getSnapshot:b}}),c=f[0].inst,g=f[1];n(function(){c.value=d;c.getSnapshot=b;r(c)&&g({inst:c});},[a,d,b]);m(function(){r(c)&&g({inst:c});return a(function(){r(c)&&g({inst:c});})},[a]);p(d);return d} | |
| function r(a){var b=a.getSnapshot;a=a.value;try{var d=b();return !k(a,d)}catch(f){return !0}}function t(a,b){return b()}var u="undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement?t:q;useSyncExternalStoreShim_production_min.useSyncExternalStore=void 0!==e.useSyncExternalStore?e.useSyncExternalStore:u; | |
| return useSyncExternalStoreShim_production_min; | |
| } | |
| var useSyncExternalStoreShim_development = {}; | |
| /** | |
| * @license React | |
| * use-sync-external-store-shim.development.js | |
| * | |
| * Copyright (c) Facebook, Inc. and its affiliates. | |
| * | |
| * This source code is licensed under the MIT license found in the | |
| * LICENSE file in the root directory of this source tree. | |
| */ | |
| var hasRequiredUseSyncExternalStoreShim_development; | |
| function requireUseSyncExternalStoreShim_development () { | |
| if (hasRequiredUseSyncExternalStoreShim_development) return useSyncExternalStoreShim_development; | |
| hasRequiredUseSyncExternalStoreShim_development = 1; | |
| if (process.env.NODE_ENV !== "production") { | |
| (function() { | |
| /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ | |
| if ( | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === | |
| 'function' | |
| ) { | |
| __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); | |
| } | |
| var React$1 = React; | |
| var ReactSharedInternals = React$1.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; | |
| function error(format) { | |
| { | |
| { | |
| for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | |
| args[_key2 - 1] = arguments[_key2]; | |
| } | |
| printWarning('error', format, args); | |
| } | |
| } | |
| } | |
| function printWarning(level, format, args) { | |
| // When changing this logic, you might want to also | |
| // update consoleWithStackDev.www.js as well. | |
| { | |
| var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; | |
| var stack = ReactDebugCurrentFrame.getStackAddendum(); | |
| if (stack !== '') { | |
| format += '%s'; | |
| args = args.concat([stack]); | |
| } // eslint-disable-next-line react-internal/safe-string-coercion | |
| var argsWithFormat = args.map(function (item) { | |
| return String(item); | |
| }); // Careful: RN currently depends on this prefix | |
| argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it | |
| // breaks IE9: https://github.com/facebook/react/issues/13610 | |
| // eslint-disable-next-line react-internal/no-production-logging | |
| Function.prototype.apply.call(console[level], console, argsWithFormat); | |
| } | |
| } | |
| /** | |
| * inlined Object.is polyfill to avoid requiring consumers ship their own | |
| * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is | |
| */ | |
| function is(x, y) { | |
| return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare | |
| ; | |
| } | |
| var objectIs = typeof Object.is === 'function' ? Object.is : is; | |
| // dispatch for CommonJS interop named imports. | |
| var useState = React$1.useState, | |
| useEffect = React$1.useEffect, | |
| useLayoutEffect = React$1.useLayoutEffect, | |
| useDebugValue = React$1.useDebugValue; | |
| var didWarnOld18Alpha = false; | |
| var didWarnUncachedGetSnapshot = false; // Disclaimer: This shim breaks many of the rules of React, and only works | |
| // because of a very particular set of implementation details and assumptions | |
| // -- change any one of them and it will break. The most important assumption | |
| // is that updates are always synchronous, because concurrent rendering is | |
| // only available in versions of React that also have a built-in | |
| // useSyncExternalStore API. And we only use this shim when the built-in API | |
| // does not exist. | |
| // | |
| // Do not assume that the clever hacks used by this hook also work in general. | |
| // The point of this shim is to replace the need for hacks by other libraries. | |
| function useSyncExternalStore(subscribe, getSnapshot, // Note: The shim does not use getServerSnapshot, because pre-18 versions of | |
| // React do not expose a way to check if we're hydrating. So users of the shim | |
| // will need to track that themselves and return the correct value | |
| // from `getSnapshot`. | |
| getServerSnapshot) { | |
| { | |
| if (!didWarnOld18Alpha) { | |
| if (React$1.startTransition !== undefined) { | |
| didWarnOld18Alpha = true; | |
| error('You are using an outdated, pre-release alpha of React 18 that ' + 'does not support useSyncExternalStore. The ' + 'use-sync-external-store shim will not work correctly. Upgrade ' + 'to a newer pre-release.'); | |
| } | |
| } | |
| } // Read the current snapshot from the store on every render. Again, this | |
| // breaks the rules of React, and only works here because of specific | |
| // implementation details, most importantly that updates are | |
| // always synchronous. | |
| var value = getSnapshot(); | |
| { | |
| if (!didWarnUncachedGetSnapshot) { | |
| var cachedValue = getSnapshot(); | |
| if (!objectIs(value, cachedValue)) { | |
| error('The result of getSnapshot should be cached to avoid an infinite loop'); | |
| didWarnUncachedGetSnapshot = true; | |
| } | |
| } | |
| } // Because updates are synchronous, we don't queue them. Instead we force a | |
| // re-render whenever the subscribed state changes by updating an some | |
| // arbitrary useState hook. Then, during render, we call getSnapshot to read | |
| // the current value. | |
| // | |
| // Because we don't actually use the state returned by the useState hook, we | |
| // can save a bit of memory by storing other stuff in that slot. | |
| // | |
| // To implement the early bailout, we need to track some things on a mutable | |
| // object. Usually, we would put that in a useRef hook, but we can stash it in | |
| // our useState hook instead. | |
| // | |
| // To force a re-render, we call forceUpdate({inst}). That works because the | |
| // new object always fails an equality check. | |
| var _useState = useState({ | |
| inst: { | |
| value: value, | |
| getSnapshot: getSnapshot | |
| } | |
| }), | |
| inst = _useState[0].inst, | |
| forceUpdate = _useState[1]; // Track the latest getSnapshot function with a ref. This needs to be updated | |
| // in the layout phase so we can access it during the tearing check that | |
| // happens on subscribe. | |
| useLayoutEffect(function () { | |
| inst.value = value; | |
| inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the | |
| // commit phase if there was an interleaved mutation. In concurrent mode | |
| // this can happen all the time, but even in synchronous mode, an earlier | |
| // effect may have mutated the store. | |
| if (checkIfSnapshotChanged(inst)) { | |
| // Force a re-render. | |
| forceUpdate({ | |
| inst: inst | |
| }); | |
| } | |
| }, [subscribe, value, getSnapshot]); | |
| useEffect(function () { | |
| // Check for changes right before subscribing. Subsequent changes will be | |
| // detected in the subscription handler. | |
| if (checkIfSnapshotChanged(inst)) { | |
| // Force a re-render. | |
| forceUpdate({ | |
| inst: inst | |
| }); | |
| } | |
| var handleStoreChange = function () { | |
| // TODO: Because there is no cross-renderer API for batching updates, it's | |
| // up to the consumer of this library to wrap their subscription event | |
| // with unstable_batchedUpdates. Should we try to detect when this isn't | |
| // the case and print a warning in development? | |
| // The store changed. Check if the snapshot changed since the last time we | |
| // read from the store. | |
| if (checkIfSnapshotChanged(inst)) { | |
| // Force a re-render. | |
| forceUpdate({ | |
| inst: inst | |
| }); | |
| } | |
| }; // Subscribe to the store and return a clean-up function. | |
| return subscribe(handleStoreChange); | |
| }, [subscribe]); | |
| useDebugValue(value); | |
| return value; | |
| } | |
| function checkIfSnapshotChanged(inst) { | |
| var latestGetSnapshot = inst.getSnapshot; | |
| var prevValue = inst.value; | |
| try { | |
| var nextValue = latestGetSnapshot(); | |
| return !objectIs(prevValue, nextValue); | |
| } catch (error) { | |
| return true; | |
| } | |
| } | |
| function useSyncExternalStore$1(subscribe, getSnapshot, getServerSnapshot) { | |
| // Note: The shim does not use getServerSnapshot, because pre-18 versions of | |
| // React do not expose a way to check if we're hydrating. So users of the shim | |
| // will need to track that themselves and return the correct value | |
| // from `getSnapshot`. | |
| return getSnapshot(); | |
| } | |
| var canUseDOM = !!(typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined'); | |
| var isServerEnvironment = !canUseDOM; | |
| var shim = isServerEnvironment ? useSyncExternalStore$1 : useSyncExternalStore; | |
| var useSyncExternalStore$2 = React$1.useSyncExternalStore !== undefined ? React$1.useSyncExternalStore : shim; | |
| useSyncExternalStoreShim_development.useSyncExternalStore = useSyncExternalStore$2; | |
| /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ | |
| if ( | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === | |
| 'function' | |
| ) { | |
| __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); | |
| } | |
| })(); | |
| } | |
| return useSyncExternalStoreShim_development; | |
| } | |
| if (process.env.NODE_ENV === 'production') { | |
| shim.exports = requireUseSyncExternalStoreShim_production_min(); | |
| } else { | |
| shim.exports = requireUseSyncExternalStoreShim_development(); | |
| } | |
| var shimExports = shim.exports; | |
| const mergeRefs = (...refs) => { | |
| return (node) => { | |
| refs.forEach(ref => { | |
| if (typeof ref === 'function') { | |
| ref(node); | |
| } | |
| else if (ref) { | |
| ref.current = node; | |
| } | |
| }); | |
| }; | |
| }; | |
| /** | |
| * This component renders all of the editor's node views. | |
| */ | |
| const Portals = ({ contentComponent, }) => { | |
| // For performance reasons, we render the node view portals on state changes only | |
| const renderers = shimExports.useSyncExternalStore(contentComponent.subscribe, contentComponent.getSnapshot, contentComponent.getServerSnapshot); | |
| // This allows us to directly render the portals without any additional wrapper | |
| return (React.createElement(React.Fragment, null, Object.values(renderers))); | |
| }; | |
| function getInstance() { | |
| const subscribers = new Set(); | |
| let renderers = {}; | |
| return { | |
| /** | |
| * Subscribe to the editor instance's changes. | |
| */ | |
| subscribe(callback) { | |
| subscribers.add(callback); | |
| return () => { | |
| subscribers.delete(callback); | |
| }; | |
| }, | |
| getSnapshot() { | |
| return renderers; | |
| }, | |
| getServerSnapshot() { | |
| return renderers; | |
| }, | |
| /** | |
| * Adds a new NodeView Renderer to the editor. | |
| */ | |
| setRenderer(id, renderer) { | |
| renderers = { | |
| ...renderers, | |
| [id]: ReactDOM.createPortal(renderer.reactElement, renderer.element, id), | |
| }; | |
| subscribers.forEach(subscriber => subscriber()); | |
| }, | |
| /** | |
| * Removes a NodeView Renderer from the editor. | |
| */ | |
| removeRenderer(id) { | |
| const nextRenderers = { ...renderers }; | |
| delete nextRenderers[id]; | |
| renderers = nextRenderers; | |
| subscribers.forEach(subscriber => subscriber()); | |
| }, | |
| }; | |
| } | |
| class PureEditorContent extends React.Component { | |
| constructor(props) { | |
| var _a; | |
| super(props); | |
| this.editorContentRef = React.createRef(); | |
| this.initialized = false; | |
| this.state = { | |
| hasContentComponentInitialized: Boolean((_a = props.editor) === null || _a === void 0 ? void 0 : _a.contentComponent), | |
| }; | |
| } | |
| componentDidMount() { | |
| this.init(); | |
| } | |
| componentDidUpdate() { | |
| this.init(); | |
| } | |
| init() { | |
| const editor = this.props.editor; | |
| if (editor && !editor.isDestroyed && editor.options.element) { | |
| if (editor.contentComponent) { | |
| return; | |
| } | |
| const element = this.editorContentRef.current; | |
| element.append(...editor.options.element.childNodes); | |
| editor.setOptions({ | |
| element, | |
| }); | |
| editor.contentComponent = getInstance(); | |
| // Has the content component been initialized? | |
| if (!this.state.hasContentComponentInitialized) { | |
| // Subscribe to the content component | |
| this.unsubscribeToContentComponent = editor.contentComponent.subscribe(() => { | |
| this.setState(prevState => { | |
| if (!prevState.hasContentComponentInitialized) { | |
| return { | |
| hasContentComponentInitialized: true, | |
| }; | |
| } | |
| return prevState; | |
| }); | |
| // Unsubscribe to previous content component | |
| if (this.unsubscribeToContentComponent) { | |
| this.unsubscribeToContentComponent(); | |
| } | |
| }); | |
| } | |
| editor.createNodeViews(); | |
| this.initialized = true; | |
| } | |
| } | |
| componentWillUnmount() { | |
| const editor = this.props.editor; | |
| if (!editor) { | |
| return; | |
| } | |
| this.initialized = false; | |
| if (!editor.isDestroyed) { | |
| editor.view.setProps({ | |
| nodeViews: {}, | |
| }); | |
| } | |
| if (this.unsubscribeToContentComponent) { | |
| this.unsubscribeToContentComponent(); | |
| } | |
| 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, innerRef, ...rest } = this.props; | |
| return (React.createElement(React.Fragment, null, | |
| React.createElement("div", { ref: mergeRefs(innerRef, this.editorContentRef), ...rest }), | |
| (editor === null || editor === void 0 ? void 0 : editor.contentComponent) && React.createElement(Portals, { contentComponent: editor.contentComponent }))); | |
| } | |
| } | |
| // EditorContent should be re-created whenever the Editor instance changes | |
| const EditorContentWithKey = forwardRef((props, ref) => { | |
| const key = React.useMemo(() => { | |
| return Math.floor(Math.random() * 0xffffffff).toString(); | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, [props.editor]); | |
| // Can't use JSX here because it conflicts with the type definition of Vue's JSX, so use createElement | |
| return React.createElement(PureEditorContent, { | |
| key, | |
| innerRef: ref, | |
| ...props, | |
| }); | |
| }); | |
| const EditorContent = React.memo(EditorContentWithKey); | |
| var react = function equal(a, b) { | |
| if (a === b) return true; | |
| if (a && b && typeof a == 'object' && typeof b == 'object') { | |
| if (a.constructor !== b.constructor) return false; | |
| var length, i, keys; | |
| if (Array.isArray(a)) { | |
| length = a.length; | |
| if (length != b.length) return false; | |
| for (i = length; i-- !== 0;) | |
| if (!equal(a[i], b[i])) return false; | |
| return true; | |
| } | |
| if ((a instanceof Map) && (b instanceof Map)) { | |
| if (a.size !== b.size) return false; | |
| for (i of a.entries()) | |
| if (!b.has(i[0])) return false; | |
| for (i of a.entries()) | |
| if (!equal(i[1], b.get(i[0]))) return false; | |
| return true; | |
| } | |
| if ((a instanceof Set) && (b instanceof Set)) { | |
| if (a.size !== b.size) return false; | |
| for (i of a.entries()) | |
| if (!b.has(i[0])) return false; | |
| return true; | |
| } | |
| if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { | |
| length = a.length; | |
| if (length != b.length) return false; | |
| for (i = length; i-- !== 0;) | |
| if (a[i] !== b[i]) return false; | |
| return true; | |
| } | |
| if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; | |
| if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); | |
| if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); | |
| keys = Object.keys(a); | |
| length = keys.length; | |
| if (length !== Object.keys(b).length) return false; | |
| for (i = length; i-- !== 0;) | |
| if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; | |
| for (i = length; i-- !== 0;) { | |
| var key = keys[i]; | |
| if (key === '_owner' && a.$$typeof) { | |
| // React-specific: avoid traversing React elements' _owner. | |
| // _owner contains circular references | |
| // and is not needed when comparing the actual elements (and not their owners) | |
| continue; | |
| } | |
| if (!equal(a[key], b[key])) return false; | |
| } | |
| return true; | |
| } | |
| // true if both NaN, false otherwise | |
| return a!==a && b!==b; | |
| }; | |
| var deepEqual = /*@__PURE__*/getDefaultExportFromCjs(react); | |
| var withSelector = {exports: {}}; | |
| var withSelector_production_min = {}; | |
| /** | |
| * @license React | |
| * use-sync-external-store-shim/with-selector.production.min.js | |
| * | |
| * Copyright (c) Facebook, Inc. and its affiliates. | |
| * | |
| * This source code is licensed under the MIT license found in the | |
| * LICENSE file in the root directory of this source tree. | |
| */ | |
| var hasRequiredWithSelector_production_min; | |
| function requireWithSelector_production_min () { | |
| if (hasRequiredWithSelector_production_min) return withSelector_production_min; | |
| hasRequiredWithSelector_production_min = 1; | |
| var h=React,n=shimExports;function p(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}var q="function"===typeof Object.is?Object.is:p,r=n.useSyncExternalStore,t=h.useRef,u=h.useEffect,v=h.useMemo,w=h.useDebugValue; | |
| withSelector_production_min.useSyncExternalStoreWithSelector=function(a,b,e,l,g){var c=t(null);if(null===c.current){var f={hasValue:!1,value:null};c.current=f;}else f=c.current;c=v(function(){function a(a){if(!c){c=!0;d=a;a=l(a);if(void 0!==g&&f.hasValue){var b=f.value;if(g(b,a))return k=b}return k=a}b=k;if(q(d,a))return b;var e=l(a);if(void 0!==g&&g(b,e))return b;d=a;return k=e}var c=!1,d,k,m=void 0===e?null:e;return [function(){return a(b())},null===m?void 0:function(){return a(m())}]},[b,e,l,g]);var d=r(a,c[0],c[1]); | |
| u(function(){f.hasValue=!0;f.value=d;},[d]);w(d);return d}; | |
| return withSelector_production_min; | |
| } | |
| var withSelector_development = {}; | |
| /** | |
| * @license React | |
| * use-sync-external-store-shim/with-selector.development.js | |
| * | |
| * Copyright (c) Facebook, Inc. and its affiliates. | |
| * | |
| * This source code is licensed under the MIT license found in the | |
| * LICENSE file in the root directory of this source tree. | |
| */ | |
| var hasRequiredWithSelector_development; | |
| function requireWithSelector_development () { | |
| if (hasRequiredWithSelector_development) return withSelector_development; | |
| hasRequiredWithSelector_development = 1; | |
| if (process.env.NODE_ENV !== "production") { | |
| (function() { | |
| /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ | |
| if ( | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === | |
| 'function' | |
| ) { | |
| __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); | |
| } | |
| var React$1 = React; | |
| var shim = shimExports; | |
| /** | |
| * inlined Object.is polyfill to avoid requiring consumers ship their own | |
| * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is | |
| */ | |
| function is(x, y) { | |
| return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare | |
| ; | |
| } | |
| var objectIs = typeof Object.is === 'function' ? Object.is : is; | |
| var useSyncExternalStore = shim.useSyncExternalStore; | |
| // for CommonJS interop. | |
| var useRef = React$1.useRef, | |
| useEffect = React$1.useEffect, | |
| useMemo = React$1.useMemo, | |
| useDebugValue = React$1.useDebugValue; // Same as useSyncExternalStore, but supports selector and isEqual arguments. | |
| function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) { | |
| // Use this to track the rendered snapshot. | |
| var instRef = useRef(null); | |
| var inst; | |
| if (instRef.current === null) { | |
| inst = { | |
| hasValue: false, | |
| value: null | |
| }; | |
| instRef.current = inst; | |
| } else { | |
| inst = instRef.current; | |
| } | |
| var _useMemo = useMemo(function () { | |
| // Track the memoized state using closure variables that are local to this | |
| // memoized instance of a getSnapshot function. Intentionally not using a | |
| // useRef hook, because that state would be shared across all concurrent | |
| // copies of the hook/component. | |
| var hasMemo = false; | |
| var memoizedSnapshot; | |
| var memoizedSelection; | |
| var memoizedSelector = function (nextSnapshot) { | |
| if (!hasMemo) { | |
| // The first time the hook is called, there is no memoized result. | |
| hasMemo = true; | |
| memoizedSnapshot = nextSnapshot; | |
| var _nextSelection = selector(nextSnapshot); | |
| if (isEqual !== undefined) { | |
| // Even if the selector has changed, the currently rendered selection | |
| // may be equal to the new selection. We should attempt to reuse the | |
| // current value if possible, to preserve downstream memoizations. | |
| if (inst.hasValue) { | |
| var currentSelection = inst.value; | |
| if (isEqual(currentSelection, _nextSelection)) { | |
| memoizedSelection = currentSelection; | |
| return currentSelection; | |
| } | |
| } | |
| } | |
| memoizedSelection = _nextSelection; | |
| return _nextSelection; | |
| } // We may be able to reuse the previous invocation's result. | |
| // We may be able to reuse the previous invocation's result. | |
| var prevSnapshot = memoizedSnapshot; | |
| var prevSelection = memoizedSelection; | |
| if (objectIs(prevSnapshot, nextSnapshot)) { | |
| // The snapshot is the same as last time. Reuse the previous selection. | |
| return prevSelection; | |
| } // The snapshot has changed, so we need to compute a new selection. | |
| // The snapshot has changed, so we need to compute a new selection. | |
| var nextSelection = selector(nextSnapshot); // If a custom isEqual function is provided, use that to check if the data | |
| // has changed. If it hasn't, return the previous selection. That signals | |
| // to React that the selections are conceptually equal, and we can bail | |
| // out of rendering. | |
| // If a custom isEqual function is provided, use that to check if the data | |
| // has changed. If it hasn't, return the previous selection. That signals | |
| // to React that the selections are conceptually equal, and we can bail | |
| // out of rendering. | |
| if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) { | |
| return prevSelection; | |
| } | |
| memoizedSnapshot = nextSnapshot; | |
| memoizedSelection = nextSelection; | |
| return nextSelection; | |
| }; // Assigning this to a constant so that Flow knows it can't change. | |
| // Assigning this to a constant so that Flow knows it can't change. | |
| var maybeGetServerSnapshot = getServerSnapshot === undefined ? null : getServerSnapshot; | |
| var getSnapshotWithSelector = function () { | |
| return memoizedSelector(getSnapshot()); | |
| }; | |
| var getServerSnapshotWithSelector = maybeGetServerSnapshot === null ? undefined : function () { | |
| return memoizedSelector(maybeGetServerSnapshot()); | |
| }; | |
| return [getSnapshotWithSelector, getServerSnapshotWithSelector]; | |
| }, [getSnapshot, getServerSnapshot, selector, isEqual]), | |
| getSelection = _useMemo[0], | |
| getServerSelection = _useMemo[1]; | |
| var value = useSyncExternalStore(subscribe, getSelection, getServerSelection); | |
| useEffect(function () { | |
| inst.hasValue = true; | |
| inst.value = value; | |
| }, [value]); | |
| useDebugValue(value); | |
| return value; | |
| } | |
| withSelector_development.useSyncExternalStoreWithSelector = useSyncExternalStoreWithSelector; | |
| /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ | |
| if ( | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && | |
| typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop === | |
| 'function' | |
| ) { | |
| __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error()); | |
| } | |
| })(); | |
| } | |
| return withSelector_development; | |
| } | |
| if (process.env.NODE_ENV === 'production') { | |
| withSelector.exports = requireWithSelector_production_min(); | |
| } else { | |
| withSelector.exports = requireWithSelector_development(); | |
| } | |
| var withSelectorExports = withSelector.exports; | |
| const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; | |
| /** | |
| * To synchronize the editor instance with the component state, | |
| * we need to create a separate instance that is not affected by the component re-renders. | |
| */ | |
| class EditorStateManager { | |
| constructor(initialEditor) { | |
| this.transactionNumber = 0; | |
| this.lastTransactionNumber = 0; | |
| this.subscribers = new Set(); | |
| this.editor = initialEditor; | |
| this.lastSnapshot = { editor: initialEditor, transactionNumber: 0 }; | |
| this.getSnapshot = this.getSnapshot.bind(this); | |
| this.getServerSnapshot = this.getServerSnapshot.bind(this); | |
| this.watch = this.watch.bind(this); | |
| this.subscribe = this.subscribe.bind(this); | |
| } | |
| /** | |
| * Get the current editor instance. | |
| */ | |
| getSnapshot() { | |
| if (this.transactionNumber === this.lastTransactionNumber) { | |
| return this.lastSnapshot; | |
| } | |
| this.lastTransactionNumber = this.transactionNumber; | |
| this.lastSnapshot = { editor: this.editor, transactionNumber: this.transactionNumber }; | |
| return this.lastSnapshot; | |
| } | |
| /** | |
| * Always disable the editor on the server-side. | |
| */ | |
| getServerSnapshot() { | |
| return { editor: null, transactionNumber: 0 }; | |
| } | |
| /** | |
| * Subscribe to the editor instance's changes. | |
| */ | |
| subscribe(callback) { | |
| this.subscribers.add(callback); | |
| return () => { | |
| this.subscribers.delete(callback); | |
| }; | |
| } | |
| /** | |
| * Watch the editor instance for changes. | |
| */ | |
| watch(nextEditor) { | |
| this.editor = nextEditor; | |
| if (this.editor) { | |
| /** | |
| * This will force a re-render when the editor state changes. | |
| * This is to support things like `editor.can().toggleBold()` in components that `useEditor`. | |
| * This could be more efficient, but it's a good trade-off for now. | |
| */ | |
| const fn = () => { | |
| this.transactionNumber += 1; | |
| this.subscribers.forEach(callback => callback()); | |
| }; | |
| const currentEditor = this.editor; | |
| currentEditor.on('transaction', fn); | |
| return () => { | |
| currentEditor.off('transaction', fn); | |
| }; | |
| } | |
| return undefined; | |
| } | |
| } | |
| /** | |
| * This hook allows you to watch for changes on the editor instance. | |
| * It will allow you to select a part of the editor state and re-render the component when it changes. | |
| * @example | |
| * ```tsx | |
| * const editor = useEditor({...options}) | |
| * const { currentSelection } = useEditorState({ | |
| * editor, | |
| * selector: snapshot => ({ currentSelection: snapshot.editor.state.selection }), | |
| * }) | |
| */ | |
| function useEditorState(options) { | |
| var _a; | |
| const [editorStateManager] = useState(() => new EditorStateManager(options.editor)); | |
| // Using the `useSyncExternalStore` hook to sync the editor instance with the component state | |
| const selectedState = withSelectorExports.useSyncExternalStoreWithSelector(editorStateManager.subscribe, editorStateManager.getSnapshot, editorStateManager.getServerSnapshot, options.selector, (_a = options.equalityFn) !== null && _a !== void 0 ? _a : deepEqual); | |
| useIsomorphicLayoutEffect(() => { | |
| return editorStateManager.watch(options.editor); | |
| }, [options.editor, editorStateManager]); | |
| useDebugValue(selectedState); | |
| return selectedState; | |
| } | |
| const isDev = process.env.NODE_ENV !== 'production'; | |
| const isSSR = typeof window === 'undefined'; | |
| const isNext = isSSR || Boolean(typeof window !== 'undefined' && window.next); | |
| /** | |
| * This class handles the creation, destruction, and re-creation of the editor instance. | |
| */ | |
| class EditorInstanceManager { | |
| constructor(options) { | |
| /** | |
| * The current editor instance. | |
| */ | |
| this.editor = null; | |
| /** | |
| * The subscriptions to notify when the editor instance | |
| * has been created or destroyed. | |
| */ | |
| this.subscriptions = new Set(); | |
| /** | |
| * Whether the editor has been mounted. | |
| */ | |
| this.isComponentMounted = false; | |
| /** | |
| * The most recent dependencies array. | |
| */ | |
| this.previousDeps = null; | |
| /** | |
| * The unique instance ID. This is used to identify the editor instance. And will be re-generated for each new instance. | |
| */ | |
| this.instanceId = ''; | |
| this.options = options; | |
| this.subscriptions = new Set(); | |
| this.setEditor(this.getInitialEditor()); | |
| this.scheduleDestroy(); | |
| this.getEditor = this.getEditor.bind(this); | |
| this.getServerSnapshot = this.getServerSnapshot.bind(this); | |
| this.subscribe = this.subscribe.bind(this); | |
| this.refreshEditorInstance = this.refreshEditorInstance.bind(this); | |
| this.scheduleDestroy = this.scheduleDestroy.bind(this); | |
| this.onRender = this.onRender.bind(this); | |
| this.createEditor = this.createEditor.bind(this); | |
| } | |
| setEditor(editor) { | |
| this.editor = editor; | |
| this.instanceId = Math.random().toString(36).slice(2, 9); | |
| // Notify all subscribers that the editor instance has been created | |
| this.subscriptions.forEach(cb => cb()); | |
| } | |
| getInitialEditor() { | |
| if (this.options.current.immediatelyRender === undefined) { | |
| if (isSSR || isNext) { | |
| // TODO in the next major release, we should throw an error here | |
| if (isDev) { | |
| /** | |
| * Throw an error in development, to make sure the developer is aware that tiptap cannot be SSR'd | |
| * and that they need to set `immediatelyRender` to `false` to avoid hydration mismatches. | |
| */ | |
| console.warn('Tiptap Error: SSR has been detected, please set `immediatelyRender` explicitly to `false` to avoid hydration mismatches.'); | |
| } | |
| // Best faith effort in production, run the code in the legacy mode to avoid hydration mismatches and errors in production | |
| return null; | |
| } | |
| // Default to immediately rendering when client-side rendering | |
| return this.createEditor(); | |
| } | |
| if (this.options.current.immediatelyRender && isSSR && isDev) { | |
| // Warn in development, to make sure the developer is aware that tiptap cannot be SSR'd, set `immediatelyRender` to `false` to avoid hydration mismatches. | |
| throw new Error('Tiptap Error: SSR has been detected, and `immediatelyRender` has been set to `true` this is an unsupported configuration that may result in errors, explicitly set `immediatelyRender` to `false` to avoid hydration mismatches.'); | |
| } | |
| if (this.options.current.immediatelyRender) { | |
| return this.createEditor(); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Create a new editor instance. And attach event listeners. | |
| */ | |
| createEditor() { | |
| const optionsToApply = { | |
| ...this.options.current, | |
| // Always call the most recent version of the callback function by default | |
| onBeforeCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBeforeCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onBlur: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onCreate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onCreate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onDestroy: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDestroy) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onFocus: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onSelectionUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onSelectionUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onTransaction: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onTransaction) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onUpdate: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onUpdate) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onContentError: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onContentError) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onDrop: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onDrop) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| onPaste: (...args) => { var _a, _b; return (_b = (_a = this.options.current).onPaste) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, | |
| }; | |
| const editor = new Editor(optionsToApply); | |
| // no need to keep track of the event listeners, they will be removed when the editor is destroyed | |
| return editor; | |
| } | |
| /** | |
| * Get the current editor instance. | |
| */ | |
| getEditor() { | |
| return this.editor; | |
| } | |
| /** | |
| * Always disable the editor on the server-side. | |
| */ | |
| getServerSnapshot() { | |
| return null; | |
| } | |
| /** | |
| * Subscribe to the editor instance's changes. | |
| */ | |
| subscribe(onStoreChange) { | |
| this.subscriptions.add(onStoreChange); | |
| return () => { | |
| this.subscriptions.delete(onStoreChange); | |
| }; | |
| } | |
| static compareOptions(a, b) { | |
| return Object.keys(a).every(key => { | |
| if (['onCreate', 'onBeforeCreate', 'onDestroy', 'onUpdate', 'onTransaction', 'onFocus', 'onBlur', 'onSelectionUpdate', 'onContentError', 'onDrop', 'onPaste'].includes(key)) { | |
| // we don't want to compare callbacks, they are always different and only registered once | |
| return true; | |
| } | |
| // We often encourage putting extensions inlined in the options object, so we will do a slightly deeper comparison here | |
| if (key === 'extensions' && a.extensions && b.extensions) { | |
| if (a.extensions.length !== b.extensions.length) { | |
| return false; | |
| } | |
| return a.extensions.every((extension, index) => { | |
| var _a; | |
| if (extension !== ((_a = b.extensions) === null || _a === void 0 ? void 0 : _a[index])) { | |
| return false; | |
| } | |
| return true; | |
| }); | |
| } | |
| if (a[key] !== b[key]) { | |
| // if any of the options have changed, we should update the editor options | |
| return false; | |
| } | |
| return true; | |
| }); | |
| } | |
| /** | |
| * On each render, we will create, update, or destroy the editor instance. | |
| * @param deps The dependencies to watch for changes | |
| * @returns A cleanup function | |
| */ | |
| onRender(deps) { | |
| // The returned callback will run on each render | |
| return () => { | |
| this.isComponentMounted = true; | |
| // Cleanup any scheduled destructions, since we are currently rendering | |
| clearTimeout(this.scheduledDestructionTimeout); | |
| if (this.editor && !this.editor.isDestroyed && deps.length === 0) { | |
| // if the editor does exist & deps are empty, we don't need to re-initialize the editor generally | |
| if (!EditorInstanceManager.compareOptions(this.options.current, this.editor.options)) { | |
| // But, the options are different, so we need to update the editor options | |
| // Still, this is faster than re-creating the editor | |
| this.editor.setOptions({ | |
| ...this.options.current, | |
| editable: this.editor.isEditable, | |
| }); | |
| } | |
| } | |
| else { | |
| // When the editor: | |
| // - does not yet exist | |
| // - is destroyed | |
| // - the deps array changes | |
| // We need to destroy the editor instance and re-initialize it | |
| this.refreshEditorInstance(deps); | |
| } | |
| return () => { | |
| this.isComponentMounted = false; | |
| this.scheduleDestroy(); | |
| }; | |
| }; | |
| } | |
| /** | |
| * Recreate the editor instance if the dependencies have changed. | |
| */ | |
| refreshEditorInstance(deps) { | |
| if (this.editor && !this.editor.isDestroyed) { | |
| // Editor instance already exists | |
| if (this.previousDeps === null) { | |
| // If lastDeps has not yet been initialized, reuse the current editor instance | |
| this.previousDeps = deps; | |
| return; | |
| } | |
| const depsAreEqual = this.previousDeps.length === deps.length | |
| && this.previousDeps.every((dep, index) => dep === deps[index]); | |
| if (depsAreEqual) { | |
| // deps exist and are equal, no need to recreate | |
| return; | |
| } | |
| } | |
| if (this.editor && !this.editor.isDestroyed) { | |
| // Destroy the editor instance if it exists | |
| this.editor.destroy(); | |
| } | |
| this.setEditor(this.createEditor()); | |
| // Update the lastDeps to the current deps | |
| this.previousDeps = deps; | |
| } | |
| /** | |
| * Schedule the destruction of the editor instance. | |
| * This will only destroy the editor if it was not mounted on the next tick. | |
| * This is to avoid destroying the editor instance when it's actually still mounted. | |
| */ | |
| scheduleDestroy() { | |
| const currentInstanceId = this.instanceId; | |
| const currentEditor = this.editor; | |
| // Wait two ticks to see if the component is still mounted | |
| this.scheduledDestructionTimeout = setTimeout(() => { | |
| if (this.isComponentMounted && this.instanceId === currentInstanceId) { | |
| // If still mounted on the following tick, with the same instanceId, do not destroy the editor | |
| if (currentEditor) { | |
| // just re-apply options as they might have changed | |
| currentEditor.setOptions(this.options.current); | |
| } | |
| return; | |
| } | |
| if (currentEditor && !currentEditor.isDestroyed) { | |
| currentEditor.destroy(); | |
| if (this.instanceId === currentInstanceId) { | |
| this.setEditor(null); | |
| } | |
| } | |
| // This allows the effect to run again between ticks | |
| // which may save us from having to re-create the editor | |
| }, 1); | |
| } | |
| } | |
| function useEditor(options = {}, deps = []) { | |
| const mostRecentOptions = useRef(options); | |
| mostRecentOptions.current = options; | |
| const [instanceManager] = useState(() => new EditorInstanceManager(mostRecentOptions)); | |
| const editor = shimExports.useSyncExternalStore(instanceManager.subscribe, instanceManager.getEditor, instanceManager.getServerSnapshot); | |
| useDebugValue(editor); | |
| // This effect will handle creating/updating the editor instance | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| useEffect(instanceManager.onRender(deps)); | |
| // The default behavior is to re-render on each transaction | |
| // This is legacy behavior that will be removed in future versions | |
| useEditorState({ | |
| editor, | |
| selector: ({ transactionNumber }) => { | |
| if (options.shouldRerenderOnTransaction === false) { | |
| // This will prevent the editor from re-rendering on each transaction | |
| return null; | |
| } | |
| // This will avoid re-rendering on the first transaction when `immediatelyRender` is set to `true` | |
| if (options.immediatelyRender && transactionNumber === 0) { | |
| return 0; | |
| } | |
| return transactionNumber + 1; | |
| }, | |
| }); | |
| return editor; | |
| } | |
| const EditorContext = createContext({ | |
| editor: null, | |
| }); | |
| const EditorConsumer = EditorContext.Consumer; | |
| /** | |
| * A hook to get the current editor instance. | |
| */ | |
| const useCurrentEditor = () => useContext(EditorContext); | |
| /** | |
| * This is the provider component for the editor. | |
| * It allows the editor to be accessible across the entire component tree | |
| * with `useCurrentEditor`. | |
| */ | |
| function EditorProvider({ children, slotAfter, slotBefore, editorContainerProps = {}, ...editorOptions }) { | |
| const editor = useEditor(editorOptions); | |
| if (!editor) { | |
| return null; | |
| } | |
| return (React.createElement(EditorContext.Provider, { value: { editor } }, | |
| slotBefore, | |
| React.createElement(EditorConsumer, null, ({ editor: currentEditor }) => (React.createElement(EditorContent, { editor: currentEditor, ...editorContainerProps }))), | |
| children, | |
| slotAfter)); | |
| } | |
| const BubbleMenu = (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 = 'bubbleMenu', editor, tippyOptions = {}, updateDelay, shouldShow = null, } = props; | |
| const menuEditor = editor || currentEditor; | |
| if (!menuEditor) { | |
| console.warn('BubbleMenu component is not rendered inside of an editor component or does not have editor prop.'); | |
| return; | |
| } | |
| const plugin = BubbleMenuPlugin({ | |
| updateDelay, | |
| editor: menuEditor, | |
| element, | |
| pluginKey, | |
| shouldShow, | |
| tippyOptions, | |
| }); | |
| 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)); | |
| }; | |
| const 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)); | |
| }; | |
| const ReactNodeViewContext = createContext({ | |
| onDragStart: undefined, | |
| }); | |
| const useReactNodeView = () => useContext(ReactNodeViewContext); | |
| const NodeViewContent = props => { | |
| const Tag = props.as || 'div'; | |
| const { nodeViewContentRef } = useReactNodeView(); | |
| return ( | |
| // @ts-ignore | |
| React.createElement(Tag, { ...props, ref: nodeViewContentRef, "data-node-view-content": "", style: { | |
| whiteSpace: 'pre-wrap', | |
| ...props.style, | |
| } })); | |
| }; | |
| const NodeViewWrapper = React.forwardRef((props, ref) => { | |
| const { onDragStart } = useReactNodeView(); | |
| const Tag = props.as || 'div'; | |
| return ( | |
| // @ts-ignore | |
| React.createElement(Tag, { ...props, ref: ref, "data-node-view-wrapper": "", onDragStart: onDragStart, style: { | |
| whiteSpace: 'normal', | |
| ...props.style, | |
| } })); | |
| }); | |
| /** | |
| * Check if a component is a class component. | |
| * @param Component | |
| * @returns {boolean} | |
| */ | |
| function isClassComponent(Component) { | |
| return !!(typeof Component === 'function' | |
| && Component.prototype | |
| && Component.prototype.isReactComponent); | |
| } | |
| /** | |
| * Check if a component is a forward ref component. | |
| * @param Component | |
| * @returns {boolean} | |
| */ | |
| function isForwardRefComponent(Component) { | |
| return !!(typeof Component === 'object' | |
| && Component.$$typeof | |
| && (Component.$$typeof.toString() === 'Symbol(react.forward_ref)' | |
| || Component.$$typeof.description === 'react.forward_ref')); | |
| } | |
| /** | |
| * Check if a component is a memoized component. | |
| * @param Component | |
| * @returns {boolean} | |
| */ | |
| function isMemoComponent(Component) { | |
| return !!(typeof Component === 'object' | |
| && Component.$$typeof | |
| && (Component.$$typeof.toString() === 'Symbol(react.memo)' || Component.$$typeof.description === 'react.memo')); | |
| } | |
| /** | |
| * Check if a component can safely receive a ref prop. | |
| * This includes class components, forwardRef components, and memoized components | |
| * that wrap forwardRef or class components. | |
| * @param Component | |
| * @returns {boolean} | |
| */ | |
| function canReceiveRef(Component) { | |
| // Check if it's a class component | |
| if (isClassComponent(Component)) { | |
| return true; | |
| } | |
| // Check if it's a forwardRef component | |
| if (isForwardRefComponent(Component)) { | |
| return true; | |
| } | |
| // Check if it's a memoized component | |
| if (isMemoComponent(Component)) { | |
| // For memoized components, check the wrapped component | |
| const wrappedComponent = Component.type; | |
| if (wrappedComponent) { | |
| return isClassComponent(wrappedComponent) || isForwardRefComponent(wrappedComponent); | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Check if we're running React 19+ by detecting if function components support ref props | |
| * @returns {boolean} | |
| */ | |
| function isReact19Plus() { | |
| // React 19 is detected by checking React version if available | |
| // In practice, we'll use a more conservative approach and assume React 18 behavior | |
| // unless we can definitively detect React 19 | |
| try { | |
| // @ts-ignore | |
| if (version) { | |
| const majorVersion = parseInt(version.split('.')[0], 10); | |
| return majorVersion >= 19; | |
| } | |
| } | |
| catch { | |
| // Fallback to React 18 behavior if we can't determine version | |
| } | |
| return false; | |
| } | |
| /** | |
| * The ReactRenderer class. It's responsible for rendering React components inside the editor. | |
| * @example | |
| * new ReactRenderer(MyComponent, { | |
| * editor, | |
| * props: { | |
| * foo: 'bar', | |
| * }, | |
| * as: 'span', | |
| * }) | |
| */ | |
| class ReactRenderer { | |
| /** | |
| * Immediately creates element and renders the provided React component. | |
| */ | |
| constructor(component, { editor, props = {}, as = 'div', className = '', }) { | |
| this.ref = null; | |
| this.id = Math.floor(Math.random() * 0xFFFFFFFF).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 the editor is already initialized, we will need to | |
| // synchronously render the component to ensure it renders | |
| // together with Prosemirror's rendering. | |
| if (this.editor.isInitialized) { | |
| flushSync(() => { | |
| this.render(); | |
| }); | |
| } | |
| else { | |
| queueMicrotask(() => { | |
| this.render(); | |
| }); | |
| } | |
| } | |
| /** | |
| * Render the React component. | |
| */ | |
| render() { | |
| var _a; | |
| const Component = this.component; | |
| const props = this.props; | |
| const editor = this.editor; | |
| // Handle ref forwarding with React 18/19 compatibility | |
| const isReact19 = isReact19Plus(); | |
| const componentCanReceiveRef = canReceiveRef(Component); | |
| const elementProps = { ...props }; | |
| // Always remove ref if the component cannot receive it (unless React 19+) | |
| if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) { | |
| delete elementProps.ref; | |
| } | |
| // Only assign our own ref if allowed | |
| if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) { | |
| // @ts-ignore - Setting ref prop for compatible components | |
| elementProps.ref = (ref) => { | |
| this.ref = ref; | |
| }; | |
| } | |
| this.reactElement = React.createElement(Component, { ...elementProps }); | |
| (_a = editor === null || editor === void 0 ? void 0 : editor.contentComponent) === null || _a === void 0 ? void 0 : _a.setRenderer(this.id, this); | |
| } | |
| /** | |
| * Re-renders the React component with new props. | |
| */ | |
| updateProps(props = {}) { | |
| this.props = { | |
| ...this.props, | |
| ...props, | |
| }; | |
| this.render(); | |
| } | |
| /** | |
| * Destroy the React component. | |
| */ | |
| destroy() { | |
| var _a; | |
| const editor = this.editor; | |
| (_a = editor === null || editor === void 0 ? void 0 : editor.contentComponent) === null || _a === void 0 ? void 0 : _a.removeRenderer(this.id); | |
| } | |
| /** | |
| * Update the attributes of the element that holds the React component. | |
| */ | |
| updateAttributes(attributes) { | |
| Object.keys(attributes).forEach(key => { | |
| this.element.setAttribute(key, attributes[key]); | |
| }); | |
| } | |
| } | |
| class ReactNodeView extends NodeView { | |
| constructor(component, props, options) { | |
| super(component, props, options); | |
| if (!this.node.isLeaf) { | |
| if (this.options.contentDOMElementTag) { | |
| this.contentDOMElement = document.createElement(this.options.contentDOMElementTag); | |
| } | |
| else { | |
| this.contentDOMElement = document.createElement(this.node.isInline ? 'span' : 'div'); | |
| } | |
| this.contentDOMElement.dataset.nodeViewContentReact = ''; | |
| this.contentDOMElement.dataset.nodeViewWrapper = ''; | |
| // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari | |
| // With this fix it seems to work fine | |
| // See: https://github.com/ueberdosis/tiptap/issues/1197 | |
| this.contentDOMElement.style.whiteSpace = 'inherit'; | |
| const contentTarget = this.dom.querySelector('[data-node-view-content]'); | |
| if (!contentTarget) { | |
| return; | |
| } | |
| contentTarget.appendChild(this.contentDOMElement); | |
| } | |
| } | |
| /** | |
| * Setup the React component. | |
| * Called on initialization. | |
| */ | |
| mount() { | |
| const props = { | |
| editor: this.editor, | |
| node: this.node, | |
| decorations: this.decorations, | |
| innerDecorations: this.innerDecorations, | |
| view: this.view, | |
| selected: false, | |
| extension: this.extension, | |
| HTMLAttributes: this.HTMLAttributes, | |
| getPos: () => this.getPos(), | |
| updateAttributes: (attributes = {}) => this.updateAttributes(attributes), | |
| deleteNode: () => this.deleteNode(), | |
| ref: createRef(), | |
| }; | |
| if (!this.component.displayName) { | |
| const capitalizeFirstChar = (string) => { | |
| return string.charAt(0).toUpperCase() + string.substring(1); | |
| }; | |
| this.component.displayName = capitalizeFirstChar(this.extension.name); | |
| } | |
| const onDragStart = this.onDragStart.bind(this); | |
| const nodeViewContentRef = element => { | |
| if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) { | |
| // remove the nodeViewWrapper attribute from the element | |
| if (element.hasAttribute('data-node-view-wrapper')) { | |
| element.removeAttribute('data-node-view-wrapper'); | |
| } | |
| element.appendChild(this.contentDOMElement); | |
| } | |
| }; | |
| const context = { onDragStart, nodeViewContentRef }; | |
| const Component = this.component; | |
| // For performance reasons, we memoize the provider component | |
| // And all of the things it requires are declared outside of the component, so it doesn't need to re-render | |
| const ReactNodeViewProvider = memo(componentProps => { | |
| return (React.createElement(ReactNodeViewContext.Provider, { value: context }, createElement(Component, componentProps))); | |
| }); | |
| ReactNodeViewProvider.displayName = 'ReactNodeView'; | |
| 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.renderer = new ReactRenderer(ReactNodeViewProvider, { | |
| editor: this.editor, | |
| props, | |
| as, | |
| className: `node-${this.node.type.name} ${className}`.trim(), | |
| }); | |
| this.editor.on('selectionUpdate', this.handleSelectionUpdate); | |
| this.updateElementAttributes(); | |
| } | |
| /** | |
| * Return the DOM element. | |
| * This is the element that will be used to display the node view. | |
| */ | |
| 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; | |
| } | |
| /** | |
| * Return the content DOM element. | |
| * This is the element that will be used to display the rich-text content of the node. | |
| */ | |
| get contentDOM() { | |
| if (this.node.isLeaf) { | |
| return null; | |
| } | |
| return this.contentDOMElement; | |
| } | |
| /** | |
| * On editor selection update, check if the node is selected. | |
| * If it is, call `selectNode`, otherwise call `deselectNode`. | |
| */ | |
| handleSelectionUpdate() { | |
| const { from, to } = this.editor.state.selection; | |
| const pos = this.getPos(); | |
| if (typeof pos !== 'number') { | |
| return; | |
| } | |
| if (from <= pos && to >= pos + this.node.nodeSize) { | |
| if (this.renderer.props.selected) { | |
| return; | |
| } | |
| this.selectNode(); | |
| } | |
| else { | |
| if (!this.renderer.props.selected) { | |
| return; | |
| } | |
| this.deselectNode(); | |
| } | |
| } | |
| /** | |
| * On update, update the React component. | |
| * To prevent unnecessary updates, the `update` option can be used. | |
| */ | |
| update(node, decorations, innerDecorations) { | |
| const rerenderComponent = (props) => { | |
| this.renderer.updateProps(props); | |
| if (typeof this.options.attrs === 'function') { | |
| this.updateElementAttributes(); | |
| } | |
| }; | |
| if (node.type !== this.node.type) { | |
| return false; | |
| } | |
| if (typeof this.options.update === 'function') { | |
| const oldNode = this.node; | |
| const oldDecorations = this.decorations; | |
| const oldInnerDecorations = this.innerDecorations; | |
| this.node = node; | |
| this.decorations = decorations; | |
| this.innerDecorations = innerDecorations; | |
| return this.options.update({ | |
| oldNode, | |
| oldDecorations, | |
| newNode: node, | |
| newDecorations: decorations, | |
| oldInnerDecorations, | |
| innerDecorations, | |
| updateProps: () => rerenderComponent({ node, decorations, innerDecorations }), | |
| }); | |
| } | |
| if (node === this.node | |
| && this.decorations === decorations | |
| && this.innerDecorations === innerDecorations) { | |
| return true; | |
| } | |
| this.node = node; | |
| this.decorations = decorations; | |
| this.innerDecorations = innerDecorations; | |
| rerenderComponent({ node, decorations, innerDecorations }); | |
| return true; | |
| } | |
| /** | |
| * Select the node. | |
| * Add the `selected` prop and the `ProseMirror-selectednode` class. | |
| */ | |
| selectNode() { | |
| this.renderer.updateProps({ | |
| selected: true, | |
| }); | |
| this.renderer.element.classList.add('ProseMirror-selectednode'); | |
| } | |
| /** | |
| * Deselect the node. | |
| * Remove the `selected` prop and the `ProseMirror-selectednode` class. | |
| */ | |
| deselectNode() { | |
| this.renderer.updateProps({ | |
| selected: false, | |
| }); | |
| this.renderer.element.classList.remove('ProseMirror-selectednode'); | |
| } | |
| /** | |
| * Destroy the React component instance. | |
| */ | |
| destroy() { | |
| this.renderer.destroy(); | |
| this.editor.off('selectionUpdate', this.handleSelectionUpdate); | |
| this.contentDOMElement = null; | |
| } | |
| /** | |
| * Update the attributes of the top-level element that holds the React component. | |
| * Applying the attributes defined in the `attrs` option. | |
| */ | |
| updateElementAttributes() { | |
| if (this.options.attrs) { | |
| let attrsObj = {}; | |
| if (typeof this.options.attrs === 'function') { | |
| const extensionAttributes = this.editor.extensionManager.attributes; | |
| const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes); | |
| attrsObj = this.options.attrs({ node: this.node, HTMLAttributes }); | |
| } | |
| else { | |
| attrsObj = this.options.attrs; | |
| } | |
| this.renderer.updateAttributes(attrsObj); | |
| } | |
| } | |
| } | |
| /** | |
| * Create a React node view renderer. | |
| */ | |
| function ReactNodeViewRenderer(component, options) { | |
| return props => { | |
| // try to get the parent component | |
| // this is important for vue devtools to show the component hierarchy correctly | |
| // maybe it’s `undefined` because <editor-content> isn’t rendered yet | |
| if (!props.editor.contentComponent) { | |
| return {}; | |
| } | |
| return new ReactNodeView(component, props, options); | |
| }; | |
| } | |
| export { BubbleMenu, EditorConsumer, EditorContent, EditorContext, EditorProvider, FloatingMenu, NodeViewContent, NodeViewWrapper, PureEditorContent, ReactNodeView, ReactNodeViewContext, ReactNodeViewRenderer, ReactRenderer, useCurrentEditor, useEditor, useEditorState, useReactNodeView }; | |
| //# sourceMappingURL=index.js.map | |
Xet Storage Details
- Size:
- 61.9 kB
- Xet hash:
- a52a676505e655be79fc80b918c5b2fb4d28692f48cab9321108285186cc58e3
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.