@datalayer/lexical-loro 0.2.4 → 0.2.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/lib/App.d.ts +2 -0
- package/lib/App.js +141 -0
- package/lib/Editor.d.ts +2 -0
- package/lib/Editor.js +115 -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 +24 -0
- package/lib/collab/loro/LexicalCollaborationPlugin.js +83 -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 +102 -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 +36 -0
- package/lib/collab/loro/integrators/TreeIntegrator.js +251 -0
- package/lib/collab/loro/nodes/NodeFactory.d.ts +15 -0
- package/lib/collab/loro/nodes/NodeFactory.js +101 -0
- package/lib/collab/loro/nodes/NodesMapper.d.ts +120 -0
- package/lib/collab/loro/nodes/NodesMapper.js +277 -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 +326 -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 +440 -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 +116 -0
- package/lib/collab/loro/provider/websocket.js +911 -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 +475 -0
- package/lib/collab/loro/sync/SyncLexicalToLoro.d.ts +4 -0
- package/lib/collab/loro/sync/SyncLexicalToLoro.js +113 -0
- package/lib/collab/loro/sync/SyncLoroToLexical.d.ts +5 -0
- package/lib/collab/loro/sync/SyncLoroToLexical.js +100 -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 +260 -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/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 +35 -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 +219 -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 +1 -1
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { $getNodeByKey, $getSelection, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, } from 'lexical';
|
|
6
|
+
import { createDOMRange, createRectsFromDOMRange } from '@lexical/selection';
|
|
7
|
+
/*****************************************************************************/
|
|
8
|
+
// Helper function to find Loro tree node for a Lexical NodeKey (read-only lookup)
|
|
9
|
+
function findLoroTreeNodeForLexicalKey(nodeKey, binding) {
|
|
10
|
+
try {
|
|
11
|
+
const nodeMapper = binding.nodeMapper;
|
|
12
|
+
const treeId = nodeMapper.getTreeIDByLexicalKey(nodeKey);
|
|
13
|
+
if (!treeId) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const tree = binding.tree;
|
|
17
|
+
if (tree.has(treeId)) {
|
|
18
|
+
return tree.getNodeByID(treeId) || null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.warn('Failed to find Loro tree node for Lexical key:', nodeKey, error);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Helper function to convert a Lexical Point to a Loro Cursor.
|
|
28
|
+
// The cursor data uses TreeIDs (stable across all clients) instead of NodeKeys
|
|
29
|
+
// (local to each editor instance) to ensure correct cross-client resolution.
|
|
30
|
+
function convertLexicalPointToCursor(point, binding) {
|
|
31
|
+
try {
|
|
32
|
+
const node = $getNodeByKey(point.key);
|
|
33
|
+
if (!node) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const nodeMapper = binding.nodeMapper;
|
|
37
|
+
// --- Text node ---
|
|
38
|
+
if ($isTextNode(node)) {
|
|
39
|
+
const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
|
|
40
|
+
if (!treeId) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const textContent = node.getTextContent();
|
|
44
|
+
const offset = Math.min(point.offset, textContent.length);
|
|
45
|
+
return {
|
|
46
|
+
treeId,
|
|
47
|
+
offset,
|
|
48
|
+
pointType: 'text',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// --- Element node (paragraphs, headings, table cells, …) ---
|
|
52
|
+
if ($isElementNode(node)) {
|
|
53
|
+
const children = node.getChildren();
|
|
54
|
+
if (children.length === 0) {
|
|
55
|
+
// Empty element (e.g. freshly-created paragraph after Enter)
|
|
56
|
+
const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
|
|
57
|
+
if (!treeId)
|
|
58
|
+
return null;
|
|
59
|
+
return { treeId, offset: point.offset, pointType: 'element' };
|
|
60
|
+
}
|
|
61
|
+
// Try to resolve the child at `point.offset`
|
|
62
|
+
if (point.offset < children.length) {
|
|
63
|
+
const child = children[point.offset];
|
|
64
|
+
if (child) {
|
|
65
|
+
const childTreeId = nodeMapper.getTreeIDByLexicalKey(child.getKey());
|
|
66
|
+
if (childTreeId) {
|
|
67
|
+
return {
|
|
68
|
+
treeId: childTreeId,
|
|
69
|
+
offset: 0,
|
|
70
|
+
pointType: $isTextNode(child) ? 'text' : 'element',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Fallback: cursor is at/past the end of this element's children
|
|
76
|
+
const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
|
|
77
|
+
if (!treeId)
|
|
78
|
+
return null;
|
|
79
|
+
return { treeId, offset: point.offset, pointType: 'element' };
|
|
80
|
+
}
|
|
81
|
+
// --- Any other node type (decorator, linebreak, …) ---
|
|
82
|
+
const treeId = nodeMapper.getTreeIDByLexicalKey(point.key);
|
|
83
|
+
if (!treeId)
|
|
84
|
+
return null;
|
|
85
|
+
return { treeId, offset: point.offset, pointType: 'element' };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.warn('Failed to convert Lexical point to Loro cursor:', error);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Helper function to convert Loro cursor data back to Lexical selection.
|
|
93
|
+
// Always resolves via TreeID → NodeMapper → local NodeKey.
|
|
94
|
+
// NodeKeys are never transmitted between clients because they are
|
|
95
|
+
// editor-instance-local and would map to wrong nodes on the remote side.
|
|
96
|
+
function convertLoroSelectionToLexical(anchorCursor, focusCursor, binding) {
|
|
97
|
+
try {
|
|
98
|
+
if (!anchorCursor || !focusCursor) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const anchor = anchorCursor;
|
|
102
|
+
const focus = focusCursor;
|
|
103
|
+
const nodeMapper = binding.nodeMapper;
|
|
104
|
+
let anchorKey = null;
|
|
105
|
+
let focusKey = null;
|
|
106
|
+
if (anchor.treeId) {
|
|
107
|
+
anchorKey = nodeMapper.getLexicalKeyByLoroId(anchor.treeId);
|
|
108
|
+
}
|
|
109
|
+
if (focus.treeId) {
|
|
110
|
+
focusKey = nodeMapper.getLexicalKeyByLoroId(focus.treeId);
|
|
111
|
+
}
|
|
112
|
+
if (!anchorKey || !focusKey) {
|
|
113
|
+
// Mapping not yet available — tree operations may not have arrived yet.
|
|
114
|
+
// Cursors will be re-synced after the next tree integration.
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
anchorKey,
|
|
119
|
+
anchorOffset: anchor.offset || 0,
|
|
120
|
+
focusKey,
|
|
121
|
+
focusOffset: focus.offset || 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.warn('Failed to convert Loro cursors to Lexical selection:', error);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Helper function to set a point in Lexical selection
|
|
130
|
+
function $setPoint(point, key, offset) {
|
|
131
|
+
if (point.key !== key || point.offset !== offset) {
|
|
132
|
+
let anchorNode = $getNodeByKey(key);
|
|
133
|
+
if (anchorNode !== null &&
|
|
134
|
+
!$isElementNode(anchorNode) &&
|
|
135
|
+
!$isTextNode(anchorNode)) {
|
|
136
|
+
const parent = anchorNode.getParentOrThrow();
|
|
137
|
+
key = parent.getKey();
|
|
138
|
+
offset = anchorNode.getIndexWithinParent();
|
|
139
|
+
anchorNode = parent;
|
|
140
|
+
}
|
|
141
|
+
point.set(key, offset, $isElementNode(anchorNode) ? 'element' : 'text');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Helper functions for cursor UI management
|
|
145
|
+
function createCollabCursor(name, color) {
|
|
146
|
+
return {
|
|
147
|
+
color: color,
|
|
148
|
+
name: name,
|
|
149
|
+
selection: null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset, isCurrentUser = false) {
|
|
153
|
+
const color = cursor.color;
|
|
154
|
+
// Helper function to convert color to rgba with opacity
|
|
155
|
+
const getColorWithOpacity = (color, opacity) => {
|
|
156
|
+
if (color.startsWith('#')) {
|
|
157
|
+
const hex = color.slice(1);
|
|
158
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
159
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
160
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
161
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
162
|
+
}
|
|
163
|
+
return color;
|
|
164
|
+
};
|
|
165
|
+
const caretColor = isCurrentUser ? getColorWithOpacity(color, 0.6) : color;
|
|
166
|
+
const nameBackgroundColor = isCurrentUser ? getColorWithOpacity(color, 0.7) : color;
|
|
167
|
+
const caret = document.createElement('span');
|
|
168
|
+
caret.style.cssText = `position:absolute;top:0;bottom:0;right:-1px;width:2px;background-color:${caretColor};z-index:10;${isCurrentUser ? 'opacity:0.8;' : ''}`;
|
|
169
|
+
const name = document.createElement('span');
|
|
170
|
+
name.textContent = cursor.name;
|
|
171
|
+
name.style.cssText = `position:absolute;left:-2px;top:-16px;background-color:${nameBackgroundColor};color:#fff;line-height:12px;font-size:12px;padding:2px;font-family:Arial;font-weight:bold;white-space:nowrap;${isCurrentUser ? 'opacity:0.9;' : ''}`;
|
|
172
|
+
caret.appendChild(name);
|
|
173
|
+
return {
|
|
174
|
+
anchor: { key: anchorKey, offset: anchorOffset },
|
|
175
|
+
focus: { key: focusKey, offset: focusOffset },
|
|
176
|
+
caret,
|
|
177
|
+
color: cursor.color,
|
|
178
|
+
name,
|
|
179
|
+
selections: [],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function destroyCursor(binding, cursor) {
|
|
183
|
+
const selection = cursor.selection;
|
|
184
|
+
if (selection !== null) {
|
|
185
|
+
destroySelection(binding, selection);
|
|
186
|
+
cursor.selection = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function destroySelection(binding, selection) {
|
|
190
|
+
const cursorsContainer = binding.cursorsContainer;
|
|
191
|
+
if (cursorsContainer !== null) {
|
|
192
|
+
const selections = selection.selections;
|
|
193
|
+
const selectionsLength = selections.length;
|
|
194
|
+
for (let i = 0; i < selectionsLength; i++) {
|
|
195
|
+
cursorsContainer.removeChild(selections[i]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/*****************************************************************************/
|
|
200
|
+
export function syncCursorPositions(binding, provider, options) {
|
|
201
|
+
const { getAwarenessStates = getAwarenessStatesDefault } = options ?? {};
|
|
202
|
+
const awarenessStates = Array.from(getAwarenessStates(binding, provider));
|
|
203
|
+
const localClientID = binding.clientID;
|
|
204
|
+
const cursors = binding.cursors;
|
|
205
|
+
const editor = binding.editor;
|
|
206
|
+
const nodeMap = editor._editorState._nodeMap;
|
|
207
|
+
const visitedClientIDs = new Set();
|
|
208
|
+
// Process all cursor positions from awareness (including local user)
|
|
209
|
+
for (let i = 0; i < awarenessStates.length; i++) {
|
|
210
|
+
const awarenessState = awarenessStates[i];
|
|
211
|
+
const [clientID, awareness] = awarenessState;
|
|
212
|
+
visitedClientIDs.add(clientID);
|
|
213
|
+
const { name, color, focusing } = awareness;
|
|
214
|
+
const isCurrentUser = clientID === localClientID;
|
|
215
|
+
let selection = null;
|
|
216
|
+
let cursor = cursors.get(clientID);
|
|
217
|
+
if (cursor === undefined) {
|
|
218
|
+
// Add "(Me)" label for current user's cursor
|
|
219
|
+
const cursorName = isCurrentUser ? `${name} (Me)` : name;
|
|
220
|
+
cursor = createCollabCursor(cursorName, color);
|
|
221
|
+
cursors.set(clientID, cursor);
|
|
222
|
+
}
|
|
223
|
+
// Render cursor/selection whenever valid anchorPos/focusPos exist.
|
|
224
|
+
// We intentionally do NOT gate on `focusing` because the FOCUS_COMMAND
|
|
225
|
+
// and selection-update can race — the selection update may broadcast
|
|
226
|
+
// before FOCUS_COMMAND fires, leaving `focusing: false` even though
|
|
227
|
+
// the user clearly has the editor focused (they are selecting text).
|
|
228
|
+
const { anchorPos, focusPos } = awareness;
|
|
229
|
+
if (anchorPos !== null && focusPos !== null) {
|
|
230
|
+
const selectionInfo = convertLoroSelectionToLexical(anchorPos, focusPos, binding);
|
|
231
|
+
if (selectionInfo) {
|
|
232
|
+
const { anchorKey, anchorOffset, focusKey, focusOffset } = selectionInfo;
|
|
233
|
+
selection = cursor.selection;
|
|
234
|
+
if (selection === null) {
|
|
235
|
+
selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset, isCurrentUser);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Update existing selection
|
|
239
|
+
const anchor = selection.anchor;
|
|
240
|
+
const focus = selection.focus;
|
|
241
|
+
anchor.key = anchorKey;
|
|
242
|
+
anchor.offset = anchorOffset;
|
|
243
|
+
focus.key = focusKey;
|
|
244
|
+
focus.offset = focusOffset;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
updateCursor(binding, cursor, selection, nodeMap, isCurrentUser);
|
|
249
|
+
}
|
|
250
|
+
// Clean up cursors for clients that are no longer present
|
|
251
|
+
const allClientIDs = Array.from(cursors.keys());
|
|
252
|
+
for (let i = 0; i < allClientIDs.length; i++) {
|
|
253
|
+
const clientID = allClientIDs[i];
|
|
254
|
+
if (!visitedClientIDs.has(clientID)) {
|
|
255
|
+
const cursor = cursors.get(clientID);
|
|
256
|
+
if (cursor !== undefined) {
|
|
257
|
+
destroyCursor(binding, cursor);
|
|
258
|
+
cursors.delete(clientID);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Default function to get awareness states from provider
|
|
264
|
+
function getAwarenessStatesDefault(binding, provider) {
|
|
265
|
+
return provider.awareness.getStates();
|
|
266
|
+
}
|
|
267
|
+
// Function to update cursor visualization in the DOM
|
|
268
|
+
function updateCursor(binding, cursor, nextSelection, nodeMap, // LexicalEditor NodeMap type
|
|
269
|
+
isCurrentUser = false) {
|
|
270
|
+
const editor = binding.editor;
|
|
271
|
+
const rootElement = editor.getRootElement();
|
|
272
|
+
const cursorsContainer = binding.cursorsContainer;
|
|
273
|
+
if (cursorsContainer === null || rootElement === null) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// The cursorsContainer is a position:fixed overlay covering the viewport.
|
|
277
|
+
// Use its own getBoundingClientRect() as the coordinate reference,
|
|
278
|
+
// so that absolute children are positioned correctly relative to it.
|
|
279
|
+
const containerRect = cursorsContainer.getBoundingClientRect();
|
|
280
|
+
const prevSelection = cursor.selection;
|
|
281
|
+
if (nextSelection === null) {
|
|
282
|
+
if (prevSelection === null) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
cursor.selection = null;
|
|
287
|
+
destroySelection(binding, prevSelection);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
cursor.selection = nextSelection;
|
|
293
|
+
}
|
|
294
|
+
const caret = nextSelection.caret;
|
|
295
|
+
const color = nextSelection.color;
|
|
296
|
+
const selections = nextSelection.selections;
|
|
297
|
+
const anchor = nextSelection.anchor;
|
|
298
|
+
const focus = nextSelection.focus;
|
|
299
|
+
const anchorKey = anchor.key;
|
|
300
|
+
const focusKey = focus.key;
|
|
301
|
+
const anchorNode = nodeMap.get(anchorKey);
|
|
302
|
+
const focusNode = nodeMap.get(focusKey);
|
|
303
|
+
if (anchorNode == null || focusNode == null) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Determine if the selection is collapsed (cursor only) or expanded (text selected)
|
|
307
|
+
const isCollapsed = anchorKey === focusKey && anchor.offset === focus.offset;
|
|
308
|
+
let selectionRects;
|
|
309
|
+
// Handle collapsed selection on a linebreak
|
|
310
|
+
if (anchorNode === focusNode && $isLineBreakNode(anchorNode)) {
|
|
311
|
+
const brElement = editor.getElementByKey(anchorKey);
|
|
312
|
+
if (brElement) {
|
|
313
|
+
const brRect = brElement.getBoundingClientRect();
|
|
314
|
+
selectionRects = [brRect];
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
selectionRects = [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const range = createDOMRange(editor, anchorNode, anchor.offset, focusNode, focus.offset);
|
|
322
|
+
if (range === null) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (isCollapsed) {
|
|
326
|
+
// For collapsed cursors, get all rects (including zero-width) to position the caret.
|
|
327
|
+
// createRectsFromDOMRange filters out rects with width < 1, so use the raw
|
|
328
|
+
// range rect as a fallback for caret positioning.
|
|
329
|
+
selectionRects = createRectsFromDOMRange(editor, range);
|
|
330
|
+
if (selectionRects.length === 0) {
|
|
331
|
+
// Fallback: use the bounding rect of the collapsed range
|
|
332
|
+
const boundingRect = range.getBoundingClientRect();
|
|
333
|
+
if (boundingRect.height > 0) {
|
|
334
|
+
selectionRects = [boundingRect];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
selectionRects = createRectsFromDOMRange(editor, range);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const selectionsLength = selections.length;
|
|
343
|
+
const selectionRectsLength = selectionRects.length;
|
|
344
|
+
for (let i = 0; i < selectionRectsLength; i++) {
|
|
345
|
+
const selectionRect = selectionRects[i];
|
|
346
|
+
let selection = selections[i];
|
|
347
|
+
if (selection === undefined) {
|
|
348
|
+
selection = document.createElement('span');
|
|
349
|
+
selections[i] = selection;
|
|
350
|
+
const selectionBg = document.createElement('span');
|
|
351
|
+
selection.appendChild(selectionBg);
|
|
352
|
+
cursorsContainer.appendChild(selection);
|
|
353
|
+
}
|
|
354
|
+
const top = selectionRect.top - containerRect.top;
|
|
355
|
+
const left = selectionRect.left - containerRect.left;
|
|
356
|
+
// For collapsed cursors, give the wrapper a minimum width so the caret
|
|
357
|
+
// (positioned inside) is not clipped by some browsers.
|
|
358
|
+
const effectiveWidth = isCollapsed ? Math.max(selectionRect.width, 4) : selectionRect.width;
|
|
359
|
+
const style = `position:absolute;top:${top}px;left:${left}px;height:${selectionRect.height}px;width:${effectiveWidth}px;pointer-events:none;z-index:10;overflow:visible;`;
|
|
360
|
+
selection.style.cssText = style;
|
|
361
|
+
if (isCollapsed) {
|
|
362
|
+
// Collapsed cursor: hide the background span.
|
|
363
|
+
selection.firstChild.style.cssText =
|
|
364
|
+
`position:absolute;left:0;top:0;width:0;height:0;`;
|
|
365
|
+
// Make the caret clearly visible with explicit height and a bright border-left for contrast.
|
|
366
|
+
caret.style.cssText = `position:absolute;top:0;left:0;width:2px;height:${selectionRect.height}px;background-color:${color};z-index:10;`;
|
|
367
|
+
}
|
|
368
|
+
else if (isCurrentUser) {
|
|
369
|
+
// Current user's expanded selection: very subtle since the browser already
|
|
370
|
+
// shows the native selection highlight.
|
|
371
|
+
selection.firstChild.style.cssText =
|
|
372
|
+
`position:absolute;left:0;top:0;width:${selectionRect.width}px;height:${selectionRect.height}px;` +
|
|
373
|
+
`background-color:${color};opacity:0.08;border-radius:2px;pointer-events:none;`;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Remote user's expanded selection: clearly visible with transparency.
|
|
377
|
+
// The user's color is shown as a translucent overlay so the text beneath
|
|
378
|
+
// remains readable.
|
|
379
|
+
selection.firstChild.style.cssText =
|
|
380
|
+
`position:absolute;left:0;top:0;width:${selectionRect.width}px;height:${selectionRect.height}px;` +
|
|
381
|
+
`background-color:${color};opacity:0.25;border-radius:2px;pointer-events:none;`;
|
|
382
|
+
}
|
|
383
|
+
if (i === selectionRectsLength - 1) {
|
|
384
|
+
if (caret.parentNode !== selection) {
|
|
385
|
+
selection.appendChild(caret);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (let i = selectionsLength - 1; i >= selectionRectsLength; i--) {
|
|
390
|
+
const selection = selections[i];
|
|
391
|
+
cursorsContainer.removeChild(selection);
|
|
392
|
+
selections.pop();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export function syncLexicalSelectionToLoro(binding, provider, prevSelection, nextSelection) {
|
|
396
|
+
const awareness = provider.awareness;
|
|
397
|
+
const localState = awareness.getLocalState();
|
|
398
|
+
if (localState === null) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const { anchorPos: currentAnchorPos, focusPos: currentFocusPos, name, color, focusing, awarenessData, } = localState;
|
|
402
|
+
let anchorPos = null;
|
|
403
|
+
let focusPos = null;
|
|
404
|
+
// Convert Lexical selection to Loro cursors if we have a range selection
|
|
405
|
+
if ($isRangeSelection(nextSelection)) {
|
|
406
|
+
anchorPos = convertLexicalPointToCursor(nextSelection.anchor, binding);
|
|
407
|
+
focusPos = convertLexicalPointToCursor(nextSelection.focus, binding);
|
|
408
|
+
}
|
|
409
|
+
else if (nextSelection === null) {
|
|
410
|
+
// Selection cleared — broadcast null positions
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// Not a range selection (e.g. NodeSelection) — skip
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
// Always broadcast to keep remote cursors in sync.
|
|
417
|
+
// The shouldUpdatePosition check prevents redundant updates for unchanged positions.
|
|
418
|
+
const shouldUpdate = shouldUpdatePosition(currentAnchorPos, anchorPos) ||
|
|
419
|
+
shouldUpdatePosition(currentFocusPos, focusPos);
|
|
420
|
+
if (!shouldUpdate) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
// When we have a valid selection to broadcast, the editor IS focused —
|
|
424
|
+
// force `focusing: true` to prevent the FOCUS_COMMAND race condition
|
|
425
|
+
// where the selection update fires before FOCUS_COMMAND sets the flag.
|
|
426
|
+
const effectiveFocusing = (anchorPos !== null && focusPos !== null) ? true : focusing;
|
|
427
|
+
// Update the local state in EphemeralStore via awareness
|
|
428
|
+
awareness.setLocalState({
|
|
429
|
+
...localState,
|
|
430
|
+
anchorPos,
|
|
431
|
+
awarenessData,
|
|
432
|
+
color,
|
|
433
|
+
focusPos,
|
|
434
|
+
focusing: effectiveFocusing,
|
|
435
|
+
name,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
// Helper function to determine if cursor position should be updated
|
|
439
|
+
function shouldUpdatePosition(currentPos, pos) {
|
|
440
|
+
if (currentPos == null) {
|
|
441
|
+
return pos != null;
|
|
442
|
+
}
|
|
443
|
+
else if (pos == null) {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
// Compare using treeId (stable across clients) and offset
|
|
448
|
+
const current = currentPos;
|
|
449
|
+
const next = pos;
|
|
450
|
+
return current.treeId !== next.treeId || current.offset !== next.offset || current.pointType !== next.pointType;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/*****************************************************************************/
|
|
454
|
+
export function $syncLocalCursorPosition(binding, provider) {
|
|
455
|
+
const awareness = provider.awareness;
|
|
456
|
+
const localState = awareness.getLocalState();
|
|
457
|
+
if (localState === null) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const { anchorPos, focusPos } = localState;
|
|
461
|
+
// Convert Loro cursors back to Lexical selection
|
|
462
|
+
if (anchorPos !== null && focusPos !== null) {
|
|
463
|
+
const selectionInfo = convertLoroSelectionToLexical(anchorPos, focusPos, binding);
|
|
464
|
+
if (selectionInfo) {
|
|
465
|
+
const { anchorKey, anchorOffset, focusKey, focusOffset } = selectionInfo;
|
|
466
|
+
const selection = $getSelection();
|
|
467
|
+
if (!$isRangeSelection(selection)) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
// Update the Lexical selection to match the cursor positions
|
|
471
|
+
$setPoint(selection.anchor, anchorKey, anchorOffset);
|
|
472
|
+
$setPoint(selection.focus, focusKey, focusOffset);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { RootNode, ElementNode, TextNode, LineBreakNode, DecoratorNode, $getSelection, $getNodeByKey } from 'lexical';
|
|
6
|
+
import { propagateRootNode } from '../propagators/RootNodePropagator';
|
|
7
|
+
import { propagateLineBreakNode } from '../propagators/LineBreakNodePropagator';
|
|
8
|
+
import { propagateElementNode } from '../propagators/ElementNodePropagator';
|
|
9
|
+
import { propagateTextNode } from '../propagators/TextNodePropagator';
|
|
10
|
+
import { propagateDecoratorNode } from '../propagators/DecoratorNodePropagator';
|
|
11
|
+
import { isClassExtending, generateClientID } from '../utils/Utils';
|
|
12
|
+
import { syncLexicalSelectionToLoro } from './SyncCursors';
|
|
13
|
+
// import { scheduleAsyncCommit } from '../Bindings';
|
|
14
|
+
// import { syncCursorPositions, SyncCursorPositionsFn } from './SyncCursors';
|
|
15
|
+
export function syncLexicalToLoro(binding, provider, update) {
|
|
16
|
+
const { mutatedNodes, prevEditorState, editorState: currEditorState, } = update;
|
|
17
|
+
// Process node mutations if present.
|
|
18
|
+
// NOTE: mutatedNodes is null for selection-only updates (no DOM mutations).
|
|
19
|
+
// See Lexical docs: "Will be null if no DOM was mutated, such as when only
|
|
20
|
+
// the selection changed."
|
|
21
|
+
if (mutatedNodes) {
|
|
22
|
+
const tree = binding.tree;
|
|
23
|
+
// Ensure we have a numeric peerId for TreeID format
|
|
24
|
+
const peerId = generateClientID(binding.doc);
|
|
25
|
+
// Create options object for mutators
|
|
26
|
+
const mutatorOptions = {
|
|
27
|
+
binding,
|
|
28
|
+
tree,
|
|
29
|
+
peerId
|
|
30
|
+
};
|
|
31
|
+
// Process mutations in proper dependency order:
|
|
32
|
+
// 1. RootNode first
|
|
33
|
+
// 2. ElementNodes sorted by tree depth (parents before children)
|
|
34
|
+
// 3. TextNodes, LineBreakNodes, DecoratorNodes (leaf children)
|
|
35
|
+
//
|
|
36
|
+
// Depth sorting is critical: when a table is inserted, the mutations for
|
|
37
|
+
// table, tablerow, tablecell, and paragraph-inside-cell ALL fire in the
|
|
38
|
+
// same update. Without sorting, a tablecell may be propagated before its
|
|
39
|
+
// parent tablerow — causing `getTreeIDByLexicalKey(parent)` to return
|
|
40
|
+
// undefined and the cell to be created at the Loro tree root.
|
|
41
|
+
// Check if any nodes actually mutated (Map could be empty for selection-only changes)
|
|
42
|
+
let hasMutations = false;
|
|
43
|
+
mutatedNodes.forEach((nodeMap) => {
|
|
44
|
+
if (nodeMap.size > 0)
|
|
45
|
+
hasMutations = true;
|
|
46
|
+
});
|
|
47
|
+
if (hasMutations) {
|
|
48
|
+
// Phase 1: Process RootNode mutations
|
|
49
|
+
mutatedNodes.forEach((nodeMap, Klass) => {
|
|
50
|
+
if (isClassExtending(Klass, RootNode)) {
|
|
51
|
+
nodeMap.forEach((mutation, nodeKey) => {
|
|
52
|
+
propagateRootNode(update, mutation, nodeKey, mutatorOptions);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Phase 2: Collect all ElementNode mutations, sort by depth, then propagate
|
|
57
|
+
const elementMutations = [];
|
|
58
|
+
mutatedNodes.forEach((nodeMap, Klass) => {
|
|
59
|
+
if (isClassExtending(Klass, ElementNode) && !isClassExtending(Klass, RootNode)) {
|
|
60
|
+
nodeMap.forEach((mutation, nodeKey) => {
|
|
61
|
+
// Compute depth in Lexical tree (root=0, paragraph=1, tablecell=3, etc.)
|
|
62
|
+
let depth = 0;
|
|
63
|
+
currEditorState.read(() => {
|
|
64
|
+
const node = $getNodeByKey(nodeKey);
|
|
65
|
+
if (node) {
|
|
66
|
+
let current = node.getParent();
|
|
67
|
+
while (current) {
|
|
68
|
+
depth++;
|
|
69
|
+
current = current.getParent();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
elementMutations.push({ mutation, nodeKey, depth });
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// Sort by depth ascending → parents (depth 1) are propagated before children (depth 2, 3, …)
|
|
78
|
+
elementMutations.sort((a, b) => a.depth - b.depth);
|
|
79
|
+
for (const { mutation, nodeKey } of elementMutations) {
|
|
80
|
+
propagateElementNode(update, mutation, nodeKey, mutatorOptions);
|
|
81
|
+
}
|
|
82
|
+
// Phase 3: Process leaf children (their parents are now guaranteed to be mapped)
|
|
83
|
+
mutatedNodes.forEach((nodeMap, Klass) => {
|
|
84
|
+
if (isClassExtending(Klass, TextNode)) {
|
|
85
|
+
nodeMap.forEach((mutation, nodeKey) => {
|
|
86
|
+
propagateTextNode(update, mutation, nodeKey, mutatorOptions);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (isClassExtending(Klass, LineBreakNode)) {
|
|
90
|
+
nodeMap.forEach((mutation, nodeKey) => {
|
|
91
|
+
propagateLineBreakNode(update, mutation, nodeKey, mutatorOptions);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else if (isClassExtending(Klass, DecoratorNode)) {
|
|
95
|
+
nodeMap.forEach((mutation, nodeKey) => {
|
|
96
|
+
propagateDecoratorNode(update, mutation, nodeKey, mutatorOptions);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Commit only when there were actual node mutations (not selection-only changes)
|
|
101
|
+
binding.doc.commit({ origin: binding.doc.peerIdStr });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Always sync selection/cursor state — even for selection-only changes
|
|
105
|
+
// (e.g. user clicks or drags to select text without editing).
|
|
106
|
+
// This MUST be outside the `if (mutatedNodes)` block because Lexical sets
|
|
107
|
+
// mutatedNodes to null when only the selection changed (no DOM mutations).
|
|
108
|
+
currEditorState.read(() => {
|
|
109
|
+
const selection = $getSelection();
|
|
110
|
+
const prevSelection = prevEditorState._selection;
|
|
111
|
+
syncLexicalSelectionToLoro(binding, provider, prevSelection, selection);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { LoroEventBatch } from 'loro-crdt';
|
|
2
|
+
import { Binding } from '../Bindings';
|
|
3
|
+
import { Provider } from '../State';
|
|
4
|
+
import { SyncCursorPositionsFn } from './SyncCursors';
|
|
5
|
+
export declare function syncLoroToLexical(binding: Binding, provider: Provider, eventBatch: LoroEventBatch, isFromUndoManger: boolean, syncCursorPositionsFn?: SyncCursorPositionsFn): void;
|