Add the ability to move blocks up and down within a document field #8130
Replies: 2 comments 4 replies
-
For anyone interested in adding this feature, we found the (Edit note: This only works for Chromeful components. It won't work with chromeless at the moment as the toolbar for them is loaded outside the Slate component) Video: Screen.Recording.2022-11-29.at.7.06.10.pm.movFor those who wish to replicate, here's a CustomToolbar component I created in my spike (based on the default Toolbar component). We've gotta resolve some type issues and haven't tested thoroughly but it appears to work. /** @jsxRuntime classic */
/** @jsx jsx */
import { Fragment, useCallback, useRef } from 'react'
import { NotEditable } from '@keystone-6/fields-document/component-blocks'
import {
ToolbarGroup,
ToolbarButton,
ToolbarSeparator,
} from '@keystone-6/fields-document/primitives'
import { jsx, useTheme, Box } from '@keystone-ui/core'
import { Tooltip } from '@keystone-ui/tooltip'
import { useSlateStatic, ReactEditor } from 'slate-react'
import { ArrowUpIcon, ArrowDownIcon, Trash2Icon } from '@keystone-ui/icons'
import { Transforms, Editor } from 'slate'
export function CustomToolbar({
onShowEditMode,
onRemove,
isValid,
}: {
onShowEditMode(): void
onRemove(): void
props: any
isValid: boolean
}) {
const theme = useTheme()
const editor = useSlateStatic()
const currentElement = useRef<HTMLDivElement>(null)
const reorderBlock = useCallback(
(editor: Editor, direction: 'up' | 'down') => {
const currentPath = ReactEditor.findPath(
// @ts-ignore
editor,
// @ts-ignore
ReactEditor.toSlateNode(editor, currentElement.current)
)
if (!currentPath || !Array.isArray(currentPath) || currentPath.length < 1) return
const newPath = direction === 'up' ? [Math.max(0, currentPath[0] - 1)] : [currentPath[0] + 1]
Transforms.moveNodes(editor, { at: [currentPath[0]], to: newPath })
},
[editor, currentElement.current]
)
return (
<Box ref={currentElement}>
<ToolbarGroup as={NotEditable} marginTop="small">
<ToolbarButton
onClick={() => {
onShowEditMode()
}}
>
Edit
</ToolbarButton>
<ToolbarSeparator />
<Tooltip content="Remove" weight="subtle">
{(attrs) => (
<ToolbarButton
variant="destructive"
onClick={() => {
onRemove()
}}
{...attrs}
>
<Trash2Icon size="small" />
</ToolbarButton>
)}
</Tooltip>
<ToolbarSeparator />
<Tooltip content="Move up in the document" weight="subtle">
{(attrs) => (
<ToolbarButton variant="default" onClick={() => reorderBlock(editor, 'up')} {...attrs}>
<ArrowUpIcon size="small" />
</ToolbarButton>
)}
</Tooltip>
<Tooltip content="Move down in the document" weight="subtle">
{(attrs) => (
<ToolbarButton
variant="default"
onClick={() => reorderBlock(editor, 'down')}
{...attrs}
>
<ArrowDownIcon size="small" />
</ToolbarButton>
)}
</Tooltip>
{!isValid && (
<Fragment>
<ToolbarSeparator />
<span
css={{
color: theme.palette.red500,
display: 'flex',
alignItems: 'center',
paddingLeft: theme.spacing.small,
}}
>
Please edit the form, there are invalid fields.
</span>
</Fragment>
)}
</ToolbarGroup>
</Box>
)
} You then need to specify the following in your component blocks along side your
|
Beta Was this translation helpful? Give feedback.
-
This would be amazing. I am currently building a course platform and the content is fully based on custom components only. Having this feature build in would be amazing. |
Beta Was this translation helpful? Give feedback.
-
When there's a lot of custom components in a document field, it becomes a little hard to manage and move things around. You can only copy/cut/paste components if there's text before and after to allow them to be selected fully (this could be another feature, allowing a selected block to be cut/copied etc). So having a list of custom component blocks becomes impossible to re-order without deleting and re-adding and configuring the components.
Editor.js and a few others have the ability to quickly and easily move blocks up and down in the document. I can't see any defaults or plugins for slate.js but it looks like it should be easy with the
Transforms.moveNodes
if you know the currentpath
to be moved? Perhaps this could be added into the document field too?Editor.js demo (from their homepage)
I tried to create my own buttons in a custom component but couldn't quickly find a way to work out the path of the component being clicked (i.e. not necessarily where the current selection is). But I'd imagine keystone would have access this info.
Perhaps these buttons could be added to the Chromeful Element like this?
And Chromessless here?
Edit: The ability to drag and drop could be an alternative/stretch idea.
Beta Was this translation helpful? Give feedback.
All reactions