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>
241 lines
6.2 KiB
TypeScript
241 lines
6.2 KiB
TypeScript
import React, { useCallback, useMemo } from 'react'
|
|
import {
|
|
createEditor,
|
|
Descendant,
|
|
Editor,
|
|
Element as SlateElement,
|
|
Node as SlateNode,
|
|
Point,
|
|
Range,
|
|
Transforms,
|
|
} from 'slate'
|
|
import { withHistory } from 'slate-history'
|
|
import { Editable, ReactEditor, Slate, withReact } from 'slate-react'
|
|
import { BulletedListElement } from './custom-types.d'
|
|
|
|
const SHORTCUTS = {
|
|
'*': 'list-item',
|
|
'-': 'list-item',
|
|
'+': 'list-item',
|
|
'>': 'block-quote',
|
|
'#': 'heading-one',
|
|
'##': 'heading-two',
|
|
'###': 'heading-three',
|
|
'####': 'heading-four',
|
|
'#####': 'heading-five',
|
|
'######': 'heading-six',
|
|
}
|
|
|
|
const MarkdownShortcutsExample = () => {
|
|
const renderElement = useCallback(props => <Element {...props} />, [])
|
|
const editor = useMemo(
|
|
() => withShortcuts(withReact(withHistory(createEditor()))),
|
|
[]
|
|
)
|
|
|
|
const handleDOMBeforeInput = useCallback(
|
|
(e: InputEvent) => {
|
|
queueMicrotask(() => {
|
|
const pendingDiffs = ReactEditor.androidPendingDiffs(editor)
|
|
|
|
const scheduleFlush = pendingDiffs?.some(({ diff, path }) => {
|
|
if (!diff.text.endsWith(' ')) {
|
|
return false
|
|
}
|
|
|
|
const { text } = SlateNode.leaf(editor, path)
|
|
const beforeText = text.slice(0, diff.start) + diff.text.slice(0, -1)
|
|
if (!(beforeText in SHORTCUTS)) {
|
|
return
|
|
}
|
|
|
|
const blockEntry = Editor.above(editor, {
|
|
at: path,
|
|
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
|
|
})
|
|
if (!blockEntry) {
|
|
return false
|
|
}
|
|
|
|
const [, blockPath] = blockEntry
|
|
return Editor.isStart(editor, Editor.start(editor, path), blockPath)
|
|
})
|
|
|
|
if (scheduleFlush) {
|
|
ReactEditor.androidScheduleFlush(editor)
|
|
}
|
|
})
|
|
},
|
|
[editor]
|
|
)
|
|
|
|
return (
|
|
<Slate editor={editor} initialValue={initialValue}>
|
|
<Editable
|
|
onDOMBeforeInput={handleDOMBeforeInput}
|
|
renderElement={renderElement}
|
|
placeholder="Write some markdown..."
|
|
spellCheck
|
|
autoFocus
|
|
/>
|
|
</Slate>
|
|
)
|
|
}
|
|
|
|
const withShortcuts = editor => {
|
|
const { deleteBackward, insertText } = editor
|
|
|
|
editor.insertText = text => {
|
|
const { selection } = editor
|
|
|
|
if (text.endsWith(' ') && selection && Range.isCollapsed(selection)) {
|
|
const { anchor } = selection
|
|
const block = Editor.above(editor, {
|
|
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
|
|
})
|
|
const path = block ? block[1] : []
|
|
const start = Editor.start(editor, path)
|
|
const range = { anchor, focus: start }
|
|
const beforeText = Editor.string(editor, range) + text.slice(0, -1)
|
|
const type = SHORTCUTS[beforeText]
|
|
|
|
if (type) {
|
|
Transforms.select(editor, range)
|
|
|
|
if (!Range.isCollapsed(range)) {
|
|
Transforms.delete(editor)
|
|
}
|
|
|
|
const newProperties: Partial<SlateElement> = {
|
|
type,
|
|
}
|
|
Transforms.setNodes<SlateElement>(editor, newProperties, {
|
|
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
|
|
})
|
|
|
|
if (type === 'list-item') {
|
|
const list: BulletedListElement = {
|
|
type: 'bulleted-list',
|
|
children: [],
|
|
}
|
|
Transforms.wrapNodes(editor, list, {
|
|
match: n =>
|
|
!Editor.isEditor(n) &&
|
|
SlateElement.isElement(n) &&
|
|
n.type === 'list-item',
|
|
})
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
insertText(text)
|
|
}
|
|
|
|
editor.deleteBackward = (...args) => {
|
|
const { selection } = editor
|
|
|
|
if (selection && Range.isCollapsed(selection)) {
|
|
const match = Editor.above(editor, {
|
|
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
|
|
})
|
|
|
|
if (match) {
|
|
const [block, path] = match
|
|
const start = Editor.start(editor, path)
|
|
|
|
if (
|
|
!Editor.isEditor(block) &&
|
|
SlateElement.isElement(block) &&
|
|
block.type !== 'paragraph' &&
|
|
Point.equals(selection.anchor, start)
|
|
) {
|
|
const newProperties: Partial<SlateElement> = {
|
|
type: 'paragraph',
|
|
}
|
|
Transforms.setNodes(editor, newProperties)
|
|
|
|
if (block.type === 'list-item') {
|
|
Transforms.unwrapNodes(editor, {
|
|
match: n =>
|
|
!Editor.isEditor(n) &&
|
|
SlateElement.isElement(n) &&
|
|
n.type === 'bulleted-list',
|
|
split: true,
|
|
})
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
deleteBackward(...args)
|
|
}
|
|
}
|
|
|
|
return editor
|
|
}
|
|
|
|
const Element = ({ attributes, children, element }) => {
|
|
switch (element.type) {
|
|
case 'block-quote':
|
|
return <blockquote {...attributes}>{children}</blockquote>
|
|
case 'bulleted-list':
|
|
return <ul {...attributes}>{children}</ul>
|
|
case 'heading-one':
|
|
return <h1 {...attributes}>{children}</h1>
|
|
case 'heading-two':
|
|
return <h2 {...attributes}>{children}</h2>
|
|
case 'heading-three':
|
|
return <h3 {...attributes}>{children}</h3>
|
|
case 'heading-four':
|
|
return <h4 {...attributes}>{children}</h4>
|
|
case 'heading-five':
|
|
return <h5 {...attributes}>{children}</h5>
|
|
case 'heading-six':
|
|
return <h6 {...attributes}>{children}</h6>
|
|
case 'list-item':
|
|
return <li {...attributes}>{children}</li>
|
|
default:
|
|
return <p {...attributes}>{children}</p>
|
|
}
|
|
}
|
|
|
|
const initialValue: Descendant[] = [
|
|
{
|
|
type: 'paragraph',
|
|
children: [
|
|
{
|
|
text: 'The editor gives you full control over the logic you can add. For example, it\'s fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with "> " you get a blockquote that looks like this:',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'block-quote',
|
|
children: [{ text: 'A wise quote.' }],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
children: [
|
|
{
|
|
text: 'Order when you start a line with "## " you get a level-two heading, like this:',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: 'heading-two',
|
|
children: [{ text: 'Try it out!' }],
|
|
},
|
|
{
|
|
type: 'paragraph',
|
|
children: [
|
|
{
|
|
text: 'Try it out for yourself! Try starting a new line with ">", "-", or "#"s.',
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
export default MarkdownShortcutsExample
|