@datalayer/lexical-loro 1.0.2 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/App.d.ts +2 -0
- package/lib/App.js +141 -0
- package/lib/Editor.d.ts +2 -0
- package/lib/Editor.js +116 -0
- package/lib/Settings.d.ts +2 -0
- package/lib/Settings.js +57 -0
- package/lib/appSettings.d.ts +36 -0
- package/lib/appSettings.js +48 -0
- package/lib/collab/loro/Bindings.d.ts +41 -0
- package/lib/collab/loro/Bindings.js +99 -0
- package/lib/collab/loro/Debug.d.ts +33 -0
- package/lib/collab/loro/Debug.js +452 -0
- package/lib/collab/loro/LexicalCollaborationContext.d.ts +19 -0
- package/lib/collab/loro/LexicalCollaborationContext.js +52 -0
- package/lib/collab/loro/LexicalCollaborationPlugin.d.ts +29 -0
- package/lib/collab/loro/LexicalCollaborationPlugin.js +111 -0
- package/lib/collab/loro/State.d.ts +53 -0
- package/lib/collab/loro/State.js +94 -0
- package/lib/collab/loro/components/LoroCollaborationUI.d.ts +13 -0
- package/lib/collab/loro/components/LoroCollaborationUI.js +9 -0
- package/lib/collab/loro/components/LoroCollaborators.d.ts +8 -0
- package/lib/collab/loro/components/LoroCollaborators.js +90 -0
- package/lib/collab/loro/components/index.d.ts +2 -0
- package/lib/collab/loro/components/index.js +6 -0
- package/lib/collab/loro/index.d.ts +6 -0
- package/lib/collab/loro/index.js +10 -0
- package/lib/collab/loro/integrators/BaseIntegrator.d.ts +14 -0
- package/lib/collab/loro/integrators/BaseIntegrator.js +5 -0
- package/lib/collab/loro/integrators/CounterIntegrator.d.ts +23 -0
- package/lib/collab/loro/integrators/CounterIntegrator.js +44 -0
- package/lib/collab/loro/integrators/ListIntegrator.d.ts +23 -0
- package/lib/collab/loro/integrators/ListIntegrator.js +53 -0
- package/lib/collab/loro/integrators/MapIntegrator.d.ts +24 -0
- package/lib/collab/loro/integrators/MapIntegrator.js +235 -0
- package/lib/collab/loro/integrators/TextIntegrator.d.ts +25 -0
- package/lib/collab/loro/integrators/TextIntegrator.js +55 -0
- package/lib/collab/loro/integrators/TreeIntegrator.d.ts +41 -0
- package/lib/collab/loro/integrators/TreeIntegrator.js +293 -0
- package/lib/collab/loro/nodes/NodeFactory.d.ts +15 -0
- package/lib/collab/loro/nodes/NodeFactory.js +80 -0
- package/lib/collab/loro/nodes/NodesMapper.d.ts +120 -0
- package/lib/collab/loro/nodes/NodesMapper.js +289 -0
- package/lib/collab/loro/propagators/DecoratorNodePropagator.d.ts +60 -0
- package/lib/collab/loro/propagators/DecoratorNodePropagator.js +306 -0
- package/lib/collab/loro/propagators/ElementNodePropagator.d.ts +62 -0
- package/lib/collab/loro/propagators/ElementNodePropagator.js +393 -0
- package/lib/collab/loro/propagators/LineBreakNodePropagator.d.ts +57 -0
- package/lib/collab/loro/propagators/LineBreakNodePropagator.js +200 -0
- package/lib/collab/loro/propagators/RootNodePropagator.d.ts +55 -0
- package/lib/collab/loro/propagators/RootNodePropagator.js +174 -0
- package/lib/collab/loro/propagators/TextNodePropagator.d.ts +60 -0
- package/lib/collab/loro/propagators/TextNodePropagator.js +450 -0
- package/lib/collab/loro/propagators/index.d.ts +49 -0
- package/lib/collab/loro/propagators/index.js +30 -0
- package/lib/collab/loro/provider/websocket.d.ts +117 -0
- package/lib/collab/loro/provider/websocket.js +946 -0
- package/lib/collab/loro/servers/index.d.ts +0 -0
- package/lib/collab/loro/servers/index.js +4 -0
- package/lib/collab/loro/servers/ws/callback.d.ts +5 -0
- package/lib/collab/loro/servers/ws/callback.js +89 -0
- package/lib/collab/loro/servers/ws/server.d.ts +2 -0
- package/lib/collab/loro/servers/ws/server.js +29 -0
- package/lib/collab/loro/servers/ws/utils.d.ts +40 -0
- package/lib/collab/loro/servers/ws/utils.js +517 -0
- package/lib/collab/loro/sync/SyncCursors.d.ts +32 -0
- package/lib/collab/loro/sync/SyncCursors.js +501 -0
- package/lib/collab/loro/sync/SyncLexicalToLoro.d.ts +4 -0
- package/lib/collab/loro/sync/SyncLexicalToLoro.js +152 -0
- package/lib/collab/loro/sync/SyncLoroToLexical.d.ts +5 -0
- package/lib/collab/loro/sync/SyncLoroToLexical.js +106 -0
- package/lib/collab/loro/types/LexicalNodeData.d.ts +32 -0
- package/lib/collab/loro/types/LexicalNodeData.js +75 -0
- package/lib/collab/loro/useCollaboration.d.ts +12 -0
- package/lib/collab/loro/useCollaboration.js +405 -0
- package/lib/collab/loro/utils/InitialContent.d.ts +64 -0
- package/lib/collab/loro/utils/InitialContent.js +113 -0
- package/lib/collab/loro/utils/Invariant.d.ts +15 -0
- package/lib/collab/loro/utils/Invariant.js +24 -0
- package/lib/collab/loro/utils/LexicalToLoro.d.ts +18 -0
- package/lib/collab/loro/utils/LexicalToLoro.js +100 -0
- package/lib/collab/loro/utils/Utils.d.ts +44 -0
- package/lib/collab/loro/utils/Utils.js +157 -0
- package/lib/collab/loro/wsProvider.d.ts +8 -0
- package/lib/collab/loro/wsProvider.js +52 -0
- package/lib/collab/utils/invariant.d.ts +1 -0
- package/lib/collab/utils/invariant.js +15 -0
- package/lib/collab/utils/simpleDiffWithCursor.d.ts +5 -0
- package/lib/collab/utils/simpleDiffWithCursor.js +35 -0
- package/lib/collab/yjs/Bindings.d.ts +23 -0
- package/lib/collab/yjs/Bindings.js +26 -0
- package/lib/collab/yjs/Debug.d.ts +23 -0
- package/lib/collab/yjs/Debug.js +213 -0
- package/lib/collab/yjs/LexicalCollaborationContext.d.ts +10 -0
- package/lib/collab/yjs/LexicalCollaborationContext.js +37 -0
- package/lib/collab/yjs/LexicalCollaborationPlugin.d.ts +21 -0
- package/lib/collab/yjs/LexicalCollaborationPlugin.js +63 -0
- package/lib/collab/yjs/State.d.ts +51 -0
- package/lib/collab/yjs/State.js +35 -0
- package/lib/collab/yjs/nodes/AnyCollabNode.d.ts +5 -0
- package/lib/collab/yjs/nodes/AnyCollabNode.js +1 -0
- package/lib/collab/yjs/nodes/CollabDecoratorNode.d.ts +22 -0
- package/lib/collab/yjs/nodes/CollabDecoratorNode.js +64 -0
- package/lib/collab/yjs/nodes/CollabElementNode.d.ts +40 -0
- package/lib/collab/yjs/nodes/CollabElementNode.js +462 -0
- package/lib/collab/yjs/nodes/CollabLineBreakNode.d.ts +19 -0
- package/lib/collab/yjs/nodes/CollabLineBreakNode.js +44 -0
- package/lib/collab/yjs/nodes/CollabTextNode.d.ts +25 -0
- package/lib/collab/yjs/nodes/CollabTextNode.js +103 -0
- package/lib/collab/yjs/provider/websocket.d.ts +88 -0
- package/lib/collab/yjs/provider/websocket.js +415 -0
- package/lib/collab/yjs/servers/index.d.ts +0 -0
- package/lib/collab/yjs/servers/index.js +0 -0
- package/lib/collab/yjs/servers/ws/callback.d.ts +5 -0
- package/lib/collab/yjs/servers/ws/callback.js +72 -0
- package/lib/collab/yjs/servers/ws/server.d.ts +2 -0
- package/lib/collab/yjs/servers/ws/server.js +25 -0
- package/lib/collab/yjs/servers/ws/utils.d.ts +49 -0
- package/lib/collab/yjs/servers/ws/utils.js +284 -0
- package/lib/collab/yjs/sync/SyncCursors.d.ts +39 -0
- package/lib/collab/yjs/sync/SyncCursors.js +351 -0
- package/lib/collab/yjs/sync/SyncEditorStates.d.ts +10 -0
- package/lib/collab/yjs/sync/SyncEditorStates.js +200 -0
- package/lib/collab/yjs/useCollaboration.d.ts +12 -0
- package/lib/collab/yjs/useCollaboration.js +255 -0
- package/lib/collab/yjs/utils/Utils.d.ts +25 -0
- package/lib/collab/yjs/utils/Utils.js +402 -0
- package/lib/collab/yjs/wsProvider.d.ts +3 -0
- package/lib/collab/yjs/wsProvider.js +21 -0
- package/lib/commenting/index.d.ts +41 -0
- package/lib/commenting/index.js +328 -0
- package/lib/context/FlashMessageContext.d.ts +7 -0
- package/lib/context/FlashMessageContext.js +24 -0
- package/lib/context/SettingsContext.d.ts +12 -0
- package/lib/context/SettingsContext.js +38 -0
- package/lib/context/SharedHistoryContext.d.ts +11 -0
- package/lib/context/SharedHistoryContext.js +11 -0
- package/lib/context/ToolbarContext.d.ts +65 -0
- package/lib/context/ToolbarContext.js +84 -0
- package/lib/demo.d.ts +12 -0
- package/lib/demo.js +45 -0
- package/lib/hooks/useFlashMessage.d.ts +2 -0
- package/lib/hooks/useFlashMessage.js +8 -0
- package/lib/hooks/useModal.d.ts +5 -0
- package/lib/hooks/useModal.js +26 -0
- package/lib/hooks/useReport.d.ts +1 -0
- package/lib/hooks/useReport.js +50 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +5 -0
- package/lib/nodes/AutocompleteNode.d.ts +27 -0
- package/lib/nodes/AutocompleteNode.js +60 -0
- package/lib/nodes/CounterComponent.d.ts +6 -0
- package/lib/nodes/CounterComponent.js +137 -0
- package/lib/nodes/CounterNode.d.ts +23 -0
- package/lib/nodes/CounterNode.js +47 -0
- package/lib/nodes/DateTimeNode/DateTimeComponent.d.ts +8 -0
- package/lib/nodes/DateTimeNode/DateTimeComponent.js +119 -0
- package/lib/nodes/DateTimeNode/DateTimeNode.d.ts +27 -0
- package/lib/nodes/DateTimeNode/DateTimeNode.js +82 -0
- package/lib/nodes/EmojiNode.d.ts +18 -0
- package/lib/nodes/EmojiNode.js +54 -0
- package/lib/nodes/EquationComponent.d.ts +9 -0
- package/lib/nodes/EquationComponent.js +75 -0
- package/lib/nodes/EquationNode.d.ts +26 -0
- package/lib/nodes/EquationNode.js +109 -0
- package/lib/nodes/ExcalidrawNode/ExcalidrawComponent.d.ts +8 -0
- package/lib/nodes/ExcalidrawNode/ExcalidrawComponent.js +110 -0
- package/lib/nodes/ExcalidrawNode/ExcalidrawImage.d.ts +50 -0
- package/lib/nodes/ExcalidrawNode/ExcalidrawImage.js +55 -0
- package/lib/nodes/ExcalidrawNode/index.d.ts +32 -0
- package/lib/nodes/ExcalidrawNode/index.js +117 -0
- package/lib/nodes/FigmaNode.d.ts +20 -0
- package/lib/nodes/FigmaNode.js +52 -0
- package/lib/nodes/ImageComponent.d.ts +16 -0
- package/lib/nodes/ImageComponent.js +272 -0
- package/lib/nodes/ImageNode.d.ts +50 -0
- package/lib/nodes/ImageNode.js +151 -0
- package/lib/nodes/InlineImageNode/InlineImageComponent.d.ts +26 -0
- package/lib/nodes/InlineImageNode/InlineImageComponent.js +161 -0
- package/lib/nodes/InlineImageNode/InlineImageNode.d.ts +59 -0
- package/lib/nodes/InlineImageNode/InlineImageNode.js +162 -0
- package/lib/nodes/KeywordNode.d.ts +14 -0
- package/lib/nodes/KeywordNode.js +37 -0
- package/lib/nodes/LayoutContainerNode.d.ts +24 -0
- package/lib/nodes/LayoutContainerNode.js +95 -0
- package/lib/nodes/LayoutItemNode.d.ts +16 -0
- package/lib/nodes/LayoutItemNode.js +69 -0
- package/lib/nodes/MentionNode.d.ts +20 -0
- package/lib/nodes/MentionNode.js +85 -0
- package/lib/nodes/PageBreakNode/index.d.ts +17 -0
- package/lib/nodes/PageBreakNode/index.js +83 -0
- package/lib/nodes/PlaygroundNodes.d.ts +3 -0
- package/lib/nodes/PlaygroundNodes.js +75 -0
- package/lib/nodes/PollComponent.d.ts +9 -0
- package/lib/nodes/PollComponent.js +85 -0
- package/lib/nodes/PollNode.d.ts +43 -0
- package/lib/nodes/PollNode.js +153 -0
- package/lib/nodes/SpecialTextNode.d.ts +24 -0
- package/lib/nodes/SpecialTextNode.js +54 -0
- package/lib/nodes/StickyComponent.d.ts +10 -0
- package/lib/nodes/StickyComponent.js +162 -0
- package/lib/nodes/StickyNode.d.ts +31 -0
- package/lib/nodes/StickyNode.js +76 -0
- package/lib/nodes/TweetNode.d.ts +21 -0
- package/lib/nodes/TweetNode.js +119 -0
- package/lib/nodes/YouTubeNode.d.ts +22 -0
- package/lib/nodes/YouTubeNode.js +84 -0
- package/lib/plugins/ActionsPlugin/index.d.ts +5 -0
- package/lib/plugins/ActionsPlugin/index.js +168 -0
- package/lib/plugins/AutoEmbedPlugin/index.d.ts +19 -0
- package/lib/plugins/AutoEmbedPlugin/index.js +158 -0
- package/lib/plugins/AutoLinkPlugin/index.d.ts +2 -0
- package/lib/plugins/AutoLinkPlugin/index.js +15 -0
- package/lib/plugins/AutocompletePlugin/index.d.ts +10 -0
- package/lib/plugins/AutocompletePlugin/index.js +2477 -0
- package/lib/plugins/CodeActionMenuPlugin/components/CopyButton/index.d.ts +7 -0
- package/lib/plugins/CodeActionMenuPlugin/components/CopyButton/index.js +46 -0
- package/lib/plugins/CodeActionMenuPlugin/components/PrettierButton/index.d.ts +17 -0
- package/lib/plugins/CodeActionMenuPlugin/components/PrettierButton/index.js +115 -0
- package/lib/plugins/CodeActionMenuPlugin/index.d.ts +5 -0
- package/lib/plugins/CodeActionMenuPlugin/index.js +104 -0
- package/lib/plugins/CodeActionMenuPlugin/utils.d.ts +1 -0
- package/lib/plugins/CodeActionMenuPlugin/utils.js +22 -0
- package/lib/plugins/CodeHighlightPrismPlugin/index.d.ts +2 -0
- package/lib/plugins/CodeHighlightPrismPlugin/index.js +14 -0
- package/lib/plugins/CodeHighlightShikiPlugin/index.d.ts +2 -0
- package/lib/plugins/CodeHighlightShikiPlugin/index.js +14 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleContainerNode.d.ts +25 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleContainerNode.js +135 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleContentNode.d.ts +16 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleContentNode.js +83 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleTitleNode.d.ts +16 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleTitleNode.js +85 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleUtils.d.ts +2 -0
- package/lib/plugins/CollapsiblePlugin/CollapsibleUtils.js +12 -0
- package/lib/plugins/CollapsiblePlugin/index.d.ts +3 -0
- package/lib/plugins/CollapsiblePlugin/index.js +132 -0
- package/lib/plugins/CommentPlugin/index.d.ts +9 -0
- package/lib/plugins/CommentPlugin/index.js +460 -0
- package/lib/plugins/ComponentPickerPlugin/index.d.ts +2 -0
- package/lib/plugins/ComponentPickerPlugin/index.js +276 -0
- package/lib/plugins/ContextMenuPlugin/index.d.ts +2 -0
- package/lib/plugins/ContextMenuPlugin/index.js +112 -0
- package/lib/plugins/CounterPlugin/index.d.ts +3 -0
- package/lib/plugins/CounterPlugin/index.js +24 -0
- package/lib/plugins/DateTimePlugin/index.d.ts +8 -0
- package/lib/plugins/DateTimePlugin/index.js +28 -0
- package/lib/plugins/DebugPlugin/index.d.ts +3 -0
- package/lib/plugins/DebugPlugin/index.js +275 -0
- package/lib/plugins/DocsPlugin/index.d.ts +2 -0
- package/lib/plugins/DocsPlugin/index.js +4 -0
- package/lib/plugins/DragDropPastePlugin/index.d.ts +1 -0
- package/lib/plugins/DragDropPastePlugin/index.js +37 -0
- package/lib/plugins/DraggableBlockPlugin/index.d.ts +12 -0
- package/lib/plugins/DraggableBlockPlugin/index.js +36 -0
- package/lib/plugins/EmojiPickerPlugin/index.d.ts +1 -0
- package/lib/plugins/EmojiPickerPlugin/index.js +84 -0
- package/lib/plugins/EmojisPlugin/index.d.ts +2 -0
- package/lib/plugins/EmojisPlugin/index.js +56 -0
- package/lib/plugins/EquationsPlugin/index.d.ts +14 -0
- package/lib/plugins/EquationsPlugin/index.js +34 -0
- package/lib/plugins/ExcalidrawPlugin/index.d.ts +5 -0
- package/lib/plugins/ExcalidrawPlugin/index.js +44 -0
- package/lib/plugins/FigmaPlugin/index.d.ts +4 -0
- package/lib/plugins/FigmaPlugin/index.js +24 -0
- package/lib/plugins/FloatingLinkEditorPlugin/index.d.ts +15 -0
- package/lib/plugins/FloatingLinkEditorPlugin/index.js +280 -0
- package/lib/plugins/FloatingTextFormatToolbarPlugin/index.d.ts +7 -0
- package/lib/plugins/FloatingTextFormatToolbarPlugin/index.js +219 -0
- package/lib/plugins/ImagesPlugin/index.d.ts +24 -0
- package/lib/plugins/ImagesPlugin/index.js +195 -0
- package/lib/plugins/InlineImagePlugin/index.d.ts +17 -0
- package/lib/plugins/InlineImagePlugin/index.js +180 -0
- package/lib/plugins/KeywordsPlugin/index.d.ts +2 -0
- package/lib/plugins/KeywordsPlugin/index.js +35 -0
- package/lib/plugins/LayoutPlugin/InsertLayoutDialog.d.ts +6 -0
- package/lib/plugins/LayoutPlugin/InsertLayoutDialog.js +21 -0
- package/lib/plugins/LayoutPlugin/LayoutPlugin.d.ts +7 -0
- package/lib/plugins/LayoutPlugin/LayoutPlugin.js +135 -0
- package/lib/plugins/LinkPlugin/index.d.ts +6 -0
- package/lib/plugins/LinkPlugin/index.js +11 -0
- package/lib/plugins/MarkdownShortcutPlugin/index.d.ts +2 -0
- package/lib/plugins/MarkdownShortcutPlugin/index.js +6 -0
- package/lib/plugins/MarkdownTransformers/index.d.ts +8 -0
- package/lib/plugins/MarkdownTransformers/index.js +238 -0
- package/lib/plugins/MaxLengthPlugin/index.d.ts +3 -0
- package/lib/plugins/MaxLengthPlugin/index.js +41 -0
- package/lib/plugins/MentionsPlugin/index.d.ts +2 -0
- package/lib/plugins/MentionsPlugin/index.js +564 -0
- package/lib/plugins/PageBreakPlugin/index.d.ts +4 -0
- package/lib/plugins/PageBreakPlugin/index.js +31 -0
- package/lib/plugins/PasteLogPlugin/index.d.ts +2 -0
- package/lib/plugins/PasteLogPlugin/index.js +27 -0
- package/lib/plugins/PollPlugin/index.d.ts +8 -0
- package/lib/plugins/PollPlugin/index.js +38 -0
- package/lib/plugins/ShortcutsPlugin/index.d.ts +6 -0
- package/lib/plugins/ShortcutsPlugin/index.js +116 -0
- package/lib/plugins/ShortcutsPlugin/shortcuts.d.ts +59 -0
- package/lib/plugins/ShortcutsPlugin/shortcuts.js +173 -0
- package/lib/plugins/SpecialTextPlugin/index.d.ts +2 -0
- package/lib/plugins/SpecialTextPlugin/index.js +50 -0
- package/lib/plugins/SpeechToTextPlugin/index.d.ts +5 -0
- package/lib/plugins/SpeechToTextPlugin/index.js +86 -0
- package/lib/plugins/StickyPlugin/index.d.ts +2 -0
- package/lib/plugins/StickyPlugin/index.js +16 -0
- package/lib/plugins/TabFocusPlugin/index.d.ts +1 -0
- package/lib/plugins/TabFocusPlugin/index.js +38 -0
- package/lib/plugins/TableActionMenuPlugin/index.d.ts +5 -0
- package/lib/plugins/TableActionMenuPlugin/index.js +492 -0
- package/lib/plugins/TableCellResizer/index.d.ts +3 -0
- package/lib/plugins/TableCellResizer/index.js +297 -0
- package/lib/plugins/TableHoverActionsPlugin/index.d.ts +4 -0
- package/lib/plugins/TableHoverActionsPlugin/index.js +188 -0
- package/lib/plugins/TableOfContentsPlugin/index.d.ts +2 -0
- package/lib/plugins/TableOfContentsPlugin/index.js +116 -0
- package/lib/plugins/TablePlugin.d.ts +31 -0
- package/lib/plugins/TablePlugin.js +63 -0
- package/lib/plugins/TestRecorderPlugin/index.d.ts +3 -0
- package/lib/plugins/TestRecorderPlugin/index.js +346 -0
- package/lib/plugins/ToolbarPlugin/fontSize.d.ts +9 -0
- package/lib/plugins/ToolbarPlugin/fontSize.js +84 -0
- package/lib/plugins/ToolbarPlugin/index.d.ts +9 -0
- package/lib/plugins/ToolbarPlugin/index.js +500 -0
- package/lib/plugins/ToolbarPlugin/utils.d.ts +26 -0
- package/lib/plugins/ToolbarPlugin/utils.js +247 -0
- package/lib/plugins/TreeViewPlugin/index.d.ts +2 -0
- package/lib/plugins/TreeViewPlugin/index.js +7 -0
- package/lib/plugins/TwitterPlugin/index.d.ts +4 -0
- package/lib/plugins/TwitterPlugin/index.js +24 -0
- package/lib/plugins/TypingPerfPlugin/index.d.ts +2 -0
- package/lib/plugins/TypingPerfPlugin/index.js +97 -0
- package/lib/plugins/YouTubePlugin/index.d.ts +4 -0
- package/lib/plugins/YouTubePlugin/index.js +24 -0
- package/lib/server/validation.d.ts +1 -0
- package/lib/server/validation.js +115 -0
- package/lib/setupEnv.d.ts +2 -0
- package/lib/setupEnv.js +29 -0
- package/lib/themes/CommentEditorTheme.d.ts +4 -0
- package/lib/themes/CommentEditorTheme.js +11 -0
- package/lib/themes/PlaygroundEditorTheme.d.ts +4 -0
- package/lib/themes/PlaygroundEditorTheme.js +124 -0
- package/lib/themes/StickyEditorTheme.d.ts +4 -0
- package/lib/themes/StickyEditorTheme.js +11 -0
- package/lib/tyes.dt.d.ts +12 -0
- package/lib/tyes.dt.js +4 -0
- package/lib/ui/Button.d.ts +12 -0
- package/lib/ui/Button.js +6 -0
- package/lib/ui/ColorPicker.d.ts +14 -0
- package/lib/ui/ColorPicker.js +219 -0
- package/lib/ui/ContentEditable.d.ts +9 -0
- package/lib/ui/ContentEditable.js +6 -0
- package/lib/ui/Dialog.d.ts +10 -0
- package/lib/ui/Dialog.js +8 -0
- package/lib/ui/DropDown.d.ts +18 -0
- package/lib/ui/DropDown.js +133 -0
- package/lib/ui/DropdownColorPicker.d.ts +13 -0
- package/lib/ui/DropdownColorPicker.js +6 -0
- package/lib/ui/EquationEditor.d.ts +8 -0
- package/lib/ui/EquationEditor.js +11 -0
- package/lib/ui/ExcalidrawModal.d.ts +42 -0
- package/lib/ui/ExcalidrawModal.js +103 -0
- package/lib/ui/FileInput.d.ts +10 -0
- package/lib/ui/FileInput.js +5 -0
- package/lib/ui/FlashMessage.d.ts +7 -0
- package/lib/ui/FlashMessage.js +6 -0
- package/lib/ui/ImageResizer.d.ts +17 -0
- package/lib/ui/ImageResizer.js +171 -0
- package/lib/ui/KatexEquationAlterer.d.ts +8 -0
- package/lib/ui/KatexEquationAlterer.js +23 -0
- package/lib/ui/KatexRenderer.d.ts +6 -0
- package/lib/ui/KatexRenderer.js +24 -0
- package/lib/ui/Modal.d.ts +9 -0
- package/lib/ui/Modal.js +48 -0
- package/lib/ui/Select.d.ts +8 -0
- package/lib/ui/Select.js +5 -0
- package/lib/ui/Switch.d.ts +8 -0
- package/lib/ui/Switch.js +6 -0
- package/lib/ui/TextInput.d.ts +13 -0
- package/lib/ui/TextInput.js +7 -0
- package/lib/utils/docSerialization.d.ts +3 -0
- package/lib/utils/docSerialization.js +60 -0
- package/lib/utils/emoji-list.d.ts +20 -0
- package/lib/utils/emoji-list.js +16609 -0
- package/lib/utils/getDOMRangeRect.d.ts +8 -0
- package/lib/utils/getDOMRangeRect.js +26 -0
- package/lib/utils/getSelectedNode.d.ts +2 -0
- package/lib/utils/getSelectedNode.js +28 -0
- package/lib/utils/getThemeSelector.d.ts +2 -0
- package/lib/utils/getThemeSelector.js +14 -0
- package/lib/utils/isMobileWidth.d.ts +7 -0
- package/lib/utils/isMobileWidth.js +11 -0
- package/lib/utils/joinClasses.d.ts +1 -0
- package/lib/utils/joinClasses.js +7 -0
- package/lib/utils/setFloatingElemPosition.d.ts +1 -0
- package/lib/utils/setFloatingElemPosition.js +59 -0
- package/lib/utils/setFloatingElemPositionForLinkEditor.d.ts +1 -0
- package/lib/utils/setFloatingElemPositionForLinkEditor.js +36 -0
- package/lib/utils/swipe.d.ts +4 -0
- package/lib/utils/swipe.js +94 -0
- package/lib/utils/url.d.ts +2 -0
- package/lib/utils/url.js +31 -0
- package/package.json +4 -4
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { $getNodeByKey, $isDecoratorNode } from 'lexical';
|
|
6
|
+
import { $diffTextContentAndApplyDelta } from '../utils/Utils';
|
|
7
|
+
/**
|
|
8
|
+
* Handles map data changes (node properties, metadata updates)
|
|
9
|
+
*/
|
|
10
|
+
export class MapIntegrator {
|
|
11
|
+
integrate(diff, binding, provider) {
|
|
12
|
+
// Handle updated properties
|
|
13
|
+
if (diff.updated) {
|
|
14
|
+
Object.entries(diff.updated).forEach(([key, value]) => {
|
|
15
|
+
this.integratePropertyUpdate(key, value, binding, provider);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// Handle deleted properties
|
|
19
|
+
if (diff.deleted) {
|
|
20
|
+
diff.deleted.forEach((key) => {
|
|
21
|
+
this.integratePropertyDelete(key, binding, provider);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Enhanced integrate method with TreeID context
|
|
26
|
+
integrateWithContext(diff, treeId, binding, provider) {
|
|
27
|
+
this.integrateWithContextInternal(diff, treeId, binding, provider);
|
|
28
|
+
}
|
|
29
|
+
// Internal method for use when already inside editor.update()
|
|
30
|
+
integrateInternal(diff, binding, provider) {
|
|
31
|
+
// Handle updated properties
|
|
32
|
+
if (diff.updated) {
|
|
33
|
+
Object.entries(diff.updated).forEach(([key, value]) => {
|
|
34
|
+
this.integratePropertyUpdate(key, value, binding, provider);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Handle deleted properties
|
|
38
|
+
if (diff.deleted) {
|
|
39
|
+
diff.deleted.forEach((key) => {
|
|
40
|
+
this.integratePropertyDelete(key, binding, provider);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Internal method for use when already inside editor.update() with context
|
|
45
|
+
integrateWithContextInternal(diff, treeId, binding, provider) {
|
|
46
|
+
// Handle updated properties with TreeID context
|
|
47
|
+
if (diff.updated) {
|
|
48
|
+
Object.entries(diff.updated).forEach(([key, value]) => {
|
|
49
|
+
this.integratePropertyUpdateWithContextInternal(key, value, treeId, binding, provider);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Handle deleted properties
|
|
53
|
+
if (diff.deleted) {
|
|
54
|
+
diff.deleted.forEach((key) => {
|
|
55
|
+
this.integratePropertyDelete(key, binding, provider);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
integratePropertyUpdateWithContext(key, value, treeId, binding, provider) {
|
|
60
|
+
// Handle specific property updates with TreeID context
|
|
61
|
+
switch (key) {
|
|
62
|
+
case 'lexical':
|
|
63
|
+
this.integrateLexicalDataUpdateWithContext(value, treeId, binding);
|
|
64
|
+
break;
|
|
65
|
+
case 'textContent':
|
|
66
|
+
// Text content updates should be integrated via lexical data updates
|
|
67
|
+
break;
|
|
68
|
+
case 'elementType':
|
|
69
|
+
// Element type changes are rare, mostly for debugging
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
//
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Internal version for use when already inside editor.update()
|
|
76
|
+
integratePropertyUpdateWithContextInternal(key, value, treeId, binding, provider) {
|
|
77
|
+
// Handle specific property updates with TreeID context
|
|
78
|
+
switch (key) {
|
|
79
|
+
case 'lexical':
|
|
80
|
+
// Extract TreeID and call internal method directly (already inside editor.update())
|
|
81
|
+
let actualTreeID = treeId;
|
|
82
|
+
if (typeof treeId === 'string' && treeId.startsWith('cid:')) {
|
|
83
|
+
const parts = treeId.split(':');
|
|
84
|
+
if (parts.length >= 3) {
|
|
85
|
+
actualTreeID = parts[1];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const lexicalKey = binding.nodeMapper.getLexicalKeyByLoroId(actualTreeID);
|
|
89
|
+
if (lexicalKey) {
|
|
90
|
+
this.integrateLexicalDataUpdateInternal(value, lexicalKey, actualTreeID, binding);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case 'textContent':
|
|
94
|
+
// Text content updates should be integrated via lexical data updates
|
|
95
|
+
break;
|
|
96
|
+
case 'elementType':
|
|
97
|
+
// Element type changes are rare, mostly for debugging
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
// Generic property update with context
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
integratePropertyUpdate(key, value, binding, provider) {
|
|
105
|
+
// Handle specific property updates
|
|
106
|
+
switch (key) {
|
|
107
|
+
case 'lexical':
|
|
108
|
+
// Use targeted update only - the broad heuristic causes scrambling
|
|
109
|
+
break;
|
|
110
|
+
case 'textContent':
|
|
111
|
+
// Text content updates should be integrated via lexical data updates
|
|
112
|
+
break;
|
|
113
|
+
case 'elementType':
|
|
114
|
+
// Element type changes are rare, mostly for debugging
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
// Generic property update
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
integratePropertyDelete(key, binding, provider) {
|
|
122
|
+
// Handle specific property deletions
|
|
123
|
+
switch (key) {
|
|
124
|
+
case 'lexical':
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
// Generic property deleted
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
integrateLexicalDataUpdateWithContext(lexicalData, treeId, binding) {
|
|
132
|
+
// Extract the actual TreeID from container ID format
|
|
133
|
+
// Container ID format: "cid:6@7648424808278730813:Map"
|
|
134
|
+
// TreeID format: "6@7648424808278730813"
|
|
135
|
+
let actualTreeID = treeId;
|
|
136
|
+
if (typeof treeId === 'string' && treeId.startsWith('cid:')) {
|
|
137
|
+
const parts = treeId.split(':');
|
|
138
|
+
if (parts.length >= 3) {
|
|
139
|
+
actualTreeID = parts[1]; // Extract "6@7648424808278730813" from "cid:6@7648424808278730813:Map"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Use the TreeID to find the specific Lexical node
|
|
143
|
+
const lexicalKey = binding.nodeMapper.getLexicalKeyByLoroId(actualTreeID);
|
|
144
|
+
if (!lexicalKey) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (lexicalData && typeof lexicalData === 'object') {
|
|
148
|
+
binding.editor.update(() => {
|
|
149
|
+
this.integrateLexicalDataUpdateInternal(lexicalData, lexicalKey, actualTreeID, binding);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Internal method for use when already inside editor.update()
|
|
154
|
+
integrateLexicalDataUpdateInternal(lexicalData, lexicalKey, treeId, binding) {
|
|
155
|
+
// Handle Loro container objects that need conversion to plain JS
|
|
156
|
+
let data = lexicalData;
|
|
157
|
+
if (data && typeof data === 'object' && typeof data.toJSON === 'function') {
|
|
158
|
+
data = data.toJSON();
|
|
159
|
+
}
|
|
160
|
+
const targetNode = $getNodeByKey(lexicalKey);
|
|
161
|
+
if (!targetNode) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const targetType = targetNode.getType();
|
|
165
|
+
const textContent = data.__text || data.text || data.textContent;
|
|
166
|
+
if (targetType === 'text' && textContent !== undefined) {
|
|
167
|
+
// Text nodes: use diff approach to preserve cursor position
|
|
168
|
+
const textNode = targetNode;
|
|
169
|
+
const currentText = textNode.getTextContent();
|
|
170
|
+
if (currentText !== textContent) {
|
|
171
|
+
$diffTextContentAndApplyDelta(textNode, lexicalKey, currentText, textContent);
|
|
172
|
+
}
|
|
173
|
+
// Apply text-specific properties
|
|
174
|
+
if (data.format !== undefined) {
|
|
175
|
+
textNode.setFormat(data.format);
|
|
176
|
+
}
|
|
177
|
+
if (data.style !== undefined) {
|
|
178
|
+
textNode.setStyle(data.style);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if ($isDecoratorNode(targetNode)) {
|
|
182
|
+
// Decorator nodes (excalidraw, images, counters, JupyterCellNode, …):
|
|
183
|
+
// Many decorator implementations do NOT override `updateFromJSON` for
|
|
184
|
+
// their custom properties — they only set them in the constructor via
|
|
185
|
+
// `importJSON`. Since decorators are leaf nodes (no children), it is
|
|
186
|
+
// safe to replace the whole node with a fresh `importJSON` instance.
|
|
187
|
+
try {
|
|
188
|
+
const registeredNodes = binding.editor._nodes;
|
|
189
|
+
const nodeInfo = registeredNodes.get(targetType);
|
|
190
|
+
if (!nodeInfo) {
|
|
191
|
+
console.warn(` MapIntegrator: Node type '${targetType}' not registered`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const serializedData = { ...data };
|
|
195
|
+
if (!serializedData.type)
|
|
196
|
+
serializedData.type = targetType;
|
|
197
|
+
if (serializedData.version === undefined)
|
|
198
|
+
serializedData.version = 1;
|
|
199
|
+
if (!('children' in serializedData))
|
|
200
|
+
serializedData.children = [];
|
|
201
|
+
const newNode = nodeInfo.klass.importJSON(serializedData);
|
|
202
|
+
targetNode.replace(newNode);
|
|
203
|
+
// Update bidirectional mapping: old key → remove, new key → treeId
|
|
204
|
+
// Use removeMappingForKey (not deleteMapping) because we are on the
|
|
205
|
+
// integration side — the Loro tree node must NOT be deleted; we are
|
|
206
|
+
// only swapping which Lexical key points to it.
|
|
207
|
+
binding.nodeMapper.removeMappingForKey(lexicalKey);
|
|
208
|
+
binding.nodeMapper.setMapping(newNode.getKey(), treeId);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
console.warn(` MapIntegrator: importJSON+replace failed for decorator ${targetType} node ${lexicalKey}:`, error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
try {
|
|
216
|
+
const serializedData = { ...data };
|
|
217
|
+
if (!serializedData.type) {
|
|
218
|
+
serializedData.type = targetType;
|
|
219
|
+
}
|
|
220
|
+
if (serializedData.version === undefined) {
|
|
221
|
+
serializedData.version = 1;
|
|
222
|
+
}
|
|
223
|
+
// Provide empty children (TreeIntegrator manages children separately)
|
|
224
|
+
if (!('children' in serializedData)) {
|
|
225
|
+
serializedData.children = [];
|
|
226
|
+
}
|
|
227
|
+
const writable = targetNode.getWritable();
|
|
228
|
+
writable.updateFromJSON(serializedData);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.warn(` MapIntegrator: updateFromJSON failed for ${targetType} node ${lexicalKey}:`, error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseIntegrator } from './BaseIntegrator';
|
|
2
|
+
import { Binding } from '../Bindings';
|
|
3
|
+
import { Provider } from '../State';
|
|
4
|
+
interface TextDiff {
|
|
5
|
+
type: 'text';
|
|
6
|
+
diff?: Array<{
|
|
7
|
+
type: 'insert' | 'delete' | 'retain';
|
|
8
|
+
index?: number;
|
|
9
|
+
length?: number;
|
|
10
|
+
value?: string;
|
|
11
|
+
attributes?: any;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Handles text changes (character insertions, deletions, formatting)
|
|
16
|
+
*/
|
|
17
|
+
export declare class TextIntegrator implements BaseIntegrator<TextDiff> {
|
|
18
|
+
integrate(diff: TextDiff, binding: Binding, provider: Provider): void;
|
|
19
|
+
integrateInternal(diff: TextDiff, binding: Binding, provider: Provider): void;
|
|
20
|
+
private integrateTextInsert;
|
|
21
|
+
private integrateTextDelete;
|
|
22
|
+
private integrateTextRetain;
|
|
23
|
+
private applyTextFormatting;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Handles text changes (character insertions, deletions, formatting)
|
|
7
|
+
*/
|
|
8
|
+
export class TextIntegrator {
|
|
9
|
+
integrate(diff, binding, provider) {
|
|
10
|
+
this.integrateInternal(diff, binding, provider);
|
|
11
|
+
}
|
|
12
|
+
// Internal method for use when already inside editor.update()
|
|
13
|
+
integrateInternal(diff, binding, provider) {
|
|
14
|
+
if (diff.diff) {
|
|
15
|
+
diff.diff.forEach((change) => {
|
|
16
|
+
switch (change.type) {
|
|
17
|
+
case 'insert':
|
|
18
|
+
this.integrateTextInsert(change, binding, provider);
|
|
19
|
+
break;
|
|
20
|
+
case 'delete':
|
|
21
|
+
this.integrateTextDelete(change, binding, provider);
|
|
22
|
+
break;
|
|
23
|
+
case 'retain':
|
|
24
|
+
this.integrateTextRetain(change, binding, provider);
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
console.warn(` Unknown text change type: ${change.type}`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
integrateTextInsert(change, binding, provider) {
|
|
33
|
+
// To integrate text insertion, we need to know which TextNode this applies to
|
|
34
|
+
// This information should come from the event context or be tracked separately
|
|
35
|
+
// For now, this is a placeholder - in a complete implementation,
|
|
36
|
+
// we would need to identify the target TextNode and update it
|
|
37
|
+
}
|
|
38
|
+
integrateTextDelete(change, binding, provider) {
|
|
39
|
+
// Similar to insert, we need target node context for text deletion
|
|
40
|
+
}
|
|
41
|
+
integrateTextRetain(change, binding, provider) {
|
|
42
|
+
if (change.attributes) {
|
|
43
|
+
// Handle text formatting changes
|
|
44
|
+
this.applyTextFormatting({ ...change, attributes: change.attributes }, binding, provider);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
applyTextFormatting(change, binding, provider) {
|
|
48
|
+
// Text formatting in Lexical is typically integrated through TextNode format property
|
|
49
|
+
// This would need to be coordinated with the specific TextNode being modified
|
|
50
|
+
// Example formatting attributes might include:
|
|
51
|
+
// - bold, italic, underline
|
|
52
|
+
// - font size, color
|
|
53
|
+
// - etc.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TreeID } from 'loro-crdt';
|
|
2
|
+
import { BaseIntegrator } from './BaseIntegrator';
|
|
3
|
+
import { Binding } from '../Bindings';
|
|
4
|
+
import { Provider } from '../State';
|
|
5
|
+
interface TreeDiff {
|
|
6
|
+
type: 'tree';
|
|
7
|
+
diff: Array<{
|
|
8
|
+
action: 'create' | 'move' | 'delete';
|
|
9
|
+
target: TreeID;
|
|
10
|
+
parent?: TreeID;
|
|
11
|
+
index?: number;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Simplified tree diff integrater that trusts Loro's CRDT conflict resolution
|
|
16
|
+
* and applies operations directly without complex filtering or context classification
|
|
17
|
+
*/
|
|
18
|
+
export declare class TreeIntegrator implements BaseIntegrator<TreeDiff> {
|
|
19
|
+
integrate(diff: TreeDiff, binding: Binding, provider: Provider): void;
|
|
20
|
+
integrateInternal(diff: TreeDiff, binding: Binding, provider: Provider): void;
|
|
21
|
+
private integrateCreate;
|
|
22
|
+
/** Only element and decorator nodes may be inserted directly under the root. */
|
|
23
|
+
private canBeRootChild;
|
|
24
|
+
/** Insert a child into a parent element at an optional index. */
|
|
25
|
+
private insertChild;
|
|
26
|
+
private integrateMove;
|
|
27
|
+
private integrateDelete;
|
|
28
|
+
private collectDescendantKeys;
|
|
29
|
+
/**
|
|
30
|
+
* Topologically sort create operations so that parent nodes are created
|
|
31
|
+
* before their children.
|
|
32
|
+
*
|
|
33
|
+
* Computes depth using the `parent` field from the diff operations
|
|
34
|
+
* themselves rather than querying the Loro tree API (which may behave
|
|
35
|
+
* unexpectedly during event processing). For each create op, we count
|
|
36
|
+
* how many of its ancestors are also being created in this same batch.
|
|
37
|
+
* Ties at the same depth preserve the original diff order (stable sort).
|
|
38
|
+
*/
|
|
39
|
+
private topologicalSortCreates;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { $getRoot, $getNodeByKey, $isElementNode, $isDecoratorNode, } from 'lexical';
|
|
6
|
+
import { parseTreeID } from '../utils/Utils';
|
|
7
|
+
import { createLexicalNodeFromLoro } from '../nodes/NodeFactory';
|
|
8
|
+
import { invariant } from '../utils/Invariant';
|
|
9
|
+
/**
|
|
10
|
+
* Simplified tree diff integrater that trusts Loro's CRDT conflict resolution
|
|
11
|
+
* and applies operations directly without complex filtering or context classification
|
|
12
|
+
*/
|
|
13
|
+
export class TreeIntegrator {
|
|
14
|
+
integrate(diff, binding, provider) {
|
|
15
|
+
// Batch all changes in a single editor update
|
|
16
|
+
binding.editor.update(() => {
|
|
17
|
+
this.integrateInternal(diff, binding, provider);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// Internal method that can be called when already inside editor.update()
|
|
21
|
+
integrateInternal(diff, binding, provider) {
|
|
22
|
+
// Separate operations by action type
|
|
23
|
+
const deletes = [];
|
|
24
|
+
const creates = [];
|
|
25
|
+
const moves = [];
|
|
26
|
+
for (const op of diff.diff) {
|
|
27
|
+
switch (op.action) {
|
|
28
|
+
case 'delete':
|
|
29
|
+
deletes.push(op);
|
|
30
|
+
break;
|
|
31
|
+
case 'create':
|
|
32
|
+
creates.push(op);
|
|
33
|
+
break;
|
|
34
|
+
case 'move':
|
|
35
|
+
moves.push(op);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Loro often splits a node's placement into a parentless `create` followed
|
|
40
|
+
// by a `move` that supplies the real parent/index. Inline nodes (text,
|
|
41
|
+
// linebreak) can never live at the root — not even transiently — so fold
|
|
42
|
+
// the move's parent/index back into the create and drop the now-redundant
|
|
43
|
+
// move. This resolves the placement up front instead of skipping the node.
|
|
44
|
+
const moveByTarget = new Map();
|
|
45
|
+
for (const mv of moves) {
|
|
46
|
+
moveByTarget.set(String(mv.target), mv);
|
|
47
|
+
}
|
|
48
|
+
const foldedMoveTargets = new Set();
|
|
49
|
+
for (const cr of creates) {
|
|
50
|
+
if (cr.parent === undefined) {
|
|
51
|
+
const mv = moveByTarget.get(String(cr.target));
|
|
52
|
+
if (mv && mv.parent !== undefined) {
|
|
53
|
+
cr.parent = mv.parent;
|
|
54
|
+
cr.index = mv.index;
|
|
55
|
+
foldedMoveTargets.add(String(cr.target));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const effectiveMoves = moves.filter(mv => !foldedMoveTargets.has(String(mv.target)));
|
|
60
|
+
// Topologically sort create operations so parents are created before children.
|
|
61
|
+
// Without this, a TableCellNode may arrive before its parent TableRowNode,
|
|
62
|
+
// causing the cell to fall back to $getRoot() and appear on one flat line.
|
|
63
|
+
const sortedCreates = this.topologicalSortCreates(creates, binding);
|
|
64
|
+
// Phase 1: deletes.
|
|
65
|
+
deletes.forEach(op => this.integrateDelete(op, binding, provider));
|
|
66
|
+
// Phase 2: creates (parent-first, with deferral).
|
|
67
|
+
//
|
|
68
|
+
// Topological sort orders parents before children *within this batch*, but
|
|
69
|
+
// a child may still reference a parent whose Lexical mapping is not yet
|
|
70
|
+
// established (deeply nested tables, list items inside quotes, Jupyter
|
|
71
|
+
// cells, …). Rather than dropping such a node onto the root — which throws
|
|
72
|
+
// for inline nodes like text/linebreak ("Only element or decorator nodes
|
|
73
|
+
// can be inserted to the root node") and corrupts the document — we defer
|
|
74
|
+
// it and retry until a full pass makes no further progress.
|
|
75
|
+
//
|
|
76
|
+
// Created Lexical nodes are cached so deferred retries reuse the same
|
|
77
|
+
// instance instead of importing (and registering) duplicate nodes.
|
|
78
|
+
const nodeCache = new Map();
|
|
79
|
+
let pending = sortedCreates;
|
|
80
|
+
while (pending.length > 0) {
|
|
81
|
+
const stillPending = [];
|
|
82
|
+
for (const op of pending) {
|
|
83
|
+
if (this.integrateCreate(op, binding, provider, nodeCache) === 'deferred') {
|
|
84
|
+
stillPending.push(op);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (stillPending.length === pending.length) {
|
|
88
|
+
// No progress in a full pass: every remaining node references a parent
|
|
89
|
+
// that never arrived in this batch. This is a real ordering/data bug.
|
|
90
|
+
// Surface it instead of dropping the nodes onto the root (or silently
|
|
91
|
+
// discarding inline nodes, which would lose user content).
|
|
92
|
+
invariant(false, 'TreeIntegrator: create operations reference parents missing from the batch', {
|
|
93
|
+
pending: stillPending.map(op => ({
|
|
94
|
+
target: String(op.target),
|
|
95
|
+
parent: op.parent ? String(op.parent) : null,
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
pending = stillPending;
|
|
100
|
+
}
|
|
101
|
+
// Phase 3: moves (those not already folded into a create above).
|
|
102
|
+
effectiveMoves.forEach(op => this.integrateMove(op, binding, provider));
|
|
103
|
+
}
|
|
104
|
+
integrateCreate(operation, binding, provider, nodeCache) {
|
|
105
|
+
let { nodeKey } = parseTreeID(operation.target);
|
|
106
|
+
// Root node creation is integrated during initial setup. Only treat as
|
|
107
|
+
// root if it's actually a root-type node in Loro.
|
|
108
|
+
if (nodeKey === "0") {
|
|
109
|
+
const treeNode = binding.tree.getNodeByID(operation.target);
|
|
110
|
+
const elementType = treeNode?.data.get('elementType');
|
|
111
|
+
if (elementType === 'root' || !elementType) {
|
|
112
|
+
const root = $getRoot();
|
|
113
|
+
binding.nodeMapper.setMapping(root.getKey(), operation.target);
|
|
114
|
+
return 'created';
|
|
115
|
+
}
|
|
116
|
+
// nodeKey is "0" but not an actual root element — fall through.
|
|
117
|
+
}
|
|
118
|
+
// Node already exists with the same TreeID → idempotent, nothing to do.
|
|
119
|
+
const existingNode = $getNodeByKey(nodeKey);
|
|
120
|
+
if (existingNode) {
|
|
121
|
+
const existingTreeID = binding.nodeMapper.getTreeIDByLexicalKey(nodeKey);
|
|
122
|
+
if (existingTreeID === operation.target) {
|
|
123
|
+
return 'created';
|
|
124
|
+
}
|
|
125
|
+
// Different TreeID reuses this key — let Lexical assign a fresh one.
|
|
126
|
+
nodeKey = undefined;
|
|
127
|
+
}
|
|
128
|
+
// Create the Lexical node once and cache it, so deferred retries reuse
|
|
129
|
+
// the same instance instead of importing duplicate nodes.
|
|
130
|
+
const cacheKey = String(operation.target);
|
|
131
|
+
let lexicalNode = nodeCache.get(cacheKey);
|
|
132
|
+
if (lexicalNode === undefined) {
|
|
133
|
+
lexicalNode = createLexicalNodeFromLoro(operation.target, binding.tree, binding);
|
|
134
|
+
nodeCache.set(cacheKey, lexicalNode);
|
|
135
|
+
}
|
|
136
|
+
invariant(lexicalNode != null, 'integrateCreate: failed to materialise Lexical node from Loro data', { target: cacheKey });
|
|
137
|
+
if (operation.parent) {
|
|
138
|
+
const parentKey = binding.nodeMapper.getLexicalKeyByLoroId(operation.parent);
|
|
139
|
+
const parentLexicalNode = parentKey ? $getNodeByKey(parentKey) : null;
|
|
140
|
+
// Parent expected but not mapped yet → defer until it is created. This is
|
|
141
|
+
// legitimate ordering (a later pass/batch supplies the parent), not an
|
|
142
|
+
// error that hides a bug.
|
|
143
|
+
if (!(parentLexicalNode && $isElementNode(parentLexicalNode))) {
|
|
144
|
+
return 'deferred';
|
|
145
|
+
}
|
|
146
|
+
this.insertChild(parentLexicalNode, lexicalNode, operation.index);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// No parent → root. Only element/decorator nodes may live at the root; an
|
|
150
|
+
// inline node without a parent is a structural bug, not something to skip.
|
|
151
|
+
invariant(this.canBeRootChild(lexicalNode), 'integrateCreate: inline node has no parent (cannot live at root)', { target: cacheKey, nodeType: lexicalNode.getType() });
|
|
152
|
+
this.insertChild($getRoot(), lexicalNode, operation.index);
|
|
153
|
+
}
|
|
154
|
+
binding.nodeMapper.setMapping(lexicalNode.getKey(), operation.target);
|
|
155
|
+
return 'created';
|
|
156
|
+
}
|
|
157
|
+
/** Only element and decorator nodes may be inserted directly under the root. */
|
|
158
|
+
canBeRootChild(node) {
|
|
159
|
+
return $isElementNode(node) || $isDecoratorNode(node);
|
|
160
|
+
}
|
|
161
|
+
/** Insert a child into a parent element at an optional index. */
|
|
162
|
+
insertChild(parent, child, index) {
|
|
163
|
+
if (index !== undefined) {
|
|
164
|
+
parent.splice(index, 0, [child]);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
parent.append(child);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
integrateMove(operation, binding, provider) {
|
|
171
|
+
const lexicalKey = binding.nodeMapper.getLexicalKeyByLoroId(operation.target);
|
|
172
|
+
invariant(lexicalKey != null, 'integrateMove: no Lexical key for move target', { target: String(operation.target) });
|
|
173
|
+
const nodeToMove = $getNodeByKey(lexicalKey);
|
|
174
|
+
invariant(nodeToMove != null, 'integrateMove: node to move not found', { lexicalKey });
|
|
175
|
+
// Resolve the new parent.
|
|
176
|
+
let newParent;
|
|
177
|
+
if (operation.parent) {
|
|
178
|
+
const parentKey = binding.nodeMapper.getLexicalKeyByLoroId(operation.parent);
|
|
179
|
+
const parentNode = parentKey ? $getNodeByKey(parentKey) : null;
|
|
180
|
+
invariant(parentNode != null && $isElementNode(parentNode), 'integrateMove: move target parent is not a mapped element', { target: String(operation.target), parent: String(operation.parent) });
|
|
181
|
+
newParent = parentNode;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// No parent → root, only valid for element/decorator nodes.
|
|
185
|
+
invariant(this.canBeRootChild(nodeToMove), 'integrateMove: inline node cannot be moved to the root', { lexicalKey, nodeType: nodeToMove.getType() });
|
|
186
|
+
newParent = $getRoot();
|
|
187
|
+
}
|
|
188
|
+
// Remove from current position and insert at new position.
|
|
189
|
+
nodeToMove.remove();
|
|
190
|
+
if (operation.index !== undefined) {
|
|
191
|
+
newParent.splice(operation.index, 0, [nodeToMove]);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
newParent.append(nodeToMove);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
integrateDelete(operation, binding, provider) {
|
|
198
|
+
const lexicalKey = binding.nodeMapper.getLexicalKeyByLoroId(operation.target);
|
|
199
|
+
// Parent-first delete batches can make child mappings stale before their
|
|
200
|
+
// own delete ops arrive. Missing mapping here is therefore benign.
|
|
201
|
+
if (!lexicalKey) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const nodeToDelete = $getNodeByKey(lexicalKey);
|
|
205
|
+
// Node may already be gone due to an ancestor deletion in this same batch.
|
|
206
|
+
if (!nodeToDelete) {
|
|
207
|
+
binding.nodeMapper.removeMappingForKey(lexicalKey);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// The root is never deletable in Lexical; a delete resolving to it means a
|
|
211
|
+
// mapping is wrong upstream — surface it rather than silently ignoring.
|
|
212
|
+
if (nodeToDelete === $getRoot()) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Clean descendant mappings first so later child-delete ops in the same
|
|
216
|
+
// batch become harmless no-ops instead of failing with stale mappings.
|
|
217
|
+
const descendantKeys = this.collectDescendantKeys(nodeToDelete);
|
|
218
|
+
for (const key of descendantKeys) {
|
|
219
|
+
binding.nodeMapper.removeMappingForKey(key);
|
|
220
|
+
}
|
|
221
|
+
// Remove from the Lexical tree.
|
|
222
|
+
nodeToDelete.remove();
|
|
223
|
+
// Clean up mapping only — the Loro tree already processed this deletion
|
|
224
|
+
// from the remote peer; calling tree.delete() again would throw.
|
|
225
|
+
binding.nodeMapper.removeMappingForKey(lexicalKey);
|
|
226
|
+
}
|
|
227
|
+
collectDescendantKeys(node) {
|
|
228
|
+
if (!$isElementNode(node)) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const keys = [];
|
|
232
|
+
const stack = [...node.getChildren()];
|
|
233
|
+
while (stack.length > 0) {
|
|
234
|
+
const current = stack.pop();
|
|
235
|
+
keys.push(current.getKey());
|
|
236
|
+
if ($isElementNode(current)) {
|
|
237
|
+
stack.push(...current.getChildren());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return keys;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Topologically sort create operations so that parent nodes are created
|
|
244
|
+
* before their children.
|
|
245
|
+
*
|
|
246
|
+
* Computes depth using the `parent` field from the diff operations
|
|
247
|
+
* themselves rather than querying the Loro tree API (which may behave
|
|
248
|
+
* unexpectedly during event processing). For each create op, we count
|
|
249
|
+
* how many of its ancestors are also being created in this same batch.
|
|
250
|
+
* Ties at the same depth preserve the original diff order (stable sort).
|
|
251
|
+
*/
|
|
252
|
+
topologicalSortCreates(creates, binding) {
|
|
253
|
+
if (creates.length <= 1)
|
|
254
|
+
return creates;
|
|
255
|
+
// Build lookup: target → parent (only for operations in this batch)
|
|
256
|
+
const batchTargets = new Set(creates.map(op => String(op.target)));
|
|
257
|
+
const parentOf = new Map();
|
|
258
|
+
for (const op of creates) {
|
|
259
|
+
if (op.parent) {
|
|
260
|
+
parentOf.set(String(op.target), String(op.parent));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const depthCache = new Map();
|
|
264
|
+
const getDepth = (targetStr) => {
|
|
265
|
+
if (depthCache.has(targetStr))
|
|
266
|
+
return depthCache.get(targetStr);
|
|
267
|
+
let depth = 0;
|
|
268
|
+
let current = targetStr;
|
|
269
|
+
const visited = new Set();
|
|
270
|
+
// Walk up the parent chain; count only ancestors that are also being
|
|
271
|
+
// created in this batch (i.e. don't exist yet and must come first).
|
|
272
|
+
while (parentOf.has(current)) {
|
|
273
|
+
const parent = parentOf.get(current);
|
|
274
|
+
if (visited.has(parent))
|
|
275
|
+
break; // cycle guard
|
|
276
|
+
visited.add(parent);
|
|
277
|
+
if (batchTargets.has(parent)) {
|
|
278
|
+
depth++;
|
|
279
|
+
current = parent;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
break; // parent already exists locally — stop counting
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
depthCache.set(targetStr, depth);
|
|
286
|
+
return depth;
|
|
287
|
+
};
|
|
288
|
+
// Tag each operation with its original index (for stable tie-breaking)
|
|
289
|
+
const tagged = creates.map((op, i) => ({ op, depth: getDepth(String(op.target)), idx: i }));
|
|
290
|
+
tagged.sort((a, b) => a.depth - b.depth || a.idx - b.idx);
|
|
291
|
+
return tagged.map(t => t.op);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TreeID, LoroTree } from 'loro-crdt';
|
|
2
|
+
import { LexicalNode, NodeKey } from 'lexical';
|
|
3
|
+
import { Binding } from '../Bindings';
|
|
4
|
+
/**
|
|
5
|
+
* Factory function to create Lexical nodes from Loro TreeID.
|
|
6
|
+
*
|
|
7
|
+
* Uses the registered node's static `importJSON()` method, which is the
|
|
8
|
+
* canonical Lexical API for restoring nodes from serialized data. This works
|
|
9
|
+
* generically for ALL node types — built-in (paragraph, heading, table,
|
|
10
|
+
* list, quote, code…), decorator (excalidraw, image, counter…), and any
|
|
11
|
+
* externally-defined custom nodes (e.g. JupyterCellNode) as long as they
|
|
12
|
+
* are registered with the editor and implement the standard `importJSON`
|
|
13
|
+
* static method required by Lexical's serialization contract.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createLexicalNodeFromLoro(treeId: TreeID, loroTree: LoroTree, binding: Binding, parentKey?: NodeKey, nodeDataFromDiff?: any): LexicalNode | null;
|