Skip to content

Commit

Permalink
Merge pull request #690 from contember/feat/add-plate-playground
Browse files Browse the repository at this point in the history
feat(playground): add plate to playground
  • Loading branch information
matej21 authored Apr 29, 2024
2 parents 02dc70a + 84878e4 commit 13c1150
Show file tree
Hide file tree
Showing 42 changed files with 4,529 additions and 40 deletions.
5 changes: 3 additions & 2 deletions packages/playground/admin/app/components/navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArchiveIcon, BrushIcon, FormInputIcon, GripVertical, HomeIcon, KanbanIcon, LanguagesIcon, TableIcon, UploadIcon } from 'lucide-react'
import { Menu, MenuItem, MenuList } from '../../lib/components/ui/menu'
import { ArchiveIcon, BrushIcon, FormInputIcon, GripVertical, HomeIcon, KanbanIcon, LanguagesIcon, PencilIcon, TableIcon, UploadIcon } from 'lucide-react'
import { Menu, MenuItem } from '../../lib/components/ui/menu'


export const Navigation = () => {
Expand Down Expand Up @@ -49,6 +49,7 @@ export const Navigation = () => {
<MenuItem icon={line} label={'Image repeater'} to={'upload/imageList'} />
</MenuItem>
<MenuItem icon={<LanguagesIcon size={16} />} label={'Dimensions'} to={'dimensions'} />
<MenuItem icon={<PencilIcon size={16} />} label={'Editor'} to={'editor'} />
</Menu>
</div>
)
Expand Down
52 changes: 52 additions & 0 deletions packages/playground/admin/app/pages/editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Binding, PersistButton } from '../../lib/components/binding'
import { Slots } from '../../lib/components/slots'
import { InputField } from '../../lib/components/form'
import * as React from 'react'
import { EntitySubTree, useField } from '@contember/interface'
import { PlateEditor } from '../../lib/components/richt-text-editor/plate-editor'
import { Button } from '../../lib/components/ui/button'

export default () => <>
<Binding>
<Slots.Actions>
<PersistButton />
</Slots.Actions>
<EntitySubTree entity={'EditorContent(unique=unique)'} setOnCreate={'(unique=unique)'}>
<PlateEditor field="data">

</PlateEditor>
<PlateJson />
<PasteSample />
</EntitySubTree>
</Binding>
</>


const PlateJson = () => {
const data = useField('data')

return (
<pre className="bg-gray-800 text-white p-4">
{JSON.stringify(data.value, null, 2)}
</pre>
)
}


const PasteSample = () => {
const data = useField('data')
return <Button onClick={() => {
data.updateValue({
'children': [
{
'type': 'p',
'children': [
{
'text': 'Lorem ipsum',
},
],
},
],
})
}}>Paste sample data</Button>
}
2 changes: 1 addition & 1 deletion packages/playground/admin/lib/components/form/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const FormLabelWrapperUI = uic('div', {
displayName: 'FormLabelWrapper',
})
export const FormLabelUI = uic(Label, {
baseClass: 'data-[invalid]:text-destructive text-right',
baseClass: 'data-[invalid]:text-destructive text-left',
displayName: 'FormLabel',
})
export const FormContainerUI = uic('div', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AutoformatBlockRule } from '@udecode/plate-autoformat'
import { ELEMENT_CODE_BLOCK, ELEMENT_CODE_LINE } from '@udecode/plate-code-block'
import { getParentNode, isElement, isType, PlateEditor } from '@udecode/plate-common'
import { toggleList, unwrapList } from '@udecode/plate-list'

export const preFormat: AutoformatBlockRule['preFormat'] = editor =>
unwrapList(editor)

export const format = (editor: PlateEditor, customFormatting: any) => {
if (editor.selection) {
const parentEntry = getParentNode(editor, editor.selection)
if (!parentEntry) return
const [node] = parentEntry
if (
isElement(node) &&
!isType(editor, node, ELEMENT_CODE_BLOCK) &&
!isType(editor, node, ELEMENT_CODE_LINE)
) {
customFormatting()
}
}
}

export const formatList = (editor: PlateEditor, elementType: string) => {
format(editor, () =>
toggleList(editor, {
type: elementType,
}),
)
}

export const formatText = (editor: PlateEditor, text: string) => {
format(editor, () => editor.insertText(text))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { AutoformatRule } from '@udecode/plate-autoformat'
import { MARK_BOLD, MARK_ITALIC, MARK_STRIKETHROUGH, MARK_UNDERLINE } from '@udecode/plate-basic-marks'
import { ELEMENT_H1, ELEMENT_H2, ELEMENT_H3, ELEMENT_H4, ELEMENT_H5, ELEMENT_H6 } from '@udecode/plate-heading'
import { preFormat } from './auto-format-utils'

export const autoFormatBlocks: AutoformatRule[] = [
{
mode: 'block',
type: ELEMENT_H1,
match: '# ',
preFormat,
},
{
mode: 'block',
type: ELEMENT_H2,
match: '## ',
preFormat,
},
{
mode: 'block',
type: ELEMENT_H3,
match: '### ',
preFormat,
},
{
mode: 'block',
type: ELEMENT_H4,
match: '#### ',
preFormat,
},
{
mode: 'block',
type: ELEMENT_H5,
match: '##### ',
preFormat,
},
{
mode: 'block',
type: ELEMENT_H6,
match: '###### ',
preFormat,
},
]

export const autoFormatMarks: AutoformatRule[] = [
{
mode: 'mark',
type: [MARK_BOLD, MARK_ITALIC],
match: '***',
},
{
mode: 'mark',
type: [MARK_UNDERLINE, MARK_ITALIC],
match: '__*',
},
{
mode: 'mark',
type: [MARK_UNDERLINE, MARK_BOLD],
match: '__**',
},
{
mode: 'mark',
type: [MARK_UNDERLINE, MARK_BOLD, MARK_ITALIC],
match: '___***',
},
{
mode: 'mark',
type: MARK_BOLD,
match: '**',
},
{
mode: 'mark',
type: MARK_UNDERLINE,
match: '__',
},
{
mode: 'mark',
type: MARK_ITALIC,
match: '*',
},
{
mode: 'mark',
type: MARK_ITALIC,
match: '_',
},
{
mode: 'mark',
type: MARK_STRIKETHROUGH,
match: '~~',
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { findEventRange, PlatePlugin } from '@udecode/plate-common'
import { cursorStore } from '../plate-ui/cursor-overlay'

export const KEY_DRAG_OVER_CURSOR = 'dragOverCursor'

export const dragOverCursorPlugin: PlatePlugin = {
key: KEY_DRAG_OVER_CURSOR,
handlers: {
onDragOver: editor => event => {
if (editor.isDragging) return

const range = findEventRange(editor, event)
if (!range) return

cursorStore.set.cursors({
drag: {
key: 'drag',
data: {
style: {
backgroundColor: 'hsl(222.2 47.4% 11.2%)',
width: 3,
},
},
selection: range,
},
})
},
onDragLeave: () => () => {
cursorStore.set.cursors({})
},
onDragEnd: () => () => {
cursorStore.set.cursors({})
},
onDrop: () => () => {
cursorStore.set.cursors({})
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useCallback, useState } from 'react'

export const useOpenState = () => {
const [open, setOpen] = useState(false)

const onOpenChange = useCallback(
(_value = !open) => {
setOpen(_value)
},
[open],
)

return {
open,
onOpenChange,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Component, Field, SugarableRelativeSingleField, useField } from '@contember/interface'
import { cn } from '@udecode/cn'
import { normalizeInitialValue, Plate, replaceNodeChildren, useEditorRef } from '@udecode/plate-common'
import { useEffect, useRef } from 'react'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { plugins } from './plate-plugins'
import { Editor } from './plate-ui/editor'
import { FixedToolbar } from './plate-ui/fixed-toolbar'
import { FixedToolbarButtons } from './plate-ui/fixed-toolbar-buttons'
import { FloatingToolbar } from './plate-ui/floating-toolbar'
import { FloatingToolbarButtons } from './plate-ui/floating-toolbar-buttons'
import { TooltipProvider } from './plate-ui/tooltip'
import { isJsonContent, isJsonObject } from './utils'


export interface PlateEditorProps {
field: string | SugarableRelativeSingleField
}

export const PlateEditor = Component(
({ field }: PlateEditorProps) => {
const containerRef = useRef(null)
const contentField = useField(field)

const handleEditorOnChange = (value: any) => {
const contentJson = isJsonObject(value) ? { children: value } : null

if (contentJson && contentField.value && (isJsonContent(contentField.value) && contentField.value.children === value)) {
return
}

contentField.updateValue(contentJson)
}

return (
<DndProvider backend={HTML5Backend}>
<TooltipProvider>
<Plate
plugins={plugins}
value={isJsonContent(contentField?.value) ? contentField?.value?.children : undefined}
onChange={handleEditorOnChange}
normalizeInitialValue
>
<PlateContentSync />
<div
ref={containerRef}
className={cn(
'relative',
'border rounded-md',
'[&_.slate-start-area-left]:!w-[64px] [&_.slate-start-area-right]:!w-[64px] [&_.slate-start-area-top]:!h-4',
)}
>

<div className={'relative'}>
<FixedToolbar>
<FixedToolbarButtons/>
</FixedToolbar>

<Editor
className="px-[96px] py-16"
autoFocus
focusRing={false}
variant="ghost"
size="md"
/>

<FloatingToolbar>
<FloatingToolbarButtons/>
</FloatingToolbar>
</div>
</div>
</Plate>
</TooltipProvider>
</DndProvider>
)
},
({ field }) => (
<>
<Field field={field}/>
</>
),
'PlateEditor',
)

const PlateContentSync = () => {
const field = useField('data')
const fieldAccessor = field.getAccessor
const fieldValue = field.value
const editor = useEditorRef()

useEffect(() => {
if (fieldValue !== fieldAccessor().value) {
return
}
const currValue = isJsonContent(fieldValue) && fieldValue.children.length > 0
? fieldValue.children
: editor.childrenFactory()
if (currValue === editor.children) {
return
}
const normalizedValue = normalizeInitialValue(editor, currValue) ?? currValue
replaceNodeChildren(editor, {
at: [],
nodes: normalizedValue,
} as any)
}, [editor, fieldAccessor, fieldValue])

return null
}
Loading

0 comments on commit 13c1150

Please sign in to comment.