Skip to content

Latest commit

 

History

History

README.md

@portabletext/plugin-markdown-shortcuts

Add helpful Markdown shortcuts to the editor

Installation

npm install @portabletext/plugin-markdown-shortcuts

Usage

Import the MarkdownShortcutsPlugin React component and place it inside the EditorProvider and tell it about your schema:

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, 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.