Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
matej21 committed May 6, 2024
1 parent 5cca3e3 commit 4357c4c
Show file tree
Hide file tree
Showing 60 changed files with 729 additions and 801 deletions.
2 changes: 1 addition & 1 deletion packages/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
121 changes: 69 additions & 52 deletions packages/playground/admin/app/pages/legacyEditor.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -17,8 +17,8 @@ import {
Heading1Icon,
Heading2Icon,
Heading3Icon,
HighlighterIcon,
ItalicIcon,
HighlighterIcon, ImageIcon,
ItalicIcon, Link2Icon,
LinkIcon,
ListIcon,
ListOrderedIcon,
Expand Down Expand Up @@ -47,7 +47,7 @@ import {
horizontalRuleElementType,
italicMark,
orderedListElementType,
paragraphElementType,
paragraphElementType, referenceElementType,
scrollTargetElementType,
strikeThroughMark,
tableElementType,
Expand All @@ -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 = () => <>
<Binding>
Expand All @@ -87,6 +90,10 @@ export const richtext = () => <>
</Binding>
</>

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 = () => <>
<Binding>
<Slots.Actions>
Expand Down Expand Up @@ -122,46 +129,51 @@ export const blocks = () => <>
})
},
]}
controls={
<HoveringToolbars
inlineButtons={<>
>
<BlockToolbar>
<EditorReferenceTrigger referenceType="quote"><BlockButton><QuoteIcon /> Quote</BlockButton></EditorReferenceTrigger>
<EditorReferenceTrigger referenceType="image"><BlockButton><ImageIcon /> Image</BlockButton></EditorReferenceTrigger>
<EditorElementTrigger elementType={tableElementType}><BlockButton><TableIcon /> Table</BlockButton></EditorElementTrigger>
<EditorElementTrigger elementType={scrollTargetElementType}><BlockButton><LocateIcon /> Scroll target</BlockButton></EditorElementTrigger>
<EditorElementTrigger elementType={horizontalRuleElementType}><BlockButton><MinusIcon /> Horizontal rule</BlockButton></EditorElementTrigger>
</BlockToolbar>
<InlineHoveringToolbar>
<div>
<EditorMarkTrigger mark={boldMark}><Toggle><BoldIcon className="h-3 w-3" /></Toggle></EditorMarkTrigger>
<EditorMarkTrigger mark={italicMark}><Toggle><ItalicIcon className="h-3 w-3" /></Toggle></EditorMarkTrigger>
<EditorMarkTrigger mark={underlineMark}><Toggle><UnderlineIcon className="h-3 w-3" /></Toggle></EditorMarkTrigger>
<EditorMarkTrigger mark={strikeThroughMark}><Toggle><StrikethroughIcon className="h-3 w-3" /></Toggle></EditorMarkTrigger>
<EditorMarkTrigger mark={highlightMark}><Toggle><HighlighterIcon className="h-3 w-3" /></Toggle></EditorMarkTrigger>
<EditorMarkTrigger mark={codeMark}><Toggle><CodeIcon className="h-3 w-3" /></Toggle></EditorMarkTrigger>
<EditorElementTrigger elementType={anchorElementType}><Toggle><LinkIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={anchorElementType}><Toggle><Link2Icon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<Popover>
<PopoverTrigger asChild>
<Toggle><LinkIcon className="h-3 w-3" /></Toggle>
</PopoverTrigger>
<PopoverContent>
<EditorInlineReferencePortal referenceType="link">
<LinkField field="target" />
<ConfirmReferenceButton />
</EditorInlineReferencePortal>
</PopoverContent>
</Popover>
</div>
<div>
<EditorElementTrigger elementType={paragraphElementType} suchThat={{ isNumbered: false }}><Toggle><PilcrowIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={headingElementType} suchThat={{ level: 1, isNumbered: false }}><Toggle><Heading1Icon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={headingElementType} suchThat={{ level: 1, isNumbered: true }}><Toggle className="relative"><Heading1Icon className="h-3 w-3" />
<HashIcon className="h-2.5 w-2.5 absolute right-2 top-1" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={headingElementType} suchThat={{ level: 2, isNumbered: false }}><Toggle><Heading2Icon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={headingElementType} suchThat={{ level: 2, isNumbered: true }}><Toggle className="relative"><Heading2Icon className="h-3 w-3" />
<HashIcon className="h-2.5 w-2.5 absolute right-2 top-1" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={headingElementType} suchThat={{ level: 3, isNumbered: false }}><Toggle><Heading3Icon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={headingElementType} suchThat={{ level: 3, isNumbered: true }}><Toggle className="relative"><Heading3Icon className="h-3 w-3" />
<HashIcon className="h-2.5 w-2.5 absolute right-2 top-1" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={horizontalRuleElementType}><Toggle><MinusIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={unorderedListElementType}><Toggle><ListIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={orderedListElementType}><Toggle><ListOrderedIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={scrollTargetElementType}><Toggle><LocateIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={paragraphElementType} suchThat={{ isNumbered: false }}><Toggle><PilcrowIcon className="h-3 w-3" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={paragraphElementType} suchThat={{ isNumbered: true }}><Toggle className="relative"><PilcrowIcon className="h-3 w-3" />
<HashIcon className="h-2.5 w-2.5 absolute right-2 top-1" /></Toggle></EditorElementTrigger>
<EditorElementTrigger elementType={tableElementType}><Toggle><TableIcon className="h-3 w-3" /> </Toggle></EditorElementTrigger>
<EditorGenericTrigger {...createAlignHandler('start')}><Toggle><AlignLeftIcon className="h-3 w-3" /></Toggle></EditorGenericTrigger>

<EditorGenericTrigger {...createAlignHandler('start')}><Toggle className="ml-4"><AlignLeftIcon className="h-3 w-3" /></Toggle></EditorGenericTrigger>
<EditorGenericTrigger {...createAlignHandler('end')}><Toggle><AlignRightIcon className="h-3 w-3" /></Toggle></EditorGenericTrigger>
<EditorGenericTrigger {...createAlignHandler('center')}><Toggle><AlignCenterIcon className="h-3 w-3" /></Toggle></EditorGenericTrigger>
<EditorGenericTrigger {...createAlignHandler('justify')}><Toggle><AlignJustifyIcon className="h-3 w-3" /></Toggle></EditorGenericTrigger>
</>}
blockButtons={<>
<EditorReferenceTrigger referenceType="quote"><Button><QuoteIcon /></Button></EditorReferenceTrigger>
</>}
/>
}
>
</div>
</InlineHoveringToolbar>
<Block discriminateBy="quote" label="Quote">
<BlockEditor.ContentOutlet />
<BlockEditor.ContentOutlet placeholder="Type here..." />
</Block>
<Block discriminateBy="embed" label="Embed">
<Block discriminateBy="spotify" label="Spotify" />
Expand All @@ -177,24 +189,43 @@ export const blocks = () => <>
</Binding>
</>

const ConfirmReferenceButton = () => {
const reference = useEntity()

return (
<PopoverClose asChild>
<EditorWrapNodeTrigger
elementType={referenceElementType}
suchThat={{ type: 'link', referenceId: reference.id }}
>
<Button>Insert</Button>
</EditorWrapNodeTrigger>
</PopoverClose>
)
}


const LinkElement = (props: EditorRenderElementProps) => {
const editor = useEditor()
return (
<span {...props.attributes} style={{ color: 'var(--cui-control-color)' }}>
{props.children}
<span {...props.attributes}>
<span className="bg-gray-50 border-b border-b-blue-300">
{props.children}
</span>
<span contentEditable={false}>
<Popover
>
<PopoverTrigger asChild>
<Button><LinkIcon/></Button>
<button className="hover:bg-gray-200 p-1.5 border rounded"><LinkIcon className="w-2 h-2" /></button>
</PopoverTrigger>
<PopoverContent>
<LinkField field="target" />
<div className="flex gap-2 items-center">
<LinkField field="target" />

<Button onClick={() => EditorTransforms.unwrapNodes(editor, { at: [], match: node => node === props.element })}>
<TrashIcon/>
</Button>
<Button onClick={() => EditorTransforms.unwrapNodes(editor, { at: [], match: node => node === props.element })} variant="destructive" size="sm">
<TrashIcon className="w-3 h-3"/>
</Button>
</div>
</PopoverContent>
</Popover>
</span>
Expand All @@ -203,20 +234,6 @@ const LinkElement = (props: EditorRenderElementProps) => {
}


const InsertLink = Component<InitializeReferenceContentProps>(
({ onSuccess, onCancel }) => (
<>
<LinkField field="target" />
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1em', minWidth: '350px' }}>
<Button onClick={onCancel}>Cancel</Button>
<Button onClick={() => onSuccess({ createElement: { type: 'link' } })}>Insert</Button>
</div>
</>
),
() => <LinkField field="target" />,
)


export const LinkField = Component<{ field: string }>(({ field }) => {
return (
<HasOne field={field}>
Expand Down
4 changes: 4 additions & 0 deletions packages/playground/admin/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@
--ring: 240 4.9% 83.9%;
}
}

:focus-visible {
outline: 0
}
74 changes: 29 additions & 45 deletions packages/playground/admin/lib-extra/legacy-editor/BlockEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlockEditorProps, 'renderSortableBlock' | 'renderReference'>
& {
placeholder?: string
controls?: ReactNode
}

export const BlockEditorField = Component<BlockEditorFieldProps>(({ placeholder, controls, children, ...props }) => {
export const BlockEditorField = Component<BlockEditorFieldProps>(({ placeholder, children, ...props }) => {
return (
<BlockEditor {...props} renderSortableBlock={SortableBlock} renderReference={ReferenceElementRenderer}>
<BlockEditorInner placeholder={placeholder}>
<StaticRender>
<RepeaterSortable>
<BlockEditorInner placeholder={placeholder}>
{children}
</StaticRender>
{controls}
</BlockEditorInner>
</BlockEditorInner>
</RepeaterSortable>
</BlockEditor>
)
})
Expand All @@ -31,44 +31,28 @@ export const BlockEditorInner = ({ children, placeholder }: {
children: ReactNode
}) => {
const editor = useEditor()
const entities = useRepeaterSortedEntities()

return (
<EditorCanvas
underlyingComponent={EditableCanvas}
componentProps={{
renderElement: editor.renderElement,
renderLeaf: editor.renderLeaf,
onKeyDown: editor.onKeyDown,
onFocusCapture: editor.onFocus,
onBlurCapture: editor.onBlur,
onDOMBeforeInput: editor.onDOMBeforeInput,
onDrop: (e => {
e.preventDefault()
}),
placeholder: placeholder,
}}
<SortableContext items={entities} strategy={verticalListSortingStrategy}>
<EditorCanvas
underlyingComponent={EditableCanvas}
componentProps={{
renderElement: editor.renderElement,
renderLeaf: editor.renderLeaf,
onKeyDown: editor.onKeyDown,
onFocusCapture: editor.onFocus,
onBlurCapture: editor.onBlur,
onDOMBeforeInput: editor.onDOMBeforeInput,
onDrop: (e => {
e.preventDefault()
}),
placeholder: placeholder,
}}

>
{children}
</EditorCanvas>
>
{children}
</EditorCanvas>
</SortableContext>
)
}


// 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 (
// <Content key={i} referenceId="" editor={0 as any} selection={null} onSuccess={noop} onCancel={noop} />
// )
// })}
// </>
// }
17 changes: 17 additions & 0 deletions packages/playground/admin/lib-extra/legacy-editor/BlockToolbar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="static" contentEditable={false}>
<div className={cn('-mx-3 -my-2 p-2 rounded-b-md border-t bg-gray-50 space-x-2', !selection && 'pointer-events-none opacity-50')}>{children}</div>
</div>
)
})
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -40,7 +39,6 @@ export const EditableCanvas = ({ className, ...editableProps }: EditableCanvasPr

return (
<div
className={useClassName('editable-canvas', className)}
onKeyDownCapture={handleCapturedKeyDown}
onKeyUp={handleSlateNodeChange}
onMouseDownCapture={handleCapturedMouseDown}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const EditorCanvas = memo(<P extends HTMLTextAreaDivTargetProps>({
}: EditorCanvasProps<P>) => {

return (
<div data-focus-ring={dataAttribute(focusRing)} className={'w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'}>
<Component {...props} />
<div data-focus-ring={dataAttribute(focusRing)} className={'relative w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'}>
<Component className="focus-visible:ring-0" {...props} />
{children}
</div>
)
Expand Down

This file was deleted.

Loading

0 comments on commit 4357c4c

Please sign in to comment.