EditeurToolbar
Utilisation
Le composant EditorToolbar affiche une barre d'outils de boutons de mise en forme qui synchronisent automatiquement leur état actif avec le contenu de l'éditeur. Il prend en charge trois modes de mise en page à l'aide du package @tiptap/vue-3/menus:
fixed(toujours visible)bubble(apparaît sur la sélection de texte)floating(s'affiche sur les lignes vides)
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: EditorToolbarItem[][] = [
[
{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start'
},
items: [
{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
},
{
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
},
{
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
},
{
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}
]
}
],
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' }
},
{
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' }
},
{
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' }
},
{
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' }
},
{
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' }
}
]
]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-21">
<UEditorToolbar :editor="editor" :items="items" layout="bubble" />
</UEditor>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`# Toolbar
Select some text to see the formatting toolbar appear above your selection.`)
const items: EditorToolbarItem[][] = [
[
{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start'
},
items: [
{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
},
{
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
},
{
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
},
{
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}
]
}
],
[
{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' }
},
{
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' }
},
{
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' }
},
{
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' }
},
{
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' }
}
]
]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-21">
<UEditorToolbar :editor="editor" :items="items" layout="bubble" />
</UEditor>
</template>Éléments
Utilisez le prop items comme un tableau d'objets avec les propriétés suivantes:
- x
label?: stringx{lang="ts-type"} - x
icon?: stringx{lang="ts-type"} - x
color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"xx{lang="ts-type"} - x
activeColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"x{lang="ts-type"} - x
variant?: "solid" | "outline" | "soft" | "ghost" | "link" | "subtle"x{lang="ts-type"} - x
activeVariant?: "solid" | "outline" | "soft" | "ghost" | "link" | "subtle"x{lang="ts-type"} - x
size?: "xs" | "sm" | "md" | "lg" | "xl"x{lang="ts-type"} Xph052xxxkind?: "mark" | "textAlign" | "heading" | "link" | "image" | "blockquote" | "bulletList" | "orderedList" | "taskList" | "codeBlock" | "horizontalRule" | "paragraph" | "undo" | "redo" | "clearFormatting" | "duplicate" | "delete" | "moveUp" | "moveDown" | "suggestion" | "mention" | "emoji"x{lang="ts-type"}x - xx
disabled?: booleanxx{lang="ts-type"} - x
loading?: booleanxx{lang="ts-type"} - xx
active?: booleanxx{lang="ts-type"} - x
tooltip?: TooltipPropsx{lang="ts-type"} Xph071xxxslot?: stringx{lang="ts-type"}x - x
onClick?: (e: MouseEvent) => voidxx{lang="ts-type"} - x
items?: EditorToolbarItem[] | EditorToolbarItem[][]xx{lang="ts-type"} - x
class?: anyxx{lang="ts-type"}
Vous pouvez passer n'importe quelle propriété du composant Button telle que color, variant, size, etc.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
import { TextAlign } from '@tiptap/extension-text-align'
const value = ref(`This toolbar showcases **all available formatting options** using built-in handlers. Try the different controls to see them in action!
You can apply **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, and \`inline code\` formatting to your text.
`)
const items: EditorToolbarItem[][] = [
// History controls
[{
kind: 'undo',
icon: 'i-lucide-undo',
tooltip: { text: 'Undo' }
}, {
kind: 'redo',
icon: 'i-lucide-redo',
tooltip: { text: 'Redo' }
}],
// Block types
[{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start'
},
items: [{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
}, {
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
}, {
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
}, {
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}]
}, {
icon: 'i-lucide-list',
tooltip: { text: 'Lists' },
content: {
align: 'start'
},
items: [{
kind: 'bulletList',
icon: 'i-lucide-list',
label: 'Bullet List'
}, {
kind: 'orderedList',
icon: 'i-lucide-list-ordered',
label: 'Ordered List'
}]
}, {
kind: 'blockquote',
icon: 'i-lucide-text-quote',
tooltip: { text: 'Blockquote' }
}, {
kind: 'codeBlock',
icon: 'i-lucide-square-code',
tooltip: { text: 'Code Block' }
}, {
kind: 'horizontalRule',
icon: 'i-lucide-separator-horizontal',
tooltip: { text: 'Horizontal Rule' }
}],
// Text formatting
[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' }
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' }
}, {
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' }
}, {
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' }
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' }
}],
// Link
[{
kind: 'link',
icon: 'i-lucide-link',
tooltip: { text: 'Link' }
}],
// Text alignment
[{
icon: 'i-lucide-align-justify',
tooltip: { text: 'Text Align' },
content: {
align: 'end'
},
items: [{
kind: 'textAlign',
align: 'left',
icon: 'i-lucide-align-left',
label: 'Align Left'
}, {
kind: 'textAlign',
align: 'center',
icon: 'i-lucide-align-center',
label: 'Align Center'
}, {
kind: 'textAlign',
align: 'right',
icon: 'i-lucide-align-right',
label: 'Align Right'
}, {
kind: 'textAlign',
align: 'justify',
icon: 'i-lucide-align-justify',
label: 'Align Justify'
}]
}]
]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="w-full min-h-37 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="items" class="sm:px-8 overflow-x-auto" />
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/ui'
import { TextAlign } from '@tiptap/extension-text-align'
const value = ref(`This toolbar showcases **all available formatting options** using built-in handlers. Try the different controls to see them in action!
You can apply **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, and \`inline code\` formatting to your text.
`)
const items: EditorToolbarItem[][] = [
// History controls
[{
kind: 'undo',
icon: 'i-lucide-undo',
tooltip: { text: 'Undo' }
}, {
kind: 'redo',
icon: 'i-lucide-redo',
tooltip: { text: 'Redo' }
}],
// Block types
[{
icon: 'i-lucide-heading',
tooltip: { text: 'Headings' },
content: {
align: 'start'
},
items: [{
kind: 'heading',
level: 1,
icon: 'i-lucide-heading-1',
label: 'Heading 1'
}, {
kind: 'heading',
level: 2,
icon: 'i-lucide-heading-2',
label: 'Heading 2'
}, {
kind: 'heading',
level: 3,
icon: 'i-lucide-heading-3',
label: 'Heading 3'
}, {
kind: 'heading',
level: 4,
icon: 'i-lucide-heading-4',
label: 'Heading 4'
}]
}, {
icon: 'i-lucide-list',
tooltip: { text: 'Lists' },
content: {
align: 'start'
},
items: [{
kind: 'bulletList',
icon: 'i-lucide-list',
label: 'Bullet List'
}, {
kind: 'orderedList',
icon: 'i-lucide-list-ordered',
label: 'Ordered List'
}]
}, {
kind: 'blockquote',
icon: 'i-lucide-text-quote',
tooltip: { text: 'Blockquote' }
}, {
kind: 'codeBlock',
icon: 'i-lucide-square-code',
tooltip: { text: 'Code Block' }
}, {
kind: 'horizontalRule',
icon: 'i-lucide-separator-horizontal',
tooltip: { text: 'Horizontal Rule' }
}],
// Text formatting
[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold',
tooltip: { text: 'Bold' }
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic',
tooltip: { text: 'Italic' }
}, {
kind: 'mark',
mark: 'underline',
icon: 'i-lucide-underline',
tooltip: { text: 'Underline' }
}, {
kind: 'mark',
mark: 'strike',
icon: 'i-lucide-strikethrough',
tooltip: { text: 'Strikethrough' }
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code',
tooltip: { text: 'Code' }
}],
// Link
[{
kind: 'link',
icon: 'i-lucide-link',
tooltip: { text: 'Link' }
}],
// Text alignment
[{
icon: 'i-lucide-align-justify',
tooltip: { text: 'Text Align' },
content: {
align: 'end'
},
items: [{
kind: 'textAlign',
align: 'left',
icon: 'i-lucide-align-left',
label: 'Align Left'
}, {
kind: 'textAlign',
align: 'center',
icon: 'i-lucide-align-center',
label: 'Align Center'
}, {
kind: 'textAlign',
align: 'right',
icon: 'i-lucide-align-right',
label: 'Align Right'
}, {
kind: 'textAlign',
align: 'justify',
icon: 'i-lucide-align-justify',
label: 'Align Justify'
}]
}]
]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
:extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] })]"
class="w-full min-h-37 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="items" class="sm:px-8 overflow-x-auto" />
</UEditor>
</template>
items pour créer des groupes d'éléments séparés.items d'objets avec les mêmes propriétés que le prop items pour créer un menu Dropdown .layout
Utilisez la prop layout pour modifier la façon dont la barre d'outils est affichée. Par défaut, fixed.
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
defineProps<{
layout: 'fixed' | 'bubble' | 'floating'
}>()
const value = ref(`Switch between layouts to see the different toolbar modes.
The **fixed** layout displays the toolbar above the editor. The **bubble** layout shows the toolbar when you select text. The **floating** layout appears on empty lines.`)
const items: EditorToolbarItem[][] = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code'
}]]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-26 flex flex-col gap-4">
<UEditorToolbar
:key="layout"
:editor="editor"
:items="items"
:layout="layout"
:data-layout="layout"
class="data-[layout=fixed]:sm:px-8"
/>
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/ui'
defineProps<{
layout: 'fixed' | 'bubble' | 'floating'
}>()
const value = ref(`Switch between layouts to see the different toolbar modes.
The **fixed** layout displays the toolbar above the editor. The **bubble** layout shows the toolbar when you select text. The **floating** layout appears on empty lines.`)
const items: EditorToolbarItem[][] = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
kind: 'mark',
mark: 'code',
icon: 'i-lucide-code'
}]]
</script>
<template>
<UEditor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-26 flex flex-col gap-4">
<UEditorToolbar
:key="layout"
:editor="editor"
:items="items"
:layout="layout"
:data-layout="layout"
class="data-[layout=fixed]:sm:px-8"
/>
</UEditor>
</template>
Options
Lorsque vous utilisez des mises en page bubble ou floating, utilisez la prop options pour personnaliser le comportement de positionnement à l'aide des options d'interface utilisateur flottante ](https://floating-ui.com/docs/computeposition#options).
<template>
<UEditor v-slot="{ editor }">
<UEditorToolbar
:editor="editor"
:items="items"
layout="bubble"
:options="{
placement: 'top',
offset: 8,
flip: { padding: 8 },
shift: { padding: 8 }
}"
/>
</UEditor>
</template>
Should Afficher
Lorsque vous utilisez des mises en page bubble ou floating, utilisez la prop should-show pour contrôler l'affichage de la barre d'outils. Cette fonction reçoit le contexte de l'état de l'éditeur et renvoie un booléen.
<template>
<UEditor v-slot="{ editor }">
<UEditorToolbar
:editor="editor"
:items="items"
layout="bubble"
:should-show="({ view, state }) => {
const { selection } = state
const { from, to } = selection
const text = state.doc.textBetween(from, to)
return view.hasFocus() && !selection.empty && text.length > 10
}"
/>
</UEditor>
</template>
Exemples
With barre d'outils d'image
Utilisez la prop should-show pour créer des barres d'outils contextuelles spécifiques qui apparaissent uniquement pour certains types de nœuds. Cet exemple montre une barre d'outils bubble avec des actions de téléchargement et de suppression qui n'apparaissent que lorsqu 'une image est sélectionnée.
<script setup lang="ts">
import type { Editor } from '@tiptap/vue-3'
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`Click on the image below to see the image-specific toolbar:
`)
const items = (editor: Editor): EditorToolbarItem[][] => {
const node = editor.state.doc.nodeAt(editor.state.selection.from)
return [[{
icon: 'i-lucide-download',
to: node?.attrs?.src,
download: true,
tooltip: { text: 'Download' }
}], [{
icon: 'i-lucide-trash',
tooltip: { text: 'Delete' },
onClick: () => {
const { state } = editor
const { selection } = state
const pos = selection.from
const node = state.doc.nodeAt(pos)
if (node && node.type.name === 'image') {
editor.chain().focus().deleteRange({ from: pos, to: pos + node.nodeSize }).run()
}
}
}]]
}
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-113"
>
<UEditorToolbar
:editor="editor"
:items="items(editor)"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { Editor } from '@tiptap/vue-3'
import type { EditorToolbarItem } from '@nuxt/ui'
const value = ref(`Click on the image below to see the image-specific toolbar:
`)
const items = (editor: Editor): EditorToolbarItem[][] => {
const node = editor.state.doc.nodeAt(editor.state.selection.from)
return [[{
icon: 'i-lucide-download',
to: node?.attrs?.src,
download: true,
tooltip: { text: 'Download' }
}], [{
icon: 'i-lucide-trash',
tooltip: { text: 'Delete' },
onClick: () => {
const { state } = editor
const { selection } = state
const pos = selection.from
const node = state.doc.nodeAt(pos)
if (node && node.type.name === 'image') {
editor.chain().focus().deleteRange({ from: pos, to: pos + node.nodeSize }).run()
}
}
}]]
}
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-113"
>
<UEditorToolbar
:editor="editor"
:items="items(editor)"
layout="bubble"
:should-show="({ editor, view }) => {
return editor.isActive('image') && view.hasFocus()
}"
/>
</UEditor>
</template>
With link popover
Cet exemple montre comment créer un popover de lien personnalisé à l'aide de la propriété slot sur les éléments de la barre d'outils et du composant Popover.
- Créer un composant Vue qui enveloppe un Popover avec une fonctionnalité d'édition de lien:
<script setup lang="ts">
import type { Editor } from '@tiptap/vue-3'
const props = defineProps<{
editor: Editor
autoOpen?: boolean
}>()
const open = ref(false)
const url = ref('')
const active = computed(() => props.editor.isActive('link'))
const disabled = computed(() => {
if (!props.editor.isEditable) return true
const { selection } = props.editor.state
return selection.empty && !props.editor.isActive('link')
})
watch(() => props.editor, (editor, _, onCleanup) => {
if (!editor) return
const updateUrl = () => {
const { href } = editor.getAttributes('link')
url.value = href || ''
}
updateUrl()
editor.on('selectionUpdate', updateUrl)
onCleanup(() => {
editor.off('selectionUpdate', updateUrl)
})
}, { immediate: true })
watch(active, (isActive) => {
if (isActive && props.autoOpen) {
open.value = true
}
})
function setLink() {
if (!url.value) return
const { selection } = props.editor.state
const isEmpty = selection.empty
const hasCode = props.editor.isActive('code')
let chain = props.editor.chain().focus()
// When linking code, extend the code mark range first to select the full code
if (hasCode && !isEmpty) {
chain = chain.extendMarkRange('code').setLink({ href: url.value })
} else {
chain = chain.extendMarkRange('link').setLink({ href: url.value })
if (isEmpty) {
chain = chain.insertContent({ type: 'text', text: url.value })
}
}
chain.run()
open.value = false
}
function removeLink() {
props.editor
.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.setMeta('preventAutolink', true)
.run()
url.value = ''
open.value = false
}
function openLink() {
if (!url.value) return
window.open(url.value, '_blank', 'noopener,noreferrer')
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault()
setLink()
}
}
</script>
<template>
<UPopover v-model:open="open" :ui="{ content: 'p-0.5' }">
<UTooltip text="Link">
<UButton
icon="i-lucide-link"
color="neutral"
active-color="primary"
variant="ghost"
active-variant="soft"
size="sm"
:active="active"
:disabled="disabled"
/>
</UTooltip>
<template #content>
<UInput
v-model="url"
autofocus
name="url"
type="url"
variant="none"
placeholder="Paste a link..."
@keydown="handleKeyDown"
>
<div class="flex items-center mr-0.5">
<UButton
icon="i-lucide-corner-down-left"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Apply link"
@click="setLink"
/>
<USeparator orientation="vertical" class="h-6 mx-1" />
<UButton
icon="i-lucide-external-link"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Open in new window"
@click="openLink"
/>
<UButton
icon="i-lucide-trash"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Remove link"
@click="removeLink"
/>
</div>
</UInput>
</template>
</UPopover>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { Editor } from '@tiptap/vue-3'
const props = defineProps<{
editor: Editor
autoOpen?: boolean
}>()
const open = ref(false)
const url = ref('')
const active = computed(() => props.editor.isActive('link'))
const disabled = computed(() => {
if (!props.editor.isEditable) return true
const { selection } = props.editor.state
return selection.empty && !props.editor.isActive('link')
})
watch(() => props.editor, (editor, _, onCleanup) => {
if (!editor) return
const updateUrl = () => {
const { href } = editor.getAttributes('link')
url.value = href || ''
}
updateUrl()
editor.on('selectionUpdate', updateUrl)
onCleanup(() => {
editor.off('selectionUpdate', updateUrl)
})
}, { immediate: true })
watch(active, (isActive) => {
if (isActive && props.autoOpen) {
open.value = true
}
})
function setLink() {
if (!url.value) return
const { selection } = props.editor.state
const isEmpty = selection.empty
const hasCode = props.editor.isActive('code')
let chain = props.editor.chain().focus()
// When linking code, extend the code mark range first to select the full code
if (hasCode && !isEmpty) {
chain = chain.extendMarkRange('code').setLink({ href: url.value })
} else {
chain = chain.extendMarkRange('link').setLink({ href: url.value })
if (isEmpty) {
chain = chain.insertContent({ type: 'text', text: url.value })
}
}
chain.run()
open.value = false
}
function removeLink() {
props.editor
.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.setMeta('preventAutolink', true)
.run()
url.value = ''
open.value = false
}
function openLink() {
if (!url.value) return
window.open(url.value, '_blank', 'noopener,noreferrer')
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault()
setLink()
}
}
</script>
<template>
<UPopover v-model:open="open" :ui="{ content: 'p-0.5' }">
<UTooltip text="Link">
<UButton
icon="i-lucide-link"
color="neutral"
active-color="primary"
variant="ghost"
active-variant="soft"
size="sm"
:active="active"
:disabled="disabled"
/>
</UTooltip>
<template #content>
<UInput
v-model="url"
autofocus
name="url"
type="url"
variant="none"
placeholder="Paste a link..."
@keydown="handleKeyDown"
>
<div class="flex items-center mr-0.5">
<UButton
icon="i-lucide-corner-down-left"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Apply link"
@click="setLink"
/>
<USeparator orientation="vertical" class="h-6 mx-1" />
<UButton
icon="i-lucide-external-link"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Open in new window"
@click="openLink"
/>
<UButton
icon="i-lucide-trash"
color="neutral"
variant="ghost"
size="sm"
:disabled="!url && !active"
title="Remove link"
@click="removeLink"
/>
</div>
</UInput>
</template>
</UPopover>
</template>
- Utilisez le composant personnalisé dans la barre d'outils avec un emplacement nommé:
<script setup lang="ts">
import type { EditorToolbarItem } from '@nuxt/ui'
import EditorLinkPopover from './EditorLinkPopover.vue'
const value = ref(`Select text and click the link button to add a link with the custom popover.
You can also edit existing links like [this one](https://ui.nuxt.com).`)
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
slot: 'link' as const
}]] satisfies EditorToolbarItem[][]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-30 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
<template #link>
<EditorLinkPopover :editor="editor" auto-open />
</template>
</UEditorToolbar>
</UEditor>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { EditorToolbarItem } from '@nuxt/ui'
import EditorLinkPopover from './EditorLinkPopover.vue'
const value = ref(`Select text and click the link button to add a link with the custom popover.
You can also edit existing links like [this one](https://ui.nuxt.com).`)
const toolbarItems = [[{
kind: 'mark',
mark: 'bold',
icon: 'i-lucide-bold'
}, {
kind: 'mark',
mark: 'italic',
icon: 'i-lucide-italic'
}, {
slot: 'link' as const
}]] satisfies EditorToolbarItem[][]
</script>
<template>
<UEditor
v-slot="{ editor }"
v-model="value"
content-type="markdown"
class="w-full min-h-30 flex flex-col gap-4"
>
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
<template #link>
<EditorLinkPopover :editor="editor" auto-open />
</template>
</UEditorToolbar>
</UEditor>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
editor | Editor | |
color | 'neutral' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"The color of the toolbar controls. |
variant | 'ghost' | "solid" | "outline" | "soft" | "subtle" | "ghost" | "link"The variant of the toolbar controls. |
activeColor | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"The color of the active toolbar control. |
activeVariant | 'soft' | "solid" | "outline" | "soft" | "subtle" | "ghost" | "link"The variant of the active toolbar control. |
size | 'sm' | "xs" | "sm" | "md" | "lg" | "xl"The size of the toolbar controls. |
items | T | |
layout | 'fixed' | "fixed" | "floating" | "bubble" |
pluginKey | unknownThe plugin key. The plugin key for the floating menu. | |
updateDelay | unknownThe delay in milliseconds before the menu should be updated. This can be useful to prevent performance issues. | |
resizeDelay | unknownThe delay in milliseconds before the menu position should be updated on window resize. This can be useful to prevent performance issues. | |
shouldShow | unknownA function that determines whether the menu should be shown or not.
If this function returns | |
appendTo | unknownThe DOM element to append your menu to. Default is the editor's parent element. Sometimes the menu needs to be appended to a different DOM context due to accessibility, clipping, or z-index issues. | |
getReferencedVirtualElement | unknownA function that returns the virtual element for the menu. This is useful when the menu needs to be positioned relative to a specific DOM element. | |
options | unknownThe options for the bubble menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement, hide, and inline middlewares. The options for the floating menu. Those are passed to Floating UI and include options for the placement, offset, flip, shift, arrow, size, autoPlacement, hide, and inline middlewares. | |
ui | { root?: SlotClass; base?: SlotClass; group?: SlotClass; separator?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
item | { item: NestedItem<T>; } & SlotPropsProps |
Thème
export default defineAppConfig({
ui: {
editorToolbar: {
slots: {
root: 'focus:outline-none',
base: 'flex items-stretch gap-1.5',
group: 'flex items-center gap-0.5',
separator: 'w-px self-stretch bg-border'
},
variants: {
layout: {
bubble: {
base: 'bg-default border border-default rounded-lg p-1'
},
floating: {
base: 'bg-default border border-default rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
editorToolbar: {
slots: {
root: 'focus:outline-none',
base: 'flex items-stretch gap-1.5',
group: 'flex items-center gap-0.5',
separator: 'w-px self-stretch bg-border'
},
variants: {
layout: {
bubble: {
base: 'bg-default border border-default rounded-lg p-1'
},
floating: {
base: 'bg-default border border-default rounded-lg p-1'
},
fixed: {
base: ''
}
}
}
}
}
})
]
})