Example: keymap
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
  useCallback,
  useMemo,
  useState,
} from 'preact/hooks'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/preact'
import { defineExtension } from './extension'
import Toolbar from './toolbar'
export default function Editor() {
  const editor = useMemo(() => {
    return createEditor({ extension: defineExtension() })
  }, [])
  const [submitions, setSubmitions] = useState<string[]>([])
  const pushSubmition = useCallback(
    (hotkey: string) => {
      const docString = JSON.stringify(editor.getDocJSON())
      const submition = `${new Date().toISOString()}\t${hotkey}\n${docString}`
      setSubmitions((submitions) => [...submitions, submition])
    },
    [editor],
  )
  return (
    <ProseKit editor={editor}>
      <div className="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
        <Toolbar onSubmit={pushSubmition} />
        <div className="relative w-full flex-1 box-border overflow-y-scroll">
          <div ref={editor.mount} className="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
        </div>
      </div>
      <fieldset className="mt-4 box-border flex max-w-full w-full overflow-x-auto border p-4 rounded-md shadow-sm min-w-0">
        <legend>Submit Records</legend>
        <ol>
          {submitions.map((submition, index) => (
            <li key={index}>
              <pre>{submition}</pre>
            </li>
          ))}
        </ol>
        {submitions.length === 0 && <div>No submitions yet</div>}
      </fieldset>
    </ProseKit>
  )
}import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
export function defineExtension() {
  return union(defineBasicExtension())
}
export type EditorExtension = ReturnType<typeof defineExtension>import { useState } from 'preact/hooks'
import Button from './button'
import { useSubmitKeymap } from './use-submit-keymap'
export default function Toolbar({
  onSubmit,
}: {
  onSubmit: (hotkey: string) => void
}) {
  const [hotkey, setHotkey] = useState<'Shift-Enter' | 'Enter'>('Shift-Enter')
  useSubmitKeymap(hotkey, onSubmit)
  return (
    <div className="z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center">
      <Button
        pressed={hotkey === 'Shift-Enter'}
        onClick={() => setHotkey('Shift-Enter')}
      >
        <span class="mr-1">Submit with</span>
        <kbd>Shift + Enter</kbd>
      </Button>
      <Button pressed={hotkey === 'Enter'} onClick={() => setHotkey('Enter')}>
        <span class="mr-1">Submit with</span>
        <kbd>Enter</kbd>
      </Button>
    </div>
  )
}import { useMemo } from 'preact/hooks'
import type { Keymap } from 'prosekit/core'
import { useKeymap } from 'prosekit/preact'
export function useSubmitKeymap(
  hotkey: 'Shift-Enter' | 'Enter',
  onSubmit: (hotkey: string) => void,
) {
  const keymap: Keymap = useMemo(() => {
    return {
      [hotkey]: () => {
        onSubmit(hotkey)
        // Return true to stop further keypress propagation.
        return true
      },
    }
  }, [hotkey, onSubmit])
  useKeymap(keymap)
}import type {
  ComponentChild,
  JSX,
} from 'preact'
import {
  TooltipContent,
  TooltipRoot,
  TooltipTrigger,
} from 'prosekit/preact/tooltip'
export default function Button(
  {
    pressed,
    disabled,
    onClick,
    tooltip,
    children,
  }: {
    pressed?: boolean
    disabled?: boolean
    onClick?: JSX.MouseEventHandler<HTMLButtonElement>
    tooltip?: string
    children: ComponentChild
  },
) {
  return (
    <TooltipRoot>
      <TooltipTrigger className="block">
        <button
          data-state={pressed ? 'on' : 'off'}
          disabled={disabled}
          onClick={onClick}
          onMouseDown={(event) => event.preventDefault()}
          className="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
        >
          {children}
          {tooltip ? <span className="sr-only">{tooltip}</span> : null}
        </button>
      </TooltipTrigger>
      {tooltip
        ? (
          <TooltipContent className="z-50 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs [&:not([data-state])]:hidden will-change-transform data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:animate-duration-150 data-[state=closed]:animate-duration-200 data-[side=bottom]:slide-in-from-top-2 data-[side=bottom]:slide-out-to-top-2 data-[side=left]:slide-in-from-right-2 data-[side=left]:slide-out-to-right-2 data-[side=right]:slide-in-from-left-2 data-[side=right]:slide-out-to-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=top]:slide-out-to-bottom-2">
            {tooltip}
          </TooltipContent>
        )
        : null}
    </TooltipRoot>
  )
}import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/react'
import {
  useCallback,
  useMemo,
  useState,
} from 'react'
import { defineExtension } from './extension'
import Toolbar from './toolbar'
export default function Editor() {
  const editor = useMemo(() => {
    return createEditor({ extension: defineExtension() })
  }, [])
  const [submitions, setSubmitions] = useState<string[]>([])
  const pushSubmition = useCallback(
    (hotkey: string) => {
      const docString = JSON.stringify(editor.getDocJSON())
      const submition = `${new Date().toISOString()}\t${hotkey}\n${docString}`
      setSubmitions((submitions) => [...submitions, submition])
    },
    [editor],
  )
  return (
    <ProseKit editor={editor}>
      <div className="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
        <Toolbar onSubmit={pushSubmition} />
        <div className="relative w-full flex-1 box-border overflow-y-scroll">
          <div ref={editor.mount} className="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
        </div>
      </div>
      <fieldset className="mt-4 box-border flex max-w-full w-full overflow-x-auto border p-4 rounded-md shadow-sm min-w-0">
        <legend>Submit Records</legend>
        <ol>
          {submitions.map((submition, index) => (
            <li key={index}>
              <pre>{submition}</pre>
            </li>
          ))}
        </ol>
        {submitions.length === 0 && <div>No submitions yet</div>}
      </fieldset>
    </ProseKit>
  )
}import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
export function defineExtension() {
  return union(defineBasicExtension())
}
export type EditorExtension = ReturnType<typeof defineExtension>import { useState } from 'react'
import Button from './button'
import { useSubmitKeymap } from './use-submit-keymap'
export default function Toolbar({
  onSubmit,
}: {
  onSubmit: (hotkey: string) => void
}) {
  const [hotkey, setHotkey] = useState<'Shift-Enter' | 'Enter'>('Shift-Enter')
  useSubmitKeymap(hotkey, onSubmit)
  return (
    <div className="z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center">
      <Button
        pressed={hotkey === 'Shift-Enter'}
        onClick={() => setHotkey('Shift-Enter')}
      >
        <span className="mr-1">Submit with</span>
        <kbd>Shift + Enter</kbd>
      </Button>
      <Button pressed={hotkey === 'Enter'} onClick={() => setHotkey('Enter')}>
        <span className="mr-1">Submit with</span>
        <kbd>Enter</kbd>
      </Button>
    </div>
  )
}import type { Keymap } from 'prosekit/core'
import { useKeymap } from 'prosekit/react'
import { useMemo } from 'react'
export function useSubmitKeymap(
  hotkey: 'Shift-Enter' | 'Enter',
  onSubmit: (hotkey: string) => void,
) {
  const keymap: Keymap = useMemo(() => {
    return {
      [hotkey]: () => {
        onSubmit(hotkey)
        // Return true to stop further keypress propagation.
        return true
      },
    }
  }, [hotkey, onSubmit])
  useKeymap(keymap)
}import {
  TooltipContent,
  TooltipRoot,
  TooltipTrigger,
} from 'prosekit/react/tooltip'
import type { ReactNode } from 'react'
export default function Button({
  pressed,
  disabled,
  onClick,
  tooltip,
  children,
}: {
  pressed?: boolean
  disabled?: boolean
  onClick?: VoidFunction
  tooltip?: string
  children: ReactNode
}) {
  return (
    <TooltipRoot>
      <TooltipTrigger className="block">
        <button
          data-state={pressed ? 'on' : 'off'}
          disabled={disabled}
          onClick={() => onClick?.()}
          onMouseDown={(event) => event.preventDefault()}
          className="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
        >
          {children}
          {tooltip ? <span className="sr-only">{tooltip}</span> : null}
        </button>
      </TooltipTrigger>
      {tooltip
        ? (
          <TooltipContent className="z-50 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs [&:not([data-state])]:hidden will-change-transform data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:animate-duration-150 data-[state=closed]:animate-duration-200 data-[side=bottom]:slide-in-from-top-2 data-[side=bottom]:slide-out-to-top-2 data-[side=left]:slide-in-from-right-2 data-[side=left]:slide-out-to-right-2 data-[side=right]:slide-in-from-left-2 data-[side=right]:slide-out-to-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=top]:slide-out-to-bottom-2">
            {tooltip}
          </TooltipContent>
        )
        : null}
    </TooltipRoot>
  )
}import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
  createEditor,
  jsonFromNode,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/solid'
import { createSignal } from 'solid-js'
import { defineExtension } from './extension'
import Toolbar from './toolbar'
export default function Editor() {
  const editor = createEditor({ extension: defineExtension() })
  const [submitions, setSubmitions] = createSignal<string[]>([])
  const pushSubmition = (hotkey: string) => {
    const doc = editor.view.state.doc
    const docString = JSON.stringify(jsonFromNode(doc))
    const submition = `${new Date().toISOString()}\t${hotkey}\n${docString}`
    setSubmitions((submitions) => [...submitions, submition])
  }
  return (
    <ProseKit editor={editor}>
      <div class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
        <Toolbar onSubmit={pushSubmition} />
        <div class="relative w-full flex-1 box-border overflow-y-scroll">
          <div ref={editor.mount} class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
        </div>
      </div>
      <fieldset class="mt-4 box-border flex max-w-full w-full overflow-x-auto border p-4 rounded-md shadow-sm min-w-0">
        <legend>Submit Records</legend>
        <ol>
          {submitions().map((submition) => (
            <li>
              <pre>{submition}</pre>
            </li>
          ))}
        </ol>
        {submitions().length === 0 && <div>No submitions yet</div>}
      </fieldset>
    </ProseKit>
  )
}import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
export function defineExtension() {
  return union(defineBasicExtension())
}
export type EditorExtension = ReturnType<typeof defineExtension>import { createSignal } from 'solid-js'
import Button from './button'
import { useSubmitKeymap } from './use-submit-keymap'
export default function Toolbar(props: {
  onSubmit: (hotkey: string) => void
}) {
  const [hotkey, setHotkey] = createSignal<'Shift-Enter' | 'Enter'>(
    'Shift-Enter',
  )
  useSubmitKeymap(hotkey, props.onSubmit)
  return (
    <div class="z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center">
      <Button
        pressed={() => hotkey() === 'Shift-Enter'}
        onClick={() => setHotkey('Shift-Enter')}
      >
        <span class="mr-1">Submit with</span>
        <kbd>Shift + Enter</kbd>
      </Button>
      <Button
        pressed={() => hotkey() === 'Enter'}
        onClick={() => setHotkey('Enter')}
      >
        <span class="mr-1">Submit with</span>
        <kbd>Enter</kbd>
      </Button>
    </div>
  )
}import type { Keymap } from 'prosekit/core'
import { useKeymap } from 'prosekit/solid'
export function useSubmitKeymap(
  hotkey: () => 'Shift-Enter' | 'Enter',
  onSubmit: (hotkey: string) => void,
) {
  const keymap = (): Keymap => ({
    [hotkey()]: () => {
      onSubmit(hotkey())
      // Return true to stop further keypress propagation.
      return true
    },
  })
  useKeymap(keymap)
}import {
  TooltipContent,
  TooltipRoot,
  TooltipTrigger,
} from 'prosekit/solid/tooltip'
import type { ParentProps } from 'solid-js'
import { Show } from 'solid-js'
export default function Button(
  props: ParentProps<{
    pressed: () => boolean
    disabled?: () => boolean
    onClick?: () => void
    tooltip?: string
  }>,
) {
  return (
    <TooltipRoot>
      <TooltipTrigger class="block">
        <button
          data-state={props.pressed() ? 'on' : 'off'}
          disabled={props.disabled?.()}
          onClick={() => props.onClick?.()}
          onMouseDown={(event) => event.preventDefault()}
          class="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
        >
          {props.children}
          <Show when={props.tooltip}>
            <span class="sr-only">{props.tooltip}</span>
          </Show>
        </button>
      </TooltipTrigger>
      <Show when={props.tooltip}>
        <TooltipContent class="z-50 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs [&:not([data-state])]:hidden will-change-transform data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:animate-duration-150 data-[state=closed]:animate-duration-200 data-[side=bottom]:slide-in-from-top-2 data-[side=bottom]:slide-out-to-top-2 data-[side=left]:slide-in-from-right-2 data-[side=left]:slide-out-to-right-2 data-[side=right]:slide-in-from-left-2 data-[side=right]:slide-out-to-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=top]:slide-out-to-bottom-2">
          {props.tooltip}
        </TooltipContent>
      </Show>
    </TooltipRoot>
  )
}<script lang="ts">
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { createEditor } from 'prosekit/core'
import { ProseKit } from 'prosekit/svelte'
import { writable } from 'svelte/store'
import { defineExtension } from './extension'
import Toolbar from './toolbar.svelte'
const extension = defineExtension()
const editor = createEditor({ extension })
const mount = (element: HTMLElement) => {
  editor.mount(element)
  return { destroy: () => editor.unmount() }
}
const submitions = writable<string[]>([])
const pushSubmition = (hotkey: string) => {
  const docString = JSON.stringify(editor.getDocJSON())
  const submition = `${new Date().toISOString()}\t${hotkey}\n${docString}`
  submitions.update((submitions) => [...submitions, submition])
}
</script>
<ProseKit {editor}>
  <div class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
    <Toolbar onSubmit={pushSubmition} />
    <div class="relative w-full flex-1 box-border overflow-y-scroll">
      <div use:mount class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500"></div>
    </div>
  </div>
  <fieldset class="mt-4 box-border flex max-w-full w-full overflow-x-auto border p-4 rounded-md shadow-sm min-w-0">
    <legend>Submit Records</legend>
    <ol>
      {#each $submitions as submition, index (index)}
        <li>
          <pre>{submition}</pre>
        </li>
      {/each}
      {#if $submitions.length === 0}
        <div>No submitions yet</div>
      {/if}
    </ol>
  </fieldset>
</ProseKit>import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
export function defineExtension() {
  return union(defineBasicExtension())
}
export type EditorExtension = ReturnType<typeof defineExtension><script lang="ts">
import { writable } from 'svelte/store'
import Button from './button.svelte'
import { useSubmitKeymap } from './use-submit-keymap'
export let onSubmit: (hotkey: string) => void
const hotkey = writable<'Shift-Enter' | 'Enter'>('Shift-Enter')
useSubmitKeymap(hotkey, onSubmit)
</script>
<div class="z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center">
  <Button
    pressed={$hotkey === 'Shift-Enter'}
    onClick={() => ($hotkey = 'Shift-Enter')}
  >
    <span class="mr-1">Submit with</span>
    <kbd>Shift + Enter</kbd>
  </Button>
  <Button pressed={$hotkey === 'Enter'} onClick={() => ($hotkey = 'Enter')}>
    <span class="mr-1">Submit with</span>
    <kbd>Enter</kbd>
  </Button>
</div>import type { Keymap } from 'prosekit/core'
import { useKeymap } from 'prosekit/svelte'
import {
  derived,
  type Readable,
} from 'svelte/store'
export function useSubmitKeymap(
  hotkey: Readable<'Shift-Enter' | 'Enter'>,
  onSubmit: (hotkey: string) => void,
) {
  const keymap: Readable<Keymap> = derived(hotkey, (hotkey) => {
    return {
      [hotkey]: () => {
        onSubmit(hotkey)
        // Return true to stop further keypress propagation.
        return true
      },
    }
  })
  useKeymap(keymap)
}<script lang="ts">
import {
  TooltipContent,
  TooltipRoot,
  TooltipTrigger,
} from 'prosekit/svelte/tooltip'
import type { Snippet } from 'svelte'
interface Props {
  pressed?: boolean
  disabled?: boolean
  tooltip?: string
  onClick?: VoidFunction
  children?: Snippet
}
let {
  pressed = false,
  disabled = false,
  tooltip = '',
  onClick = undefined,
  children,
}: Props = $props()
</script>
<TooltipRoot>
  <TooltipTrigger class="block">
    <button
      data-state={pressed ? 'on' : 'off'}
      {disabled}
      onclick={() => onClick?.()}
      onmousedown={(event) => event.preventDefault()}
      class="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
    >
      {@render children?.()}
      {#if tooltip}
        <span class="sr-only">{tooltip}</span>
      {/if}
    </button>
  </TooltipTrigger>
  {#if tooltip}
    <TooltipContent class="z-50 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs [&:not([data-state])]:hidden will-change-transform data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:animate-duration-150 data-[state=closed]:animate-duration-200 data-[side=bottom]:slide-in-from-top-2 data-[side=bottom]:slide-out-to-top-2 data-[side=left]:slide-in-from-right-2 data-[side=left]:slide-out-to-right-2 data-[side=right]:slide-in-from-left-2 data-[side=right]:slide-out-to-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=top]:slide-out-to-bottom-2">
      {tooltip}
    </TooltipContent>
  {/if}
</TooltipRoot><script setup lang="ts">
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
  createEditor,
  jsonFromNode,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/vue'
import {
  ref,
  watchPostEffect,
} from 'vue'
import { defineExtension } from './extension'
import Toolbar from './toolbar.vue'
const editor = createEditor({ extension: defineExtension() })
const editorRef = ref<HTMLDivElement | null>(null)
watchPostEffect((onCleanup) => {
  editor.mount(editorRef.value)
  onCleanup(() => editor.unmount())
})
const submitions = ref<string[]>([])
function pushSubmition(hotkey: string) {
  const doc = editor.view.state.doc
  const docString = JSON.stringify(jsonFromNode(doc))
  const submition = `${new Date().toISOString()}\t${hotkey}\n${docString}`
  submitions.value = [...submitions.value, submition]
}
</script>
<template>
  <ProseKit :editor="editor">
    <div class="box-border h-full w-full min-h-36 overflow-y-hidden overflow-x-hidden rounded-md border border-solid border-gray-200 dark:border-gray-700 shadow-sm flex flex-col bg-white dark:bg-gray-950 text-black dark:text-white">
      <Toolbar @submit="pushSubmition" />
      <div class="relative w-full flex-1 box-border overflow-y-scroll">
        <div ref="editorRef" class="ProseMirror box-border min-h-full px-[max(4rem,calc(50%-20rem))] py-8 outline-hidden outline-0 [&_span[data-mention=user]]:text-blue-500 [&_span[data-mention=tag]]:text-violet-500" />
      </div>
    </div>
    <fieldset class="mt-4 box-border flex max-w-full w-full overflow-x-auto border p-4 rounded-md shadow-sm min-w-0">
      <legend>Submit Records</legend>
      <ol>
        <li v-for="(submition, index) in submitions" :key="index">
          <pre>{{ submition }}</pre>
        </li>
      </ol>
      <div v-if="submitions.length === 0">No submitions yet</div>
    </fieldset>
  </ProseKit>
</template>import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
export function defineExtension() {
  return union(defineBasicExtension())
}
export type EditorExtension = ReturnType<typeof defineExtension><script setup lang="ts">
import { ref } from 'vue'
import Button from './button.vue'
import { useSubmitKeymap } from './use-submit-keymap'
const emit = defineEmits<{
  submit: [hotkey: string]
}>()
const hotkey = ref<'Shift-Enter' | 'Enter'>('Shift-Enter')
useSubmitKeymap(hotkey, (hotkey) => emit('submit', hotkey))
</script>
<template>
  <div class="z-2 box-border border-gray-200 dark:border-gray-800 border-solid border-l-0 border-r-0 border-t-0 border-b flex flex-wrap gap-1 p-2 items-center">
    <Button
      :pressed="hotkey === 'Shift-Enter'"
      @click="() => (hotkey = 'Shift-Enter')"
    >
      <span class="mr-1">Submit with</span>
      <kbd>Shift + Enter</kbd>
    </Button>
    <Button :pressed="hotkey === 'Enter'" @click="() => (hotkey = 'Enter')">
      <span class="mr-1">Submit with</span>
      <kbd>Enter</kbd>
    </Button>
  </div>
</template>import type { Keymap } from 'prosekit/core'
import { useKeymap } from 'prosekit/vue'
import {
  computed,
  type Ref,
} from 'vue'
export function useSubmitKeymap(
  hotkey: Ref<'Shift-Enter' | 'Enter'>,
  onSubmit: (hotkey: string) => void,
) {
  const keymap: Ref<Keymap> = computed(() => {
    return {
      [hotkey.value]: () => {
        onSubmit(hotkey.value)
        // Return true to stop further keypress propagation.
        return true
      },
    }
  })
  useKeymap(keymap)
}<script setup lang="ts">
import {
  TooltipContent,
  TooltipRoot,
  TooltipTrigger,
} from 'prosekit/vue/tooltip'
defineProps<{
  pressed?: Boolean
  disabled?: Boolean
  tooltip?: string
}>()
const emit = defineEmits<{
  click: []
}>()
</script>
<template>
  <TooltipRoot>
    <TooltipTrigger class="block">
      <button
        :data-state="pressed ? 'on' : 'off'"
        :disabled="disabled ? true : undefined"
        class="outline-unset focus-visible:outline-unset flex items-center justify-center rounded-md p-2 font-medium transition focus-visible:ring-2 text-sm focus-visible:ring-gray-900 dark:focus-visible:ring-gray-300 disabled:pointer-events-none min-w-9 min-h-9 text-gray-900 dark:text-gray-50 disabled:text-gray-900/50 dark:disabled:text-gray-50/50 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 data-[state=on]:bg-gray-200 dark:data-[state=on]:bg-gray-700"
        @click="() => emit('click')"
        @mousedown.prevent
      >
        <slot />
        <span v-if="tooltip" class="sr-only">{{ tooltip }}</span>
      </button>
    </TooltipTrigger>
    <TooltipContent v-if="tooltip" class="z-50 overflow-hidden rounded-md border border-solid bg-gray-900 dark:bg-gray-50 px-3 py-1.5 text-xs text-gray-50 dark:text-gray-900 shadow-xs [&:not([data-state])]:hidden will-change-transform data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[state=open]:animate-duration-150 data-[state=closed]:animate-duration-200 data-[side=bottom]:slide-in-from-top-2 data-[side=bottom]:slide-out-to-top-2 data-[side=left]:slide-in-from-right-2 data-[side=left]:slide-out-to-right-2 data-[side=right]:slide-in-from-left-2 data-[side=right]:slide-out-to-left-2 data-[side=top]:slide-in-from-bottom-2 data-[side=top]:slide-out-to-bottom-2">
      {{ tooltip }}
    </TooltipContent>
  </TooltipRoot>
</template>