@portabletext/plugin-markdown-shortcuts
Version:
Add helpful Markdown shortcuts to the editor
134 lines (118 loc) • 4.17 kB
Markdown
# `@portabletext/plugin-markdown-shortcuts`
> Add helpful Markdown shortcuts to the editor
## Installation
```sh
npm install @portabletext/plugin-markdown-shortcuts
```
## Usage
Import the `MarkdownShortcutsPlugin` React component and place it inside the `EditorProvider` and tell it about your schema:
```tsx
import {
defineSchema,
EditorProvider,
PortableTextEditable,
} from '@portabletext/editor'
import {MarkdownShortcutsPlugin} from '@portabletext/plugin-markdown-shortcuts'
const schemaDefinition = defineSchema({
blockObjects: [{name: 'break'}],
annotations: [{name: 'link', fields: [{name: 'href', type: 'string'}]}],
decorators: [
{name: 'em'},
{name: 'code'},
{name: 'strike-through'},
{name: 'strong'},
],
lists: [{name: 'bullet'}, {name: 'number'}],
styles: [
{name: 'normal'},
{name: 'h1'},
{name: 'h2'},
{name: 'h3'},
{name: 'h4'},
{name: 'h5'},
{name: 'h6'},
{name: 'blockquote'},
],
})
function App() {
return (
<EditorProvider
initialConfig={{
schemaDefinition,
}}
>
<PortableTextEditable />
<MarkdownShortcutsPlugin
boldDecorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'strong')?.name
}
codeDecorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'code')?.name
}
italicDecorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'em')?.name
}
strikeThroughDecorator={({context}) =>
context.schema.decorators.find((d) => d.name === 'strike-through')
?.name
}
defaultStyle={({context}) =>
context.schema.styles.find((s) => s.name === 'normal')?.name
}
headingStyle={({context, props}) =>
context.schema.styles.find((s) => s.name === `h${props.level}`)?.name
}
blockquoteStyle={({context}) =>
context.schema.styles.find((s) => s.name === 'blockquote')?.name
}
orderedList={({context}) =>
context.schema.lists.find((s) => s.name === 'number')?.name
}
unorderedList={({context}) =>
context.schema.lists.find((s) => s.name === 'bullet')?.name
}
horizontalRuleObject={({context}) => {
const schemaType = context.schema.blockObjects.find(
(object) => object.name === 'break',
)
if (!schemaType) {
return undefined
}
return {_type: schemaType.name}
}}
linkObject={({context, props}) => {
const schemaType = context.schema.annotations.find(
(annotation) => annotation.name === 'link',
)
const hrefField = schemaType?.fields.find(
(field) => field.name === 'href' && field.type === 'string',
)
if (!schemaType || !hrefField) {
return undefined
}
return {
_type: schemaType.name,
[hrefField.name]: props.href,
}
}}
/>
</EditorProvider>
)
}
```
## Why look up the type in the schema?
Each callback returns the type name as found in `context.schema` (for
example `context.schema.decorators.find((d) => d.name === 'strong')?.name`)
instead of a hardcoded `'strong'`. That lookup is what makes the plugin
schema-aware: when the schema does not define the type, the lookup returns
`undefined` and the shortcut is skipped, so the plugin never inserts a
decorator, style, or object the schema does not declare. Hardcoding the
string would fire the shortcut regardless and produce content the schema
forbids.
The same gating follows [containers](../schema/README.md#containers-and-sub-schemas),
because `context.schema` is the schema resolved at the caret, not always the
top-level one. Inside a code block whose sub-schema declares no decorators, a
`boldDecorator` that looks up `strong` returns `undefined`, so the shortcut
is skipped there too. Point each callback at `context.schema` rather than
capturing the top-level schema and the shortcuts stay correct at every
nesting level.