Example: code-block-themes
import 'prosekit/basic/style.css' import 'prosekit/basic/typography.css' import { createEditor } from 'prosekit/core' import { ProseKit } from 'prosekit/react' import { useMemo } from 'react' import { defaultContent } from './default-doc' import { defineExtension } from './extension' import Toolbar from './toolbar' export default function Editor() { const editor = useMemo(() => { const extension = defineExtension() return createEditor({ extension, 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 flex flex-col bg-white dark:bg-gray-950 color-black dark:color-white'> <Toolbar /> <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-none 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' export function defineExtension() { return union(defineBasicExtension()) } export type EditorExtension = ReturnType<typeof defineExtension>
import { ThemeSelector } from './theme-selector' export default function Toolbar() { 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'> <ThemeSelector /> </div> ) }
import type { NodeJSON } from 'prosekit/core' const js = `async function main() {\n while (true) {\n await sleep();\n await eat();\n await code('JavaScript!');\n }\n}` const py = `async def main():\n while True:\n await sleep()\n await eat()\n await code("Python!")` const go = `func main() {\n\tfor {\n\t\tsleep()\n\t\teat()\n\t\tcode("Go!")\n\t}\n}` export const defaultContent: NodeJSON = { type: 'doc', content: [ { type: 'codeBlock', attrs: { language: 'javascript' }, content: [{ type: 'text', text: js }], }, { type: 'codeBlock', attrs: { language: 'python' }, content: [{ type: 'text', text: py }], }, { type: 'codeBlock', attrs: { language: 'go' }, content: [{ type: 'text', text: go }], }, ], }
import { defineCodeBlockShiki, shikiBundledThemesInfo, type ShikiBundledTheme, } from 'prosekit/extensions/code-block' import { useExtension } from 'prosekit/react' import { useMemo, useState, } from 'react' export function ThemeSelector() { const [theme, setTheme] = useState('github-dark') const extension = useMemo(() => { return defineCodeBlockShiki({ themes: [theme as ShikiBundledTheme] }) }, [theme]) useExtension(extension) return ( <> <label htmlFor="code-block-theme-selector">Theme</label> <select id="code-block-theme-selector" value={theme} onChange={(event) => setTheme(event.target.value)} 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 disabled:opacity-50 hover:disabled:opacity-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' > {shikiBundledThemesInfo.map((info) => ( <option key={info.id} value={info.id}> {info.id} </option> ))} </select> </> ) }
<script lang="ts"> import 'prosekit/basic/style.css' import 'prosekit/basic/typography.css' import { createEditor } from 'prosekit/core' import { ProseKit } from 'prosekit/svelte' import { defaultContent } from './default-doc' import { defineExtension } from './extension' import Toolbar from './toolbar.svelte' const extension = defineExtension() const editor = createEditor({ extension, defaultContent }) const mount = (element: HTMLElement) => { editor.mount(element) return { destroy: () => editor.unmount() } } </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 flex flex-col bg-white dark:bg-gray-950 color-black dark:color-white'> <Toolbar /> <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-none 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' export function defineExtension() { return union(defineBasicExtension()) } export type EditorExtension = ReturnType<typeof defineExtension>
<script lang="ts"> import ThemeSelector from './theme-selector.svelte' </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'> <ThemeSelector /> </div>
import type { NodeJSON } from 'prosekit/core' const js = `async function main() {\n while (true) {\n await sleep();\n await eat();\n await code('JavaScript!');\n }\n}` const py = `async def main():\n while True:\n await sleep()\n await eat()\n await code("Python!")` const go = `func main() {\n\tfor {\n\t\tsleep()\n\t\teat()\n\t\tcode("Go!")\n\t}\n}` export const defaultContent: NodeJSON = { type: 'doc', content: [ { type: 'codeBlock', attrs: { language: 'javascript' }, content: [{ type: 'text', text: js }], }, { type: 'codeBlock', attrs: { language: 'python' }, content: [{ type: 'text', text: py }], }, { type: 'codeBlock', attrs: { language: 'go' }, content: [{ type: 'text', text: go }], }, ], }
<script lang="ts"> import { defineCodeBlockShiki, shikiBundledThemesInfo, type ShikiBundledTheme, } from 'prosekit/extensions/code-block' import { useExtension } from 'prosekit/svelte' import { writable } from 'svelte/store' let theme: ShikiBundledTheme = 'github-dark' // Ensure extension is always defined $: extension = defineCodeBlockShiki({ themes: [theme as ShikiBundledTheme] }) // Create a writable store for the extension, initialized with the current extension const extensionStore = writable(extension) // Update the store whenever the extension changes $: { extensionStore.set(extension) } // Use the store useExtension(extensionStore) function change_theme(event: Event) { const select = event.target as HTMLSelectElement theme = select.value as ShikiBundledTheme } </script> <label for="code-block-theme-selector">Theme</label> <select id="code-block-theme-selector" value={theme} on:change={change_theme} 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 disabled:opacity-50 hover:disabled:opacity-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' > {#each shikiBundledThemesInfo as info} <option value={info.id}> {info.id} </option> {/each} </select>
<script setup lang="ts"> import 'prosekit/basic/style.css' import 'prosekit/basic/typography.css' import { createEditor } from 'prosekit/core' import { ProseKit } from 'prosekit/vue' import { ref, watchPostEffect, } from 'vue' import { defaultContent } from './default-doc' import { defineExtension } from './extension' import Toolbar from './toolbar.vue' const editor = createEditor({ extension: defineExtension(), defaultContent }) const editorRef = ref<HTMLDivElement | null>(null) watchPostEffect((onCleanup) => { editor.mount(editorRef.value) onCleanup(() => editor.unmount()) }) </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 flex flex-col bg-white dark:bg-gray-950 color-black dark:color-white'> <Toolbar /> <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-none 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' export function defineExtension() { return union(defineBasicExtension()) } export type EditorExtension = ReturnType<typeof defineExtension>
<script setup lang="ts"> import ThemeSelector from './theme-selector.vue' </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'> <ThemeSelector /> </div> </template>
import type { NodeJSON } from 'prosekit/core' const js = `async function main() {\n while (true) {\n await sleep();\n await eat();\n await code('JavaScript!');\n }\n}` const py = `async def main():\n while True:\n await sleep()\n await eat()\n await code("Python!")` const go = `func main() {\n\tfor {\n\t\tsleep()\n\t\teat()\n\t\tcode("Go!")\n\t}\n}` export const defaultContent: NodeJSON = { type: 'doc', content: [ { type: 'codeBlock', attrs: { language: 'javascript' }, content: [{ type: 'text', text: js }], }, { type: 'codeBlock', attrs: { language: 'python' }, content: [{ type: 'text', text: py }], }, { type: 'codeBlock', attrs: { language: 'go' }, content: [{ type: 'text', text: go }], }, ], }
<script setup lang="ts"> import { defineCodeBlockShiki, shikiBundledThemesInfo, type ShikiBundledTheme, } from 'prosekit/extensions/code-block' import { useExtension } from 'prosekit/vue' import { computed } from 'vue' const theme = defineModel({ default: 'github-dark', type: String }) const extension = computed(() => { return defineCodeBlockShiki({ themes: [theme.value as ShikiBundledTheme] }) }) useExtension(extension) </script> <template> <label for="code-block-theme-selector">Theme</label> <select id="code-block-theme-selector" v-model="theme" 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 disabled:opacity-50 hover:disabled:opacity-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' > <option v-for="info of shikiBundledThemesInfo" :key="info.id" :value="info.id" > {{ info.id }} </option> </select> </template>