Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
ccdb068
feat(Editor): new component
benjamincanac Nov 3, 2025
9e70cb1
Merge branch 'v4' into feat/editor
benjamincanac Nov 5, 2025
f82f6dc
improve
benjamincanac Nov 6, 2025
1032eea
rename `as` to `kind`
benjamincanac Nov 6, 2025
bb85b2f
handle undo/redo
benjamincanac Nov 6, 2025
17931fa
improve theme
benjamincanac Nov 6, 2025
d0dc989
improve theme
benjamincanac Nov 6, 2025
b24f5c6
improve playground content
benjamincanac Nov 6, 2025
7ca8c29
improve
benjamincanac Nov 7, 2025
04fa8de
improve
benjamincanac Nov 7, 2025
25a83b7
clean
benjamincanac Nov 7, 2025
fc65e79
feat(EditorToolbar): disable dropdown button if all commands are disa…
benjamincanac Nov 7, 2025
d1e1c60
fix: uniformize as props with Primitive
benjamincanac Nov 7, 2025
c2bb310
fix(EditorDragHandle): wrong type
benjamincanac Nov 7, 2025
3fa5d30
test: tmp ts fix
benjamincanac Nov 7, 2025
4a4410f
improve horizontal rule
benjamincanac Nov 7, 2025
fcddd03
fix(EditorDragHandle): center vertically when under 40px
benjamincanac Nov 7, 2025
5e98ac9
test: update vue snapshots
benjamincanac Nov 7, 2025
216506d
move text align extension to playground only
benjamincanac Nov 7, 2025
94d6ef5
fix(EditorToolbar): refactor
benjamincanac Nov 7, 2025
d8c9372
feat(EditorToolbar): handle custom handlers
benjamincanac Nov 7, 2025
1a8092b
playground: remove tasklist
benjamincanac Nov 7, 2025
700e7b1
fix(EditorToolbar): clean
benjamincanac Nov 7, 2025
c89a6b7
Merge branch 'v4' into feat/editor
benjamincanac Nov 8, 2025
adc2614
feat(Editor): add `placeholder` prop
benjamincanac Nov 8, 2025
4bcd6b3
feat(Editor): implement menus
benjamincanac Nov 8, 2025
9445f33
fix: improve menus
benjamincanac Nov 9, 2025
99a926f
test: update snapshots
benjamincanac Nov 9, 2025
509127b
fix: clean code
benjamincanac Nov 9, 2025
09bba95
fix(useEditorMenu): ignore label and separator items
benjamincanac Nov 9, 2025
4ddb3db
fix(useEditorMenu): handle animations
benjamincanac Nov 9, 2025
4296e00
fix(useEditorMenu): lint
benjamincanac Nov 9, 2025
2da3c50
feat(EditorMenu): configure dropcursor
benjamincanac Nov 9, 2025
1a282ed
fix(Editor): clean
benjamincanac Nov 9, 2025
4a4fcc4
playground: fix text align extension
benjamincanac Nov 9, 2025
106a065
chore: add missing files
benjamincanac Nov 9, 2025
dda7164
docs: prepare
benjamincanac Nov 9, 2025
105053e
Merge branch 'v4' into feat/editor
benjamincanac Nov 10, 2025
1ffa4b0
chore(deps): update
benjamincanac Nov 10, 2025
ed65435
fix: consistent options
benjamincanac Nov 11, 2025
bbdd268
fix(useEditorMenu): improve positioning
benjamincanac Nov 11, 2025
0d661a0
fix(Editor): only show placeholder on p and headings
benjamincanac Nov 11, 2025
cf36ab4
Merge branch 'v4' into feat/editor
benjamincanac Nov 11, 2025
3c84530
fix(editor): clean theme
benjamincanac Nov 11, 2025
61bae97
feat(Editor): handle links with prompt for now
benjamincanac Nov 11, 2025
47113ac
feat(Editor): export `Editor` type
benjamincanac Nov 12, 2025
61862da
fix(Editor): improve execute handler
benjamincanac Nov 12, 2025
89667e2
feat(EditorToolbar): improve slots
benjamincanac Nov 12, 2025
5f85f56
playground: add link example
benjamincanac Nov 12, 2025
666b800
feat(FileUpload): add image upload extension
benjamincanac Nov 12, 2025
4b4f590
docs: add images
benjamincanac Nov 12, 2025
a3599c1
playground: fix extension import
benjamincanac Nov 12, 2025
063cd21
fix(EditorDragHandle): improve padding for mobile
benjamincanac Nov 12, 2025
2987c59
playground: fix imports
benjamincanac Nov 12, 2025
164b0ff
chore: update theme
benjamincanac Nov 12, 2025
8db80b3
Merge branch 'v4' into feat/editor
benjamincanac Nov 12, 2025
051e8c0
chore(Editor): add text align extension as baseline
benjamincanac Nov 12, 2025
8b5084a
Merge branch 'v4' into feat/editor
benjamincanac Nov 13, 2025
a4210a2
feat: add `data-slot` attrs
benjamincanac Nov 13, 2025
f816eeb
fix: add missing attr
benjamincanac Nov 13, 2025
025c42e
playground(nuxt.config): add optimize deps
benjamincanac Nov 13, 2025
6fa004b
chore(theme): update placeholder
benjamincanac Nov 13, 2025
cc12ba2
fix(VersionMenu): clean
benjamincanac Nov 14, 2025
30129f7
chore(deps): update tiptap
benjamincanac Nov 14, 2025
ace46b9
playground: update optimizeDeps
benjamincanac Nov 14, 2025
88b5795
chore(theme): update
benjamincanac Nov 14, 2025
7a01956
feat(Editor): major changes with types + mapEditorItems fn
benjamincanac Nov 14, 2025
b877ef5
chore(theme): improve selection
benjamincanac Nov 15, 2025
836b11f
fix(content.config): wrong components category
benjamincanac Nov 15, 2025
25f3724
fix(Editor): prevent content rendering before editor init
benjamincanac Nov 15, 2025
9c57394
fix(EditorDragHandle): remove blur on click
benjamincanac Nov 15, 2025
074e4f4
chore(theme): prevent selected node display for node view
benjamincanac Nov 15, 2025
f50710a
feat(Editor): improve actions
benjamincanac Nov 16, 2025
4a0f866
fix(useEditorMenu): wrong type import
benjamincanac Nov 17, 2025
dd0fd0f
Merge branch 'v4' into feat/editor
benjamincanac Nov 17, 2025
f883211
Merge branch 'v4' into feat/editor
benjamincanac Nov 17, 2025
1e3e684
Merge branch 'v4' into feat/editor
benjamincanac Nov 19, 2025
4243839
up
benjamincanac Nov 19, 2025
5ea503a
chore(deps): update tiptap
benjamincanac Nov 19, 2025
14c970f
feat(Editor): add `suggestion` handler
benjamincanac Nov 19, 2025
fb8b0ba
chore: update theme
benjamincanac Nov 19, 2025
427558c
test: add nextTick
benjamincanac Nov 19, 2025
09df76d
feat(Editor): improve
benjamincanac Nov 19, 2025
2acbf0d
chore(editor): clean utils
benjamincanac Nov 19, 2025
b06c456
feat(EditorDragHandle): expose click method
benjamincanac Nov 20, 2025
5c02973
fix: remove id from mentions
benjamincanac Nov 20, 2025
24763ae
fix: prevent toolbar display
benjamincanac Nov 20, 2025
decda0e
chore(EditorDragHandle): add gap on items
benjamincanac Nov 20, 2025
0c93edf
fix(Editor): improve list handler
benjamincanac Nov 20, 2025
83e504a
chore: improve metas
benjamincanac Nov 20, 2025
7c81e24
chore: dont re-export Editor type
benjamincanac Nov 20, 2025
af197af
fix(Editor): handle model value refresh
benjamincanac Nov 20, 2025
0c5eb66
docs: add missing image
benjamincanac Nov 20, 2025
98820c7
Merge branch 'v4' into feat/editor
benjamincanac Nov 21, 2025
1288d95
fix(Editor): dont register TextAlign extension by default
benjamincanac Nov 21, 2025
da73a91
docs: update `nuxt-component-meta` to override Editor type
benjamincanac Nov 21, 2025
151286d
fix: provide defaults to inject
benjamincanac Nov 21, 2025
e5a772b
docs: update
benjamincanac Nov 21, 2025
cca3424
docs: update
benjamincanac Nov 21, 2025
575ba17
fix(EditorToolbar/useEditorMenu): default options
benjamincanac Nov 21, 2025
00b1d01
feat(Editor): add props to configure extensions
benjamincanac Nov 22, 2025
5941f3f
Merge branch 'v4' into feat/editor
benjamincanac Nov 24, 2025
4a1678b
Merge branch 'v4' into feat/editor
benjamincanac Nov 25, 2025
d28627c
chore(editor): improve selection
benjamincanac Nov 25, 2025
00b4eaa
fix(EditorToolbar): handle children label
benjamincanac Nov 25, 2025
ba8b790
fix(Editor): allow custom keys in handlers
benjamincanac Nov 25, 2025
8a5a53e
docs: update
benjamincanac Nov 25, 2025
b6aa9f9
docs(nuxt.config): update optimizeDeps
benjamincanac Nov 25, 2025
5f235f3
feat(Editor): handle generics on custom handlers
benjamincanac Nov 25, 2025
2cfa762
docs: update
benjamincanac Nov 25, 2025
08194c1
fix(useEditorMenu): close on click outside
benjamincanac Nov 26, 2025
7ce4989
fix(Editor): various theme improvements
benjamincanac Nov 26, 2025
a5b2a58
docs: update
benjamincanac Nov 26, 2025
1a78d48
docs: update
benjamincanac Nov 27, 2025
89d2596
fix: augment `@tiptap/core`
benjamincanac Nov 27, 2025
2fe8618
docs: update
benjamincanac Nov 27, 2025
ce1a069
update
benjamincanac Nov 27, 2025
d89cd60
update
benjamincanac Nov 27, 2025
688908a
playground: fix import
benjamincanac Nov 28, 2025
e236d90
Merge branch 'v4' into feat/editor
benjamincanac Nov 28, 2025
e980da9
chore(deps): update tiptap
benjamincanac Nov 28, 2025
61cb1f2
chore: add resolutions
benjamincanac Nov 28, 2025
6dffb1c
chore(deps): update resolutions
benjamincanac Nov 28, 2025
a6881bc
chore(renovate): make group
benjamincanac Nov 28, 2025
2e915d7
chore(deps): remove `@tiptap/vue-3` resolution
benjamincanac Nov 28, 2025
a0de230
docs: update
benjamincanac Nov 28, 2025
ea9dcf6
fix(Editor): remove `Content` type export
benjamincanac Nov 28, 2025
1986137
chore(deps): remove resolutions
benjamincanac Nov 28, 2025
538814f
playground: type editor as `any`
benjamincanac Nov 28, 2025
f692297
docs(nuxt.config): clean editor props
benjamincanac Nov 28, 2025
26c9e0c
playgrounds: dont use Editor type
benjamincanac Nov 28, 2025
323ee74
docs(nuxt.config): optimize component meta
benjamincanac Nov 28, 2025
3100191
docs: update
benjamincanac Nov 28, 2025
9cebed0
fix: optimize button extends
benjamincanac Nov 28, 2025
70533a3
Merge branch 'v4' into feat/editor
benjamincanac Nov 28, 2025
0a59a15
feat: keep cursor position when external model update
larbish Nov 28, 2025
1e44530
docs: update
benjamincanac Nov 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/app/assets/icons/tiptap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/app/components/VersionMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const items = computed(() => {
trailing-icon="i-lucide-chevron-down"
size="xs"
class="-mb-[6px] font-semibold rounded-full truncate"
:class="[open && 'bg-primary/15 ']"
:class="[open && 'bg-primary/15']"
:ui="{
trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
}"
Expand Down
6 changes: 5 additions & 1 deletion docs/app/components/content/ComponentCode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ const props = defineProps<{
* Whether to add overflow-hidden to wrapper
*/
overflowHidden?: boolean
/**
* Whether to add background-elevated to wrapper
*/
elevated?: boolean
}>()

const route = useRoute()
Expand Down Expand Up @@ -416,7 +420,7 @@ const { data: ast } = await useAsyncData(codeKey, async () => {
</template>
</div>

<div v-if="component" class="flex justify-center border border-b-0 border-muted relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class, { 'overflow-hidden': props.overflowHidden }]">
<div v-if="component" class="flex justify-center border border-b-0 border-muted relative p-4 z-[1]" :class="[!options.length && 'rounded-t-md', props.class, { 'overflow-hidden': props.overflowHidden, 'dark:bg-neutral-950/50': props.elevated }]">
<component :is="component" v-bind="{ ...componentProps, ...componentEvents }">
<template v-for="slot in Object.keys(slots || {})" :key="slot" #[slot]>
<slot :name="slot" mdc-unwrap="p">
Expand Down
14 changes: 10 additions & 4 deletions docs/app/components/content/ComponentExample.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,15 @@ const props = withDefaults(defineProps<{
* Whether to add overflow-hidden to wrapper
*/
overflowHidden?: boolean
/**
* Whether to add background-elevated to wrapper
*/
elevated?: boolean
lang?: string
}>(), {
preview: true,
source: true
source: true,
lang: 'vue'
})

const slots = defineSlots<{
Expand All @@ -88,7 +94,7 @@ const code = computed(() => {
`
}

code += `\`\`\`vue ${props.preview ? '' : ` [${data.pascalName}.vue]`}${props.highlights?.length ? `{${props.highlights.join('-')}}` : ''}
code += `\`\`\`${props.lang} ${props.preview ? '' : ` [${data.pascalName}.${props.lang}]`}${props.highlights?.length ? `{${props.highlights.join('-')}}` : ''}
${data?.code ?? ''}
\`\`\``

Expand Down Expand Up @@ -208,9 +214,9 @@ const urlSearchParams = computed(() => {
v-bind="typeof iframe === 'object' ? iframe : {}"
:src="`/examples/${name}?${urlSearchParams}`"
class="relative w-full"
:class="[props.class, !iframeMobile && 'lg:left-1/2 lg:-translate-x-1/2 lg:w-[1024px]']"
:class="[props.class, { 'dark:bg-neutral-950/50 rounded-t-md': props.elevated }, !iframeMobile && 'lg:left-1/2 lg:-translate-x-1/2 lg:w-[1024px]']"
/>
<div v-else class="flex justify-center p-4" :class="props.class">
<div v-else class="flex justify-center p-4" :class="[props.class, { 'dark:bg-neutral-950/50 rounded-t-md': props.elevated }]">
<component :is="camelName" v-bind="{ ...componentProps, ...optionsValues }" />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { upperFirst } from 'scule'
import type { DropdownMenuItem } from '@nuxt/ui'
import { mapEditorItems } from '@nuxt/ui/utils/editor'
import type { Editor, Node } from '@tiptap/vue-3'

const value = ref(`Hover over the left side to see both drag handle and menu button.

Click the menu to see block actions. Try duplicating or deleting a block.`)

const selectedNode = ref<{ node: Node | null, pos: number }>()

const getMenuItems = (editor: Editor): DropdownMenuItem[][] => {
if (!selectedNode.value?.node) {
return []
}

return mapEditorItems(editor, [[
{
type: 'label',
label: upperFirst(selectedNode.value.node.type)
},
{
label: 'Turn into',
icon: 'i-lucide-repeat-2',
children: [
{ kind: 'paragraph', label: 'Paragraph', icon: 'i-lucide-type' },
{ kind: 'heading', level: 1, label: 'Heading 1', icon: 'i-lucide-heading-1' },
{ kind: 'heading', level: 2, label: 'Heading 2', icon: 'i-lucide-heading-2' },
{ kind: 'heading', level: 3, label: 'Heading 3', icon: 'i-lucide-heading-3' },
{ kind: 'heading', level: 4, label: 'Heading 4', icon: 'i-lucide-heading-4' },
{ kind: 'bulletList', label: 'Bullet List', icon: 'i-lucide-list' },
{ kind: 'orderedList', label: 'Ordered List', icon: 'i-lucide-list-ordered' },
{ kind: 'blockquote', label: 'Blockquote', icon: 'i-lucide-text-quote' },
{ kind: 'codeBlock', label: 'Code Block', icon: 'i-lucide-square-code' }
]
},
{
kind: 'clearFormatting',
pos: selectedNode.value?.pos,
label: 'Reset formatting',
icon: 'i-lucide-rotate-ccw'
}
], [
{
kind: 'duplicate',
pos: selectedNode.value?.pos,
label: 'Duplicate',
icon: 'i-lucide-copy'
},
{
label: 'Copy to clipboard',
icon: 'i-lucide-clipboard',
onSelect: async () => {
if (!selectedNode.value) return

const pos = selectedNode.value.pos
const node = editor.state.doc.nodeAt(pos)
if (node) {
await navigator.clipboard.writeText(node.textContent)
}
}
}
], [
{
kind: 'moveUp',
pos: selectedNode.value?.pos,
label: 'Move up',
icon: 'i-lucide-arrow-up'
},
{
kind: 'moveDown',
pos: selectedNode.value?.pos,
label: 'Move down',
icon: 'i-lucide-arrow-down'
}
], [
{
kind: 'delete',
pos: selectedNode.value?.pos,
label: 'Delete',
icon: 'i-lucide-trash'
}
]]) as DropdownMenuItem[][]
}
</script>

<template>
<UEditor v-slot="{ editor }" v-model="value" class="w-full min-h-21">
<UEditorDragHandle v-slot="{ ui }" :editor="editor" @node-change="selectedNode = $event">
<UDropdownMenu
v-slot="{ open }"
:modal="false"
:items="getMenuItems(editor)"
:content="{ side: 'left' }"
:ui="{ content: 'w-48', label: 'text-xs' }"
@update:open="editor.chain().setMeta('lockDragHandle', $event).run()"
>
<UButton
icon="i-lucide-more-vertical"
color="neutral"
variant="ghost"
active-variant="soft"
size="sm"
:active="open"
:class="ui.handle()"
/>
</UDropdownMenu>
</UEditorDragHandle>
</UEditor>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
const value = ref(`# Drag Handle

Hover over the left side of this block to see the drag handle appear and reorder blocks.`)
</script>

<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-21"
>
<UEditorDragHandle :editor="editor" />
</UEditor>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import type { EditorSuggestionMenuItem } from '@nuxt/ui'

const value = ref(`Click the plus button to open the suggestion menu and add new blocks.

The button appears when hovering over blocks.`)

const suggestionItems: EditorSuggestionMenuItem[][] = [[{
kind: 'heading',
level: 1,
label: 'Heading 1',
icon: 'i-lucide-heading-1'
}, {
kind: 'heading',
level: 2,
label: 'Heading 2',
icon: 'i-lucide-heading-2'
}, {
kind: 'bulletList',
label: 'Bullet List',
icon: 'i-lucide-list'
}, {
kind: 'blockquote',
label: 'Blockquote',
icon: 'i-lucide-text-quote'
}]]
</script>

<template>
<UEditor v-slot="{ editor, handlers }" v-model="value" class="w-full min-h-21">
<UEditorDragHandle v-slot="{ ui, onClick }" :editor="editor">
<UButton
icon="i-lucide-plus"
color="neutral"
variant="ghost"
size="sm"
:class="ui.handle()"
@click="(e) => {
e.stopPropagation()
const node = onClick(e)
handlers.suggestion?.execute(editor, { pos: node?.pos }).run()
}"
/>

<UButton
icon="i-lucide-grip-vertical"
color="neutral"
variant="ghost"
size="sm"
:class="ui.handle()"
/>
</UEditorDragHandle>

<UEditorSuggestionMenu :editor="editor" :items="suggestionItems" />
</UEditor>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup lang="ts">
import type { EditorEmojiMenuItem } from '@nuxt/ui'
import { Emoji, gitHubEmojis } from '@tiptap/extension-emoji'

const value = ref(`# Emoji Menu

Type : to insert emojis and select from the list of available emojis.`)

const items: EditorEmojiMenuItem[] = gitHubEmojis.filter(emoji => !emoji.name.startsWith('regional_indicator_'))

// SSR-safe function to append menus to body (avoids z-index issues in docs)
const appendToBody = import.meta.client ? () => document.body : undefined
</script>

<template>
<UEditor
v-slot="{ editor }"
v-model="value"
:extensions="[Emoji]"
content-type="markdown"
placeholder="Type : to add emojis..."
class="w-full min-h-21"
>
<UEditorEmojiMenu :editor="editor" :items="items" :append-to="appendToBody" />
</UEditor>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script setup lang="ts">
import type { EditorEmojiMenuItem } from '@nuxt/ui'
import { Emoji } from '@tiptap/extension-emoji'

const value = ref(`Type : to see popular emojis.`)

const emojiItems: EditorEmojiMenuItem[] = [{
name: 'smile',
emoji: 'πŸ˜„',
shortcodes: ['smile'],
tags: ['happy', 'joy', 'pleased']
}, {
name: 'heart',
emoji: '❀️',
shortcodes: ['heart'],
tags: ['love', 'like']
}, {
name: 'thumbsup',
emoji: 'πŸ‘',
shortcodes: ['thumbsup', '+1'],
tags: ['approve', 'ok']
}, {
name: 'fire',
emoji: 'πŸ”₯',
shortcodes: ['fire'],
tags: ['hot', 'burn']
}, {
name: 'rocket',
emoji: 'πŸš€',
shortcodes: ['rocket'],
tags: ['ship', 'launch']
}, {
name: 'eyes',
emoji: 'πŸ‘€',
shortcodes: ['eyes'],
tags: ['look', 'watch']
}, {
name: 'tada',
emoji: 'πŸŽ‰',
shortcodes: ['tada'],
tags: ['party', 'celebration']
}, {
name: 'thinking',
emoji: 'πŸ€”',
shortcodes: ['thinking'],
tags: ['hmm', 'think', 'consider']
}]
</script>

<template>
<UEditor
v-slot="{ editor }"
v-model="value"
:extensions="[Emoji]"
placeholder="Type : to add emojis..."
class="w-full min-h-21"
>
<UEditorEmojiMenu :editor="editor" :items="emojiItems" />
</UEditor>
</template>
Loading
Loading