Example: text-color
<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 { defineExtension } from './extension' import InlineMenu from './inline-menu.vue' const defaultContent = '<p>' + '<span style="color: #ef4444">Select</span> ' + '<span style="color: #f97316">some</span> ' + '<span style="color: #eab308">text</span> ' + '<span style="color: #22c55e">to</span> ' + '<span style="color: #3b82f6">change</span> ' + '<span style="color: #6366f1">the</span> ' + '<span style="color: #a855f7">color</span> ' + '</p>' 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"> <div class="relative w-full flex-1 box-border overflow-y-scroll"> <div ref="editorRef" spellcheck="false" 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" /> <InlineMenu /> </div> </div> </ProseKit> </template>
import { defineBasicExtension } from 'prosekit/basic' import { union } from 'prosekit/core' import { defineTextColor } from './text-color' export function defineExtension() { return union(defineBasicExtension(), defineTextColor()) } export type EditorExtension = ReturnType<typeof defineExtension>
<script setup lang="ts"> import { useEditor, useKeymap, } from 'prosekit/vue' import { InlinePopover } from 'prosekit/vue/inline-popover' import { ref } from 'vue' import Button from './button.vue' import type { EditorExtension } from './extension' const editor = useEditor<EditorExtension>({ update: true }) const colors = [ { name: 'default', value: '' }, { name: 'red', value: '#ef4444' }, { name: 'orange', value: '#f97316' }, { name: 'yellow', value: '#eab308' }, { name: 'green', value: '#22c55e' }, { name: 'blue', value: '#3b82f6' }, { name: 'indigo', value: '#6366f1' }, { name: 'violet', value: '#a855f7' }, ] function hasTextColor(color: string) { return editor.value.marks.textColor.isActive({ color }) } function toggleTextColor(color: string) { if (!color || hasTextColor(color)) { editor.value.commands.removeTextColor() } else { editor.value.commands.setTextColor({ color }) } } const open = ref(false) function onOpenChange(value: boolean) { open.value = value } function onEscape() { if (open.value) { open.value = false return true } return false } useKeymap({ Escape: onEscape }) </script> <template> <InlinePopover class="z-10 box-border border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 shadow-lg [&:not([data-state])]:hidden relative flex min-w-[8rem] space-x-1 overflow-auto whitespace-nowrap rounded-md p-1" :open="open" @open-change="onOpenChange" > <Button v-for="color in colors" :key="color.name" :pressed="hasTextColor(color.value)" :tooltip="color.name" @click="() => toggleTextColor(color.value)" > <span :style="{ color: color.value }">A</span> </Button> </InlinePopover> </template>
import { addMark, defineCommands, defineMarkSpec, removeMark, union, } from 'prosekit/core' import type { Command } from 'prosekit/pm/state' export interface TextColorAttrs { color: string | null } export function defineTextColorSpec() { return defineMarkSpec<'textColor', TextColorAttrs>({ name: 'textColor', attrs: { color: { default: null }, }, parseDOM: [ { style: 'color', getAttrs: (value) => { return { color: value } }, }, ], toDOM: (mark) => { return ['span', { style: `color: ${mark.attrs.color};` }, 0] }, }) } function setTextColor(attrs: TextColorAttrs): Command { return addMark({ type: 'textColor', attrs }) } function removeTextColor(): Command { return removeMark({ type: 'textColor' }) } export function defineTextColorCommands() { return defineCommands({ setTextColor, removeTextColor, }) } export function defineTextColor() { return union(defineTextColorSpec(), defineTextColorCommands()) }
<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 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" @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-sm [&: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>