From 4357c4c0b13cabddb4fd22299d373928b570a568 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 6 May 2024 17:15:41 +0200 Subject: [PATCH] WIP --- packages/admin/package.json | 2 +- .../blockEditor/state/useBlockEditorState.ts | 2 +- .../admin/app/pages/legacyEditor.tsx | 121 ++++++----- packages/playground/admin/index.css | 4 + .../lib-extra/legacy-editor/BlockEditor.tsx | 74 +++---- .../lib-extra/legacy-editor/BlockToolbar.tsx | 17 ++ .../legacy-editor/EditableCanvas.tsx | 4 +- .../lib-extra/legacy-editor/EditorCanvas.tsx | 4 +- .../legacy-editor/HoveringToolbar.tsx | 22 -- .../legacy-editor/HoveringToolbarContents.tsx | 9 - .../legacy-editor/HoveringToolbars.tsx | 38 ---- .../legacy-editor/InlineHoveringToolbar.tsx | 43 +++- .../lib-extra/legacy-editor/SortableBlcok.tsx | 20 -- .../lib-extra/legacy-editor/SortableBlock.tsx | 52 +++++ .../legacy-editor/elements/BlockElement.tsx | 51 ++--- .../elements/HeadingRenderer.tsx | 3 +- .../elements/OrderedListRenderer.tsx | 2 +- .../elements/ParagraphRenderer.tsx | 7 +- .../elements/ReferenceElementRenderer.tsx | 36 ++-- .../elements/TableCellElementRenderer.tsx | 13 +- .../elements/TableElementRenderer.tsx | 67 ++---- .../elements/TableRowElementRenderer.tsx | 5 +- .../elements/UnorderedListRenderer.tsx | 2 +- .../extract/EditorInlineReferencePortal.tsx | 53 +++++ .../extract/EditorInlineReferenceTrigger.tsx | 79 ------- .../extract/EditorWrapNodeTrigger.tsx | 36 ++++ .../2024-04-26-123613-legacy-editor.json | 202 ++++++++++++++++++ packages/playground/package.json | 2 +- packages/react-legacy-editor/package.json | 21 +- .../methods/closestBlockEntry.ts | 2 +- .../src/RichTextField/RichTextEditor.tsx | 4 +- .../baseEditor/createEditorWithEssentials.tsx | 3 +- .../src/blockEditor/BlockEditor.tsx | 63 +++--- .../blockEditor/state/useBlockEditorState.ts | 26 ++- .../src/blockEditor/state/useRefreshBlocks.ts | 6 +- .../blockEditor/utils/isInReferenceElement.ts | 4 +- .../react-legacy-editor/src/blocks/Block.tsx | 2 +- .../editorSelection/EditorSelectionAction.ts | 16 -- .../editorSelection/EditorSelectionState.ts | 28 --- .../editorSelection/editorSelectionReducer.ts | 95 -------- .../src/editorSelection/index.ts | 3 - .../src/editorSelection/useEditorSelection.ts | 90 -------- .../src/editorSelection/useToolbarsState.ts | 99 --------- packages/react-legacy-editor/src/index.ts | 1 - .../src/plugins/element/lists/ListElement.ts | 2 +- .../lists/transforms/toggleListElement.ts | 2 +- .../src/plugins/element/lists/withLists.ts | 2 +- .../element/paragraphs/ParagraphElement.ts | 2 +- .../react-legacy-editor/src/tsconfig.json | 9 +- .../src/components/RepeaterSortable.tsx | 2 +- .../RepeaterSortableDragOverlay.tsx | 2 +- .../RepeaterSortableDropIndicator.tsx | 2 +- .../components/RepeaterSortableEachItem.tsx | 2 +- .../RepeaterSortableItemActivator.tsx | 2 +- .../components/RepeaterSortableItemNode.tsx | 2 +- .../react-repeater-dnd-kit/src/contexts.ts | 13 ++ packages/react-repeater-dnd-kit/src/index.ts | 2 + .../src/internal/contexts.ts | 6 - .../src/internal/useCreateRepeaterMethods.tsx | 1 + yarn.lock | 46 ++-- 60 files changed, 729 insertions(+), 801 deletions(-) create mode 100644 packages/playground/admin/lib-extra/legacy-editor/BlockToolbar.tsx delete mode 100644 packages/playground/admin/lib-extra/legacy-editor/HoveringToolbar.tsx delete mode 100644 packages/playground/admin/lib-extra/legacy-editor/HoveringToolbarContents.tsx delete mode 100644 packages/playground/admin/lib-extra/legacy-editor/HoveringToolbars.tsx delete mode 100644 packages/playground/admin/lib-extra/legacy-editor/SortableBlcok.tsx create mode 100644 packages/playground/admin/lib-extra/legacy-editor/SortableBlock.tsx create mode 100644 packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferencePortal.tsx delete mode 100644 packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferenceTrigger.tsx create mode 100644 packages/playground/admin/lib-extra/legacy-editor/extract/EditorWrapNodeTrigger.tsx delete mode 100644 packages/react-legacy-editor/src/editorSelection/EditorSelectionAction.ts delete mode 100644 packages/react-legacy-editor/src/editorSelection/EditorSelectionState.ts delete mode 100644 packages/react-legacy-editor/src/editorSelection/editorSelectionReducer.ts delete mode 100644 packages/react-legacy-editor/src/editorSelection/index.ts delete mode 100644 packages/react-legacy-editor/src/editorSelection/useEditorSelection.ts delete mode 100644 packages/react-legacy-editor/src/editorSelection/useToolbarsState.ts create mode 100644 packages/react-repeater-dnd-kit/src/contexts.ts delete mode 100644 packages/react-repeater-dnd-kit/src/internal/contexts.ts diff --git a/packages/admin/package.json b/packages/admin/package.json index 57c25c41c5..a34cf844c5 100644 --- a/packages/admin/package.json +++ b/packages/admin/package.json @@ -80,7 +80,7 @@ "lucide-react": "^0.302.0", "qrcode-generator": "^1.4.4", "react-dropzone": "^14.2.3", - "react-error-boundary": "4.0.12", + "react-error-boundary": "^4.0.12", "react-sortable-hoc": "2.0.0", "slate": "0.73.1", "slate-history": "0.66.0", diff --git a/packages/admin/src/components/bindingFacade/richText/blockEditor/state/useBlockEditorState.ts b/packages/admin/src/components/bindingFacade/richText/blockEditor/state/useBlockEditorState.ts index 3c5e9b55b2..446d2ccda7 100644 --- a/packages/admin/src/components/bindingFacade/richText/blockEditor/state/useBlockEditorState.ts +++ b/packages/admin/src/components/bindingFacade/richText/blockEditor/state/useBlockEditorState.ts @@ -9,7 +9,7 @@ import { } from '@contember/react-binding' import { useBlockElementPathRefs } from './useBlockElementPathRefs' import { useBlockEditorOnChange } from './useBlockEditorOnChange' -import { MutableRefObject, useRef } from 'react' +import { MutableRefObject, useEffect, useRef } from 'react' import { useBlockEditorSlateNodes } from '../useBlockEditorSlateNodes' import { useRefreshBlocks } from './useRefreshBlocks' diff --git a/packages/playground/admin/app/pages/legacyEditor.tsx b/packages/playground/admin/app/pages/legacyEditor.tsx index d433131db2..b1ef10943d 100644 --- a/packages/playground/admin/app/pages/legacyEditor.tsx +++ b/packages/playground/admin/app/pages/legacyEditor.tsx @@ -1,7 +1,7 @@ import { Binding, PersistButton } from '../../lib/components/binding' import { Slots } from '../../lib/components/slots' import * as React from 'react' -import { Component, EntitySubTree, HasOne } from '@contember/interface' +import { Component, EntitySubTree, HasOne, useEntity } from '@contember/interface' import { RichTextField } from '../../lib-extra/legacy-editor/RichTextField' import { InlineHoveringToolbar } from '../../lib-extra/legacy-editor/InlineHoveringToolbar' import { EditorMarkTrigger } from '../../lib-extra/legacy-editor/extract/EditorMarkTrigger' @@ -17,8 +17,8 @@ import { Heading1Icon, Heading2Icon, Heading3Icon, - HighlighterIcon, - ItalicIcon, + HighlighterIcon, ImageIcon, + ItalicIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, @@ -47,7 +47,7 @@ import { horizontalRuleElementType, italicMark, orderedListElementType, - paragraphElementType, + paragraphElementType, referenceElementType, scrollTargetElementType, strikeThroughMark, tableElementType, @@ -59,10 +59,13 @@ import { BlockEditorField } from '../../lib-extra/legacy-editor/BlockEditor' import { ImageField, InputField } from '../../lib/components/form' import { Popover, PopoverContent, PopoverTrigger } from '../../lib/components/ui/popover' import { Button } from '../../lib/components/ui/button' -import { HoveringToolbars } from '../../lib-extra/legacy-editor/HoveringToolbars' -import { InitializeReferenceContentProps } from '../../lib-extra/legacy-editor/extract/EditorInlineReferenceTrigger' +import { BlockToolbar } from '../../lib-extra/legacy-editor/BlockToolbar' +import { EditorInlineReferencePortal, InitializeReferenceContentProps } from '../../lib-extra/legacy-editor/extract/EditorInlineReferencePortal' import { blockEditorPlugins } from '../../lib-extra/legacy-editor/plugins' import { EditorReferenceTrigger } from '../../lib-extra/legacy-editor/extract/EditorReferenceTrigger' +import { EditorWrapNodeTrigger } from '../../lib-extra/legacy-editor/extract/EditorWrapNodeTrigger' +import { PopoverClose } from '@radix-ui/react-popover' +import { uic } from '../../lib/utils/uic' export const richtext = () => <> @@ -87,6 +90,10 @@ export const richtext = () => <> +const BlockButton = uic('button', { + baseClass: 'bg-white p-2 inline-flex flex-col hover:bg-gray-100 border rounded-md w-32 items-center justify-center', +}) + export const blocks = () => <> @@ -122,46 +129,51 @@ export const blocks = () => <> }) }, ]} - controls={ - + > + + Quote + Image + Table + Scroll target + Horizontal rule + + +
- + + + + + + + + + + + + +
+
+ - - - - - - - - - - - - - + + - } - blockButtons={<> - - } - /> - } - > +
+
- + @@ -177,24 +189,43 @@ export const blocks = () => <>
+const ConfirmReferenceButton = () => { + const reference = useEntity() + + return ( + + + + + + ) +} + const LinkElement = (props: EditorRenderElementProps) => { const editor = useEditor() return ( - - {props.children} + + + {props.children} + - + - +
+ - + +
@@ -203,20 +234,6 @@ const LinkElement = (props: EditorRenderElementProps) => { } -const InsertLink = Component( - ({ onSuccess, onCancel }) => ( - <> - -
- - -
- - ), - () => , -) - - export const LinkField = Component<{ field: string }>(({ field }) => { return ( diff --git a/packages/playground/admin/index.css b/packages/playground/admin/index.css index f66d9a10df..0f35dcfbbe 100644 --- a/packages/playground/admin/index.css +++ b/packages/playground/admin/index.css @@ -51,3 +51,7 @@ --ring: 240 4.9% 83.9%; } } + +:focus-visible { + outline: 0 +} diff --git a/packages/playground/admin/lib-extra/legacy-editor/BlockEditor.tsx b/packages/playground/admin/lib-extra/legacy-editor/BlockEditor.tsx index a21319e55a..c6c25d66c3 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/BlockEditor.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/BlockEditor.tsx @@ -2,26 +2,26 @@ import { ReactNode } from 'react' import { EditorCanvas } from './EditorCanvas' import { EditableCanvas } from './EditableCanvas' import { BlockEditor, BlockEditorProps, useEditor } from '@contember/react-legacy-editor' -import { Component, StaticRender } from '@contember/interface' -import { SortableBlock } from './SortableBlcok' +import { Component } from '@contember/interface' +import { SortableBlock } from './SortableBlock' import { ReferenceElementRenderer } from './elements/ReferenceElementRenderer' +import { RepeaterSortable, useRepeaterSortedEntities } from '@contember/react-repeater-dnd-kit' +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' export type BlockEditorFieldProps = & Omit & { placeholder?: string - controls?: ReactNode } -export const BlockEditorField = Component(({ placeholder, controls, children, ...props }) => { +export const BlockEditorField = Component(({ placeholder, children, ...props }) => { return ( - - + + {children} - - {controls} - + + ) }) @@ -31,44 +31,28 @@ export const BlockEditorInner = ({ children, placeholder }: { children: ReactNode }) => { const editor = useEditor() + const entities = useRepeaterSortedEntities() return ( - { - e.preventDefault() - }), - placeholder: placeholder, - }} + + { + e.preventDefault() + }), + placeholder: placeholder, + }} - > - {children} - + > + {children} + + ) } - - -// const BlockEditorStatic = (props: BlockEditorProps) => { -// const inlineButtons: ToolbarButtonSpec[] = props.inlineButtons -// ? ( -// (Array.isArray(props.inlineButtons[0]) ? props.inlineButtons : [props.inlineButtons]) as ToolbarButtonSpec[][] -// ).flat() -// : [] -// -// return <> -// {inlineButtons -// .filter((button): button is InitializeReferenceToolbarButton => 'referenceContent' in button) -// .map(({ referenceContent: Content }, i) => { -// return ( -// -// ) -// })} -// -// } diff --git a/packages/playground/admin/lib-extra/legacy-editor/BlockToolbar.tsx b/packages/playground/admin/lib-extra/legacy-editor/BlockToolbar.tsx new file mode 100644 index 0000000000..a19b0945ee --- /dev/null +++ b/packages/playground/admin/lib-extra/legacy-editor/BlockToolbar.tsx @@ -0,0 +1,17 @@ +import { memo, ReactNode } from 'react' +import { useSlateSelection } from 'slate-react' +import { cn } from '../../lib/utils/cn' + +export interface HoveringToolbarsProps { + children: ReactNode +} + +export const BlockToolbar = memo(({ children }: HoveringToolbarsProps) => { + const selection = useSlateSelection() + + return ( +
+
{children}
+
+ ) +}) diff --git a/packages/playground/admin/lib-extra/legacy-editor/EditableCanvas.tsx b/packages/playground/admin/lib-extra/legacy-editor/EditableCanvas.tsx index 6fb957536d..31529a6a33 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/EditableCanvas.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/EditableCanvas.tsx @@ -1,5 +1,4 @@ -import { useClassName } from '@contember/react-utils' -import { EventHandler, KeyboardEvent, KeyboardEventHandler, MouseEvent, ReactElement, useCallback, useRef } from 'react' +import { EventHandler, KeyboardEvent, KeyboardEventHandler, MouseEvent, useCallback, useRef } from 'react' import { Path, Transforms } from 'slate' import { Editable, useSlate } from 'slate-react' import { EditorWithBlocks } from '@contember/react-legacy-editor' @@ -40,7 +39,6 @@ export const EditableCanvas = ({ className, ...editableProps }: EditableCanvasPr return (
({ }: EditorCanvasProps

) => { return ( -

- +
+ {children}
) diff --git a/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbar.tsx b/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbar.tsx deleted file mode 100644 index b8845703dd..0000000000 --- a/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbar.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { forwardRef, memo, ReactNode } from 'react' - -export interface HoveringToolbarProps { - isActive?: boolean - children: ReactNode -} - -export const HoveringToolbar = memo(forwardRef(({ - isActive, - children, -}, ref) => { - if (!isActive) { - return null - } - - return ( -
- {children} -
- ) -})) -HoveringToolbar.displayName = 'HoveringToolbar' diff --git a/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbarContents.tsx b/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbarContents.tsx deleted file mode 100644 index 0f64863b29..0000000000 --- a/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbarContents.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { memo, ReactNode } from 'react' - -export interface HoveringToolbarContentsProps { - children: ReactNode -} - -export const HoveringToolbarContents = memo(({ children }: HoveringToolbarContentsProps) => { - return
{children}
-}) diff --git a/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbars.tsx b/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbars.tsx deleted file mode 100644 index 3cac0562ec..0000000000 --- a/packages/playground/admin/lib-extra/legacy-editor/HoveringToolbars.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { HoveringToolbar } from './HoveringToolbar' -import { memo, ReactNode, useMemo } from 'react' -import { useToolbarState } from '@contember/react-legacy-editor' -import { HoveringToolbarContents } from './HoveringToolbarContents' -import { useSlate } from 'slate-react' -import { Range as SlateRange } from 'slate' - -export interface HoveringToolbarsProps { - inlineButtons?: ReactNode - blockButtons?: ReactNode -} - -export const HoveringToolbars = memo(({ blockButtons, inlineButtons }: HoveringToolbarsProps) => { - const { inlineToolbarRef, blockToolbarActive, inlineToolbarActive } = useToolbarState() - const editor = useSlate() - - const shouldDisplayInlineToolbar = useMemo(() => { - const selection = editor.selection - return inlineToolbarActive && !(!selection || SlateRange.isCollapsed(selection)) - }, [editor.selection, inlineToolbarActive]) - - return ( - <> - <> - {inlineButtons && ( - - {inlineButtons} - - )} - - {blockButtons && ( - - {blockButtons} - - )} - - ) -}) diff --git a/packages/playground/admin/lib-extra/legacy-editor/InlineHoveringToolbar.tsx b/packages/playground/admin/lib-extra/legacy-editor/InlineHoveringToolbar.tsx index f60d279fb1..0adceb1ccb 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/InlineHoveringToolbar.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/InlineHoveringToolbar.tsx @@ -1,19 +1,46 @@ -import { HoveringToolbar } from './HoveringToolbar' -import { memo, ReactNode } from 'react' -import { useToolbarState } from '@contember/react-legacy-editor' -import { HoveringToolbarContents } from './HoveringToolbarContents' +import { memo, ReactNode, useLayoutEffect, useRef } from 'react' +import { useEditor } from '@contember/react-legacy-editor' +import { ReactEditor, useSlateSelection } from 'slate-react' export interface InlineHoveringToolbarProps { children: ReactNode } export const InlineHoveringToolbar = memo(({ children }: InlineHoveringToolbarProps) => { - const { inlineToolbarRef, inlineToolbarActive } = useToolbarState() + const ref = useRef(null) + const editor = useEditor() + const selection = useSlateSelection() + useLayoutEffect(() => { + const toolbar = ref.current + if (!toolbar) { + return + } + + if (selection && selection.focus.offset !== selection.anchor.offset) { + const domRange = ReactEditor.toDOMRange(editor, selection) + const domRect = domRange.getBoundingClientRect() + toolbar.style.top = `${domRect.top + window.scrollY - toolbar.offsetHeight}px` + toolbar.style.left = `${Math.min( + Math.max(0, document.documentElement.clientWidth - toolbar.offsetWidth), + Math.max(0, domRect.left + window.scrollX - toolbar.offsetWidth / 2 + domRect.width / 2), + ) + }px` + toolbar.style.maxWidth = `${document.documentElement.clientWidth}px` + } else { + toolbar.style.top = '-1000vh' + toolbar.style.left = '-1000vw' + toolbar.style.maxWidth = 'unset' + } + + }, [editor, selection]) + return ( <> - - {children} - +
+
+ {children} +
+
) }) diff --git a/packages/playground/admin/lib-extra/legacy-editor/SortableBlcok.tsx b/packages/playground/admin/lib-extra/legacy-editor/SortableBlcok.tsx deleted file mode 100644 index 0549e6cf2d..0000000000 --- a/packages/playground/admin/lib-extra/legacy-editor/SortableBlcok.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { SortedBlocksContext } from '@contember/react-legacy-editor' -import { ReactNode, useContext } from 'react' -import { Element } from 'slate' -import { ReactEditor, useSlateStatic } from 'slate-react' - -export const SortableBlock = ({ children, element }: { children: ReactNode, element: Element }) => { - const editor = useSlateStatic() - // intentionally passing through the context, so it redraws on order change - const sortedBlocks = useContext(SortedBlocksContext) - // intentionally finding path again and not passing from renderElement, because it might have changed - const [index] = ReactEditor.findPath(editor, element) - return <>{children} - // todo - // return ( - // - // }> - // - // - // ) -} diff --git a/packages/playground/admin/lib-extra/legacy-editor/SortableBlock.tsx b/packages/playground/admin/lib-extra/legacy-editor/SortableBlock.tsx new file mode 100644 index 0000000000..482ab84542 --- /dev/null +++ b/packages/playground/admin/lib-extra/legacy-editor/SortableBlock.tsx @@ -0,0 +1,52 @@ +import { SortedBlocksContext } from '@contember/react-legacy-editor' +import React, { ReactNode, useContext } from 'react' +import { Element } from 'slate' +import { ReactEditor, useSlateStatic } from 'slate-react' +import { useSortable } from '@dnd-kit/sortable' +import { RepeaterCurrentEntityContext, RepeaterSortableItemActivator, RepeaterSortableItemContext, RepeaterSortableItemNode } from '@contember/react-repeater-dnd-kit' +import { Entity } from '@contember/interface' +import { RepeaterDropIndicator } from '../../lib/components/repeater' +import { uic } from '../../lib/utils/uic' +import { GripVerticalIcon } from 'lucide-react' + +export const BlockeEditorHandle = uic('button', { + baseClass: 'absolute top-1/2 -left-3 h-6 w-6 flex justify-end align-center opacity-10 hover:opacity-100 transition-opacity -translate-y-1/2', + beforeChildren: , +}) + +export const SortableBlock = ({ children, element }: { children: ReactNode, element: Element }) => { + const editor = useSlateStatic() + // intentionally passing through the context, so it redraws on order change + const sortedBlocks = useContext(SortedBlocksContext) + // intentionally finding path again and not passing from renderElement, because it might have changed + const [index] = ReactEditor.findPath(editor, element) + const entity = sortedBlocks[index] + const sortable = useSortable({ + id: entity?.id, + }) + if (!entity) { + return null + } + + return ( + + + +
+ + +
+ + + + {children} +
+
+ +
+
+
+
+ ) +} + diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/BlockElement.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/BlockElement.tsx index 1a01d4bb82..cd7c5227f8 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/BlockElement.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/BlockElement.tsx @@ -1,46 +1,47 @@ import { ContemberEditor } from '@contember/react-legacy-editor' -import { EditorBlockBoundary } from '@contember/ui' import { memo } from 'react' import { ReactEditor, RenderElementProps, useSlateStatic } from 'slate-react' +import { PlusCircleIcon } from 'lucide-react' export interface BlockElementProps extends RenderElementProps { domElement?: keyof JSX.IntrinsicElements withBoundaries?: boolean + className?: string } -export const BlockElement = memo(function BlockElement({ - element, - children, - attributes, - domElement = 'div', - withBoundaries = false, -}: BlockElementProps) { +const BlockBoundary = ({ onClick }: {onClick: () => void}) => { + return (<> +
+
+
+
+ + Add paragraph +
+
+
+ ) +} + +export const BlockElement = memo(function BlockElement({ element, children, attributes, domElement = 'div', withBoundaries = false, className }: BlockElementProps) { const editor = useSlateStatic() const dataAttributes = ContemberEditor.getElementDataAttributes(element) const El = domElement as 'div' return ( - + {withBoundaries && ( - { - const elementPath = ReactEditor.findPath(editor, element) - editor.insertBetweenBlocks([element, elementPath], 'before') - }} - /> + { + const elementPath = ReactEditor.findPath(editor, element) + editor.insertBetweenBlocks([element, elementPath], 'before') + }}/> )} {children} {withBoundaries && ( - { - const elementPath = ReactEditor.findPath(editor, element) - editor.insertBetweenBlocks([element, elementPath], 'after') - }} - /> + { + const elementPath = ReactEditor.findPath(editor, element) + editor.insertBetweenBlocks([element, elementPath], 'after') + }}/> )} ) diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/HeadingRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/HeadingRenderer.tsx index fb72d154e0..bf998a45a5 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/HeadingRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/HeadingRenderer.tsx @@ -1,5 +1,4 @@ import { HeadingElement } from '@contember/react-legacy-editor' -import { EditorHeading } from '@contember/ui' import React, { FunctionComponent } from 'react' import type { RenderElementProps } from 'slate-react' import { cn } from '../../../lib/utils/cn' @@ -32,6 +31,8 @@ export const HeadingRenderer: FunctionComponent = ({ children, }: HeadingRendererProps) => { const el = lvlToHtmlEl[element.level] + + // todo numbered element.isNumbered return React.createElement( el, { diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/OrderedListRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/OrderedListRenderer.tsx index ebccfaf7f2..b34208ef97 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/OrderedListRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/OrderedListRenderer.tsx @@ -8,6 +8,6 @@ export interface OrderedListRendererProps extends Omit = props => ( - {props.children} + {props.children} ) OrderedListRenderer.displayName = 'OrderedListRenderer' diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/ParagraphRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/ParagraphRenderer.tsx index 2473daea85..8438c01139 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/ParagraphRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/ParagraphRenderer.tsx @@ -1,5 +1,4 @@ import { ParagraphElement } from '@contember/react-legacy-editor' -import { EditorParagraph } from '@contember/ui' import type { RenderElementProps } from 'slate-react' export interface ParagraphRendererProps extends Omit { @@ -8,9 +7,9 @@ export interface ParagraphRendererProps extends Omit + // TODO numbered element.isNumbered +

{children} - +

) } diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/ReferenceElementRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/ReferenceElementRenderer.tsx index 3b3c18ca89..b2cb1528a3 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/ReferenceElementRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/ReferenceElementRenderer.tsx @@ -1,10 +1,13 @@ import { BindingError, Entity, RelativeSingleField, useEntity } from '@contember/react-binding' import { BlockProps, EditorWithBlocks, EmbedHandler, ReferenceElement, ReferenceElementOptions, getDiscriminatedBlock, getDiscriminatedDatum } from '@contember/react-legacy-editor' -import { ActionableBox, Box, EditorPlaceholder, FieldSet } from '@contember/ui' import { memo, MouseEvent as ReactMouseEvent, ReactNode, useCallback } from 'react' import { Transforms } from 'slate' import { ReactEditor, RenderElementProps, useSelected, useSlateStatic } from 'slate-react' import { BlockElement } from './BlockElement' +import { Card, CardContent, CardHeader, CardTitle } from '../../../lib/components/ui/card' +import { PencilIcon, TrashIcon } from 'lucide-react' +import { Button } from '../../../lib/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '../../../lib/components/ui/popover' export interface ReferenceElementRendererProps extends RenderElementProps, ReferenceElementOptions { element: ReferenceElement @@ -69,7 +72,7 @@ export const ReferenceElementRenderer = memo((props: ReferenceElementRendererPro ? (
- {placeholder} +
{placeholder}
{props.children}
@@ -119,18 +122,27 @@ export const ReferenceElementRenderer = memo((props: ReferenceElementRendererPro blockBody = renderedBlock.children } - const alternate = renderedBlock.alternate ? {renderedBlock.alternate} : undefined const wrappedReference = ( - -
- {blockBody} -
-
+ + +
+ + {renderedBlock.label} + +
+ + {renderedBlock.alternate && ( + + {renderedBlock.alternate} + )} +
+
+
+ + {blockBody} + +
) diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/TableCellElementRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/TableCellElementRenderer.tsx index d774aefec9..91886565a0 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/TableCellElementRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/TableCellElementRenderer.tsx @@ -1,5 +1,4 @@ import { TableCellElement } from '@contember/react-legacy-editor' -import { EditorTableCellElement } from '@contember/ui' import type { RenderElementProps } from 'slate-react' export interface TableCellElementRendererProps extends Omit { @@ -8,12 +7,14 @@ export interface TableCellElementRendererProps extends Omit {props.children} - + ) } diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/TableElementRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/TableElementRenderer.tsx index 73600673e4..a430485035 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/TableElementRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/TableElementRenderer.tsx @@ -1,5 +1,4 @@ -import { isTableElement, TableCellElement, TableElement, TableModifications, TableRowElement } from '@contember/react-legacy-editor' -import { EditorTableElement } from '@contember/ui' +import { isTableElement, TableCellElement, TableElement, TableModifications } from '@contember/react-legacy-editor' import { assertNever } from '@contember/utilities' import { memo, useCallback } from 'react' import { Transforms } from 'slate' @@ -58,35 +57,7 @@ export const TableElementRenderer = memo(function TableElementRenderer(props: Ta TableModifications.justifyTableColumn(editor, props.element, index, direction), [editor, props.element], ) - // TODO this kind of works but ends up generating tons of operations when used to delete the whole table. - // it would require more testing to ensure that it works well so it will have to wait for now. - // const selectTable = useCallback(() => { - // const tablePath = ReactEditor.findPath(editor, props.element) - // const firstTablePoint = SlateEditor.start(editor, tablePath) - // const lastTablePoint = SlateEditor.end(editor, tablePath) - // - // const beforeFirst = SlateEditor.before(editor, firstTablePoint) - // const afterLast = SlateEditor.after(editor, lastTablePoint) - // - // let range: SlateRange - // if (beforeFirst) { - // range = { - // anchor: lastTablePoint, - // focus: beforeFirst, - // } - // } else if (afterLast) { - // range = { - // anchor: firstTablePoint, - // focus: afterLast, - // } - // } else { - // range = { - // anchor: firstTablePoint, - // focus: lastTablePoint, - // } - // } - // Transforms.select(editor, range) - // }, [editor, props.element]) + const deleteTable = useCallback(() => { Promise.resolve().then(() => { // The promise is a hack to avoid an exception when the table is the last element in the editor. @@ -101,21 +72,25 @@ export const TableElementRenderer = memo(function TableElementRenderer(props: Ta }, [editor, props.element]) return ( - - {props.children} - +
+ + + {props.children} +
+
) }) diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/TableRowElementRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/TableRowElementRenderer.tsx index 997b11bdc2..9ebfa3ee43 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/TableRowElementRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/TableRowElementRenderer.tsx @@ -1,5 +1,4 @@ import { TableRowElement } from '@contember/react-legacy-editor' -import { EditorTableRowElement } from '@contember/ui' import { memo } from 'react' import type { RenderElementProps } from 'slate-react' @@ -9,8 +8,8 @@ export interface TableRowElementRendererProps extends Omit + {props.children} - + ) }) diff --git a/packages/playground/admin/lib-extra/legacy-editor/elements/UnorderedListRenderer.tsx b/packages/playground/admin/lib-extra/legacy-editor/elements/UnorderedListRenderer.tsx index f38b7e75c5..63d6e9ba97 100644 --- a/packages/playground/admin/lib-extra/legacy-editor/elements/UnorderedListRenderer.tsx +++ b/packages/playground/admin/lib-extra/legacy-editor/elements/UnorderedListRenderer.tsx @@ -8,6 +8,6 @@ export interface UnorderedListRendererProps extends Omit = props => ( - {props.children} + {props.children} ) UnorderedListRenderer.displayName = 'UnorderedListRenderer' diff --git a/packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferencePortal.tsx b/packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferencePortal.tsx new file mode 100644 index 0000000000..76e6ab6187 --- /dev/null +++ b/packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferencePortal.tsx @@ -0,0 +1,53 @@ +import { Entity, useEnvironment, VariableInputTransformer } from '@contember/interface' +import { Element as SlateElement, type Range as SlateRange } from 'slate' +import { ReactNode, useEffect, useState } from 'react' +import { EntityAccessor, EntityId, OptionallyVariableFieldValue } from '@contember/binding' +import { useSlate } from 'slate-react' +import { EditorWithBlocks } from '@contember/react-legacy-editor' + +export interface InitializeReferenceContentProps { + referenceId: EntityId + editor: EditorWithBlocks + selection: SlateRange | null + onSuccess: (options?: { createElement?: Partial }) => void + onCancel: () => void +} +export interface EditorInlineReferenceTriggerProps { + referenceType: OptionallyVariableFieldValue + initializeReference?: EntityAccessor.BatchUpdatesHandler, + children: ReactNode +} + + +export const EditorInlineReferencePortal = (props: EditorInlineReferenceTriggerProps) => { + const editor = useSlate() as EditorWithBlocks + const environment = useEnvironment() + const [entity, setEntity] = useState() + useEffect(() => { + if (entity) { + return + } + const discriminateBy = VariableInputTransformer.transformValue(props.referenceType, environment) + const selection = editor.selection + const targetPath = selection?.focus.path // TODO this is awful. + // Transforms.deselect(editor) + if (targetPath === undefined) { + return + } + const reference = editor.createElementReference( + targetPath, + discriminateBy, + props.initializeReference, + ) + setEntity(reference) + }, [editor, entity, environment, props.initializeReference, props.referenceType]) + + if (!entity) { + return null + } + return ( + + {props.children} + + ) +} diff --git a/packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferenceTrigger.tsx b/packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferenceTrigger.tsx deleted file mode 100644 index 32e5ca21d1..0000000000 --- a/packages/playground/admin/lib-extra/legacy-editor/extract/EditorInlineReferenceTrigger.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { VariableInputTransformer, useEnvironment } from '@contember/interface' -import { Element as SlateElement, type Range as SlateRange, Transforms } from 'slate' -import { ReactElement } from 'react' -import { EntityAccessor, EntityId, OptionallyVariableFieldValue } from '@contember/binding' -import { useSlate } from 'slate-react' -import { EditorWithBlocks } from '@contember/react-legacy-editor' - -export interface InitializeReferenceContentProps { - referenceId: EntityId - editor: EditorWithBlocks - selection: SlateRange | null - onSuccess: (options?: { createElement?: Partial }) => void - onCancel: () => void -} -export interface EditorInlineReferenceTriggerProps { - referenceType: OptionallyVariableFieldValue - initializeReference?: EntityAccessor.BatchUpdatesHandler, - children: ReactElement -} - -export const EditorInlineReferenceTrigger = (props: EditorInlineReferenceTriggerProps) => { - const editor = useSlate() as EditorWithBlocks - const environment = useEnvironment() - const onClick = () => { - const discriminateBy = VariableInputTransformer.transformValue(props.referenceType, environment) - const selection = editor.selection - const targetPath = selection?.focus.path // TODO this is awful. - - if (targetPath === undefined) { - return - } - - Transforms.deselect(editor) - const reference = editor.createElementReference( - targetPath, - discriminateBy, - props.initializeReference, - ) - - // const Content = button.referenceContent - // const result = await openDialog({ - // heading: button.label, - // content: props => ( - // - // { - // if (createElement !== undefined) { - // if (!selection) { - // return - // } - // EditorTransforms.select(editor, selection) - // EditorTransforms.wrapNodes( - // editor, - // { - // type: referenceElementType, - // children: [{ text: '' }], - // referenceId: reference.id, - // ...createElement, - // }, - // { split: true }, - // ) - // EditorTransforms.collapse(editor, { edge: 'end' }) - // } - // props.resolve(true) - // }} - // onCancel={() => props.resolve()} - // /> - // - // ), - // }) - reference.deleteEntity() - // if (result !== true) { - // } - } - -} diff --git a/packages/playground/admin/lib-extra/legacy-editor/extract/EditorWrapNodeTrigger.tsx b/packages/playground/admin/lib-extra/legacy-editor/extract/EditorWrapNodeTrigger.tsx new file mode 100644 index 0000000000..0fc324d019 --- /dev/null +++ b/packages/playground/admin/lib-extra/legacy-editor/extract/EditorWrapNodeTrigger.tsx @@ -0,0 +1,36 @@ +import { Selection } from 'slate' +import { MouseEventHandler, ReactElement } from 'react' +import { useSlate } from 'slate-react' +import { Slot } from '@radix-ui/react-slot' +import * as React from 'react' +import { EditorTransforms, EditorWithBlocks } from '@contember/react-legacy-editor' +import { composeEventHandlers } from '@radix-ui/primitive' + +export interface EditorWrapNodeTriggerProps { + elementType: string + selection?: Selection + suchThat?: Record + children: ReactElement + onClick?: MouseEventHandler +} + +export const EditorWrapNodeTrigger = ({ elementType, suchThat, selection, ...props }: EditorWrapNodeTriggerProps) => { + const editor = useSlate() as EditorWithBlocks + const onClick = () => { + if (selection) { + EditorTransforms.select(editor, selection) + } + EditorTransforms.wrapNodes( + editor, + { + type: elementType, + children: [{ text: '' }], + ...suchThat, + }, + { split: true }, + ) + EditorTransforms.collapse(editor, { edge: 'end' }) + } + + return +} diff --git a/packages/playground/api/migrations/2024-04-26-123613-legacy-editor.json b/packages/playground/api/migrations/2024-04-26-123613-legacy-editor.json index ee8f6da010..b20e623f0e 100644 --- a/packages/playground/api/migrations/2024-04-26-123613-legacy-editor.json +++ b/packages/playground/api/migrations/2024-04-26-123613-legacy-editor.json @@ -438,6 +438,208 @@ "unique" ] } + }, + { + "modification": "patchAclSchema", + "patch": [ + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorBlock", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "content": true, + "order": true, + "data": true, + "references": true + }, + "create": { + "id": true, + "content": true, + "order": true, + "data": true, + "references": true + }, + "update": { + "id": true, + "content": true, + "order": true, + "data": true, + "references": true + }, + "delete": true, + "customPrimary": true + } + } + }, + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorContent", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "unique": true, + "blocks": true + }, + "create": { + "id": true, + "unique": true, + "blocks": true + }, + "update": { + "id": true, + "unique": true, + "blocks": true + }, + "delete": true, + "customPrimary": true + } + } + }, + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorEmbed", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "type": true, + "youtubeId": true, + "vimeoId": true, + "reference": true + }, + "create": { + "id": true, + "type": true, + "youtubeId": true, + "vimeoId": true, + "reference": true + }, + "update": { + "id": true, + "type": true, + "youtubeId": true, + "vimeoId": true, + "reference": true + }, + "delete": true, + "customPrimary": true + } + } + }, + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorImage", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "url": true + }, + "create": { + "id": true, + "url": true + }, + "update": { + "id": true, + "url": true + }, + "delete": true, + "customPrimary": true + } + } + }, + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorLink", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "url": true + }, + "create": { + "id": true, + "url": true + }, + "update": { + "id": true, + "url": true + }, + "delete": true, + "customPrimary": true + } + } + }, + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorReference", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "block": true, + "type": true, + "target": true, + "embed": true, + "image": true + }, + "create": { + "id": true, + "block": true, + "type": true, + "target": true, + "embed": true, + "image": true + }, + "update": { + "id": true, + "block": true, + "type": true, + "target": true, + "embed": true, + "image": true + }, + "delete": true, + "customPrimary": true + } + } + }, + { + "op": "add", + "path": "/roles/admin/entities/LegacyEditorTextArea", + "value": { + "predicates": {}, + "operations": { + "read": { + "id": true, + "unique": true, + "data": true + }, + "create": { + "id": true, + "unique": true, + "data": true + }, + "update": { + "id": true, + "unique": true, + "data": true + }, + "delete": true, + "customPrimary": true + } + } + } + ] } ] } diff --git a/packages/playground/package.json b/packages/playground/package.json index c79e7876d2..d702830e10 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -79,7 +79,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-twc": "^1.3.0", - "slate": "^0.102.0", + "slate": "^0.103.0", "slate-history": "^0.100.0", "slate-hyperscript": "^0.100.0", "slate-react": "^0.102.0", diff --git a/packages/react-legacy-editor/package.json b/packages/react-legacy-editor/package.json index 8038c0c51b..bcb1d05cef 100644 --- a/packages/react-legacy-editor/package.json +++ b/packages/react-legacy-editor/package.json @@ -40,8 +40,23 @@ "directory": "packages/react-legacy-editor" }, "dependencies": { - "slate": "0.73.1", - "slate-history": "0.66.0", - "slate-react": "0.74.2" + "@contember/react-binding": "workspace:*", + "@contember/react-multipass-rendering": "workspace:*", + "@contember/react-repeater": "workspace:*", + "@contember/react-utils": "workspace:*", + "@contember/utilities": "workspace:*", + "is-hotkey": "^0.2.0", + "react-error-boundary": "^4.0.13", + "slate": "^0.103.0", + "slate-history": "^0.100.0", + "slate-react": "^0.102.0" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + }, + "devDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" } } diff --git a/packages/react-legacy-editor/src/ContemberEditor/methods/closestBlockEntry.ts b/packages/react-legacy-editor/src/ContemberEditor/methods/closestBlockEntry.ts index a93f1fd28d..09e196894f 100644 --- a/packages/react-legacy-editor/src/ContemberEditor/methods/closestBlockEntry.ts +++ b/packages/react-legacy-editor/src/ContemberEditor/methods/closestBlockEntry.ts @@ -10,5 +10,5 @@ export const closestBlockEntry = ( ) => closest(editor, { at: options?.at, - match: node => Editor.isBlock(editor, node) && (options?.match ? options.match(node) : true), + match: node => !Editor.isEditor(node) && Editor.isBlock(editor, node) && (options?.match ? options.match(node) : true), }) diff --git a/packages/react-legacy-editor/src/RichTextField/RichTextEditor.tsx b/packages/react-legacy-editor/src/RichTextField/RichTextEditor.tsx index 4158d377eb..17d9d330b1 100644 --- a/packages/react-legacy-editor/src/RichTextField/RichTextEditor.tsx +++ b/packages/react-legacy-editor/src/RichTextField/RichTextEditor.tsx @@ -68,7 +68,7 @@ export const RichTextEditor: FunctionComponent = Component( }) } } - if (Editor.isBlock(editor, node) && path.length > 1) { + if (SlateElement.isElement(node) && Editor.isBlock(editor, node) && path.length > 1) { return Transforms.unwrapNodes(editor, { at: path }) } normalizeNode(nodeEntry) @@ -105,7 +105,7 @@ export const RichTextEditor: FunctionComponent = Component( ) return ( - + {props.children} ) diff --git a/packages/react-legacy-editor/src/baseEditor/createEditorWithEssentials.tsx b/packages/react-legacy-editor/src/baseEditor/createEditorWithEssentials.tsx index 8408af7324..280be37a79 100644 --- a/packages/react-legacy-editor/src/baseEditor/createEditorWithEssentials.tsx +++ b/packages/react-legacy-editor/src/baseEditor/createEditorWithEssentials.tsx @@ -123,7 +123,8 @@ export const createEditorWithEssentials = (defaultElementType: string): Editor = }, renderElement: props => { - return createElement(elements.get(props.element.type)?.render ?? DefaultElement, props) + const component = elements.get(props.element.type)?.render ?? DefaultElement + return createElement(component, props) }, renderLeafChildren: props => props.children, diff --git a/packages/react-legacy-editor/src/blockEditor/BlockEditor.tsx b/packages/react-legacy-editor/src/blockEditor/BlockEditor.tsx index 58b538e6b8..230f9bd919 100644 --- a/packages/react-legacy-editor/src/blockEditor/BlockEditor.tsx +++ b/packages/react-legacy-editor/src/blockEditor/BlockEditor.tsx @@ -1,13 +1,13 @@ import { BindingError, Component, Environment, FieldValue, HasMany, SugaredField, SugaredFieldProps, SugaredRelativeEntityList, useDesugaredRelativeSingleField, useEnvironment, VariableInputTransformer } from '@contember/react-binding' import { emptyArray, useReferentiallyStableCallback } from '@contember/react-utils' -import { ComponentType, Fragment, FunctionComponent, ReactElement, ReactNode, useLayoutEffect, useMemo, useState } from 'react' +import { ComponentType, Fragment, FunctionComponent, ReactElement, ReactNode, useEffect, useMemo, useState } from 'react' import { Slate } from 'slate-react' import { getDiscriminatedBlock, useNormalizedBlocks } from '../blocks' import { SugaredDiscriminateBy, useDiscriminatedData } from '../discrimination' import { createEditorWithEssentials } from '../baseEditor' import type { CreateEditorPublicOptions } from '../editorFactory' import { paragraphElementType } from '../plugins' -import { EditorWithBlocks, initBlockEditor } from './editor' +import { initBlockEditor } from './editor' import type { EmbedHandler } from './embed' import { useCreateElementReference } from './references' import { ReferencesProvider } from './references/ReferencesProvider' @@ -19,6 +19,9 @@ import { ContentOutlet, ContentOutletProps, useEditorReferenceBlocks } from './t import { OverrideRenderElementOptions } from './editor/overrideRenderElement' import { EditorReferenceBlocksContext } from '../contexts' import { ReferenceElementRendererProps } from './elements' +import { Repeater } from '@contember/react-repeater' +import { useEditor } from '../slate-reexport' +import { Descendant, insertNodes, Node, removeNodes, withoutNormalizing } from 'slate' export interface BlockEditorProps extends SugaredRelativeEntityList, CreateEditorPublicOptions { @@ -131,45 +134,25 @@ const BlockEditorComponent: FunctionComponent = Component( renderReference, })) - - - - const [_, setMeaninglessState] = useState(0) - useLayoutEffect(() => { - if (editor.children !== nodes && JSON.stringify(editor.children) !== JSON.stringify(nodes)) { - editor.children = nodes - // Force a re-render - setMeaninglessState(meaninglessState => meaninglessState + 1) - } - }, [nodes, editor]) - + useEffect(() => { + refreshBlocks() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) return ( + + - + + {children} - {/* {*/} - {/* Transforms.moveNodes(editor, {*/} - {/* at: [data.oldIndex],*/} - {/* to: [data.newIndex],*/} - {/* })*/} - {/* }, [editor])}*/} - {/* shouldCancelStart={shouldCancelStart}*/} - {/*>*/} - {/**/} + ) }, (props, environment) => { @@ -209,6 +192,24 @@ const BlockEditorComponent: FunctionComponent = Component( 'BlockEditor', ) +const SyncValue = ({ nodes }: { nodes: Descendant[] }) => { + const editor = useEditor() + useEffect(() => { + if (editor.children !== nodes && JSON.stringify(editor.children) !== JSON.stringify(nodes)) { + withoutNormalizing(editor, () => { + for (const [, childPath] of Node.children(editor, [], { + reverse: true, + })) { + removeNodes(editor, { at: childPath }) + } + insertNodes(editor, nodes) + }) + } + + }, [editor, nodes]) + return null +} + /** * The `BlockEditor` component is the main component of the editor. It is responsible for rendering the content editor. * diff --git a/packages/react-legacy-editor/src/blockEditor/state/useBlockEditorState.ts b/packages/react-legacy-editor/src/blockEditor/state/useBlockEditorState.ts index 3c5e9b55b2..02ca75d3f1 100644 --- a/packages/react-legacy-editor/src/blockEditor/state/useBlockEditorState.ts +++ b/packages/react-legacy-editor/src/blockEditor/state/useBlockEditorState.ts @@ -1,15 +1,9 @@ import { useBlockElementCache } from './useBlockElementCache' import { Descendant, Editor } from 'slate' -import { - EntityAccessor, - SugaredFieldProps, - SugaredRelativeEntityList, - useEntityList, - useSortedEntities, -} from '@contember/react-binding' +import { EntityAccessor, EntityId, sortEntities, SugaredFieldProps, SugaredRelativeEntityList, useDesugaredRelativeSingleField, useEntityList } from '@contember/react-binding' import { useBlockElementPathRefs } from './useBlockElementPathRefs' import { useBlockEditorOnChange } from './useBlockEditorOnChange' -import { MutableRefObject, useRef } from 'react' +import { MutableRefObject, useEffect, useMemo, useRef } from 'react' import { useBlockEditorSlateNodes } from '../useBlockEditorSlateNodes' import { useRefreshBlocks } from './useRefreshBlocks' @@ -28,13 +22,23 @@ export const useBlockEditorState = ({ editor, blockList, sortableBy, contentFiel referencesField?: SugaredRelativeEntityList | string monolithicReferencesMode?: boolean }): useBlockEditorStateResult => { - const { entities: sortedBlocks } = useSortedEntities(useEntityList(blockList), sortableBy) + const entityList = useEntityList(blockList) + const desugaredSortableByField = useDesugaredRelativeSingleField(sortableBy) + const trashFakeBlockId = useRef() + const sortedBlocks = useMemo(() => { + return sortEntities( + Array.from(entityList).filter(it => it.id !== trashFakeBlockId.current), + desugaredSortableByField, + ) + }, [desugaredSortableByField, entityList]) const sortedBlocksRef = useRef(sortedBlocks) - sortedBlocksRef.current = sortedBlocks + useEffect(() => { + sortedBlocksRef.current = sortedBlocks + }, [sortedBlocks]) const blockElementCache = useBlockElementCache({ editor, blockList, sortableBy, contentField }) const blockElementPathRefs = useBlockElementPathRefs({ editor, blockList }) - const refreshBlocks = useRefreshBlocks({ editor, sortableBy, contentField, blockList, blockElementCache, blockElementPathRefs, referencesField, monolithicReferencesMode, sortedBlocksRef }) + const refreshBlocks = useRefreshBlocks({ editor, sortableBy, contentField, blockList, blockElementCache, blockElementPathRefs, referencesField, monolithicReferencesMode, sortedBlocksRef, trashFakeBlockId }) const onChange = useBlockEditorOnChange({ editor, blockList, contentField, blockElementCache, sortedBlocksRef, refreshBlocks }) const nodes = useBlockEditorSlateNodes({ editor, blockElementCache, blockElementPathRefs, blockContentField: contentField, topLevelBlocks: sortedBlocks }) diff --git a/packages/react-legacy-editor/src/blockEditor/state/useRefreshBlocks.ts b/packages/react-legacy-editor/src/blockEditor/state/useRefreshBlocks.ts index bec267923e..800a39e746 100644 --- a/packages/react-legacy-editor/src/blockEditor/state/useRefreshBlocks.ts +++ b/packages/react-legacy-editor/src/blockEditor/state/useRefreshBlocks.ts @@ -11,7 +11,7 @@ import { MutableRefObject, useCallback, useRef } from 'react' import { BlockElementCache } from './useBlockElementCache' import { BlockElementPathRefs } from './useBlockElementPathRefs' -export const useRefreshBlocks = ({ editor, sortedBlocksRef, sortableBy, contentField, blockList, blockElementCache, blockElementPathRefs, referencesField, monolithicReferencesMode }: { +export const useRefreshBlocks = ({ editor, sortedBlocksRef, sortableBy, contentField, blockList, blockElementCache, blockElementPathRefs, referencesField, monolithicReferencesMode, trashFakeBlockId }: { editor: Editor, sortedBlocksRef: MutableRefObject, contentField: SugaredFieldProps['field'], @@ -21,12 +21,12 @@ export const useRefreshBlocks = ({ editor, sortedBlocksRef, sortableBy, contentF blockElementPathRefs: BlockElementPathRefs referencesField?: SugaredRelativeEntityList | string monolithicReferencesMode?: boolean + trashFakeBlockId: MutableRefObject }) => { const desugaredBlockList = useDesugaredRelativeEntityList(blockList) const desugaredBlockContentField = useDesugaredRelativeSingleField(contentField) const desugaredSortableByField = useDesugaredRelativeSingleField(sortableBy) const getParentEntityRef = useGetParentEntityRef() - const trashFakeBlockId = useRef() useEntityBeforePersist(() => { if (trashFakeBlockId.current) { const block = getParentEntityRef.current().getRelativeEntityList(desugaredBlockList).getChildEntityById(trashFakeBlockId.current) @@ -168,5 +168,5 @@ export const useRefreshBlocks = ({ editor, sortedBlocksRef, sortableBy, contentF } cleanupStack() }) - }, [blockElementCache, blockElementPathRefs, desugaredBlockContentField, desugaredBlockList, desugaredSortableByField, editor, getParentEntityRef, monolithicReferencesMode, referencesField, sortedBlocksRef]) + }, [blockElementCache, blockElementPathRefs, desugaredBlockContentField, desugaredBlockList, desugaredSortableByField, editor, getParentEntityRef, monolithicReferencesMode, referencesField, sortedBlocksRef, trashFakeBlockId]) } diff --git a/packages/react-legacy-editor/src/blockEditor/utils/isInReferenceElement.ts b/packages/react-legacy-editor/src/blockEditor/utils/isInReferenceElement.ts index 39a316a6a4..6f348453d7 100644 --- a/packages/react-legacy-editor/src/blockEditor/utils/isInReferenceElement.ts +++ b/packages/react-legacy-editor/src/blockEditor/utils/isInReferenceElement.ts @@ -1,4 +1,4 @@ -import { Editor, Node as SlateNode, Path } from 'slate' +import { Editor, Element as SlateElement, Node as SlateNode, Path } from 'slate' import { isElementWithReference } from '../elements' const isPathInReferenceElement = (editor: Editor, path: Path) => { @@ -6,7 +6,7 @@ const isPathInReferenceElement = (editor: Editor, path: Path) => { if (isElementWithReference(node)) { return true } - if (Editor.isBlock(editor, node)) { + if (SlateElement.isElement(node) && Editor.isBlock(editor, node)) { break } } diff --git a/packages/react-legacy-editor/src/blocks/Block.tsx b/packages/react-legacy-editor/src/blocks/Block.tsx index f6a66fa332..e39a48c0ce 100644 --- a/packages/react-legacy-editor/src/blocks/Block.tsx +++ b/packages/react-legacy-editor/src/blocks/Block.tsx @@ -24,7 +24,7 @@ export interface BlockProps { * @group Blocks and repeaters */ export const Block: FunctionComponent = Component( - props => <>{props.children}, + props => null, props => ( <> {props.alternate} diff --git a/packages/react-legacy-editor/src/editorSelection/EditorSelectionAction.ts b/packages/react-legacy-editor/src/editorSelection/EditorSelectionAction.ts deleted file mode 100644 index f7ba9fb0f3..0000000000 --- a/packages/react-legacy-editor/src/editorSelection/EditorSelectionAction.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type EditorSelectionAction = - | { - type: 'blur' - } - | { - type: 'setSelection' - selection: Selection - } - | { - type: 'setMousePointerSelectionStart' - event: MouseEvent - } - | { - type: 'setMousePointerSelectionFinish' - event: MouseEvent | undefined - } diff --git a/packages/react-legacy-editor/src/editorSelection/EditorSelectionState.ts b/packages/react-legacy-editor/src/editorSelection/EditorSelectionState.ts deleted file mode 100644 index cec863df49..0000000000 --- a/packages/react-legacy-editor/src/editorSelection/EditorSelectionState.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type EditorSelectionState = - | { - name: 'unfocused' - } - | { - name: 'emergingPointerSelection' - selection: Selection | undefined - selectedString: string - startEvent: MouseEvent - finishEvent: MouseEvent | undefined - } - | { - name: 'expandedPointerSelection' - selection: Selection - selectedString: string - startEvent: MouseEvent - finishEvent: MouseEvent - } - | { - name: 'expandedNonPointerSelection' - selection: Selection - selectedString: string - } - | { - name: 'collapsedSelection' - selection: Selection - selectedString: string - } diff --git a/packages/react-legacy-editor/src/editorSelection/editorSelectionReducer.ts b/packages/react-legacy-editor/src/editorSelection/editorSelectionReducer.ts deleted file mode 100644 index 74039937f6..0000000000 --- a/packages/react-legacy-editor/src/editorSelection/editorSelectionReducer.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { EditorSelectionAction } from './EditorSelectionAction' -import type { EditorSelectionState } from './EditorSelectionState' - -export const defaultEditorSelectionState: EditorSelectionState = { - name: 'unfocused', -} - -// TODO this whole thing needs reworked because the selection object is actually super mutable and that leads to some -// fun situations… -export const editorSelectionReducer = ( - previousState: EditorSelectionState, - action: EditorSelectionAction, -): EditorSelectionState => { - switch (action.type) { - case 'blur': { - return defaultEditorSelectionState - } - case 'setSelection': { - if (action.selection.type === 'Caret') { - return { - name: 'collapsedSelection', - selection: action.selection, - selectedString: action.selection.toString(), - } - } else if (action.selection.type === 'Range') { - if (previousState.name === 'emergingPointerSelection') { - if (!previousState.finishEvent) { - return { - ...previousState, - selection: action.selection, - } - } - return { - name: 'expandedPointerSelection', - startEvent: previousState.startEvent, - finishEvent: previousState.finishEvent, - selection: action.selection, - selectedString: action.selection.toString(), - } - } - return { - name: 'expandedNonPointerSelection', - selection: action.selection, - selectedString: action.selection.toString(), - } - } - return previousState - } - case 'setMousePointerSelectionStart': { - return { - name: 'emergingPointerSelection', - selection: undefined, - selectedString: '', - startEvent: action.event, - finishEvent: undefined, - } - } - case 'setMousePointerSelectionFinish': { - if (previousState.name === 'emergingPointerSelection') { - const selection = previousState.selection - - if (!selection) { - return { - ...previousState, - finishEvent: action.event, - } - } else if (selection.type === 'Caret') { - return { - name: 'collapsedSelection', - selection, - selectedString: selection.toString(), - } - } else if (selection.type === 'Range') { - if (action.event) { - return { - name: 'expandedPointerSelection', - startEvent: previousState.startEvent, - finishEvent: action.event, - selection, - selectedString: selection.toString(), - } - } - return { - name: 'expandedNonPointerSelection', - selection, - selectedString: selection.toString(), - } - } - return previousState - } - return previousState - } - } - return previousState -} diff --git a/packages/react-legacy-editor/src/editorSelection/index.ts b/packages/react-legacy-editor/src/editorSelection/index.ts deleted file mode 100644 index 610592ea67..0000000000 --- a/packages/react-legacy-editor/src/editorSelection/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './EditorSelectionState' -export * from './useEditorSelection' -export * from './useToolbarsState' diff --git a/packages/react-legacy-editor/src/editorSelection/useEditorSelection.ts b/packages/react-legacy-editor/src/editorSelection/useEditorSelection.ts deleted file mode 100644 index b197cd81b9..0000000000 --- a/packages/react-legacy-editor/src/editorSelection/useEditorSelection.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useDebounceCallback } from '@contember/react-utils' -import { useCallback, useEffect, useReducer, useRef } from 'react' -import { Editor, Range as SlateRange } from 'slate' -import { ReactEditor, useSlateStatic } from 'slate-react' -import { defaultEditorSelectionState, editorSelectionReducer } from './editorSelectionReducer' -import type { EditorSelectionState } from './EditorSelectionState' - -export const useEditorSelection = (maxInterval: number = 100): EditorSelectionState => { - const editor = useSlateStatic() - const [selectionState, dispatch] = useReducer(editorSelectionReducer, defaultEditorSelectionState) - const selectionStateRef = useRef(selectionState) - - selectionStateRef.current = selectionState - - const onDOMSelectionChange = useDebounceCallback( - useCallback(() => { - const domSelection = getSelection() - const isRelevant = - domSelection && - domSelection.anchorNode && - ReactEditor.hasDOMNode(editor, domSelection.anchorNode, { editable: true }) - - if (isRelevant) { - if ( - (selectionStateRef.current.name === 'expandedPointerSelection' || - selectionStateRef.current.name === 'expandedNonPointerSelection') && - !domSelection!.isCollapsed && - selectionStateRef.current.selection - ) { - const stateSelectionRange = ReactEditor.toSlateRange(editor, selectionStateRef.current.selection, { exactMatch: false, suppressThrow: false }) - const domSelectionRange = ReactEditor.toSlateRange(editor, domSelection!, { exactMatch: false, suppressThrow: false }) - - if ( - SlateRange.equals(stateSelectionRange, domSelectionRange) && - (!editor.selection || Editor.string(editor, editor.selection) === selectionStateRef.current.selectedString) - ) { - // We've likely just changed a mark or something. To the DOM, this *is* a selection change but as far as we're - // concerned they are the same. - return - } - } - dispatch({ - type: 'setSelection', - selection: domSelection!, - }) - } else { - dispatch({ - type: 'blur', - }) - } - }, [editor]), - maxInterval, - ) - const onMouseDown = useCallback( - (e: MouseEvent) => { - e.target && - e.target instanceof Node && - ReactEditor.hasDOMNode(editor, e.target) && - dispatch({ - type: 'setMousePointerSelectionStart', - event: e, - }) - }, - [editor], - ) - const onMouseUp = useCallback( - (e: MouseEvent) => { - const relevantTarget = !!e.target && e.target instanceof Node && ReactEditor.hasDOMNode(editor, e.target) - dispatch({ - type: 'setMousePointerSelectionFinish', - event: relevantTarget ? e : undefined, - }) - }, - [editor], - ) - - useEffect(() => { - document.addEventListener('selectionchange', onDOMSelectionChange) - document.addEventListener('mousedown', onMouseDown) - document.addEventListener('mouseup', onMouseUp) - - return () => { - document.removeEventListener('selectionchange', onDOMSelectionChange) - document.removeEventListener('mousedown', onMouseDown) - document.removeEventListener('mouseup', onMouseUp) - } - }, [onDOMSelectionChange, onMouseDown, onMouseUp]) - - return selectionState -} diff --git a/packages/react-legacy-editor/src/editorSelection/useToolbarsState.ts b/packages/react-legacy-editor/src/editorSelection/useToolbarsState.ts deleted file mode 100644 index e3c644c868..0000000000 --- a/packages/react-legacy-editor/src/editorSelection/useToolbarsState.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { RefObject, useEffect, useLayoutEffect, useRef } from 'react' -import { EditorSelectionState } from './EditorSelectionState' -import { useEditorSelection } from './useEditorSelection' - -export interface ToolbarsState { - inlineToolbarRef: RefObject - inlineToolbarActive: boolean - blockToolbarActive: boolean -} - -function updateToolbarStyle(container: HTMLDivElement, selectionState: EditorSelectionState) { - let top, left, maxWidth - let domRangeRect: DOMRect | undefined - - if (selectionState.name === 'expandedNonPointerSelection') { - domRangeRect = selectionState.selection.getRangeAt(0).getBoundingClientRect() - } else if (selectionState.name === 'expandedPointerSelection') { - if (document.caretRangeFromPoint) { - domRangeRect = - document - .caretRangeFromPoint(selectionState.finishEvent.clientX, selectionState.finishEvent.clientY) - ?.getBoundingClientRect() || undefined - } else if (document.caretPositionFromPoint) { - domRangeRect = - document - .caretPositionFromPoint(selectionState.finishEvent.clientX, selectionState.finishEvent.clientY) - ?.getClientRect() || undefined - } - } - - if (domRangeRect) { - top = `${domRangeRect.top + window.pageYOffset - container.offsetHeight}px` - left = `${Math.min( - Math.max(0, document.documentElement.clientWidth - container.offsetWidth), - Math.max(0, domRangeRect.left + window.pageXOffset - container.offsetWidth / 2 + domRangeRect.width / 2), - ) - }px` - maxWidth = `${document.documentElement.clientWidth}px` - } else { - top = '-1000vh' - left = '-1000vw' - maxWidth = 'unset' - } - - console.log({ top, left, maxWidth }) - - container.style.top = top - container.style.left = left - container.style.maxWidth = maxWidth -} - -export const useToolbarState = (): ToolbarsState => { - const inlineToolbarRef = useRef(null) - const selectionState = useEditorSelection() - - const inlineToolbarActive = - selectionState.name === 'expandedPointerSelection' || selectionState.name === 'expandedNonPointerSelection' - const blockToolbarActive = - selectionState.name === 'collapsedSelection' || selectionState.name === 'emergingPointerSelection' - - useLayoutEffect(() => { - const container = inlineToolbarRef.current - - if (!container) { - return - } - - updateToolbarStyle(container, selectionState) - }, [selectionState]) - - useEffect(() => { - const container = inlineToolbarRef.current - let timeoutId: ReturnType - - function resize() { - if (!container) { - return - } - - clearTimeout(timeoutId) - - timeoutId = setTimeout(() => { - updateToolbarStyle(container, selectionState) - }, 100) - } - - window.addEventListener('resize', resize) - - return () => { - window.removeEventListener('resize', resize) - } - }, [selectionState]) - - return { - inlineToolbarRef, - blockToolbarActive, - inlineToolbarActive, - } -} diff --git a/packages/react-legacy-editor/src/index.ts b/packages/react-legacy-editor/src/index.ts index 7e9539edf9..01dd156950 100644 --- a/packages/react-legacy-editor/src/index.ts +++ b/packages/react-legacy-editor/src/index.ts @@ -2,7 +2,6 @@ export * from './baseEditor' export * from './blockEditor' export * from './discrimination' export * from './ContemberEditor' -export * from './editorSelection' export type { CreateEditorPublicOptions, } from './editorFactory' diff --git a/packages/react-legacy-editor/src/plugins/element/lists/ListElement.ts b/packages/react-legacy-editor/src/plugins/element/lists/ListElement.ts index 8ac459b6b9..f25f96b2ac 100644 --- a/packages/react-legacy-editor/src/plugins/element/lists/ListElement.ts +++ b/packages/react-legacy-editor/src/plugins/element/lists/ListElement.ts @@ -11,7 +11,7 @@ export const isListElement = { const closestNonDefaultEntry = ContemberEditor.closest(editor, { - match: node => Editor.isBlock(editor, node) && !editor.isDefaultElement(node), + match: node => SlateElement.isElement(node) && Editor.isBlock(editor, node) && !editor.isDefaultElement(node), }) if (!closestNonDefaultEntry) { return undefined diff --git a/packages/react-legacy-editor/src/plugins/element/lists/transforms/toggleListElement.ts b/packages/react-legacy-editor/src/plugins/element/lists/transforms/toggleListElement.ts index a9bab1297a..477f0ee717 100644 --- a/packages/react-legacy-editor/src/plugins/element/lists/transforms/toggleListElement.ts +++ b/packages/react-legacy-editor/src/plugins/element/lists/transforms/toggleListElement.ts @@ -77,7 +77,7 @@ export const toggleListElement = Text.isText(node) || Editor.isInline(editor, node), + match: node => Text.isText(node) || SlateElement.isElement(node) && Editor.isInline(editor, node), at: { anchor: Editor.start(editor, [...targetParentPath, 1]), focus: Editor.end(editor, [...targetParentPath, targetParent.children.length]), diff --git a/packages/react-legacy-editor/src/plugins/element/lists/withLists.ts b/packages/react-legacy-editor/src/plugins/element/lists/withLists.ts index f82f1a88ae..7308b06fb0 100644 --- a/packages/react-legacy-editor/src/plugins/element/lists/withLists.ts +++ b/packages/react-legacy-editor/src/plugins/element/lists/withLists.ts @@ -165,7 +165,7 @@ export const withLists = ({ renderListItem, renderUnorderedList, renderOrderedLi return Transforms.splitNodes(editor, { always: true, at: selection.focus, - match: node => Editor.isBlock(editor, node) && editor.isDefaultElement(node), + match: node => SlateElement.isElement(node) && Editor.isBlock(editor, node) && editor.isDefaultElement(node), }) } else if (isListItemElement(closestBlockElement)) { // We want to create a newline but the closest block is the list item. diff --git a/packages/react-legacy-editor/src/plugins/element/paragraphs/ParagraphElement.ts b/packages/react-legacy-editor/src/plugins/element/paragraphs/ParagraphElement.ts index f9522e334b..83d7e02ef2 100644 --- a/packages/react-legacy-editor/src/plugins/element/paragraphs/ParagraphElement.ts +++ b/packages/react-legacy-editor/src/plugins/element/paragraphs/ParagraphElement.ts @@ -50,7 +50,7 @@ export const paragraphElementPlugin = ({ render }: {render: ElementRenderer { for (const [i, child] of element.children.entries()) { - if (Editor.isBlock(editor, child)) { + if (SlateElement.isElement(child) && Editor.isBlock(editor, child)) { Transforms.unwrapNodes(editor, { at: [...path, i] }) return preventDefault() } diff --git a/packages/react-legacy-editor/src/tsconfig.json b/packages/react-legacy-editor/src/tsconfig.json index 8542cac73a..d3990996c8 100644 --- a/packages/react-legacy-editor/src/tsconfig.json +++ b/packages/react-legacy-editor/src/tsconfig.json @@ -2,5 +2,12 @@ "extends": "../../../tsconfig.settings.json", "compilerOptions": { "outDir": "../dist/types" - } + }, + "references": [ + { "path": "../../react-binding/src" }, + { "path": "../../react-repeater/src" }, + { "path": "../../react-multipass-rendering/src" }, + { "path": "../../react-utils/src" }, + { "path": "../../utilities/src" }, + ] } diff --git a/packages/react-repeater-dnd-kit/src/components/RepeaterSortable.tsx b/packages/react-repeater-dnd-kit/src/components/RepeaterSortable.tsx index 2dcabdc374..cea6ec99b5 100644 --- a/packages/react-repeater-dnd-kit/src/components/RepeaterSortable.tsx +++ b/packages/react-repeater-dnd-kit/src/components/RepeaterSortable.tsx @@ -10,7 +10,7 @@ import { useSensor, useSensors, } from '@dnd-kit/core' -import { RepeaterActiveEntityContext } from '../internal/contexts' +import { RepeaterActiveEntityContext } from '../contexts' import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' import { useRepeaterSortedEntities } from '@contember/react-repeater' import { useRepeaterMethods } from '@contember/react-repeater' diff --git a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDragOverlay.tsx b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDragOverlay.tsx index 4a51c66c1d..63fc73ffe9 100644 --- a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDragOverlay.tsx +++ b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDragOverlay.tsx @@ -1,5 +1,5 @@ import React, { ReactNode } from 'react' -import { useRepeaterActiveEntity } from '../internal/contexts' +import { useRepeaterActiveEntity } from '../contexts' import { Portal } from '@radix-ui/react-portal' import { DragOverlay } from '@dnd-kit/core' import { AccessorTree, Entity, useAccessorTreeState } from '@contember/react-binding' diff --git a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDropIndicator.tsx b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDropIndicator.tsx index c63be8c739..6aa686daa5 100644 --- a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDropIndicator.tsx +++ b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableDropIndicator.tsx @@ -1,5 +1,5 @@ import React, { ReactNode } from 'react' -import { useRepeaterSortableItem } from '../internal/contexts' +import { useRepeaterSortableItem } from '../contexts' export const RepeaterSortableDropIndicator = ({ children, position }: { children: ReactNode diff --git a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableEachItem.tsx b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableEachItem.tsx index b4d6fe62f8..28c220376e 100644 --- a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableEachItem.tsx +++ b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableEachItem.tsx @@ -1,7 +1,7 @@ import React, { ReactNode } from 'react' import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable' import { RepeaterEachItem, useRepeaterSortedEntities } from '@contember/react-repeater' -import { RepeaterSortableItemContext } from '../internal/contexts' +import { RepeaterSortableItemContext } from '../contexts' import { useEntity } from '@contember/react-binding' export const RepeaterSortableEachItem = ({ children }: { diff --git a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemActivator.tsx b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemActivator.tsx index 3a8fc38b0e..e53e49e9d1 100644 --- a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemActivator.tsx +++ b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemActivator.tsx @@ -1,5 +1,5 @@ import React, { forwardRef, ReactNode } from 'react' -import { useRepeaterSortableItem } from '../internal/contexts' +import { useRepeaterSortableItem } from '../contexts' import { Slot } from '@radix-ui/react-slot' import { useComposeRef } from '@contember/react-utils' diff --git a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemNode.tsx b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemNode.tsx index 259748590b..3799d367fc 100644 --- a/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemNode.tsx +++ b/packages/react-repeater-dnd-kit/src/components/RepeaterSortableItemNode.tsx @@ -1,5 +1,5 @@ import React, { ReactNode } from 'react' -import { useRepeaterSortableItem } from '../internal/contexts' +import { useRepeaterSortableItem } from '../contexts' import { Slot } from '@radix-ui/react-slot' export const RepeaterSortableItemNode = ({ children }: { diff --git a/packages/react-repeater-dnd-kit/src/contexts.ts b/packages/react-repeater-dnd-kit/src/contexts.ts new file mode 100644 index 0000000000..a67264d860 --- /dev/null +++ b/packages/react-repeater-dnd-kit/src/contexts.ts @@ -0,0 +1,13 @@ +import { createRequiredContext, createContext } from '@contember/react-utils' +import { EntityAccessor } from '@contember/react-binding' +import { useSortable } from '@dnd-kit/sortable' + +const RepeaterSortableItemContext_ = createRequiredContext>('RepeaterSortableItemContext') +/** @internal */ +export const RepeaterSortableItemContext = RepeaterSortableItemContext_[0] +export const useRepeaterSortableItem = RepeaterSortableItemContext_[1] + +const RepeaterActiveEntityContext_ = createContext('RepeaterActiveEntityContext', undefined) +/** @internal */ +export const RepeaterActiveEntityContext = RepeaterActiveEntityContext_[0] +export const useRepeaterActiveEntity = RepeaterActiveEntityContext_[1] diff --git a/packages/react-repeater-dnd-kit/src/index.ts b/packages/react-repeater-dnd-kit/src/index.ts index 3becd18468..8a33065358 100644 --- a/packages/react-repeater-dnd-kit/src/index.ts +++ b/packages/react-repeater-dnd-kit/src/index.ts @@ -1,3 +1,5 @@ export * from './components' +export * from './contexts' + export * from '@contember/react-repeater' diff --git a/packages/react-repeater-dnd-kit/src/internal/contexts.ts b/packages/react-repeater-dnd-kit/src/internal/contexts.ts deleted file mode 100644 index 89e15a9bed..0000000000 --- a/packages/react-repeater-dnd-kit/src/internal/contexts.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createRequiredContext, createContext } from '@contember/react-utils' -import { EntityAccessor } from '@contember/react-binding' -import { useSortable } from '@dnd-kit/sortable' - -export const [RepeaterSortableItemContext, useRepeaterSortableItem] = createRequiredContext>('RepeaterSortableItemContext') -export const [RepeaterActiveEntityContext, useRepeaterActiveEntity] = createContext('RepeaterActiveEntityContext', undefined) diff --git a/packages/react-repeater/src/internal/useCreateRepeaterMethods.tsx b/packages/react-repeater/src/internal/useCreateRepeaterMethods.tsx index 16aeae687d..c22ef8fe96 100644 --- a/packages/react-repeater/src/internal/useCreateRepeaterMethods.tsx +++ b/packages/react-repeater/src/internal/useCreateRepeaterMethods.tsx @@ -64,6 +64,7 @@ export const useCreateRepeaterMethods = ({ accessor, sortableBy }: { })() const newSorted = arrayMove(sortedEntities, currentIndex, resolvedIndex) repairEntitiesOrder(sortableBy, newSorted) + console.log('items moved') } : undefined, removeItem: (entity: EntityAccessor) => { if (sortableBy) { diff --git a/yarn.lock b/yarn.lock index a9abe3fd71..5f5f817f86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1074,7 +1074,7 @@ __metadata: react: ^18.2.0 react-dom: ^18.2.0 react-dropzone: ^14.2.3 - react-error-boundary: 4.0.12 + react-error-boundary: ^4.0.12 react-sortable-hoc: 2.0.0 slate: 0.73.1 slate-history: 0.66.0 @@ -1346,7 +1346,7 @@ __metadata: react-dnd-html5-backend: ^16.0.1 react-dom: ^18.2.0 react-twc: ^1.3.0 - slate: ^0.102.0 + slate: ^0.103.0 slate-history: ^0.100.0 slate-hyperscript: ^0.100.0 slate-react: ^0.102.0 @@ -1699,9 +1699,21 @@ __metadata: version: 0.0.0-use.local resolution: "@contember/react-legacy-editor@workspace:packages/react-legacy-editor" dependencies: - slate: 0.73.1 - slate-history: 0.66.0 - slate-react: 0.74.2 + "@contember/react-binding": "workspace:*" + "@contember/react-multipass-rendering": "workspace:*" + "@contember/react-repeater": "workspace:*" + "@contember/react-utils": "workspace:*" + "@contember/utilities": "workspace:*" + is-hotkey: ^0.2.0 + react: ^18.2.0 + react-dom: ^18.2.0 + react-error-boundary: ^4.0.13 + slate: ^0.103.0 + slate-history: ^0.100.0 + slate-react: ^0.102.0 + peerDependencies: + react: ^17 || ^18 + react-dom: ^17 || ^18 languageName: unknown linkType: soft @@ -11569,25 +11581,25 @@ __metadata: languageName: node linkType: hard -"react-error-boundary@npm:4.0.12": - version: 4.0.12 - resolution: "react-error-boundary@npm:4.0.12" +"react-error-boundary@npm:^3.1.0": + version: 3.1.4 + resolution: "react-error-boundary@npm:3.1.4" dependencies: "@babel/runtime": ^7.12.5 peerDependencies: react: ">=16.13.1" - checksum: c4a14c2b609037baba394d8871fb021a7c16fb1abfd8d23b2d2aa331bdcc420e5cff4b3d48f2f2ac7fa64ac90c322d0bcf358d706995b30df5a8d5fcb0c27b1e + checksum: f36270a5d775a25c8920f854c0d91649ceea417b15b5bc51e270a959b0476647bb79abb4da3be7dd9a4597b029214e8fe43ea914a7f16fa7543c91f784977f1b languageName: node linkType: hard -"react-error-boundary@npm:^3.1.0": - version: 3.1.4 - resolution: "react-error-boundary@npm:3.1.4" +"react-error-boundary@npm:^4.0.12, react-error-boundary@npm:^4.0.13": + version: 4.0.13 + resolution: "react-error-boundary@npm:4.0.13" dependencies: "@babel/runtime": ^7.12.5 peerDependencies: react: ">=16.13.1" - checksum: f36270a5d775a25c8920f854c0d91649ceea417b15b5bc51e270a959b0476647bb79abb4da3be7dd9a4597b029214e8fe43ea914a7f16fa7543c91f784977f1b + checksum: 50398d080015d51d22c6f94c56f4ea336d10232d72345b36ee6f15b6b643666d20b072829b02f091a80e5af68fe67f68a62ef0d2b649dbd759ead929304449af languageName: node linkType: hard @@ -12445,14 +12457,14 @@ __metadata: languageName: node linkType: hard -"slate@npm:^0.102.0": - version: 0.102.0 - resolution: "slate@npm:0.102.0" +"slate@npm:^0.103.0": + version: 0.103.0 + resolution: "slate@npm:0.103.0" dependencies: immer: ^10.0.3 is-plain-object: ^5.0.0 tiny-warning: ^1.0.3 - checksum: 69bb9160c24342804a3771d9ef69d9f58749b361181576a58b60e8fdcdfe3ee4f8afddb4745f5b3cd7e1d99af0ff949586c5720d1c15c600f4e121a59a488369 + checksum: 50a2f79f80b11be7f475c74d666bdccd39720354cb08932864161dd48094e991f10f66a4d8ecc455fff9327895a6d7cf8591d113c606ca2565404d56d4d7fea3 languageName: node linkType: hard