@mpdev_ab/document-creator
v0.2.4
Published
A rich text editor Vue 3 component built on Tiptap. Drop-in replacement for TinyMCE with font selection, tables, image upload (base64), templates, and full formatting — outputs HTML.
Downloads
644
Readme
@mpdev_ab/document-creator
A rich text editor Vue 3 component built on Tiptap. Drop-in replacement for TinyMCE with font selection, tables, image upload (base64), templates, and full formatting — outputs HTML.
Install
npm install @mpdev_ab/document-creatorPeer dependencies
Your project needs Vue 3 and the Tiptap packages:
npm install vue@^3.3.0 @tiptap/core @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit @tiptap/extension-text-style @tiptap/extension-font-family @tiptap/extension-image @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-underline @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-text-align @tiptap/extension-link @tiptap/extension-color @tiptap/extension-placeholderBasic usage
<template>
<DocumentEditor
v-model="content"
@save="onSave"
/>
</template>
<script setup>
import { ref } from 'vue'
import { DocumentEditor } from '@mpdev_ab/document-creator'
const content = ref('')
function onSave(html) {
// html is a string — send it to your API however you like
console.log(html)
}
</script>Nuxt 3
Create a plugin at plugins/document-creator.client.ts:
import { DocumentEditor } from '@mpdev_ab/document-creator'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component('DocumentEditor', DocumentEditor)
})Then use it in any page/component:
<template>
<ClientOnly>
<DocumentEditor v-model="content" @save="onSave" />
</ClientOnly>
</template>Wrap in <ClientOnly> since the editor requires the DOM.
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| v-model | string | Yes | — | HTML content (two-way binding) |
| templates | Template[] | No | [] | List of templates to show in the load modal |
| templatesLoading | boolean | No | false | Shows loading state in the load templates modal |
| image | ImageConfig | No | { maxSize: 5MB, allowedTypes: ['image/jpeg', 'image/png', 'image/gif'] } | Image upload constraints |
| theme | Partial<ThemeConfig> | No | See defaults below | Tailwind class overrides |
| placeholder | string | No | '' | Placeholder text when editor is empty |
| readonly | boolean | No | false | Hides toolbar and disables editing |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| update:modelValue | string | Emitted on every content change (used by v-model) |
| save | string | Emitted with the HTML string when the user clicks Save |
| saveTemplate | { name: string, type: 'document' \| 'snippet', content: string } | Emitted when the user saves a template |
| loadTemplates | — | Emitted when the load templates modal opens — fetch your templates and pass them via the templates prop |
| deleteTemplate | Template | Emitted when the user clicks delete on a template |
| error | { message: string, detail: unknown } | Emitted on image upload errors |
How saving works
The editor does not make any API calls. When the user clicks Save, it emits the save event with the HTML string. Your app handles persistence:
<template>
<DocumentEditor v-model="content" @save="onSave" />
</template>
<script setup>
import { ref } from 'vue'
import { DocumentEditor } from '@mpdev_ab/document-creator'
import { useMutation } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const content = ref('')
const SAVE_DOCUMENT = gql`
mutation SaveDocument($content: String!) {
saveDocument(content: $content) {
id
}
}
`
const { mutate } = useMutation(SAVE_DOCUMENT)
async function onSave(html) {
await mutate({ content: html })
}
</script>Templates
Templates are managed by your app. The editor emits events — you handle fetching, saving, and deleting.
<template>
<DocumentEditor
v-model="content"
:templates="templates"
:templates-loading="loading"
@save="onSave"
@load-templates="fetchTemplates"
@save-template="onSaveTemplate"
@delete-template="onDeleteTemplate"
/>
</template>
<script setup>
import { ref } from 'vue'
import { DocumentEditor } from '@mpdev_ab/document-creator'
const content = ref('')
const templates = ref([])
const loading = ref(false)
async function fetchTemplates() {
loading.value = true
// Fetch from your GraphQL API, REST, etc.
const result = await yourApi.getTemplates()
templates.value = result // must be { id, name, type, content }[]
loading.value = false
}
async function onSave(html) {
await yourApi.saveDocument(html)
}
async function onSaveTemplate(data) {
// data = { name: string, type: 'document' | 'snippet', content: string }
await yourApi.createTemplate(data)
await fetchTemplates() // refresh the list
}
async function onDeleteTemplate(template) {
// template = { id, name, type, content }
await yourApi.deleteTemplate(template.id)
await fetchTemplates() // refresh the list
}
</script>Template object shape
interface Template {
id: string | number
name: string
type: 'document' | 'snippet'
content: string
}Template types:
document— replaces the entire editor content when loadedsnippet— inserts at the cursor position when loaded
ImageConfig
const imageConfig = {
maxSize: 10 * 1024 * 1024, // 10MB (default: 5MB)
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
}Images are converted to base64 and stored inline in the HTML. After inserting, users can resize images using preset buttons (25%, 50%, 75%, 100%) or enter a custom width.
ThemeConfig
Override any part of the default Tailwind classes:
<DocumentEditor
v-model="content"
:theme="{
toolbar: 'bg-gray-900 border-b border-gray-700 p-2 flex flex-wrap items-center gap-1',
toolbarButton: 'hover:bg-gray-700 rounded p-1.5 text-gray-300 cursor-pointer',
toolbarButtonActive: 'bg-blue-900 text-blue-300',
editor: 'bg-gray-800 text-white min-h-[400px] p-4 prose prose-invert max-w-none',
dropdown: 'bg-gray-800 border border-gray-700 rounded shadow-lg py-1 z-50',
modal: 'bg-gray-800 text-white rounded-lg shadow-xl p-6',
saveButton: 'bg-blue-500 text-white rounded px-4 py-2 hover:bg-blue-600',
}"
/>Defaults:
| Key | Default classes |
|-----|----------------|
| toolbar | bg-white border-b border-gray-200 p-2 flex flex-wrap items-center gap-1 |
| toolbarButton | hover:bg-gray-100 rounded p-1.5 text-gray-600 cursor-pointer |
| toolbarButtonActive | bg-blue-100 text-blue-600 |
| editor | bg-white min-h-[400px] p-4 prose max-w-none focus:outline-none |
| dropdown | bg-white border border-gray-200 rounded shadow-lg py-1 z-50 |
| modal | bg-white rounded-lg shadow-xl p-6 |
| saveButton | bg-blue-600 text-white rounded px-4 py-2 hover:bg-blue-700 |
useDocumentEditor composable
Access the editor instance from child components:
<script setup>
import { useDocumentEditor } from '@mpdev_ab/document-creator'
const { editor, getHTML, getText, setContent, focus, isEmpty } = useDocumentEditor()
// Get current HTML
const html = getHTML()
// Set content programmatically
setContent('<p>New content</p>')
// Check if empty
if (isEmpty()) {
// ...
}
</script>This must be called from a component that is a child of <DocumentEditor>.
Full example
<template>
<DocumentEditor
v-model="content"
placeholder="Write your document..."
:readonly="false"
:templates="templates"
:templates-loading="templatesLoading"
:image="{
maxSize: 10 * 1024 * 1024,
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
}"
@save="onSave"
@save-template="onSaveTemplate"
@load-templates="onLoadTemplates"
@delete-template="onDeleteTemplate"
@error="onError"
/>
</template>
<script setup>
import { ref } from 'vue'
import { DocumentEditor } from '@mpdev_ab/document-creator'
const content = ref('<p>Hello world</p>')
const templates = ref([])
const templatesLoading = ref(false)
function onSave(html) {
// Send html to your backend
}
function onSaveTemplate(data) {
// data = { name, type, content }
}
async function onLoadTemplates() {
templatesLoading.value = true
templates.value = await fetchTemplatesFromApi()
templatesLoading.value = false
}
function onDeleteTemplate(template) {
// template = { id, name, type, content }
}
function onError(error) {
console.error(error.message, error.detail)
}
</script>Features
- Bold, italic, underline, strikethrough, subscript, superscript
- Font family (web-safe fonts), font size, font color
- Text alignment (left, center, right, justify)
- Line height
- Headings (H1-H6) and paragraph
- Bullet lists, numbered lists, blockquotes, horizontal rules
- Links with URL input
- Image upload (base64) with in-editor resize (25/50/75/100% presets + custom width)
- Tables with Google Docs-style grid picker, row/column operations, merge/split cells, header toggle, cell background color, column resize
- Video embed (YouTube/Vimeo URL)
- Save/load templates (full documents and snippets)
- Readonly mode
- Tailwind CSS theming via class string props
