mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-02-22 08:02:25 +01:00
* incremental upgrade to React 18, TS 4.9, etc. * update yarn config * fix build * minor cleanup in type definitions * incremental updates for TS 5.0 * fix build * upgrade to typescript 5.2 * update dependencies * fix lint issues * update to latest Playwright version * update changesets dep * update emotion/css * incremental dependency updates * more small dependency updates * upgrade prettier and eslint * fix lint issues * update dependencies rollup * fix @types/node resolution to restore linting * update tiny-invariant dependency * update dependencies * update dependencies lerna * upgrade react-router-dom * update @types/react and @types/node * update babel dependencies * udpate simple-git-hooks * update @types/node resolution * update lint-staged * remove cypress from dependency list * update @types/node to support Node 20 * update workflows to Node 20 * set resolutions for @types/react * downgrade @types/react to 18.2.28 * update mocha * update rimraf * update @types/js-dom * remove .lintstagedrc.js * upgrade next to latest * v0.61.4 * update lerna * update faker and rollup * update immer * fix yarn clean command * attempt to fix integration tests * attempt to stabilize integration tests * wip fix integration tests * skip unstable integration test * Add changeset --------- Co-authored-by: Dalibor Tosic <dalibortosic00@gmail.com> Co-authored-by: Nikola <nikolabijelic14@gmail.com>
272 lines
6.6 KiB
TypeScript
272 lines
6.6 KiB
TypeScript
import React, { useCallback, useMemo } from 'react'
|
|
import isHotkey from 'is-hotkey'
|
|
import { Editable, withReact, useSlate, Slate } from 'slate-react'
|
|
import {
|
|
Editor,
|
|
Transforms,
|
|
createEditor,
|
|
Descendant,
|
|
Element as SlateElement,
|
|
} from 'slate'
|
|
import { withHistory } from 'slate-history'
|
|
|
|
import { Button, Icon, Toolbar } from '../components'
|
|
|
|
const HOTKEYS = {
|
|
'mod+b': 'bold',
|
|
'mod+i': 'italic',
|
|
'mod+u': 'underline',
|
|
'mod+`': 'code',
|
|
}
|
|
|
|
const LIST_TYPES = ['numbered-list', 'bulleted-list']
|
|
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify']
|
|
|
|
const RichTextExample = () => {
|
|
const renderElement = useCallback(props => <Element {...props} />, [])
|
|
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
|
|
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
|
|
|
|
return (
|
|
<Slate editor={editor} initialValue={initialValue}>
|
|
<Toolbar>
|
|
<MarkButton format="bold" icon="format_bold" />
|
|
<MarkButton format="italic" icon="format_italic" />
|
|
<MarkButton format="underline" icon="format_underlined" />
|
|
<MarkButton format="code" icon="code" />
|
|
<BlockButton format="heading-one" icon="looks_one" />
|
|
<BlockButton format="heading-two" icon="looks_two" />
|
|
<BlockButton format="block-quote" icon="format_quote" />
|
|
<BlockButton format="numbered-list" icon="format_list_numbered" />
|
|
<BlockButton format="bulleted-list" icon="format_list_bulleted" />
|
|
<BlockButton format="left" icon="format_align_left" />
|
|
<BlockButton format="center" icon="format_align_center" />
|
|
<BlockButton format="right" icon="format_align_right" />
|
|
<BlockButton format="justify" icon="format_align_justify" />
|
|
</Toolbar>
|
|
<Editable
|
|
renderElement={renderElement}
|
|
renderLeaf={renderLeaf}
|
|
placeholder="Enter some rich text…"
|
|
spellCheck
|
|
autoFocus
|
|
onKeyDown={event => {
|
|
for (const hotkey in HOTKEYS) {
|
|
if (isHotkey(hotkey, event as any)) {
|
|
event.preventDefault()
|
|
const mark = HOTKEYS[hotkey]
|
|
toggleMark(editor, mark)
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
</Slate>
|
|
)
|
|
}
|
|
|
|
const toggleBlock = (editor, format) => {
|
|
const isActive = isBlockActive(
|
|
editor,
|
|
format,
|
|
TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
|
|
)
|
|
const isList = LIST_TYPES.includes(format)
|
|
|
|
Transforms.unwrapNodes(editor, {
|
|
match: n =>
|
|
!Editor.isEditor(n) &&
|
|
SlateElement.isElement(n) &&
|
|
LIST_TYPES.includes(n.type) &&
|
|
!TEXT_ALIGN_TYPES.includes(format),
|
|
split: true,
|
|
})
|
|
let newProperties: Partial<SlateElement>
|
|
if (TEXT_ALIGN_TYPES.includes(format)) {
|
|
newProperties = {
|
|
align: isActive ? undefined : format,
|
|
}
|
|
} else {
|
|
newProperties = {
|
|
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
|
|
}
|
|
}
|
|
Transforms.setNodes<SlateElement>(editor, newProperties)
|
|
|
|
if (!isActive && isList) {
|
|
const block = { type: format, children: [] }
|
|
Transforms.wrapNodes(editor, block)
|
|
}
|
|
}
|
|
|
|
const toggleMark = (editor, format) => {
|
|
const isActive = isMarkActive(editor, format)
|
|
|
|
if (isActive) {
|
|
Editor.removeMark(editor, format)
|
|
} else {
|
|
Editor.addMark(editor, format, true)
|
|
}
|
|
}
|
|
|
|
const isBlockActive = (editor, format, blockType = 'type') => {
|
|
const { selection } = editor
|
|
if (!selection) return false
|
|
|
|
const [match] = Array.from(
|
|
Editor.nodes(editor, {
|
|
at: Editor.unhangRange(editor, selection),
|
|
match: n =>
|
|
!Editor.isEditor(n) &&
|
|
SlateElement.isElement(n) &&
|
|
n[blockType] === format,
|
|
})
|
|
)
|
|
|
|
return !!match
|
|
}
|
|
|
|
const isMarkActive = (editor, format) => {
|
|
const marks = Editor.marks(editor)
|
|
return marks ? marks[format] === true : false
|
|
}
|
|
|
|
const Element = ({ attributes, children, element }) => {
|
|
const style = { textAlign: element.align }
|
|
switch (element.type) {
|
|
case 'block-quote':
|
|
return (
|
|
<blockquote style={style} {...attributes}>
|
|
{children}
|
|
</blockquote>
|
|
)
|
|
case 'bulleted-list':
|
|
return (
|
|
<ul style={style} {...attributes}>
|
|
{children}
|
|
</ul>
|
|
)
|
|
case 'heading-one':
|
|
return (
|
|
<h1 style={style} {...attributes}>
|
|
{children}
|
|
</h1>
|
|
)
|
|
case 'heading-two':
|
|
return (
|
|
<h2 style={style} {...attributes}>
|
|
{children}
|
|
</h2>
|
|
)
|
|
case 'list-item':
|
|
return (
|
|
<li style={style} {...attributes}>
|
|
{children}
|
|
</li>
|
|
)
|
|
case 'numbered-list':
|
|
return (
|
|
<ol style={style} {...attributes}>
|
|
{children}
|
|
</ol>
|
|
)
|
|
default:
|
|
return (
|
|
<p style={style} {...attributes}>
|
|
{children}
|
|
</p>
|
|
)
|
|
}
|
|
}
|
|
|
|
const Leaf = ({ attributes, children, leaf }) => {
|
|
if (leaf.bold) {
|
|
children = <strong>{children}</strong>
|
|
}
|
|
|
|
if (leaf.code) {
|
|
children = <code>{children}</code>
|
|
}
|
|
|
|
if (leaf.italic) {
|
|
children = <em>{children}</em>
|
|
}
|
|
|
|
if (leaf.underline) {
|
|
children = <u>{children}</u>
|
|
}
|
|
|
|
return <span {...attributes}>{children}</span>
|
|
}
|
|
|
|
const BlockButton = ({ format, icon }) => {
|
|
const editor = useSlate()
|
|
return (
|
|
<Button
|
|
active={isBlockActive(
|
|
editor,
|
|
format,
|
|
TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
|
|
)}
|
|
onMouseDown={event => {
|
|
event.preventDefault()
|
|
toggleBlock(editor, format)
|
|
}}
|
|
>
|
|
<Icon>{icon}</Icon>
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
const MarkButton = ({ format, icon }) => {
|
|
const editor = useSlate()
|
|
return (
|
|
<Button
|
|
active={isMarkActive(editor, format)}
|
|
onMouseDown={event => {
|
|
event.preventDefault()
|
|
toggleMark(editor, format)
|
|
}}
|
|
>
|
|
<Icon>{icon}</Icon>
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
const initialValue: Descendant[] = [
|
|
{
|
|
type: 'paragraph',
|
|
children: [
|
|
{ text: 'This is editable ' },
|
|
{ text: 'rich', bold: true },
|
|
{ text: ' text, ' },
|
|
{ text: 'much', italic: true },
|
|
{ text: ' better than a ' },
|
|
{ text: '<textarea>', code: true },
|
|
{ text: '!' },
|
|
],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
children: [
|
|
{
|
|
text: "Since it's rich text, you can do things like turn a selection of text ",
|
|
},
|
|
{ text: 'bold', bold: true },
|
|
{
|
|
text: ', or add a semantically rendered block quote in the middle of the page, like this:',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'block-quote',
|
|
children: [{ text: 'A wise quote.' }],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
align: 'center',
|
|
children: [{ text: 'Try it out for yourself!' }],
|
|
},
|
|
]
|
|
|
|
export default RichTextExample
|