mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-01 11:12:42 +02:00
Next (#3093)
* remove some key usage from core, refactor Operations.apply * undeprecate some methods * convert more key usage to paths * update deprecations * convert selection commands to use all paths * refactor word boundary selection logic * convert many at-range commands to use paths * convert wrapBlock and wrapInline to not use keys * cleanup * remove chainability from editor * simplify commands, queries and middleware * convert deleteAtRange * remove key usage from schema, deprecate *ByKey methods * migrate *ByKey tests, remove index from *ByPath signatures * rename at-current-range tests * deprecate mode key usage, migrate more tests away from keys * deprecate range and point methods which rely on keys to work * refactor insertBlock, without fixing warnings * add pathRef/pointRef, fix insertBlock/Inline deprecations, work on insertFragment * refactor insertFragment * get rich-text example rendering * fix lint * refactor query files, fix more tests * remove unused queries, refactor others * deprecate splitDescendantsByPath * merge master * add typescript, convert slate, slate-hyperscript, slate-plain-serializer * add Point, Path, Range, Annotation tests * add Annotation, Change, Element, Fragment, Mark, Range, Selection, Value interfaces tests * add Operation and Text tests * add Node tests * get operations and normalization tests working for slate * get *AtPath command tests passing * rename *AtPath command tests * rename * get *AtPoint tests working * rename * rename * add value queries tests * add element, mark and path queries tests * convert most on-selection tests * convert on-selection commands * rename * get addMarks and delete commands working * rename * rename * rename * refactor value.positions(), work on delete tests * progress on delete tests * more delete work * finish delete tests * start converting to at-based commands * restructure query tests * restructure operations tests * more work converting to multi-purpose commands * lots of progress on converting to at-based commands * add unwrapNodes * remove setValue * more progress * refactor node commands to use consistent matching logic * cleanup, get non-fragment commands passing * remove annotations and isAtomic * rename surround/pluck to cover/uncover * add location concept, change at-path to from-path for iterables * refactor batches * add location-based queries * refactor hanging logic * more location query work * renaming * use getMatch more * add split to wrap/unwrap * flip levels/ancestors ordering * switch splitNodes to use levels * change split to always:false by default * fix tests * add more queries tests * fixing more delete logic * add more splitNodes tests * get rest of delete tests passing * fix location-based logic in some commands * cleanup * get previous packages tests passing again * add slate-history package * start slate-schema work * start of react working * rendering fixes * get rich and plain text examples working * get image example working with hooks and dropping * refactor onDrop to be internal * inline more event handlers * refactor lots of event-related logic * change rendering to use render props * delete unused stuff * cleanup dom utils * remove unused deps * remove unnecessary packages, add placeholder * remove slate-react-placeholder package * remove unused dep * remove unnecessary tests, fix readonly example * convert checklists example * switch to next from webpack * get link example working * convert more examples * preserve keys, memoized leafs/texts, fix node lookup * fix to always useLayoutEffect for ordering * fix annotations to be maps, memoize elements * remove Change interface * remove String interface * rename Node.entries to Node.nodes * remove unnecessary value queries * default to selection when iterating, cleanup * remove unused files * update scroll into view logic * fix undoing, remove constructor types * dont sync selection while composing * add workflows * remove unused deps * convert mentions example * tweaks * convert remaining examples * rename h to jsx, update schema * fix schema tests * fix slate-schema logic and tests * really fix slate-schema and forced-layout example * get start of insertFragment tests working * remove Fragment interface * remove debugger * get all non-skipped tests passing * cleanup deps * run prettier * configure eslint for typescript * more eslint fixes... * more passing * update some docs * fix examples * port windows undo hotkey change * fix deps, add basic firefox support * add event overriding, update walkthroughs * add commands, remove classes, cleanup examples * cleanup rollup config * update tests * rename queries tests * update other tests * update walkthroughs * cleanup interface exports * cleanup, change mark transforms to require location * undo mark transform change * more * fix tests * fix example * update walkthroughs * update docs * update docs * remove annotations * remove value, move selection and children to editor * add migrating doc * fix lint * fix tests * fix DOM types aliasing * add next export * update deps, fix prod build * fix prod build * update scripts * update docs and changelogs * update workflow and pull request template
This commit is contained in:
195
docs/walkthroughs/03-defining-custom-elements.md
Normal file
195
docs/walkthroughs/03-defining-custom-elements.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Defining Custom Block Nodes
|
||||
|
||||
In our previous example, we started with a paragraph, but we never actually told Slate anything about the `paragraph` block type. We just let it use its internal default renderer, which uses a plain old `<div>`.
|
||||
|
||||
But that's not all you can do. Slate lets you define any type of custom blocks you want, like block quotes, code blocks, list items, etc.
|
||||
|
||||
We'll show you how. Let's start with our app from earlier:
|
||||
|
||||
```js
|
||||
const App = () => {
|
||||
const editor = useMemo(() => withReact(createEditor()), [])
|
||||
return (
|
||||
<Slate editor={editor} defaultValue={defaultValue}>
|
||||
<Editable
|
||||
onKeyDown={event => {
|
||||
if (event.key === '&') {
|
||||
event.preventDefault()
|
||||
editor.exec({ type: 'insert_text', text: 'and' })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Slate>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Now let's add "code blocks" to our editor.
|
||||
|
||||
The problem is, code blocks won't just be rendered as a plain paragraph, they'll need to be rendered differently. To make that happen, we need to define a "renderer" for `code` element nodes.
|
||||
|
||||
Element renderers are just simple React components, like so:
|
||||
|
||||
```js
|
||||
// Define a React component renderer for our code blocks.
|
||||
const CodeElement = props => {
|
||||
return (
|
||||
<pre {...props.attributes}>
|
||||
<code>{props.children}</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Easy enough.
|
||||
|
||||
See the `props.attributes` reference? Slate passes attributes that should be rendered on the top-most element of your blocks, so that you don't have to build them up yourself. You **must** mix the attributes into your component.
|
||||
|
||||
And see that `props.children` reference? Slate will automatically render all of the children of a block for you, and then pass them to you just like any other React component would, via `props.children`. That way you don't have to muck around with rendering the proper text nodes or anything like that. You **must** render the children as the lowest leaf in your component.
|
||||
|
||||
And here's a component for the "default" elements:
|
||||
|
||||
```js
|
||||
const DefaultElement = props => {
|
||||
return <p {...props.attributes}>{props.children}</p>
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's add that renderer to our `Editor`:
|
||||
|
||||
```js
|
||||
const App = () => {
|
||||
const editor = useMemo(() => withReact(createEditor()), [])
|
||||
|
||||
// Define a rendering function based on the element passed to `props`. We use
|
||||
// `useCallback` here to memoize the function for subsequent renders.
|
||||
const renderElement = useCallback(props => {
|
||||
switch (props.element.type) {
|
||||
case 'code':
|
||||
return <CodeElement {...props} />
|
||||
default:
|
||||
return <DefaultElement {...props} />
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Slate editor={editor} defaultValue={defaultValue}>
|
||||
<Editable
|
||||
// Pass in the `renderElement` function.
|
||||
renderElement={renderElement}
|
||||
onKeyDown={event => {
|
||||
if (event.key === '&') {
|
||||
event.preventDefault()
|
||||
editor.exec({ type: 'insert_text', text: 'and' })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Slate>
|
||||
)
|
||||
}
|
||||
|
||||
const CodeElement = props => {
|
||||
return (
|
||||
<pre {...props.attributes}>
|
||||
<code>{props.children}</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
const DefaultElement = props => {
|
||||
return <p {...props.attributes}>{props.children}</p>
|
||||
}
|
||||
```
|
||||
|
||||
Okay, but now we'll need a way for the user to actually turn a block into a code block. So let's change our `onKeyDown` function to add a `Ctrl-\`` shortcut that does just that:
|
||||
|
||||
```js
|
||||
// Import the `Editor` helpers from Slate.
|
||||
import { Editor } from 'slate'
|
||||
|
||||
const App = () => {
|
||||
const editor = useMemo(() => withReact(createEditor()), [])
|
||||
const renderElement = useCallback(props => {
|
||||
switch (props.element.type) {
|
||||
case 'code':
|
||||
return <CodeElement {...props} />
|
||||
default:
|
||||
return <DefaultElement {...props} />
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Slate editor={editor} defaultValue={defaultValue}>
|
||||
<Editable
|
||||
renderElement={renderElement}
|
||||
onKeyDown={event => {
|
||||
if (event.key === '`' && event.ctrlKey) {
|
||||
// Prevent the "`" from being inserted by default.
|
||||
event.preventDefault()
|
||||
// Otherwise, set the currently selected blocks type to "code".
|
||||
Editor.setNodes(editor, { type: 'code' }, { match: 'block' })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Slate>
|
||||
)
|
||||
}
|
||||
|
||||
const CodeElement = props => {
|
||||
return (
|
||||
<pre {...props.attributes}>
|
||||
<code>{props.children}</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
const DefaultElement = props => {
|
||||
return <p {...props.attributes}>{props.children}</p>
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you press `Ctrl-\`` the block your cursor is in should turn into a code block! Magic!
|
||||
|
||||
But we forgot one thing. When you hit `Ctrl-\`` again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:
|
||||
|
||||
```js
|
||||
const App = () => {
|
||||
const [value, setValue] = useState(initialValue)
|
||||
const editor = useMemo(() => withReact(createEditor()), [])
|
||||
const renderElement = useCallback(props => {
|
||||
switch (props.element.type) {
|
||||
case 'code':
|
||||
return <CodeElement {...props} />
|
||||
default:
|
||||
return <DefaultElement {...props} />
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Slate editor={editor} defaultValue={defaultValue}>
|
||||
<Editable
|
||||
renderElement={renderElement}
|
||||
onKeyDown={event => {
|
||||
if (event.key === '`' && event.ctrlKey) {
|
||||
event.preventDefault()
|
||||
// Determine whether any of the currently selected blocks are code blocks.
|
||||
const { selection } = editor
|
||||
const isCode = selection
|
||||
? Editor.match(editor, selection, { type: 'code' })
|
||||
: false
|
||||
|
||||
// Toggle the block type depending on `isCode`.
|
||||
Editor.setNodes(
|
||||
editor,
|
||||
{ type: isCode ? 'paragraph' : 'code' },
|
||||
{ match: 'block' }
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Slate>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
And there you have it! If you press `Ctrl-\`` while inside a code block, it should turn back into a paragraph!
|
Reference in New Issue
Block a user