1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-22 08:02:25 +01:00
slate/site/examples/markdown-shortcuts.tsx
Dylan Schiemann c4c14882ed
Update dependencies to React 18, Node 20, TS 5.2, etc. (#5528)
* 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>
2023-10-20 08:34:24 -07:00

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