Subscript
The subscript mark is used to represent text that is subscripted (lowered below the baseline). It will be rendered as <sub> element in HTML.
'use client'
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-sub-sup.ts'
import { defineExtension } from './extension.ts'
import Toolbar from './toolbar.tsx'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
const extension = defineExtension()
return createEditor({ extension, 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-[canvas] text-black dark:text-white">
<Toolbar />
<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 { canUseRegexLookbehind, defineBaseKeymap, union } from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineMarkInputRule } from 'prosekit/extensions/input-rule'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineSubscript } from 'prosekit/extensions/subscript'
import { defineSuperscript } from 'prosekit/extensions/superscript'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineSubscript(),
defineSuperscript(),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`~([^\s~]|[^\s~][^~]*[^\s~])~$`,
),
type: 'subscript',
}),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`\^([^\s^]|[^\s^][^^]*[^\s^])\^$`,
),
type: 'superscript',
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.tsx''use client'
import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/react'
import { Button } from '../../ui/button/index.ts'
import type { EditorExtension } from './extension.ts'
function getToolbarItems(editor: Editor<EditorExtension>) {
return {
subscript: {
isActive: editor.marks.subscript.isActive(),
canExec: editor.commands.toggleSubscript.canExec(),
command: () => editor.commands.toggleSubscript(),
},
superscript: {
isActive: editor.marks.superscript.isActive(),
canExec: editor.commands.toggleSuperscript.canExec(),
command: () => editor.commands.toggleSuperscript(),
},
}
}
export default function Toolbar() {
const items = useEditorDerivedValue(getToolbarItems)
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={items.subscript.isActive}
disabled={!items.subscript.canExec}
onClick={items.subscript.command}
>
Subscript
</Button>
<Button
pressed={items.superscript.isActive}
disabled={!items.superscript.canExec}
onClick={items.superscript.command}
>
Superscript
</Button>
</div>
)
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'H',
},
{
type: 'text',
marks: [
{
type: 'subscript',
},
],
text: '2',
},
{
type: 'text',
text: 'O is water. x',
},
{
type: 'text',
marks: [
{
type: 'superscript',
},
],
text: '2',
},
{
type: 'text',
text: ' is a square.',
},
],
},
],
}'use client'
import { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/react/tooltip'
import type { MouseEventHandler, ReactNode } from 'react'
export default function Button(props: {
pressed?: boolean
disabled?: boolean
onClick?: MouseEventHandler<HTMLButtonElement>
tooltip?: string
children: ReactNode
}) {
return (
<TooltipRoot>
<TooltipTrigger className="block">
<button
data-state={props.pressed ? 'on' : 'off'}
disabled={props.disabled}
onClick={props.onClick}
onMouseDown={(event) => {
// Prevent the editor from being blurred when the button is clicked
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"
>
{props.children}
{props.tooltip ? <span className="sr-only">{props.tooltip}</span> : null}
</button>
</TooltipTrigger>
{props.tooltip
? (
<TooltipPositioner className="block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none">
<TooltipPopup className="flex box-border origin-(--transform-origin) transition-[opacity,scale] transition-discrete motion-reduce:transition-none duration-100 data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 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 text-nowrap">
{props.tooltip}
</TooltipPopup>
</TooltipPositioner>
)
: null}
</TooltipRoot>
)
}export { default as Button } from './button.tsx'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-sub-sup.ts'
import { defineExtension } from './extension.ts'
import Toolbar from './toolbar.tsx'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps) {
const defaultContent = props.initialContent ?? sampleContent
const editor = useMemo(() => {
const extension = defineExtension()
return createEditor({ extension, 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-[canvas] text-black dark:text-white">
<Toolbar />
<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 { canUseRegexLookbehind, defineBaseKeymap, union } from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineMarkInputRule } from 'prosekit/extensions/input-rule'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineSubscript } from 'prosekit/extensions/subscript'
import { defineSuperscript } from 'prosekit/extensions/superscript'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineSubscript(),
defineSuperscript(),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`~([^\s~]|[^\s~][^~]*[^\s~])~$`,
),
type: 'subscript',
}),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`\^([^\s^]|[^\s^][^^]*[^\s^])\^$`,
),
type: 'superscript',
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.tsx'import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/preact'
import { Button } from '../../ui/button/index.ts'
import type { EditorExtension } from './extension.ts'
function getToolbarItems(editor: Editor<EditorExtension>) {
return {
subscript: {
isActive: editor.marks.subscript.isActive(),
canExec: editor.commands.toggleSubscript.canExec(),
command: () => editor.commands.toggleSubscript(),
},
superscript: {
isActive: editor.marks.superscript.isActive(),
canExec: editor.commands.toggleSuperscript.canExec(),
command: () => editor.commands.toggleSuperscript(),
},
}
}
export default function Toolbar() {
const items = useEditorDerivedValue(getToolbarItems)
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={items.subscript.isActive}
disabled={!items.subscript.canExec}
onClick={items.subscript.command}
>
Subscript
</Button>
<Button
pressed={items.superscript.isActive}
disabled={!items.superscript.canExec}
onClick={items.superscript.command}
>
Superscript
</Button>
</div>
)
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'H',
},
{
type: 'text',
marks: [
{
type: 'subscript',
},
],
text: '2',
},
{
type: 'text',
text: 'O is water. x',
},
{
type: 'text',
marks: [
{
type: 'superscript',
},
],
text: '2',
},
{
type: 'text',
text: ' is a square.',
},
],
},
],
}import type { ComponentChild, MouseEventHandler } from 'preact'
import { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/preact/tooltip'
export default function Button(props: {
pressed?: boolean
disabled?: boolean
onClick?: MouseEventHandler<HTMLButtonElement>
tooltip?: string
children: ComponentChild
}) {
return (
<TooltipRoot>
<TooltipTrigger className="block">
<button
data-state={props.pressed ? 'on' : 'off'}
disabled={props.disabled}
onClick={props.onClick}
onMouseDown={(event) => {
// Prevent the editor from being blurred when the button is clicked
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"
>
{props.children}
{props.tooltip ? <span className="sr-only">{props.tooltip}</span> : null}
</button>
</TooltipTrigger>
{props.tooltip
? (
<TooltipPositioner className="block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none">
<TooltipPopup className="flex box-border origin-(--transform-origin) transition-[opacity,scale] transition-discrete motion-reduce:transition-none duration-100 data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 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 text-nowrap">
{props.tooltip}
</TooltipPopup>
</TooltipPositioner>
)
: null}
</TooltipRoot>
)
}export { default as Button } from './button.tsx'import 'prosekit/basic/style.css'
import 'prosekit/basic/typography.css'
import { createEditor, type NodeJSON } from 'prosekit/core'
import { ProseKit } from 'prosekit/solid'
import type { JSX } from 'solid-js'
import { sampleContent } from '../../sample/sample-doc-sub-sup.ts'
import { defineExtension } from './extension.ts'
import Toolbar from './toolbar.tsx'
interface EditorProps {
initialContent?: NodeJSON
}
export default function Editor(props: EditorProps): JSX.Element {
const defaultContent = props.initialContent ?? sampleContent
const extension = defineExtension()
const editor = createEditor({ extension, 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-[canvas] text-black dark:text-white">
<Toolbar />
<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 { canUseRegexLookbehind, defineBaseKeymap, union } from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineMarkInputRule } from 'prosekit/extensions/input-rule'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineSubscript } from 'prosekit/extensions/subscript'
import { defineSuperscript } from 'prosekit/extensions/superscript'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineSubscript(),
defineSuperscript(),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`~([^\s~]|[^\s~][^~]*[^\s~])~$`,
),
type: 'subscript',
}),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`\^([^\s^]|[^\s^][^^]*[^\s^])\^$`,
),
type: 'superscript',
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.tsx'import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/solid'
import type { JSX } from 'solid-js'
import { Button } from '../../ui/button/index.ts'
import type { EditorExtension } from './extension.ts'
function getToolbarItems(editor: Editor<EditorExtension>) {
return {
subscript: {
isActive: editor.marks.subscript.isActive(),
canExec: editor.commands.toggleSubscript.canExec(),
command: () => editor.commands.toggleSubscript(),
},
superscript: {
isActive: editor.marks.superscript.isActive(),
canExec: editor.commands.toggleSuperscript.canExec(),
command: () => editor.commands.toggleSuperscript(),
},
}
}
export default function Toolbar(): JSX.Element {
const items = useEditorDerivedValue(getToolbarItems)
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={items().subscript.isActive}
disabled={!items().subscript.canExec}
onClick={items().subscript.command}
>
Subscript
</Button>
<Button
pressed={items().superscript.isActive}
disabled={!items().superscript.canExec}
onClick={items().superscript.command}
>
Superscript
</Button>
</div>
)
}import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'H',
},
{
type: 'text',
marks: [
{
type: 'subscript',
},
],
text: '2',
},
{
type: 'text',
text: 'O is water. x',
},
{
type: 'text',
marks: [
{
type: 'superscript',
},
],
text: '2',
},
{
type: 'text',
text: ' is a square.',
},
],
},
],
}import { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/solid/tooltip'
import type { JSX } from 'solid-js'
export default function Button(props: {
pressed?: boolean
disabled?: boolean
onClick?: () => void
tooltip?: string
children: JSX.Element
}): JSX.Element {
return (
<TooltipRoot>
<TooltipTrigger class="block">
<button
data-state={props.pressed ? 'on' : 'off'}
disabled={props.disabled}
onClick={props.onClick}
onMouseDown={(event) => {
// Prevent the editor from being blurred when the button is clicked
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}
{props.tooltip ? <span class="sr-only">{props.tooltip}</span> : null}
</button>
</TooltipTrigger>
{props.tooltip
? (
<TooltipPositioner class="block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none">
<TooltipPopup class="flex box-border origin-(--transform-origin) transition-[opacity,scale] transition-discrete motion-reduce:transition-none duration-100 data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 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 text-nowrap">
{props.tooltip}
</TooltipPopup>
</TooltipPositioner>
)
: null}
</TooltipRoot>
)
}export { default as Button } from './button.tsx'<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 { untrack } from 'svelte'
import { sampleContent } from '../../sample/sample-doc-sub-sup.ts'
import { defineExtension } from './extension.ts'
import Toolbar from './toolbar.svelte'
const props: {
initialContent?: NodeJSON
} = $props()
const extension = defineExtension()
const defaultContent = untrack(() => 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-[canvas] text-black dark:text-white">
<Toolbar />
<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 { canUseRegexLookbehind, defineBaseKeymap, union } from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineMarkInputRule } from 'prosekit/extensions/input-rule'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineSubscript } from 'prosekit/extensions/subscript'
import { defineSuperscript } from 'prosekit/extensions/superscript'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineSubscript(),
defineSuperscript(),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`~([^\s~]|[^\s~][^~]*[^\s~])~$`,
),
type: 'subscript',
}),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`\^([^\s^]|[^\s^][^^]*[^\s^])\^$`,
),
type: 'superscript',
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.svelte'<script lang="ts">
import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/svelte'
import { Button } from '../../ui/button/index.ts'
import type { EditorExtension } from './extension.ts'
function getToolbarItems(editor: Editor<EditorExtension>) {
return {
subscript: {
isActive: editor.marks.subscript.isActive(),
canExec: editor.commands.toggleSubscript.canExec(),
command: () => editor.commands.toggleSubscript(),
},
superscript: {
isActive: editor.marks.superscript.isActive(),
canExec: editor.commands.toggleSuperscript.canExec(),
command: () => editor.commands.toggleSuperscript(),
},
}
}
const items = useEditorDerivedValue(getToolbarItems)
</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={$items.subscript.isActive}
disabled={!$items.subscript.canExec}
onClick={$items.subscript.command}
>
Subscript
</Button>
<Button
pressed={$items.superscript.isActive}
disabled={!$items.superscript.canExec}
onClick={$items.superscript.command}
>
Superscript
</Button>
</div>import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'H',
},
{
type: 'text',
marks: [
{
type: 'subscript',
},
],
text: '2',
},
{
type: 'text',
text: 'O is water. x',
},
{
type: 'text',
marks: [
{
type: 'superscript',
},
],
text: '2',
},
{
type: 'text',
text: ' is a square.',
},
],
},
],
}<script lang="ts">
import { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/svelte/tooltip'
interface Props {
pressed?: boolean
disabled?: boolean
onClick?: () => void
tooltip?: string
children?: import('svelte').Snippet
}
const props: Props = $props()
const pressed = $derived(props.pressed ?? false)
const disabled = $derived(props.disabled ?? false)
</script>
<TooltipRoot>
<TooltipTrigger class="block">
<button
data-state={pressed ? 'on' : 'off'}
{disabled}
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"
onclick={props.onClick}
onmousedown={(e) => e.preventDefault()}
>
{@render props.children?.()}
{#if props.tooltip}
<span class="sr-only">{props.tooltip}</span>
{/if}
</button>
</TooltipTrigger>
{#if props.tooltip}
<TooltipPositioner class="block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none">
<TooltipPopup class="flex box-border origin-(--transform-origin) transition-[opacity,scale] transition-discrete motion-reduce:transition-none duration-100 data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 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 text-nowrap">
{props.tooltip}
</TooltipPopup>
</TooltipPositioner>
{/if}
</TooltipRoot>export { default as Button } from './button.svelte'<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-sub-sup.ts'
import { defineExtension } from './extension.ts'
import Toolbar from './toolbar.vue'
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-[canvas] text-black dark:text-white">
<Toolbar />
<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 { canUseRegexLookbehind, defineBaseKeymap, union } from 'prosekit/core'
import { defineDoc } from 'prosekit/extensions/doc'
import { defineMarkInputRule } from 'prosekit/extensions/input-rule'
import { defineParagraph } from 'prosekit/extensions/paragraph'
import { defineSubscript } from 'prosekit/extensions/subscript'
import { defineSuperscript } from 'prosekit/extensions/superscript'
import { defineText } from 'prosekit/extensions/text'
export function defineExtension() {
return union(
defineBaseKeymap(),
defineDoc(),
defineText(),
defineParagraph(),
defineSubscript(),
defineSuperscript(),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`~([^\s~]|[^\s~][^~]*[^\s~])~$`,
),
type: 'subscript',
}),
defineMarkInputRule({
regex: new RegExp(
(canUseRegexLookbehind() ? String.raw`(?<=\s|^)` : '')
+ String.raw`\^([^\s^]|[^\s^][^^]*[^\s^])\^$`,
),
type: 'superscript',
}),
)
}
export type EditorExtension = ReturnType<typeof defineExtension>export { default as ExampleEditor } from './editor.vue'<script setup lang="ts">
import type { Editor } from 'prosekit/core'
import { useEditorDerivedValue } from 'prosekit/vue'
import { Button } from '../../ui/button/index.ts'
import type { EditorExtension } from './extension.ts'
function getToolbarItems(editor: Editor<EditorExtension>) {
return {
subscript: {
isActive: editor.marks.subscript.isActive(),
canExec: editor.commands.toggleSubscript.canExec(),
command: () => editor.commands.toggleSubscript(),
},
superscript: {
isActive: editor.marks.superscript.isActive(),
canExec: editor.commands.toggleSuperscript.canExec(),
command: () => editor.commands.toggleSuperscript(),
},
}
}
const items = useEditorDerivedValue(getToolbarItems)
</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="items.subscript.isActive"
:disabled="!items.subscript.canExec"
@click="items.subscript.command"
>
Subscript
</Button>
<Button
:pressed="items.superscript.isActive"
:disabled="!items.superscript.canExec"
@click="items.superscript.command"
>
Superscript
</Button>
</div>
</template>import type { NodeJSON } from 'prosekit/core'
export const sampleContent: NodeJSON = {
type: 'doc',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'H',
},
{
type: 'text',
marks: [
{
type: 'subscript',
},
],
text: '2',
},
{
type: 'text',
text: 'O is water. x',
},
{
type: 'text',
marks: [
{
type: 'superscript',
},
],
text: '2',
},
{
type: 'text',
text: ' is a square.',
},
],
},
],
}<script setup lang="ts">
import { TooltipPopup, TooltipPositioner, TooltipRoot, TooltipTrigger } from 'prosekit/vue/tooltip'
const props = defineProps<{
pressed?: boolean
disabled?: boolean
onClick?: () => void
tooltip?: string
}>()
</script>
<template>
<TooltipRoot>
<TooltipTrigger class="block">
<button
:data-state="props.pressed ? 'on' : 'off'"
:disabled="props.disabled"
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="props.onClick"
@mousedown.prevent
>
<slot />
<span v-if="props.tooltip" class="sr-only">{{ props.tooltip }}</span>
</button>
</TooltipTrigger>
<TooltipPositioner v-if="props.tooltip" class="block overflow-visible w-min h-min z-50 ease-out transition-transform duration-100 motion-reduce:transition-none">
<TooltipPopup class="flex box-border origin-(--transform-origin) transition-[opacity,scale] transition-discrete motion-reduce:transition-none duration-100 data-[state=closed]:duration-150 data-[state=closed]:opacity-0 starting:opacity-0 data-[state=closed]:scale-95 starting:scale-95 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 text-nowrap">
{{ props.tooltip }}
</TooltipPopup>
</TooltipPositioner>
</TooltipRoot>
</template>export { default as Button } from './button.vue'Commands
Section titled “Commands”toggleSubscript
Section titled “toggleSubscript”editor const editor: Editor<SubscriptExtension> .commands Editor<SubscriptExtension>.commands: ToCommandAction<{
toggleSubscript: [];
}>
All
{@link
CommandAction
}
s defined by the editor. .toggleSubscript toggleSubscript: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully
executed, otherwise `false`. ()Input Rules
Section titled “Input Rules”Input rules are not included by default. You can add them manually using defineMarkInputRule from prosekit/extensions/input-rule:
import { canUseRegexLookbehind const canUseRegexLookbehind: () => booleanChecks if the browser supports [regex lookbehind assertion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertion). , union function union<const E extends readonly Extension[]>(...exts: E): Union<E> (+1 overload)Merges multiple extensions into one. You can pass multiple extensions as
arguments or a single array containing multiple extensions.@throwsIf no extensions are provided.@example```ts
function defineFancyNodes() {
return union(
defineFancyParagraph(),
defineFancyHeading(),
)
}
```@example```ts
function defineFancyNodes() {
return union([
defineFancyParagraph(),
defineFancyHeading(),
])
}
``` } from 'prosekit/core'
import { defineMarkInputRule function defineMarkInputRule(options: MarkInputRuleOptions): PlainExtensionDefines an input rule for automatically adding inline marks when a given
pattern is typed. } from 'prosekit/extensions/input-rule'
import { defineSubscript function defineSubscript(): SubscriptExtension } from 'prosekit/extensions/subscript'
const extension const extension: Union<readonly [SubscriptExtension, PlainExtension]> = union union<readonly [SubscriptExtension, PlainExtension]>(exts_0: SubscriptExtension, exts_1: PlainExtension): Union<readonly [SubscriptExtension, PlainExtension]> (+1 overload)Merges multiple extensions into one. You can pass multiple extensions as
arguments or a single array containing multiple extensions.@throwsIf no extensions are provided.@example```ts
function defineFancyNodes() {
return union(
defineFancyParagraph(),
defineFancyHeading(),
)
}
```@example```ts
function defineFancyNodes() {
return union([
defineFancyParagraph(),
defineFancyHeading(),
])
}
``` (
defineSubscript function defineSubscript(): SubscriptExtension (),
defineMarkInputRule function defineMarkInputRule(options: MarkInputRuleOptions): PlainExtensionDefines an input rule for automatically adding inline marks when a given
pattern is typed. ({
regex MarkInputRuleOptions.regex: RegExpThe regular expression to match against, which should end with `$` and has
exactly one capture group. All other matched text outside the capture group
will be deleted. : new RegExp var RegExp: RegExpConstructor
new (pattern: RegExp | string, flags?: string) => RegExp (+2 overloads)
(
(canUseRegexLookbehind function canUseRegexLookbehind(): booleanChecks if the browser supports [regex lookbehind assertion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertion). () ? String var String: StringConstructorAllows manipulation and formatting of text strings and determination and location of substrings within strings. .raw StringConstructor.raw(template: {
raw: readonly string[] | ArrayLike<string>;
}, ...substitutions: any[]): string
String.raw is usually used as a tag function of a Tagged Template String. When called as
such, the first argument will be a well formed template call site object and the rest
parameter will contain the substitution values. It can also be called directly, for example,
to interleave strings and values from your own tag function, and in this case the only thing
it needs from the first argument is the raw property.@paramtemplate A well-formed template string call site representation.@paramsubstitutions A set of substitution values. `(?<=\s|^)` : '')
+ String var String: StringConstructorAllows manipulation and formatting of text strings and determination and location of substrings within strings. .raw StringConstructor.raw(template: {
raw: readonly string[] | ArrayLike<string>;
}, ...substitutions: any[]): string
String.raw is usually used as a tag function of a Tagged Template String. When called as
such, the first argument will be a well formed template call site object and the rest
parameter will contain the substitution values. It can also be called directly, for example,
to interleave strings and values from your own tag function, and in this case the only thing
it needs from the first argument is the raw property.@paramtemplate A well-formed template string call site representation.@paramsubstitutions A set of substitution values. `~([^\s~]|[^\s~][^~]*[^\s~])~$`,
),
type MarkInputRuleOptions.type: string | MarkTypeThe type of mark to set. : 'subscript',
}),
)Typing ~text~ followed by a space will automatically convert the text to subscript.