Example: view-adapter
Install this example with
shadcn:npx shadcn@latest add @prosekit/react-example-view-adapternpx shadcn@latest add @prosekit/preact-example-view-adapternpx shadcn@latest add @prosekit/solid-example-view-adapternpx shadcn@latest add @prosekit/svelte-example-view-adapternpx shadcn@latest add @prosekit/vue-example-view-adapterimport { useCallback } from 'preact/hooks'
import {
useEditorDerivedValue,
type PreactNodeViewProps,
} from 'prosekit/preact'
export function AtomBlockView(props: PreactNodeViewProps) {
const docJSON = useEditorDerivedValue(useCallback((editor) => {
return JSON.stringify(editor.getDocJSON(), null, 2)
}, []))
return (
<div data-atom-block="true" data-atom-block-view="true" className="bg-green-500/30">
<div data-testid="atom-block-view-label">Atom Block View</div>
<div data-testid="atom-block-view-pos">{props.getPos()}</div>
<div data-testid="atom-block-view-context">
<pre>{docJSON}</pre>
</div>
</div>
)
}import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { useMemo } from 'preact/hooks'
import {
createEditor,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/preact'
import { sampleContent } from '../../sample/sample-doc-view-adapter'
import { defineExtension } from './extension'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
return createEditor({ extension: defineExtension(), defaultContent })
}, [defaultContent])
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">
<div className="relative w-full flex-1 box-border overflow-y-auto">
<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>
</ProseKit>
)
}import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
import { definePreactNodeView } from 'prosekit/preact'
import { defineAtomBlock } from '../../sample/define-atom-block'
import { AtomBlockView } from './atom-block-view'
export function defineExtension() {
return union(
defineBasicExtension(),
defineAtomBlock(),
definePreactNodeView({
name: 'atomBlock',
component: AtomBlockView,
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor'import { defineNodeSpec } from 'prosekit/core'
/**
* Defines an atom block node for testing purposes.
*/
export function defineAtomBlock() {
return defineNodeSpec({
name: 'atomBlock',
group: 'block',
atom: true,
parseDOM: [{
tag: 'div[data-atom-block]',
}],
toDOM: () => {
return ['div', { 'data-atom-block': 'true' }, 'atom block']
},
})
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'An example of using prosemirror-adapter to render custom node views',
},
],
},
{
type: 'atomBlock',
},
],
}import {
useEditorDerivedValue,
type ReactNodeViewProps,
} from 'prosekit/react'
import { useCallback } from 'react'
export function AtomBlockView(props: ReactNodeViewProps) {
const docJSON = useEditorDerivedValue(useCallback(editor => {
return JSON.stringify(editor.getDocJSON(), null, 2)
}, []))
return (
<div data-atom-block="true" data-atom-block-view="true" className="bg-green-500/30">
<div data-testid="atom-block-view-label">Atom Block View</div>
<div data-testid="atom-block-view-pos">{props.getPos()}</div>
<div data-testid="atom-block-view-context">
<pre>{docJSON}</pre>
</div>
</div>
)
}import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
createEditor,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/react'
import { useMemo } from 'react'
import { sampleContent } from '../../sample/sample-doc-view-adapter'
import { defineExtension } from './extension'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
return createEditor({ extension: defineExtension(), defaultContent })
}, [defaultContent])
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">
<div className="relative w-full flex-1 box-border overflow-y-auto">
<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>
</ProseKit>
)
}import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
import { defineReactNodeView } from 'prosekit/react'
import { defineAtomBlock } from '../../sample/define-atom-block'
import { AtomBlockView } from './atom-block-view'
export function defineExtension() {
return union(
defineBasicExtension(),
defineAtomBlock(),
defineReactNodeView({
name: 'atomBlock',
component: AtomBlockView,
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>'use client'
export { default as ExampleEditor } from './editor'import { defineNodeSpec } from 'prosekit/core'
/**
* Defines an atom block node for testing purposes.
*/
export function defineAtomBlock() {
return defineNodeSpec({
name: 'atomBlock',
group: 'block',
atom: true,
parseDOM: [{
tag: 'div[data-atom-block]',
}],
toDOM: () => {
return ['div', { 'data-atom-block': 'true' }, 'atom block']
},
})
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'An example of using prosemirror-adapter to render custom node views',
},
],
},
{
type: 'atomBlock',
},
],
}import {
useEditorDerivedValue,
type SolidNodeViewProps,
} from 'prosekit/solid'
import type { JSX } from 'solid-js'
export function AtomBlockView(props: SolidNodeViewProps): JSX.Element {
const docJSON = useEditorDerivedValue((editor) => {
return JSON.stringify(editor.getDocJSON(), null, 2)
})
return (
<div attr:data-atom-block="true" attr:data-atom-block-view="true" class="bg-green-500/30">
<div attr:data-testid="atom-block-view-label">Atom Block View</div>
<div attr:data-testid="atom-block-view-pos">{props.getPos()}</div>
<div attr:data-testid="atom-block-view-context">
<pre>{docJSON()}</pre>
</div>
</div>
)
}import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
createEditor,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/solid'
import {
createMemo,
type JSX,
} from 'solid-js'
import { sampleContent } from '../../sample/sample-doc-view-adapter'
import { defineExtension } from './extension'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps): JSX.Element {
const defaultContent = () => props.initialContent ?? sampleContent
const editor = createMemo(() => {
return createEditor({ extension: defineExtension(), defaultContent: defaultContent() })
})
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">
<div class="relative w-full flex-1 box-border overflow-y-auto">
<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>
</ProseKit>
)
}import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
import { defineSolidNodeView } from 'prosekit/solid'
import { defineAtomBlock } from '../../sample/define-atom-block'
import { AtomBlockView } from './atom-block-view'
export function defineExtension() {
return union(
defineBasicExtension(),
defineAtomBlock(),
defineSolidNodeView({
name: 'atomBlock',
component: AtomBlockView,
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor'import { defineNodeSpec } from 'prosekit/core'
/**
* Defines an atom block node for testing purposes.
*/
export function defineAtomBlock() {
return defineNodeSpec({
name: 'atomBlock',
group: 'block',
atom: true,
parseDOM: [{
tag: 'div[data-atom-block]',
}],
toDOM: () => {
return ['div', { 'data-atom-block': 'true' }, 'atom block']
},
})
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'An example of using prosemirror-adapter to render custom node views',
},
],
},
{
type: 'atomBlock',
},
],
}<script lang="ts">
import {
useEditorDerivedValue,
type SvelteNodeViewProps,
} from 'prosekit/svelte'
const props: SvelteNodeViewProps = $props()
const docJSON = useEditorDerivedValue((editor) => {
return JSON.stringify(editor.getDocJSON(), null, 2)
})
</script>
<div data-atom-block="true" data-atom-block-view="true" class="bg-green-500/30">
<div data-testid="atom-block-view-label">Atom Block View</div>
<div data-testid="atom-block-view-pos">{props.getPos()}</div>
<div data-testid="atom-block-view-context">
<pre>{$docJSON}</pre>
</div>
</div><script lang="ts">
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
createEditor,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/svelte'
import { sampleContent } from '../../sample/sample-doc-view-adapter'
import { defineExtension } from './extension'
export interface Props {
initialContent?: NodeJSON
}
const props: Props = $props()
const extension = defineExtension()
const defaultContent = props.initialContent ?? sampleContent
const editor = createEditor({ extension, defaultContent })
</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">
<div class="relative w-full flex-1 box-border overflow-y-auto">
<div {@attach 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>
</ProseKit>import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
import { defineSvelteNodeView } from 'prosekit/svelte'
import { defineAtomBlock } from '../../sample/define-atom-block'
import AtomBlockView from './atom-block-view.svelte'
export function defineExtension() {
return union(
defineBasicExtension(),
defineAtomBlock(),
defineSvelteNodeView({
name: 'atomBlock',
component: AtomBlockView,
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.svelte'import { defineNodeSpec } from 'prosekit/core'
/**
* Defines an atom block node for testing purposes.
*/
export function defineAtomBlock() {
return defineNodeSpec({
name: 'atomBlock',
group: 'block',
atom: true,
parseDOM: [{
tag: 'div[data-atom-block]',
}],
toDOM: () => {
return ['div', { 'data-atom-block': 'true' }, 'atom block']
},
})
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'An example of using prosemirror-adapter to render custom node views',
},
],
},
{
type: 'atomBlock',
},
],
}<script setup lang="ts">
import {
useEditorDerivedValue,
type VueNodeViewProps,
} from 'prosekit/vue'
const props = defineProps<VueNodeViewProps>()
const docJSON = useEditorDerivedValue((editor) => {
return JSON.stringify(editor.getDocJSON(), null, 2)
})
</script>
<template>
<div data-atom-block="true" data-atom-block-view="true" class="bg-green-500/30">
<div data-testid="atom-block-view-label">Atom Block View</div>
<div data-testid="atom-block-view-pos">{{ props.getPos() }}</div>
<div data-testid="atom-block-view-context">
<pre>{{ docJSON }}</pre>
</div>
</div>
</template><script setup lang="ts">
import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import {
createEditor,
type NodeJSON,
} from 'prosekit/core'
import { ProseKit } from 'prosekit/vue'
import { sampleContent } from '../../sample/sample-doc-view-adapter'
import { defineExtension } from './extension'
const props = defineProps<{
initialContent?: NodeJSON
}>()
const extension = defineExtension()
const defaultContent = props.initialContent ?? sampleContent
const editor = createEditor({ extension, defaultContent })
</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">
<div class="relative w-full flex-1 box-border overflow-y-auto">
<div :ref="(el) => editor.mount(el as HTMLElement | null)" 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>
</ProseKit>
</template>import { defineBasicExtension } from 'prosekit/basic'
import { union } from 'prosekit/core'
import {
defineVueNodeView,
type VueNodeViewComponent,
} from 'prosekit/vue'
import { defineAtomBlock } from '../../sample/define-atom-block'
import AtomBlockView from './atom-block-view.vue'
export function defineExtension() {
return union(
defineBasicExtension(),
defineAtomBlock(),
defineVueNodeView({
name: 'atomBlock',
component: AtomBlockView as VueNodeViewComponent,
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.vue'import { defineNodeSpec } from 'prosekit/core'
/**
* Defines an atom block node for testing purposes.
*/
export function defineAtomBlock() {
return defineNodeSpec({
name: 'atomBlock',
group: 'block',
atom: true,
parseDOM: [{
tag: 'div[data-atom-block]',
}],
toDOM: () => {
return ['div', { 'data-atom-block': 'true' }, 'atom block']
},
})
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'An example of using prosemirror-adapter to render custom node views',
},
],
},
{
type: 'atomBlock',
},
],
}