Skip to content

Commit

Permalink
feat(editor): Add TipTag Example as alternative Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
sfxcode committed Apr 16, 2023
1 parent d3fbaee commit 15c7e48
Show file tree
Hide file tree
Showing 8 changed files with 966 additions and 63 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ THX to [antfu / Vitesse Nuxt3](https://github.com/antfu/vitesse-nuxt3) for start
- Use icons from any icon sets in Pure CSS, powered by [UnoCSS](https://github.com/antfu/unocss)
- [State Management via Pinia](https://pinia.esm.dev)
- PrimeVue 3.18.x
- [TipTap](https://tiptap.dev) - Headless Editor
- Vitest

## Nuxt Modules
Expand Down
260 changes: 260 additions & 0 deletions components/tiptap/TipTap.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
<script setup>
import { EditorContent, Editor, Extension } from '@tiptap/vue-3'
import { Highlight } from '@tiptap/extension-highlight'
import { TextAlign } from '@tiptap/extension-text-align'
import { StarterKit } from '@tiptap/starter-kit'
const props = defineProps({
modelValue: {
type: String,
default: '',
required: true
},
editable: {
type: Boolean,
default: true
}
})
const editor = ref()
const extensionNames = computed(() => {
return props.extensions.map(ext => ext.name)
})
watch(
() => props.modelValue,
(value) => {
const isSame = editor.value.getHTML() === value
if (!isSame) {
editor.value.commands.setContent(value, false)
}
}
)
const emit = defineEmits(['update:modelValue'])
onMounted(() => {
editor.value = new Editor({
content: props.modelValue,
editable: props.editable,
editorProps: {
attributes: {
class: ''
}
},
extensions: [StarterKit, Highlight, TextAlign.configure({ types: ['heading', 'paragraph'] })],
onUpdate: () => {
emit('update:modelValue', editor.value?.getHTML())
}
})
})
onBeforeUnmount(() => {
editor.value?.destroy()
editor.value = null
})
</script>
<template>
<div v-if="editor">
<Toolbar id="toolbar">
<template #start>
<Button
id="bold"
size="small"
label="B"
:class="{ 'p-button-outlined': !editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
/>
<Button
id="italic"
size="small"
label="I"
:class="{ 'p-button-outlined': !editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
/>
<Button
id="strike"
size="small"
label="T"
:class="{ 'p-button-outlined': !editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
/>
<Button
size="small"
label="Clear"
class="p-button-outlined"
@click="editor.chain().focus().unsetAllMarks().run()"
/>
<span class="separator" />
<Button
size="small"
label="P"
:class="{ 'p-button-outlined': !editor.isActive('paragraph') }"
@click="editor.chain().focus().setParagraph().run()"
/>
<Button
size="small"
label="H1"
:class="{ 'p-button-outlined': !editor.isActive('heading', { level: 1 }) }"
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
/>
<Button
size="small"
label="H2"
:class="{ 'p-button-outlined': !editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
/>
<Button
size="small"
label="H3"
:class="{ 'p-button-outlined': !editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
/>
<Button
size="small"
label="H4"
:class="{ 'p-button-outlined': !editor.isActive('heading', { level: 4 }) }"
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
/>
<Button
size="small"
label="H5"
:class="{ 'p-button-outlined': !editor.isActive('heading', { level: 5 }) }"
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
/>
<Button
size="small"
label="H6"
:class="{ 'p-button-outlined': !editor.isActive('heading', { level: 6 }) }"
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
/>
</template>
<template #center>
<Button
size="small"
icon="pi pi-list"
icon-only
:class="{ 'p-button-outlined': !editor.isActive('bulletList') }"
@click="editor.chain().focus().toggleBulletList().run()"
/>
<Button
size="small"
icon="pi pi-code"
icon-only
:class="{ 'p-button-outlined': !editor.isActive('codeBlock') }"
@click="editor.chain().focus().toggleCodeBlock().run()"
/>
<Button
size="small"
icon="pi pi-minus"
icon-only
class="p-button-outlined"
@click="editor.chain().focus().setHorizontalRule().run()"
/>
<Button
size="small"
icon="pi pi-bolt"
icon-only
:class="{ 'p-button-outlined': !editor.isActive('highlight') }"
@click="editor.chain().focus().toggleHighlight().run()"
/>
<Button
size="small"
label="Quote"
:class="{ 'p-button-outlined': !editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
/>
<span class="separator" />
<Button
size="small"
icon="pi pi-align-left"
icon-only
:class="{ 'p-button-outlined': !editor.isActive({ textAlign: 'left' }) }"
@click="editor.chain().focus().setTextAlign('left').run()"
/>
<Button
size="small"
icon="pi pi-align-center"
icon-only
:class="{ 'p-button-outlined': !editor.isActive({ textAlign: 'center' }) }"
@click="editor.chain().focus().setTextAlign('center').run()"
/>
<Button
size="small"
icon="pi pi-align-right"
icon-only
:class="{ 'p-button-outlined': !editor.isActive({ textAlign: 'right' }) }"
@click="editor.chain().focus().setTextAlign('right').run()"
/>
<Button
size="small"
icon="pi pi-align-justify"
icon-only
:class="{ 'p-button-outlined': !editor.isActive({ textAlign: 'justify' }) }"
@click="editor.chain().focus().setTextAlign('justify').run()"
/>
</template>
<template #end>
<Button
size="small"
severity="secondary"
label="Undo"
:disabled="!editor.can().chain().focus().undo().run()"
@click="editor.chain().focus().undo().run()"
/>
<Button
size="small"
severity="secondary"
label="Redo"
:disabled="!editor.can().chain().focus().redo().run()"
@click="editor.chain().focus().redo().run()"
/>
</template>
</Toolbar>
<editor-content :editor="editor" class="p-tiptap p-inputtext" />
</div>
</template>
<style scoped lang="scss">
.separator {
content: '';
margin-right: 0.5rem;
}
#toolbar {
padding: 0.5rem;
border-bottom: none;
button {
margin-right: 0.25rem;
font-weight: bold;
}
.p-colorpicker {
margin-right: 0.25rem;
}
.p-button.p-button-sm {
padding: 0.4375rem 0.4375rem;
}
}
.p-tiptap :focus {
outline-offset:2px;
outline:2px solid transparent;
}
#bold {
font-weight: 900;
}
#italic {
font-style: italic;
}
#strike {
text-decoration:line-through;
}
</style>
3 changes: 2 additions & 1 deletion composables/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export function useNavigationMenu () {
label: 'UI',
items: [
{ label: 'UnoCSS', icon: 'pi pi-fw pi-user-edit', to: '/ui/uno' },
{ label: 'Icons', icon: 'pi pi-fw pi-user-edit', to: '/ui/icons' }
{ label: 'Icons', icon: 'pi pi-fw pi-user-edit', to: '/ui/icons' },
{ label: 'TipTap', icon: 'pi pi-fw pi-user-edit', to: '/ui/tiptap' }
]
},
{
Expand Down
6 changes: 6 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export default defineNuxtConfig({
'@sfxcode/formkit-primevue/dist/sass/formkit-prime-inputs.scss',
'@sfxcode/formkit-primevue/dist/sass/formkit-primevue.scss'
],
pinia: {
autoImports: [
// automatically imports `defineStore`
'defineStore' // import { defineStore } from 'pinia'
]
},
build: {
transpile: ['nuxt', 'primevue']
},
Expand Down
14 changes: 10 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"story:preview": "histoire preview"
},
"devDependencies": {
"@histoire/plugin-nuxt": "^0.16.0",
"@histoire/plugin-vue": "^0.16.0",
"@histoire/plugin-nuxt": "^0.16.1",
"@histoire/plugin-vue": "^0.16.1",
"@iconify-json/carbon": "^1.1.16",
"@iconify-json/mdi": "^1.1.50",
"@iconify-json/prime": "^1.1.5",
Expand All @@ -31,13 +31,19 @@
"@nuxtjs/eslint-config-typescript": "12.0.0",
"@pinia/nuxt": "^0.4.8",
"@sfxcode/nuxt-primevue": "^1.0.9",
"@tiptap/extension-highlight": "^2.0.3",
"@tiptap/extension-text-align": "^2.0.3",
"@tiptap/extension-text-style": "^2.0.3",
"@tiptap/pm": "^2.0.3",
"@tiptap/starter-kit": "^2.0.3",
"@tiptap/vue-3": "^2.0.3",
"@unocss/nuxt": "^0.51.4",
"@vitejs/plugin-vue": "^4.1.0",
"@vitest/ui": "^0.30.1",
"@vueuse/nuxt": "^9.13.0",
"@vueuse/nuxt": "^10.0.2",
"c8": "^7.13.0",
"eslint": "^8.38.0",
"histoire": "^0.16.0",
"histoire": "^0.16.1",
"jsdom": "^21.1.1",
"nuxt": "3.4.1",
"sass": "^1.62.0",
Expand Down
22 changes: 22 additions & 0 deletions pages/ui/tiptap.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
import TipTap from '~/components/tiptap/TipTap.vue'
const startText = '<p>I’m running Tiptap with Vue.js and Nuxt. 🎉</p>'
const editorValue = ref(startText)
function resetText () {
editorValue.value = startText
}
</script>

<template>
<div class="card">
<h5>TipTap as alternative Editor</h5>

<div class="pb-2">
<Button size="small" label="Reset" @click="resetText" />
</div>
<TipTap v-model="editorValue" />
</div>
</template>

<style scoped></style>
Loading

0 comments on commit 15c7e48

Please sign in to comment.