@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,393 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { $createParagraphNode, $isElementNode } from 'lexical';
|
|
6
|
+
import { getNodeMapper } from '../nodes/NodesMapper';
|
|
7
|
+
import { invariant } from '../utils/Invariant';
|
|
8
|
+
/**
|
|
9
|
+
* Create ElementNode in Loro tree
|
|
10
|
+
*/
|
|
11
|
+
export function createElementNodeInLoro(nodeKey, elementType, // 'paragraph', 'heading', 'quote', 'link', etc.
|
|
12
|
+
parentId, index, metadata, lexicalNodeJSON, // JSON object from exportJSON()
|
|
13
|
+
options) {
|
|
14
|
+
const mapper = getNodeMapper();
|
|
15
|
+
// Use mapper to get or create the tree node (don't pass lexicalNode to avoid context issues)
|
|
16
|
+
const treeNode = mapper.getLoroNodeByLexicalKey(nodeKey, undefined, parentId, index);
|
|
17
|
+
// Store complete lexical node data as JSON object (without the key) if provided
|
|
18
|
+
if (lexicalNodeJSON) {
|
|
19
|
+
// Remove all key-related fields and children from lexical node data
|
|
20
|
+
const { key, __key, lexicalKey, children, ...cleanedLexicalData } = lexicalNodeJSON;
|
|
21
|
+
treeNode.data.set('lexical', cleanedLexicalData);
|
|
22
|
+
}
|
|
23
|
+
// Store only essential metadata (elementType for debug panel)
|
|
24
|
+
treeNode.data.set('elementType', elementType);
|
|
25
|
+
treeNode.data.set('createdAt', Date.now());
|
|
26
|
+
// Return the TreeID from the node's ID
|
|
27
|
+
return treeNode.id;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Update ElementNode in Loro tree
|
|
31
|
+
*/
|
|
32
|
+
export function updateElementNodeInLoro(nodeKey, elementType, parentId, index, previousSiblingId, nextSiblingId, metadata, lexicalNodeJSON, // JSON object from exportJSON()
|
|
33
|
+
options) {
|
|
34
|
+
const mapper = getNodeMapper();
|
|
35
|
+
// Get the existing tree node using the mapper (don't pass lexicalNode to avoid context issues)
|
|
36
|
+
const treeNode = mapper.getLoroNodeByLexicalKey(nodeKey, undefined);
|
|
37
|
+
// Store the lexical node data if provided
|
|
38
|
+
if (lexicalNodeJSON) {
|
|
39
|
+
const { key, __key, lexicalKey, children, ...cleanedLexicalData } = lexicalNodeJSON;
|
|
40
|
+
treeNode.data.set('lexical', cleanedLexicalData);
|
|
41
|
+
}
|
|
42
|
+
// Update only essential metadata
|
|
43
|
+
if (elementType !== undefined) {
|
|
44
|
+
treeNode.data.set('elementType', elementType);
|
|
45
|
+
}
|
|
46
|
+
// Move the node only if its position in Loro actually differs from Lexical.
|
|
47
|
+
if (parentId !== undefined || index !== undefined) {
|
|
48
|
+
const { tree } = options;
|
|
49
|
+
// A node can never be its own parent — that indicates a wrong parent
|
|
50
|
+
// mapping upstream, not something to silently skip.
|
|
51
|
+
invariant(!(parentId && treeNode.id === parentId), 'updateElementNodeInLoro: cycle move (node is its own parent)', { nodeKey, treeId: treeNode.id });
|
|
52
|
+
// Where does the node currently sit in the Loro tree?
|
|
53
|
+
const currentParent = treeNode.parent();
|
|
54
|
+
const currentParentId = currentParent ? currentParent.id : undefined;
|
|
55
|
+
const siblings = currentParent ? (currentParent.children() ?? []) : tree.roots();
|
|
56
|
+
const currentIndex = siblings.findIndex(child => child.id === treeNode.id);
|
|
57
|
+
const sameParent = currentParentId === parentId;
|
|
58
|
+
// Re-issuing a move on every content edit is unnecessary and races with
|
|
59
|
+
// sibling creation (the classic source of "index out of range"). Only move
|
|
60
|
+
// when the parent or index genuinely changed. This is a real no-op check,
|
|
61
|
+
// not a masked failure.
|
|
62
|
+
const needsMove = !sameParent || (index !== undefined && index !== currentIndex);
|
|
63
|
+
if (needsMove) {
|
|
64
|
+
const parentNode = parentId ? tree.getNodeByID(parentId) : null;
|
|
65
|
+
const parentChildCount = parentNode
|
|
66
|
+
? (parentNode.children()?.length ?? 0)
|
|
67
|
+
: tree.roots().length;
|
|
68
|
+
if (index !== undefined) {
|
|
69
|
+
// Loro move index bounds:
|
|
70
|
+
// same-parent reorder : [0, children.length - 1]
|
|
71
|
+
// cross-parent move : [0, children.length]
|
|
72
|
+
// An index outside this range means the position was computed against
|
|
73
|
+
// siblings that are not yet synced. Before failing, resolve index
|
|
74
|
+
// from mapped sibling anchors when available.
|
|
75
|
+
let targetIndex = index;
|
|
76
|
+
const maxIndex = sameParent
|
|
77
|
+
? Math.max(parentChildCount - 1, 0)
|
|
78
|
+
: parentChildCount;
|
|
79
|
+
if (targetIndex < 0 || targetIndex > maxIndex) {
|
|
80
|
+
const targetSiblings = parentNode
|
|
81
|
+
? (parentNode.children() ?? [])
|
|
82
|
+
: tree.roots();
|
|
83
|
+
if (previousSiblingId) {
|
|
84
|
+
const prevIndex = targetSiblings.findIndex(child => child.id === previousSiblingId);
|
|
85
|
+
if (prevIndex >= 0) {
|
|
86
|
+
targetIndex = prevIndex + 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if ((targetIndex < 0 || targetIndex > maxIndex) && nextSiblingId) {
|
|
90
|
+
const nextIndex = targetSiblings.findIndex(child => child.id === nextSiblingId);
|
|
91
|
+
if (nextIndex >= 0) {
|
|
92
|
+
targetIndex = nextIndex;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// A move can observe transiently sparse sibling mappings within one
|
|
96
|
+
// update batch (e.g. pressing Enter at the end of the document, where
|
|
97
|
+
// the sibling created in the same batch is not yet mapped). When the
|
|
98
|
+
// anchors cannot resolve the position, clamp into the valid Loro range
|
|
99
|
+
// instead of throwing: a hard failure rolls back the user's keystroke,
|
|
100
|
+
// whereas a bounded placement still converges via the CRDT and can be
|
|
101
|
+
// refined by subsequent updates.
|
|
102
|
+
if (targetIndex < 0 || targetIndex > maxIndex) {
|
|
103
|
+
const clamped = Math.min(Math.max(targetIndex, 0), maxIndex);
|
|
104
|
+
console.warn(`[loro-collab] updateElementNodeInLoro: clamping out-of-range move index`, {
|
|
105
|
+
nodeKey,
|
|
106
|
+
treeId: treeNode.id,
|
|
107
|
+
index,
|
|
108
|
+
targetIndex,
|
|
109
|
+
clamped,
|
|
110
|
+
maxIndex,
|
|
111
|
+
sameParent,
|
|
112
|
+
previousSiblingId,
|
|
113
|
+
nextSiblingId,
|
|
114
|
+
});
|
|
115
|
+
targetIndex = clamped;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
tree.move(treeNode.id, parentId, targetIndex);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
tree.move(treeNode.id, parentId, parentChildCount);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// The exported Lexical node data is already propagated by the mapper.
|
|
126
|
+
treeNode.data.set('lastUpdated', Date.now());
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Delete ElementNode from Loro tree
|
|
130
|
+
*/
|
|
131
|
+
export function deleteElementNodeInLoro(nodeKey, options) {
|
|
132
|
+
const mapper = getNodeMapper();
|
|
133
|
+
mapper.deleteMapping(nodeKey);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create ElementNode in Lexical from Loro tree data
|
|
137
|
+
*/
|
|
138
|
+
export function createElementNodeFromLoro(treeId, parentNode, // The Lexical parent node where this should be inserted
|
|
139
|
+
index, options) {
|
|
140
|
+
const { tree } = options;
|
|
141
|
+
if (!tree.has(treeId)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
145
|
+
if (!treeNode) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
// Get LexicalNodeData (JSON object format only)
|
|
149
|
+
const lexicalData = treeNode.data.get('lexical');
|
|
150
|
+
let elementNode;
|
|
151
|
+
if (lexicalData && typeof lexicalData === 'object') {
|
|
152
|
+
try {
|
|
153
|
+
// lexicalData is a direct JSON object, create the appropriate node type
|
|
154
|
+
const lexicalDataObj = lexicalData;
|
|
155
|
+
const nodeType = lexicalDataObj.type || lexicalDataObj.__type;
|
|
156
|
+
if (nodeType === 'paragraph') {
|
|
157
|
+
elementNode = $createParagraphNode();
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// For other node types, fall back to paragraph for now
|
|
161
|
+
console.warn(`Unsupported ElementNode type: ${nodeType}, creating paragraph instead`);
|
|
162
|
+
elementNode = $createParagraphNode();
|
|
163
|
+
}
|
|
164
|
+
// Apply formatting if available
|
|
165
|
+
if (lexicalDataObj.format || lexicalDataObj.__format) {
|
|
166
|
+
elementNode.setFormat(lexicalDataObj.format || lexicalDataObj.__format);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.warn('Failed to create ElementNode from JSON data for TreeID:', treeId, error);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// No lexical JSON data found - cannot create ElementNode
|
|
176
|
+
console.warn('No lexical JSON data found for ElementNode TreeID:', treeId);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
// Insert into the parent at the specified index
|
|
180
|
+
if (index !== undefined && index >= 0) {
|
|
181
|
+
parentNode.splice(index, 0, [elementNode]);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
parentNode.append(elementNode);
|
|
185
|
+
}
|
|
186
|
+
return elementNode;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Update ElementNode in Lexical from Loro tree data
|
|
190
|
+
*/
|
|
191
|
+
export function updateElementNodeFromLoro(treeId, lexicalNode, newParentNode, newIndex, options) {
|
|
192
|
+
const { tree } = options;
|
|
193
|
+
if (!tree.has(treeId)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
197
|
+
if (!treeNode || treeNode.data.get('nodeType') !== 'element') {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Update formatting if it has changed
|
|
201
|
+
const format = treeNode.data.get('format');
|
|
202
|
+
if (format !== undefined && typeof lexicalNode.setFormat === 'function' && typeof format === 'number') {
|
|
203
|
+
lexicalNode.setFormat(format);
|
|
204
|
+
}
|
|
205
|
+
const style = treeNode.data.get('style');
|
|
206
|
+
if (style !== undefined && typeof lexicalNode.setStyle === 'function' && typeof style === 'string') {
|
|
207
|
+
lexicalNode.setStyle(style);
|
|
208
|
+
}
|
|
209
|
+
const direction = treeNode.data.get('direction');
|
|
210
|
+
if (direction !== undefined && typeof lexicalNode.setDirection === 'function' &&
|
|
211
|
+
(direction === 'ltr' || direction === 'rtl')) {
|
|
212
|
+
lexicalNode.setDirection(direction);
|
|
213
|
+
}
|
|
214
|
+
// If parent or position changed, move the node
|
|
215
|
+
if (newParentNode && newIndex !== undefined) {
|
|
216
|
+
// Remove from current location
|
|
217
|
+
lexicalNode.remove();
|
|
218
|
+
// Insert at new location
|
|
219
|
+
newParentNode.splice(newIndex, 0, [lexicalNode]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Delete ElementNode from Lexical
|
|
224
|
+
*/
|
|
225
|
+
export function deleteElementNodeFromLoro(treeId, lexicalNode, options) {
|
|
226
|
+
if ($isElementNode(lexicalNode)) {
|
|
227
|
+
lexicalNode.remove();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Utility to check if a tree node represents an ElementNode
|
|
232
|
+
*/
|
|
233
|
+
export function isElementNodeInTree(treeId, tree) {
|
|
234
|
+
if (!tree.has(treeId)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
238
|
+
return treeNode?.data.get('nodeType') === 'element';
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get ElementNode data from Loro tree
|
|
242
|
+
*/
|
|
243
|
+
export function getElementNodeDataFromTree(treeId, tree) {
|
|
244
|
+
if (!tree.has(treeId)) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
248
|
+
if (!treeNode || treeNode.data.get('nodeType') !== 'element') {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
nodeType: 'element',
|
|
253
|
+
elementType: treeNode.data.get('elementType'),
|
|
254
|
+
format: treeNode.data.get('format'),
|
|
255
|
+
style: treeNode.data.get('style'),
|
|
256
|
+
direction: treeNode.data.get('direction'),
|
|
257
|
+
level: treeNode.data.get('level'), // For headings
|
|
258
|
+
createdAt: treeNode.data.get('createdAt'),
|
|
259
|
+
lastUpdated: treeNode.data.get('lastUpdated'),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Sync ElementNode children relationships in Loro tree
|
|
264
|
+
*/
|
|
265
|
+
export function syncElementNodeChildrenInLoro(nodeKey, childKeys, options) {
|
|
266
|
+
const mapper = getNodeMapper();
|
|
267
|
+
const { tree } = options;
|
|
268
|
+
// Get the existing Loro node from the mapper
|
|
269
|
+
const treeNode = mapper.getLoroNodeByLexicalKey(nodeKey);
|
|
270
|
+
if (!treeNode || treeNode.data.get('nodeType') !== 'element') {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Store children relationships for collaborative editing
|
|
274
|
+
treeNode.data.set('childKeys', childKeys);
|
|
275
|
+
treeNode.data.set('childrenLastUpdated', Date.now());
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Main propagate method for ElementNode - propagates all mutation types
|
|
279
|
+
*/
|
|
280
|
+
export function propagateElementNode(update, mutation, nodeKey, options) {
|
|
281
|
+
const { tree, peerId } = options;
|
|
282
|
+
switch (mutation) {
|
|
283
|
+
case 'created': {
|
|
284
|
+
const currentNode = update.editorState._nodeMap.get(nodeKey);
|
|
285
|
+
if (currentNode && $isElementNode(currentNode)) {
|
|
286
|
+
// Export node data and collect metadata within editor context
|
|
287
|
+
let parent, parentId, index;
|
|
288
|
+
let elementType;
|
|
289
|
+
const metadata = {};
|
|
290
|
+
let lexicalNodeJSON;
|
|
291
|
+
update.editorState.read(() => {
|
|
292
|
+
// Get parent and index for proper positioning within editor state context
|
|
293
|
+
parent = currentNode.getParent();
|
|
294
|
+
// Get parentId from the mapper instead of constructing it manually
|
|
295
|
+
const mapper = getNodeMapper();
|
|
296
|
+
parentId = parent ? mapper.getTreeIDByLexicalKey(parent.getKey()) : undefined;
|
|
297
|
+
index = currentNode.getIndexWithinParent();
|
|
298
|
+
if (parentId) {
|
|
299
|
+
const ownTreeID = mapper.getTreeIDByLexicalKey(nodeKey);
|
|
300
|
+
if (ownTreeID === parentId) {
|
|
301
|
+
parentId = undefined; // Prevent cycle
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Determine element type
|
|
305
|
+
elementType = currentNode.getType(); // 'paragraph', 'heading', etc.
|
|
306
|
+
// If parent exists but doesn't have mapping yet, this shouldn't happen
|
|
307
|
+
// because SyncLexicalToLoro sorts element mutations by depth.
|
|
308
|
+
// But as a safety net, log a warning.
|
|
309
|
+
if (parent && !parentId) {
|
|
310
|
+
console.warn(` ElementNodePropagator: parent mapping missing for ${elementType} nodeKey=${nodeKey}, parentKey=${parent.getKey()}`);
|
|
311
|
+
}
|
|
312
|
+
// Collect metadata (format, style, direction, etc.)
|
|
313
|
+
if (typeof currentNode.getFormat === 'function') {
|
|
314
|
+
metadata.format = currentNode.getFormat();
|
|
315
|
+
}
|
|
316
|
+
if (typeof currentNode.getStyle === 'function') {
|
|
317
|
+
metadata.style = currentNode.getStyle();
|
|
318
|
+
}
|
|
319
|
+
if (typeof currentNode.getDirection === 'function') {
|
|
320
|
+
metadata.direction = currentNode.getDirection();
|
|
321
|
+
}
|
|
322
|
+
// Export node data within editor context where node methods are available
|
|
323
|
+
lexicalNodeJSON = currentNode.exportJSON();
|
|
324
|
+
});
|
|
325
|
+
// Create the node in Loro after safely reading from editor state
|
|
326
|
+
createElementNodeInLoro(nodeKey, elementType, parentId, index, metadata, lexicalNodeJSON, options);
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case 'updated': {
|
|
331
|
+
const currentNode = update.editorState._nodeMap.get(nodeKey);
|
|
332
|
+
if (currentNode && $isElementNode(currentNode)) {
|
|
333
|
+
// Use editorState.read() to safely access node methods
|
|
334
|
+
let parent, parentId, index;
|
|
335
|
+
let previousSiblingId;
|
|
336
|
+
let nextSiblingId;
|
|
337
|
+
let elementType;
|
|
338
|
+
const metadata = {};
|
|
339
|
+
// Export node data and collect metadata within editor context
|
|
340
|
+
let lexicalNodeJSON;
|
|
341
|
+
update.editorState.read(() => {
|
|
342
|
+
// Get current position within editor state context
|
|
343
|
+
parent = currentNode.getParent();
|
|
344
|
+
// Get parentId from the mapper instead of constructing it manually
|
|
345
|
+
const mapper = getNodeMapper();
|
|
346
|
+
parentId = parent ? mapper.getTreeIDByLexicalKey(parent.getKey()) : undefined;
|
|
347
|
+
index = currentNode.getIndexWithinParent();
|
|
348
|
+
// Resolve nearest mapped sibling anchors to stabilize target index
|
|
349
|
+
// when Lexical index includes siblings not yet represented in Loro.
|
|
350
|
+
if (parent) {
|
|
351
|
+
const siblings = parent.getChildren();
|
|
352
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
353
|
+
const candidate = siblings[i];
|
|
354
|
+
const candidateTreeId = mapper.getTreeIDByLexicalKey(candidate.getKey());
|
|
355
|
+
if (candidateTreeId && tree.has(candidateTreeId)) {
|
|
356
|
+
previousSiblingId = candidateTreeId;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (let i = index + 1; i < siblings.length; i++) {
|
|
361
|
+
const candidate = siblings[i];
|
|
362
|
+
const candidateTreeId = mapper.getTreeIDByLexicalKey(candidate.getKey());
|
|
363
|
+
if (candidateTreeId && tree.has(candidateTreeId)) {
|
|
364
|
+
nextSiblingId = candidateTreeId;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Get element type and metadata
|
|
370
|
+
elementType = currentNode.getType();
|
|
371
|
+
if (typeof currentNode.getFormat === 'function') {
|
|
372
|
+
metadata.format = currentNode.getFormat();
|
|
373
|
+
}
|
|
374
|
+
if (typeof currentNode.getStyle === 'function') {
|
|
375
|
+
metadata.style = currentNode.getStyle();
|
|
376
|
+
}
|
|
377
|
+
if (typeof currentNode.getDirection === 'function') {
|
|
378
|
+
metadata.direction = currentNode.getDirection();
|
|
379
|
+
}
|
|
380
|
+
// Export node data within editor context where node methods are available
|
|
381
|
+
lexicalNodeJSON = currentNode.exportJSON();
|
|
382
|
+
});
|
|
383
|
+
// Update the node in Loro after safely reading from editor state
|
|
384
|
+
updateElementNodeInLoro(nodeKey, elementType, parentId, index, previousSiblingId, nextSiblingId, metadata, lexicalNodeJSON, options);
|
|
385
|
+
}
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case 'destroyed': {
|
|
389
|
+
deleteElementNodeInLoro(nodeKey, options);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TreeID, LoroTree } from 'loro-crdt';
|
|
2
|
+
import { LineBreakNode, UpdateListenerPayload, NodeKey, ElementNode } from 'lexical';
|
|
3
|
+
import { Binding } from '../Bindings';
|
|
4
|
+
/**
|
|
5
|
+
* LineBreakNode Propagator for Loro Tree Collaboration
|
|
6
|
+
*
|
|
7
|
+
* LineBreakNode characteristics:
|
|
8
|
+
* - Represents '\n' characters in the editor
|
|
9
|
+
* - Should never have '\n' in text nodes, use LineBreakNode instead
|
|
10
|
+
* - Works consistently across browsers and operating systems
|
|
11
|
+
* - Is a leaf node (cannot have children)
|
|
12
|
+
* - Cannot be extended/subclassed
|
|
13
|
+
*/
|
|
14
|
+
export interface LineBreakNodeMutatorOptions {
|
|
15
|
+
binding: Binding;
|
|
16
|
+
tree: LoroTree;
|
|
17
|
+
peerId: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create LineBreakNode in Loro tree
|
|
21
|
+
*/
|
|
22
|
+
export declare function createLineBreakNodeInLoro(nodeKey: NodeKey, parentId?: TreeID, index?: number, lexicalNodeJSON?: any, // JSON object from exportJSON()
|
|
23
|
+
options?: LineBreakNodeMutatorOptions): TreeID;
|
|
24
|
+
/**
|
|
25
|
+
* Update LineBreakNode in Loro tree
|
|
26
|
+
* Note: LineBreakNodes typically don't change much since they represent '\n'
|
|
27
|
+
*/
|
|
28
|
+
export declare function updateLineBreakNodeInLoro(nodeKey: NodeKey, parentId?: TreeID, index?: number, lexicalNodeJSON?: any, // JSON object from exportJSON()
|
|
29
|
+
options?: LineBreakNodeMutatorOptions): void;
|
|
30
|
+
/**
|
|
31
|
+
* Delete LineBreakNode from Loro tree
|
|
32
|
+
*/
|
|
33
|
+
export declare function deleteLineBreakNodeInLoro(nodeKey: NodeKey, options: LineBreakNodeMutatorOptions): void;
|
|
34
|
+
/**
|
|
35
|
+
* Create LineBreakNode in Lexical from Loro tree data
|
|
36
|
+
*/
|
|
37
|
+
export declare function createLineBreakNodeFromLoro(treeId: TreeID, parentNode: ElementNode, index?: number, options?: LineBreakNodeMutatorOptions): LineBreakNode;
|
|
38
|
+
/**
|
|
39
|
+
* Update LineBreakNode in Lexical from Loro tree data
|
|
40
|
+
*/
|
|
41
|
+
export declare function updateLineBreakNodeFromLoro(treeId: TreeID, lexicalNode: LineBreakNode, newParentNode?: ElementNode, newIndex?: number, options?: LineBreakNodeMutatorOptions): void;
|
|
42
|
+
/**
|
|
43
|
+
* Delete LineBreakNode from Lexical
|
|
44
|
+
*/
|
|
45
|
+
export declare function deleteLineBreakNodeFromLoro(treeId: TreeID, lexicalNode: LineBreakNode, options?: LineBreakNodeMutatorOptions): void;
|
|
46
|
+
/**
|
|
47
|
+
* Utility to check if a tree node represents a LineBreakNode
|
|
48
|
+
*/
|
|
49
|
+
export declare function isLineBreakNodeInTree(treeId: TreeID, tree: LoroTree): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Get LineBreakNode data from Loro tree
|
|
52
|
+
*/
|
|
53
|
+
export declare function getLineBreakNodeDataFromTree(treeId: TreeID, tree: LoroTree): any;
|
|
54
|
+
/**
|
|
55
|
+
* Main propagate method for LineBreakNode - propagates all mutation types
|
|
56
|
+
*/
|
|
57
|
+
export declare function propagateLineBreakNode(update: UpdateListenerPayload, mutation: 'created' | 'updated' | 'destroyed', nodeKey: NodeKey, options: LineBreakNodeMutatorOptions): void;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { $createLineBreakNode, $isLineBreakNode } from 'lexical';
|
|
6
|
+
import { getNodeMapper } from '../nodes/NodesMapper';
|
|
7
|
+
/**
|
|
8
|
+
* Create LineBreakNode in Loro tree
|
|
9
|
+
*/
|
|
10
|
+
export function createLineBreakNodeInLoro(nodeKey, parentId, index, lexicalNodeJSON, // JSON object from exportJSON()
|
|
11
|
+
options) {
|
|
12
|
+
const mapper = getNodeMapper();
|
|
13
|
+
// Use mapper to get or create the tree node (don't pass lexicalNode to avoid context issues)
|
|
14
|
+
const treeNode = mapper.getLoroNodeByLexicalKey(nodeKey, undefined, // don't pass lexicalNode to avoid context issues
|
|
15
|
+
parentId, index);
|
|
16
|
+
// Store complete lexical node data as clean JSON if provided
|
|
17
|
+
if (lexicalNodeJSON) {
|
|
18
|
+
// Store complete lexical JSON without all key-related fields and children
|
|
19
|
+
const { key, __key, lexicalKey, children, ...cleanedData } = lexicalNodeJSON;
|
|
20
|
+
treeNode.data.set('lexical', cleanedData);
|
|
21
|
+
}
|
|
22
|
+
// Store only essential metadata
|
|
23
|
+
treeNode.data.set('elementType', 'linebreak');
|
|
24
|
+
treeNode.data.set('createdAt', Date.now());
|
|
25
|
+
// The exported Lexical node data is already propagated by the mapper
|
|
26
|
+
// Return the TreeID from the node's ID
|
|
27
|
+
return treeNode.id;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Update LineBreakNode in Loro tree
|
|
31
|
+
* Note: LineBreakNodes typically don't change much since they represent '\n'
|
|
32
|
+
*/
|
|
33
|
+
export function updateLineBreakNodeInLoro(nodeKey, parentId, index, lexicalNodeJSON, // JSON object from exportJSON()
|
|
34
|
+
options) {
|
|
35
|
+
const mapper = getNodeMapper();
|
|
36
|
+
const { tree } = options;
|
|
37
|
+
// Get the existing Loro node from the mapper
|
|
38
|
+
const treeNode = mapper.getLoroNodeByLexicalKey(nodeKey, undefined);
|
|
39
|
+
if (!treeNode) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const treeId = treeNode.id;
|
|
43
|
+
// Store complete lexical node data as clean JSON if provided
|
|
44
|
+
if (lexicalNodeJSON) {
|
|
45
|
+
// Store complete lexical JSON without all key-related fields and children
|
|
46
|
+
const { key, __key, lexicalKey, children, ...cleanedData } = lexicalNodeJSON;
|
|
47
|
+
treeNode.data.set('lexical', cleanedData);
|
|
48
|
+
}
|
|
49
|
+
// Move the node if parent or position changed
|
|
50
|
+
if (parentId !== undefined || index !== undefined) {
|
|
51
|
+
tree.move(treeId, parentId, index);
|
|
52
|
+
}
|
|
53
|
+
// Update only essential metadata
|
|
54
|
+
treeNode.data.set('elementType', 'linebreak');
|
|
55
|
+
treeNode.data.set('lastUpdated', Date.now());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Delete LineBreakNode from Loro tree
|
|
59
|
+
*/
|
|
60
|
+
export function deleteLineBreakNodeInLoro(nodeKey, options) {
|
|
61
|
+
const mapper = getNodeMapper();
|
|
62
|
+
// Use the mapper's delete method which propagates TreeID lookup internally
|
|
63
|
+
mapper.deleteMapping(nodeKey);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create LineBreakNode in Lexical from Loro tree data
|
|
67
|
+
*/
|
|
68
|
+
export function createLineBreakNodeFromLoro(treeId, parentNode, index, options) {
|
|
69
|
+
const { tree } = options;
|
|
70
|
+
if (!tree.has(treeId)) {
|
|
71
|
+
return $createLineBreakNode(); // Fallback to empty line break
|
|
72
|
+
}
|
|
73
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
74
|
+
let lineBreakNode;
|
|
75
|
+
// Get LexicalNodeData (JSON object format only)
|
|
76
|
+
const lexicalData = treeNode?.data.get('lexical');
|
|
77
|
+
if (lexicalData && typeof lexicalData === 'object') {
|
|
78
|
+
// lexicalData is a direct JSON object - LineBreak nodes are simple
|
|
79
|
+
lineBreakNode = $createLineBreakNode();
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// No lexical data - just create a new line break node
|
|
83
|
+
lineBreakNode = $createLineBreakNode();
|
|
84
|
+
}
|
|
85
|
+
// Insert into the parent at the specified index
|
|
86
|
+
if (index !== undefined && index >= 0) {
|
|
87
|
+
parentNode.splice(index, 0, [lineBreakNode]);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
parentNode.append(lineBreakNode);
|
|
91
|
+
}
|
|
92
|
+
return lineBreakNode;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Update LineBreakNode in Lexical from Loro tree data
|
|
96
|
+
*/
|
|
97
|
+
export function updateLineBreakNodeFromLoro(treeId, lexicalNode, newParentNode, newIndex, options) {
|
|
98
|
+
// If parent or position changed, move the node
|
|
99
|
+
if (newParentNode && newIndex !== undefined) {
|
|
100
|
+
// Remove from current location
|
|
101
|
+
lexicalNode.remove();
|
|
102
|
+
// Insert at new location
|
|
103
|
+
newParentNode.splice(newIndex, 0, [lexicalNode]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete LineBreakNode from Lexical
|
|
108
|
+
*/
|
|
109
|
+
export function deleteLineBreakNodeFromLoro(treeId, lexicalNode, options) {
|
|
110
|
+
if ($isLineBreakNode(lexicalNode)) {
|
|
111
|
+
lexicalNode.remove();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Utility to check if a tree node represents a LineBreakNode
|
|
116
|
+
*/
|
|
117
|
+
export function isLineBreakNodeInTree(treeId, tree) {
|
|
118
|
+
if (!tree.has(treeId)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
122
|
+
return treeNode?.data.get('nodeType') === 'linebreak';
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get LineBreakNode data from Loro tree
|
|
126
|
+
*/
|
|
127
|
+
export function getLineBreakNodeDataFromTree(treeId, tree) {
|
|
128
|
+
if (!tree.has(treeId)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const treeNode = tree.getNodeByID(treeId);
|
|
132
|
+
if (!treeNode || treeNode.data.get('nodeType') !== 'linebreak') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
nodeType: 'linebreak',
|
|
137
|
+
createdAt: treeNode.data.get('createdAt'),
|
|
138
|
+
lastUpdated: treeNode.data.get('lastUpdated'),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Main propagate method for LineBreakNode - propagates all mutation types
|
|
143
|
+
*/
|
|
144
|
+
export function propagateLineBreakNode(update, mutation, nodeKey, options) {
|
|
145
|
+
const { tree, peerId } = options;
|
|
146
|
+
switch (mutation) {
|
|
147
|
+
case 'created': {
|
|
148
|
+
const currentNode = update.editorState._nodeMap.get(nodeKey);
|
|
149
|
+
if (currentNode && $isLineBreakNode(currentNode)) {
|
|
150
|
+
// Get parent, positioning, and JSON data using editor state context
|
|
151
|
+
const { parentId, index, lexicalNodeJSON } = update.editorState.read(() => {
|
|
152
|
+
const parent = currentNode.getParent();
|
|
153
|
+
// Get parentId from the mapper instead of constructing it manually
|
|
154
|
+
const mapper = getNodeMapper();
|
|
155
|
+
const parentId = parent ? mapper.getTreeIDByLexicalKey(parent.getKey()) : undefined;
|
|
156
|
+
const index = currentNode.getIndexWithinParent();
|
|
157
|
+
// Export node data as JSON object within editor context where node methods are available
|
|
158
|
+
let lexicalNodeJSON = undefined;
|
|
159
|
+
try {
|
|
160
|
+
lexicalNodeJSON = currentNode.exportJSON();
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.warn('Failed to export node JSON in propagateLineBreakNode created:', error);
|
|
164
|
+
}
|
|
165
|
+
return { parentId, index, lexicalNodeJSON };
|
|
166
|
+
});
|
|
167
|
+
createLineBreakNodeInLoro(nodeKey, parentId, index, lexicalNodeJSON, options);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'updated': {
|
|
172
|
+
const currentNode = update.editorState._nodeMap.get(nodeKey);
|
|
173
|
+
if (currentNode && $isLineBreakNode(currentNode)) {
|
|
174
|
+
// Check if position changed and export data using editor state context
|
|
175
|
+
const { parentId, index, lexicalNodeJSON } = update.editorState.read(() => {
|
|
176
|
+
const parent = currentNode.getParent();
|
|
177
|
+
// Get parentId from the mapper instead of constructing it manually
|
|
178
|
+
const mapper = getNodeMapper();
|
|
179
|
+
const parentId = parent ? mapper.getTreeIDByLexicalKey(parent.getKey()) : undefined;
|
|
180
|
+
const index = currentNode.getIndexWithinParent();
|
|
181
|
+
// Export node data within editor context where node methods are available
|
|
182
|
+
let lexicalNodeJSON = undefined;
|
|
183
|
+
try {
|
|
184
|
+
lexicalNodeJSON = currentNode.exportJSON();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
console.warn('Failed to export node data in propagateLineBreakNode updated:', error);
|
|
188
|
+
}
|
|
189
|
+
return { parentId, index, lexicalNodeJSON };
|
|
190
|
+
});
|
|
191
|
+
updateLineBreakNodeInLoro(nodeKey, parentId, index, lexicalNodeJSON, options);
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'destroyed': {
|
|
196
|
+
deleteLineBreakNodeInLoro(nodeKey, options);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|