3 Commits

Author SHA1 Message Date
c646df9fe3 fix: republier 1.4.8 (couleurs éditeur rich text) (#41)
All checks were successful
Release / release (push) Successful in 1m8s
## Pourquoi

Le squash-merge de #40 a utilisé le titre \`release : ...\` comme message du merge commit. Le mot \`release\` n'est pas un type reconnu par le \`commit-analyzer\` (angular preset reconnaît \`feat\`, \`fix\`, \`perf\`, etc.). Résultat : semantic-release a tourné mais n'a rien publié → le code des couleurs est sur main mais 1.4.8 n'est jamais sorti.

## Quoi

Un commit vide \`fix(release) : ...\` qui force le bump patch et republie en 1.4.8.

## Note durable

Le titre des PR de release **doit** être un Conventional Commit (\`fix: ...\`, \`feat: ...\`). Avec squash-merge, c'est ce titre qui devient le message analysé.

Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr>
Co-authored-by: kevin <kevin@yuno.malio.fr>
Reviewed-on: #41
2026-05-04 18:42:23 +00:00
7fc072ad08 release : couleurs et surlignage dans l'éditeur rich text (#40)
All checks were successful
Release / release (push) Successful in 1m0s
## Résumé

Ajoute deux boutons à la toolbar de \`<MalioInputRichText>\` pour appliquer une couleur de texte ou un surlignage sur la sélection, façon Jira.

## Changements

- Extensions TipTap : \`@tiptap/extension-text-style\`, \`@tiptap/extension-color\`, \`@tiptap/extension-highlight\` (multicolor)
- Palette de 8 couleurs texte + 8 pastels surlignage + reset
- Indicateur de couleur active sous l'icône
- Fermeture popover sur clic extérieur, Échap, ou clic dans l'éditeur
- Tests : 4 nouveaux cas (15/15 OK)
- Story et \`COMPONENTS.md\` à jour

## Limite à connaître

Les couleurs ne sont **pas sérialisables en markdown** (\`tiptap-markdown\` ne les sérialise pas). Pour les conserver au save/reload, utiliser \`output-format="html"\`.

## Release attendu

Commit type \`fix:\` → semantic-release publie **1.4.8** (patch).

Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr>
Reviewed-on: #40
2026-05-04 18:03:40 +00:00
f30619a497 fix: ajout du composant rich text editor (TipTap v3) (#39)
All checks were successful
Release / release (push) Successful in 1m8s
## Résumé

Release pour publier le composant `MalioInputRichText` (mergé via #37 dans `develop`).

Inclut également un correctif de configuration `semantic-release` pour tolérer le format de commits du repo (`<type>(<scope>) : <message>` avec espace avant `:`).

## Changements

- **#37** — `feat(input-rich-text)` : composant éditeur de texte riche TipTap v3 (déjà sur `develop`)
- **chore(release)** : `parserOpts.headerPattern` ajouté à `commit-analyzer` et `release-notes-generator` dans `.releaserc.json` pour matcher le format Malio avec espace

## Pourquoi un titre `fix:` et pas `feat:`

Choix utilisateur — bump souhaité **patch** (`v1.4.6` → `v1.4.7`) plutôt que minor.

## Test plan

- [ ] Workflow Gitea Actions se déclenche après merge
- [ ] semantic-release détecte le commit `fix:` et bump en `v1.4.7`
- [ ] Tag `v1.4.7` créé
- [ ] Package publié sur Gitea Packages npm registry
- [ ] Test côté projet consommateur : `npm update @malio/layer-ui` → import `<MalioInputRichText>`

## Note durable

Pour les futures PR de release : titre de la PR = un commit Conventional Commits (ex: `fix: …`, `feat: …`, `chore: …`). Avec ce parserOpts en place, l'espace avant `:` est désormais toléré par semantic-release.

Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr>
Reviewed-on: #39
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Co-committed-by: matthieu <matthieu@yuno.malio.fr>
2026-05-04 13:27:57 +00:00
7 changed files with 381 additions and 16 deletions

View File

@@ -2,8 +2,26 @@
"branches": ["main", "master"],
"repositoryUrl": "https://gitea.malio.fr/MALIO-DEV/malio-layer-ui.git",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/commit-analyzer",
{
"preset": "angular",
"parserOpts": {
"headerPattern": "^(\\w+)(?:\\(([\\w$.\\-* ]+)\\))?\\s*:\\s+(.+)$",
"headerCorrespondence": ["type", "scope", "subject"]
}
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "angular",
"parserOpts": {
"headerPattern": "^(\\w+)(?:\\(([\\w$.\\-* ]+)\\))?\\s*:\\s+(.+)$",
"headerCorrespondence": ["type", "scope", "subject"]
}
}
],
"@semantic-release/npm"
]
}

View File

@@ -134,7 +134,9 @@ Zone de texte multiligne avec compteur et redimensionnement.
## MalioInputRichText
Éditeur de texte riche basé sur **TipTap v3** + **StarterKit** + **tiptap-markdown**. Toolbar avec gras, italique, barré, titres H2/H3, listes, citation, code, code-block, lien, undo/redo. Sortie en markdown (par défaut) ou HTML.
Éditeur de texte riche basé sur **TipTap v3** + **StarterKit** + **tiptap-markdown** + **TextStyle/Color/Highlight**. Toolbar avec gras, italique, barré, titres H2/H3, listes, citation, code, code-block, lien, **couleur du texte**, **surlignage**, undo/redo. Sortie en HTML (par défaut) ou markdown.
> Couleurs et surlignages ne sont **pas persistés en markdown**. Pour les conserver au save/reload, utiliser `output-format="html"`.
| Prop | Type | Défaut | Description |
|------|------|--------|-------------|
@@ -149,7 +151,7 @@ Zone de texte multiligne avec compteur et redimensionnement.
| `hint` | `string` | `''` | Message d'aide |
| `error` | `string` | `''` | Message d'erreur |
| `success` | `string` | `''` | Message de succès |
| `outputFormat` | `'markdown' \| 'html'` | `'markdown'` | Format émis dans `update:modelValue` |
| `outputFormat` | `'markdown' \| 'html'` | `'html'` | Format émis dans `update:modelValue` |
| `groupClass` | `string` | `''` | Classes CSS conteneur (twMerge) |
| `labelClass` | `string` | `''` | Classes CSS label (twMerge) |
| `editorClass` | `string` | `''` | Classes CSS wrapper éditeur (twMerge) |
@@ -159,7 +161,7 @@ Zone de texte multiligne avec compteur et redimensionnement.
```vue
<MalioInputRichText v-model="note" label="Note" placeholder="Écrire ici…" />
<MalioInputRichText v-model="cr" label="Compte-rendu" error="Trop court" />
<MalioInputRichText v-model="article" label="Article" output-format="html" min-height="240px" />
<MalioInputRichText v-model="article" label="Article" min-height="240px" />
<MalioInputRichText :model-value="content" :editable="false" />
```

View File

@@ -61,10 +61,42 @@ describe('MalioInputRichText', () => {
expect(wrapper.find('button[title="Gras"]').exists()).toBe(true)
expect(wrapper.find('button[title="Italique"]').exists()).toBe(true)
expect(wrapper.find('button[title="Lien"]').exists()).toBe(true)
expect(wrapper.find('button[title="Couleur du texte"]').exists()).toBe(true)
expect(wrapper.find('button[title="Surlignage"]').exists()).toBe(true)
expect(wrapper.find('button[title="Annuler"]').exists()).toBe(true)
expect(wrapper.find('button[title="Rétablir"]').exists()).toBe(true)
})
it('opens and closes the text color palette', async () => {
const wrapper = await mountComponent({modelValue: ''})
expect(wrapper.find('[aria-label="Palette couleur du texte"]').exists()).toBe(false)
await wrapper.get('button[title="Couleur du texte"]').trigger('click')
expect(wrapper.find('[aria-label="Palette couleur du texte"]').exists()).toBe(true)
await wrapper.get('button[title="Couleur du texte"]').trigger('click')
expect(wrapper.find('[aria-label="Palette couleur du texte"]').exists()).toBe(false)
})
it('opens the highlight palette and closes the color palette', async () => {
const wrapper = await mountComponent({modelValue: ''})
await wrapper.get('button[title="Couleur du texte"]').trigger('click')
expect(wrapper.find('[aria-label="Palette couleur du texte"]').exists()).toBe(true)
await wrapper.get('button[title="Surlignage"]').trigger('click')
expect(wrapper.find('[aria-label="Palette de surlignage"]').exists()).toBe(true)
expect(wrapper.find('[aria-label="Palette couleur du texte"]').exists()).toBe(false)
})
it('disables color and highlight buttons when readonly', async () => {
const wrapper = await mountComponent({readonly: true, modelValue: ''})
expect(wrapper.get('button[title="Couleur du texte"]').attributes('disabled')).toBeDefined()
expect(wrapper.get('button[title="Surlignage"]').attributes('disabled')).toBeDefined()
})
it('does not render the toolbar in readonly display mode (editable=false)', async () => {
const wrapper = await mountComponent({editable: false, modelValue: '**hi**'})

View File

@@ -46,6 +46,110 @@
<span class="mx-1 h-5 w-px bg-m-border" aria-hidden="true" />
<div class="relative">
<button
type="button"
class="flex h-8 w-8 flex-col items-center justify-center rounded text-m-text transition-colors hover:bg-m-primary/10 disabled:cursor-not-allowed disabled:opacity-40"
:class="colorPickerOpen ? 'bg-m-primary/15 text-m-primary' : ''"
title="Couleur du texte"
aria-label="Couleur du texte"
:aria-expanded="colorPickerOpen"
:disabled="disabled || readonly"
@mousedown.prevent
@click="toggleColorPicker"
>
<IconifyIcon icon="mdi:format-color-text" :width="18" :height="18" />
<span
class="-mt-0.5 block h-1 w-4 rounded-sm"
:style="{ backgroundColor: currentTextColor ?? 'transparent' }"
aria-hidden="true"
/>
</button>
<div
v-if="colorPickerOpen"
class="absolute left-0 top-full z-10 mt-1 flex w-44 flex-col gap-2 rounded-md border border-m-border bg-white p-2 shadow-lg"
role="dialog"
aria-label="Palette couleur du texte"
>
<div class="grid grid-cols-4 gap-1">
<button
v-for="swatch in textColorSwatches"
:key="swatch.value"
type="button"
class="h-7 w-7 rounded border border-m-border transition-transform hover:scale-110"
:class="currentTextColor === swatch.value ? 'ring-2 ring-m-primary ring-offset-1' : ''"
:style="{ backgroundColor: swatch.value }"
:title="swatch.label"
:aria-label="swatch.label"
@mousedown.prevent
@click="applyTextColor(swatch.value)"
/>
</div>
<button
type="button"
class="flex items-center justify-center gap-1 rounded border border-m-border px-2 py-1 text-xs text-m-text transition-colors hover:bg-m-bg"
@mousedown.prevent
@click="applyTextColor(null)"
>
<IconifyIcon icon="mdi:format-color-marker-cancel" :width="14" :height="14" />
Aucune couleur
</button>
</div>
</div>
<div class="relative">
<button
type="button"
class="flex h-8 w-8 flex-col items-center justify-center rounded text-m-text transition-colors hover:bg-m-primary/10 disabled:cursor-not-allowed disabled:opacity-40"
:class="highlightPickerOpen ? 'bg-m-primary/15 text-m-primary' : ''"
title="Surlignage"
aria-label="Surlignage"
:aria-expanded="highlightPickerOpen"
:disabled="disabled || readonly"
@mousedown.prevent
@click="toggleHighlightPicker"
>
<IconifyIcon icon="mdi:marker" :width="18" :height="18" />
<span
class="-mt-0.5 block h-1 w-4 rounded-sm"
:style="{ backgroundColor: currentHighlightColor ?? 'transparent' }"
aria-hidden="true"
/>
</button>
<div
v-if="highlightPickerOpen"
class="absolute left-0 top-full z-10 mt-1 flex w-44 flex-col gap-2 rounded-md border border-m-border bg-white p-2 shadow-lg"
role="dialog"
aria-label="Palette de surlignage"
>
<div class="grid grid-cols-4 gap-1">
<button
v-for="swatch in highlightSwatches"
:key="swatch.value"
type="button"
class="h-7 w-7 rounded border border-m-border transition-transform hover:scale-110"
:class="currentHighlightColor === swatch.value ? 'ring-2 ring-m-primary ring-offset-1' : ''"
:style="{ backgroundColor: swatch.value }"
:title="swatch.label"
:aria-label="swatch.label"
@mousedown.prevent
@click="applyHighlight(swatch.value)"
/>
</div>
<button
type="button"
class="flex items-center justify-center gap-1 rounded border border-m-border px-2 py-1 text-xs text-m-text transition-colors hover:bg-m-bg"
@mousedown.prevent
@click="applyHighlight(null)"
>
<IconifyIcon icon="mdi:format-color-marker-cancel" :width="14" :height="14" />
Aucun surlignage
</button>
</div>
</div>
<span class="mx-1 h-5 w-px bg-m-border" aria-hidden="true" />
<button
type="button"
class="flex h-8 w-8 items-center justify-center rounded text-m-text transition-colors hover:bg-m-primary/10 disabled:cursor-not-allowed disabled:opacity-40"
@@ -97,11 +201,14 @@
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, shallowRef, useId, watch } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, useId, watch } from 'vue'
import { Icon as IconifyIcon } from '@iconify/vue'
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import { TextStyle } from '@tiptap/extension-text-style'
import Color from '@tiptap/extension-color'
import Highlight from '@tiptap/extension-highlight'
import { Markdown } from 'tiptap-markdown'
import { twMerge } from 'tailwind-merge'
@@ -139,7 +246,7 @@ const props = withDefaults(
hint: '',
error: '',
success: '',
outputFormat: 'markdown',
outputFormat: 'html',
groupClass: '',
labelClass: '',
editorClass: '',
@@ -207,9 +314,18 @@ const mergedReadonlyClass = computed(() =>
const focusEditor = () => {
if (isInteractionLocked.value) return
closePickers()
editor.value?.commands.focus()
}
const htmlPattern = /<\/?[a-z][\s\S]*>/i
const normalizeEditorInput = (value: string | null | undefined): string => {
const content = (value ?? '').replace(/\r\n?/g, '\n')
if (htmlPattern.test(content)) return content
return content.split('\n').join('\n\n').replace(/\n{3,}/g, '\n\n')
}
const promptForLink = () => {
if (!editor.value) return
const previous = editor.value.getAttributes('link').href as string | undefined
@@ -222,6 +338,78 @@ const promptForLink = () => {
editor.value.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
}
type ColorSwatch = { label: string; value: string }
const textColorSwatches: ColorSwatch[] = [
{ label: 'Rouge', value: '#bf2600' },
{ label: 'Orange', value: '#ff8b00' },
{ label: 'Jaune', value: '#ffc400' },
{ label: 'Vert', value: '#00875a' },
{ label: 'Turquoise', value: '#00a3bf' },
{ label: 'Bleu', value: '#0747a6' },
{ label: 'Violet', value: '#5243aa' },
{ label: 'Gris', value: '#42526e' },
]
const highlightSwatches: ColorSwatch[] = [
{ label: 'Rouge', value: '#fdd0c8' },
{ label: 'Orange', value: '#ffe2c2' },
{ label: 'Jaune', value: '#fff0b3' },
{ label: 'Vert', value: '#c6edd0' },
{ label: 'Turquoise', value: '#c1ecf0' },
{ label: 'Bleu', value: '#cce0ff' },
{ label: 'Violet', value: '#dfd8fa' },
{ label: 'Gris', value: '#dfe1e6' },
]
const colorPickerOpen = ref(false)
const highlightPickerOpen = ref(false)
const closePickers = () => {
colorPickerOpen.value = false
highlightPickerOpen.value = false
}
const toggleColorPicker = () => {
highlightPickerOpen.value = false
colorPickerOpen.value = !colorPickerOpen.value
}
const toggleHighlightPicker = () => {
colorPickerOpen.value = false
highlightPickerOpen.value = !highlightPickerOpen.value
}
const applyTextColor = (value: string | null) => {
if (!editor.value) return
if (value === null) {
editor.value.chain().focus().unsetColor().run()
} else {
editor.value.chain().focus().setColor(value).run()
}
colorPickerOpen.value = false
}
const applyHighlight = (value: string | null) => {
if (!editor.value) return
if (value === null) {
editor.value.chain().focus().unsetHighlight().run()
} else {
editor.value.chain().focus().setHighlight({ color: value }).run()
}
highlightPickerOpen.value = false
}
const currentTextColor = computed(() => {
const attrs = editor.value?.getAttributes('textStyle') as { color?: string } | undefined
return attrs?.color ?? null
})
const currentHighlightColor = computed(() => {
const attrs = editor.value?.getAttributes('highlight') as { color?: string } | undefined
return attrs?.color ?? null
})
const toolbarButtons = computed(() => {
const e = editor.value
return [
@@ -242,13 +430,34 @@ const toolbarButtons = computed(() => {
const getCurrentValue = (): string => {
if (!editor.value) return ''
if (props.outputFormat === 'html') return editor.value.getHTML()
const storage = editor.value.storage.markdown as { getMarkdown: () => string } | undefined
return storage ? storage.getMarkdown() : editor.value.getHTML()
const storage = (editor.value.storage as unknown as Record<string, { getMarkdown?: () => string } | undefined>).markdown
return storage?.getMarkdown ? storage.getMarkdown() : editor.value.getHTML()
}
const handleDocumentMousedown = (event: MouseEvent) => {
if (!colorPickerOpen.value && !highlightPickerOpen.value) return
const target = event.target as Node | null
if (!target) return
const popovers = document.querySelectorAll(`#${editorId.value} [role="dialog"]`)
const triggers = document.querySelectorAll(`#${editorId.value} [aria-expanded]`)
for (const node of [...popovers, ...triggers]) {
if (node.contains(target)) return
}
closePickers()
}
const handleDocumentKeydown = (event: KeyboardEvent) => {
if (event.key === 'Escape' && (colorPickerOpen.value || highlightPickerOpen.value)) {
closePickers()
}
}
onMounted(() => {
document.addEventListener('mousedown', handleDocumentMousedown)
document.addEventListener('keydown', handleDocumentKeydown)
editor.value = new Editor({
content: props.modelValue ?? '',
content: normalizeEditorInput(props.modelValue),
editable: props.editable && !props.disabled && !props.readonly,
extensions: [
StarterKit.configure({
@@ -259,11 +468,14 @@ onMounted(() => {
HTMLAttributes: { rel: 'noopener noreferrer nofollow', target: '_blank' },
},
}),
TextStyle,
Color.configure({ types: ['textStyle'] }),
Highlight.configure({ multicolor: true }),
Placeholder.configure({
placeholder: props.placeholder,
}),
Markdown.configure({
html: false,
html: true,
tightLists: true,
bulletListMarker: '-',
linkify: true,
@@ -290,20 +502,22 @@ onMounted(() => {
})
onBeforeUnmount(() => {
document.removeEventListener('mousedown', handleDocumentMousedown)
document.removeEventListener('keydown', handleDocumentKeydown)
editor.value?.destroy()
})
watch(() => props.modelValue, (incoming) => {
if (!editor.value) return
if ((incoming ?? '') === getCurrentValue()) return
editor.value.commands.setContent(incoming ?? '', { emitUpdate: false })
editor.value.commands.setContent(normalizeEditorInput(incoming), { emitUpdate: false })
})
watch(() => [props.editable, props.disabled, props.readonly], () => {
editor.value?.setEditable(props.editable && !props.disabled && !props.readonly)
})
</script>
<style scoped>
.malio-rich-text :deep(.ProseMirror) {
outline: none;
@@ -323,4 +537,38 @@ watch(() => [props.editable, props.disabled, props.readonly], () => {
pointer-events: none;
height: 0;
}
.malio-rich-text :deep(h2) {
margin: 0.75rem 0 0.5rem;
font-size: 1.5rem;
line-height: 2rem;
font-weight: 700;
color: rgb(var(--m-text));
}
.malio-rich-text :deep(h3) {
margin: 0.65rem 0 0.4rem;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 700;
color: rgb(var(--m-text));
}
.malio-rich-text :deep(p) {
margin: 0.45rem 0;
}
.malio-rich-text :deep(ul),
.malio-rich-text :deep(ol) {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.malio-rich-text :deep(ul) {
list-style: disc;
}
.malio-rich-text :deep(ol) {
list-style: decimal;
}
.malio-rich-text :deep(blockquote) {
margin: 0.75rem 0;
border-left: 3px solid rgb(var(--m-border));
padding-left: 0.75rem;
color: rgb(var(--m-muted));
}
</style>

View File

@@ -72,6 +72,17 @@
min-height="200px"
/>
</div>
<div class="rounded-lg border p-4 lg:col-span-2">
<h2 class="mb-4 text-xl font-bold">Couleurs &amp; surlignage</h2>
<MalioInputRichText
v-model="colorValue"
label="Note colorée"
output-format="html"
min-height="180px"
hint="Tester les boutons couleur du texte et surlignage (palettes Jira-like)"
/>
</div>
</div>
</Story>
</template>
@@ -80,7 +91,7 @@
# MalioInputRichText
Éditeur de texte riche basé sur **TipTap v3** + **StarterKit** + **tiptap-markdown**.
Sortie en **markdown** (par défaut) ou en **HTML**. Aligné sur le thème Malio
Sortie en **HTML** (par défaut) ou en **markdown**. Aligné sur le thème Malio
(couleurs `m-*`, icônes `mdi:*`, états error / success / hint).
------------------------------------------------------------------------
@@ -144,10 +155,10 @@ Sortie en **markdown** (par défaut) ou en **HTML**. Aligné sur le thème Malio
### outputFormat
- Type: `'markdown' | 'html'`
- Défaut: `'markdown'`
- Défaut: `'html'`
- Description: Format émis dans `update:modelValue`.
- `markdown` : utilise `tiptap-markdown` (`getMarkdown()`).
- `html` : utilise `editor.getHTML()`.
- `markdown` : utilise `tiptap-markdown` (`getMarkdown()`).
### groupClass / labelClass / editorClass
@@ -166,8 +177,15 @@ Boutons (icônes `mdi:*`) :
- Citation
- Code inline, Bloc de code
- Lien (prompt URL ; vide pour retirer)
- Couleur du texte (palette de 8 swatches + reset)
- Surlignage (palette de 8 swatches + reset)
- Annuler / Rétablir
Les palettes couleur/surlignage s'ouvrent en popover sous leur bouton.
Fermeture : clic sur un swatch, clic en dehors, ou touche **Échap**.
> Les couleurs et surlignages ne sont **pas persistés en markdown** (spec Markdown ne couvre pas la couleur). Pour préserver les couleurs au save/reload, utiliser `output-format="html"`.
------------------------------------------------------------------------
## Accessibilité
@@ -199,4 +217,5 @@ const successValue = ref('Tout est bon de mon côté.')
const disabledValue = ref('Contenu indisponible.')
const readonlyValue = ref('## Compte-rendu\n\n- Point 1\n- Point 2\n\n> Citation importante')
const htmlValue = ref('<p>Contenu <strong>riche</strong>.</p>')
const colorValue = ref('<p>Sélectionner du texte puis utiliser les boutons <span style="color: #bf2600">couleur</span> ou <mark data-color="#fff0b3" style="background-color: #fff0b3">surlignage</mark>.</p>')
</script>

43
package-lock.json generated
View File

@@ -10,7 +10,10 @@
"dependencies": {
"@nuxt/icon": "^2.2.1",
"@nuxtjs/tailwindcss": "^6.14.0",
"@tiptap/extension-color": "^3.22.5",
"@tiptap/extension-highlight": "^3.22.5",
"@tiptap/extension-placeholder": "^3.22.5",
"@tiptap/extension-text-style": "^3.22.5",
"@tiptap/pm": "^3.22.5",
"@tiptap/starter-kit": "^3.22.5",
"@tiptap/vue-3": "^3.22.5",
@@ -5142,6 +5145,19 @@
"@tiptap/pm": "3.22.5"
}
},
"node_modules/@tiptap/extension-color": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.22.5.tgz",
"integrity": "sha512-4aTygOUlTFBYCvJy67SeKVdXCQw7du3Rj+N5ZutVnDnrpfzUBWsO7f+I+iDS8eMQFbWxVFLlWxGMcTbjtk1a+Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-text-style": "3.22.5"
}
},
"node_modules/@tiptap/extension-document": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.22.5.tgz",
@@ -5223,6 +5239,19 @@
"@tiptap/core": "3.22.5"
}
},
"node_modules/@tiptap/extension-highlight": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.22.5.tgz",
"integrity": "sha512-byWruAOKcqRN0OuzVSKqLLrced3M9AZaR2pD1BV3aUZHzMzeBjLBfByh8s4lExH2Z547xQUdHHnUflBQ572I5A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "3.22.5"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.5.tgz",
@@ -5373,6 +5402,20 @@
"@tiptap/core": "3.22.5"
}
},
"node_modules/@tiptap/extension-text-style": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.22.5.tgz",
"integrity": "sha512-jt63jy8YbhZJUGMxTUzeivLhowGtFp6YbCFrrmZJ7G6IHu8X8LJzO81ksz5nT5l8DKpldGwnINUfA6iE91JIAg==",
"license": "MIT",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "3.22.5"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "3.22.5",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.5.tgz",

View File

@@ -42,7 +42,10 @@
"dependencies": {
"@nuxt/icon": "^2.2.1",
"@nuxtjs/tailwindcss": "^6.14.0",
"@tiptap/extension-color": "^3.22.5",
"@tiptap/extension-highlight": "^3.22.5",
"@tiptap/extension-placeholder": "^3.22.5",
"@tiptap/extension-text-style": "^3.22.5",
"@tiptap/pm": "^3.22.5",
"@tiptap/starter-kit": "^3.22.5",
"@tiptap/vue-3": "^3.22.5",