@mpdev_ab/document-creator 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# @mpdev_ab/document-creator
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mpdev_ab/document-creator
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer dependencies
|
|
12
|
+
|
|
13
|
+
Your project needs Vue 3 and the Tiptap packages:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
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-placeholder
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Basic usage
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<DocumentEditor
|
|
24
|
+
v-model="content"
|
|
25
|
+
@save="onSave"
|
|
26
|
+
/>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
import { ref } from 'vue'
|
|
31
|
+
import { DocumentEditor } from '@mpdev_ab/document-creator'
|
|
32
|
+
|
|
33
|
+
const content = ref('')
|
|
34
|
+
|
|
35
|
+
function onSave(html) {
|
|
36
|
+
// html is a string — send it to your API however you like
|
|
37
|
+
console.log(html)
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Nuxt 3
|
|
43
|
+
|
|
44
|
+
Create a plugin at `plugins/document-creator.client.ts`:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { DocumentEditor } from '@mpdev_ab/document-creator'
|
|
48
|
+
|
|
49
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
50
|
+
nuxtApp.vueApp.component('DocumentEditor', DocumentEditor)
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then use it in any page/component:
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<template>
|
|
58
|
+
<ClientOnly>
|
|
59
|
+
<DocumentEditor v-model="content" @save="onSave" />
|
|
60
|
+
</ClientOnly>
|
|
61
|
+
</template>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Wrap in `<ClientOnly>` since the editor requires the DOM.
|
|
65
|
+
|
|
66
|
+
## Props
|
|
67
|
+
|
|
68
|
+
| Prop | Type | Required | Default | Description |
|
|
69
|
+
|------|------|----------|---------|-------------|
|
|
70
|
+
| `v-model` | `string` | Yes | — | HTML content (two-way binding) |
|
|
71
|
+
| `templates` | `Template[]` | No | `[]` | List of templates to show in the load modal |
|
|
72
|
+
| `templatesLoading` | `boolean` | No | `false` | Shows loading state in the load templates modal |
|
|
73
|
+
| `image` | `ImageConfig` | No | `{ maxSize: 5MB, allowedTypes: ['image/jpeg', 'image/png', 'image/gif'] }` | Image upload constraints |
|
|
74
|
+
| `theme` | `Partial<ThemeConfig>` | No | See defaults below | Tailwind class overrides |
|
|
75
|
+
| `placeholder` | `string` | No | `''` | Placeholder text when editor is empty |
|
|
76
|
+
| `readonly` | `boolean` | No | `false` | Hides toolbar and disables editing |
|
|
77
|
+
|
|
78
|
+
## Events
|
|
79
|
+
|
|
80
|
+
| Event | Payload | Description |
|
|
81
|
+
|-------|---------|-------------|
|
|
82
|
+
| `update:modelValue` | `string` | Emitted on every content change (used by v-model) |
|
|
83
|
+
| `save` | `string` | Emitted with the HTML string when the user clicks Save |
|
|
84
|
+
| `saveTemplate` | `{ name: string, type: 'document' \| 'snippet', content: string }` | Emitted when the user saves a template |
|
|
85
|
+
| `loadTemplates` | — | Emitted when the load templates modal opens — fetch your templates and pass them via the `templates` prop |
|
|
86
|
+
| `deleteTemplate` | `Template` | Emitted when the user clicks delete on a template |
|
|
87
|
+
| `error` | `{ message: string, detail: unknown }` | Emitted on image upload errors |
|
|
88
|
+
|
|
89
|
+
## How saving works
|
|
90
|
+
|
|
91
|
+
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:
|
|
92
|
+
|
|
93
|
+
```vue
|
|
94
|
+
<template>
|
|
95
|
+
<DocumentEditor v-model="content" @save="onSave" />
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<script setup>
|
|
99
|
+
import { ref } from 'vue'
|
|
100
|
+
import { DocumentEditor } from '@mpdev_ab/document-creator'
|
|
101
|
+
import { useMutation } from '@vue/apollo-composable'
|
|
102
|
+
import gql from 'graphql-tag'
|
|
103
|
+
|
|
104
|
+
const content = ref('')
|
|
105
|
+
|
|
106
|
+
const SAVE_DOCUMENT = gql`
|
|
107
|
+
mutation SaveDocument($content: String!) {
|
|
108
|
+
saveDocument(content: $content) {
|
|
109
|
+
id
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
const { mutate } = useMutation(SAVE_DOCUMENT)
|
|
115
|
+
|
|
116
|
+
async function onSave(html) {
|
|
117
|
+
await mutate({ content: html })
|
|
118
|
+
}
|
|
119
|
+
</script>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Templates
|
|
123
|
+
|
|
124
|
+
Templates are managed by your app. The editor emits events — you handle fetching, saving, and deleting.
|
|
125
|
+
|
|
126
|
+
```vue
|
|
127
|
+
<template>
|
|
128
|
+
<DocumentEditor
|
|
129
|
+
v-model="content"
|
|
130
|
+
:templates="templates"
|
|
131
|
+
:templates-loading="loading"
|
|
132
|
+
@save="onSave"
|
|
133
|
+
@load-templates="fetchTemplates"
|
|
134
|
+
@save-template="onSaveTemplate"
|
|
135
|
+
@delete-template="onDeleteTemplate"
|
|
136
|
+
/>
|
|
137
|
+
</template>
|
|
138
|
+
|
|
139
|
+
<script setup>
|
|
140
|
+
import { ref } from 'vue'
|
|
141
|
+
import { DocumentEditor } from '@mpdev_ab/document-creator'
|
|
142
|
+
|
|
143
|
+
const content = ref('')
|
|
144
|
+
const templates = ref([])
|
|
145
|
+
const loading = ref(false)
|
|
146
|
+
|
|
147
|
+
async function fetchTemplates() {
|
|
148
|
+
loading.value = true
|
|
149
|
+
// Fetch from your GraphQL API, REST, etc.
|
|
150
|
+
const result = await yourApi.getTemplates()
|
|
151
|
+
templates.value = result // must be { id, name, type, content }[]
|
|
152
|
+
loading.value = false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function onSave(html) {
|
|
156
|
+
await yourApi.saveDocument(html)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function onSaveTemplate(data) {
|
|
160
|
+
// data = { name: string, type: 'document' | 'snippet', content: string }
|
|
161
|
+
await yourApi.createTemplate(data)
|
|
162
|
+
await fetchTemplates() // refresh the list
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function onDeleteTemplate(template) {
|
|
166
|
+
// template = { id, name, type, content }
|
|
167
|
+
await yourApi.deleteTemplate(template.id)
|
|
168
|
+
await fetchTemplates() // refresh the list
|
|
169
|
+
}
|
|
170
|
+
</script>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Template object shape
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
interface Template {
|
|
177
|
+
id: string | number
|
|
178
|
+
name: string
|
|
179
|
+
type: 'document' | 'snippet'
|
|
180
|
+
content: string
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Template types:
|
|
185
|
+
- `document` — replaces the entire editor content when loaded
|
|
186
|
+
- `snippet` — inserts at the cursor position when loaded
|
|
187
|
+
|
|
188
|
+
## ImageConfig
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const imageConfig = {
|
|
192
|
+
maxSize: 10 * 1024 * 1024, // 10MB (default: 5MB)
|
|
193
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
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.
|
|
198
|
+
|
|
199
|
+
## ThemeConfig
|
|
200
|
+
|
|
201
|
+
Override any part of the default Tailwind classes:
|
|
202
|
+
|
|
203
|
+
```vue
|
|
204
|
+
<DocumentEditor
|
|
205
|
+
v-model="content"
|
|
206
|
+
:theme="{
|
|
207
|
+
toolbar: 'bg-gray-900 border-b border-gray-700 p-2 flex flex-wrap items-center gap-1',
|
|
208
|
+
toolbarButton: 'hover:bg-gray-700 rounded p-1.5 text-gray-300 cursor-pointer',
|
|
209
|
+
toolbarButtonActive: 'bg-blue-900 text-blue-300',
|
|
210
|
+
editor: 'bg-gray-800 text-white min-h-[400px] p-4 prose prose-invert max-w-none',
|
|
211
|
+
dropdown: 'bg-gray-800 border border-gray-700 rounded shadow-lg py-1 z-50',
|
|
212
|
+
modal: 'bg-gray-800 text-white rounded-lg shadow-xl p-6',
|
|
213
|
+
saveButton: 'bg-blue-500 text-white rounded px-4 py-2 hover:bg-blue-600',
|
|
214
|
+
}"
|
|
215
|
+
/>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Defaults:**
|
|
219
|
+
| Key | Default classes |
|
|
220
|
+
|-----|----------------|
|
|
221
|
+
| `toolbar` | `bg-white border-b border-gray-200 p-2 flex flex-wrap items-center gap-1` |
|
|
222
|
+
| `toolbarButton` | `hover:bg-gray-100 rounded p-1.5 text-gray-600 cursor-pointer` |
|
|
223
|
+
| `toolbarButtonActive` | `bg-blue-100 text-blue-600` |
|
|
224
|
+
| `editor` | `bg-white min-h-[400px] p-4 prose max-w-none focus:outline-none` |
|
|
225
|
+
| `dropdown` | `bg-white border border-gray-200 rounded shadow-lg py-1 z-50` |
|
|
226
|
+
| `modal` | `bg-white rounded-lg shadow-xl p-6` |
|
|
227
|
+
| `saveButton` | `bg-blue-600 text-white rounded px-4 py-2 hover:bg-blue-700` |
|
|
228
|
+
|
|
229
|
+
## useDocumentEditor composable
|
|
230
|
+
|
|
231
|
+
Access the editor instance from child components:
|
|
232
|
+
|
|
233
|
+
```vue
|
|
234
|
+
<script setup>
|
|
235
|
+
import { useDocumentEditor } from '@mpdev_ab/document-creator'
|
|
236
|
+
|
|
237
|
+
const { editor, getHTML, getText, setContent, focus, isEmpty } = useDocumentEditor()
|
|
238
|
+
|
|
239
|
+
// Get current HTML
|
|
240
|
+
const html = getHTML()
|
|
241
|
+
|
|
242
|
+
// Set content programmatically
|
|
243
|
+
setContent('<p>New content</p>')
|
|
244
|
+
|
|
245
|
+
// Check if empty
|
|
246
|
+
if (isEmpty()) {
|
|
247
|
+
// ...
|
|
248
|
+
}
|
|
249
|
+
</script>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This must be called from a component that is a child of `<DocumentEditor>`.
|
|
253
|
+
|
|
254
|
+
## Full example
|
|
255
|
+
|
|
256
|
+
```vue
|
|
257
|
+
<template>
|
|
258
|
+
<DocumentEditor
|
|
259
|
+
v-model="content"
|
|
260
|
+
placeholder="Write your document..."
|
|
261
|
+
:readonly="false"
|
|
262
|
+
:templates="templates"
|
|
263
|
+
:templates-loading="templatesLoading"
|
|
264
|
+
:image="{
|
|
265
|
+
maxSize: 10 * 1024 * 1024,
|
|
266
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
|
267
|
+
}"
|
|
268
|
+
@save="onSave"
|
|
269
|
+
@save-template="onSaveTemplate"
|
|
270
|
+
@load-templates="onLoadTemplates"
|
|
271
|
+
@delete-template="onDeleteTemplate"
|
|
272
|
+
@error="onError"
|
|
273
|
+
/>
|
|
274
|
+
</template>
|
|
275
|
+
|
|
276
|
+
<script setup>
|
|
277
|
+
import { ref } from 'vue'
|
|
278
|
+
import { DocumentEditor } from '@mpdev_ab/document-creator'
|
|
279
|
+
|
|
280
|
+
const content = ref('<p>Hello world</p>')
|
|
281
|
+
const templates = ref([])
|
|
282
|
+
const templatesLoading = ref(false)
|
|
283
|
+
|
|
284
|
+
function onSave(html) {
|
|
285
|
+
// Send html to your backend
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function onSaveTemplate(data) {
|
|
289
|
+
// data = { name, type, content }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function onLoadTemplates() {
|
|
293
|
+
templatesLoading.value = true
|
|
294
|
+
templates.value = await fetchTemplatesFromApi()
|
|
295
|
+
templatesLoading.value = false
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function onDeleteTemplate(template) {
|
|
299
|
+
// template = { id, name, type, content }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function onError(error) {
|
|
303
|
+
console.error(error.message, error.detail)
|
|
304
|
+
}
|
|
305
|
+
</script>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Features
|
|
309
|
+
|
|
310
|
+
- Bold, italic, underline, strikethrough, subscript, superscript
|
|
311
|
+
- Font family (web-safe fonts), font size, font color
|
|
312
|
+
- Text alignment (left, center, right, justify)
|
|
313
|
+
- Line height
|
|
314
|
+
- Headings (H1-H6) and paragraph
|
|
315
|
+
- Bullet lists, numbered lists, blockquotes, horizontal rules
|
|
316
|
+
- Links with URL input
|
|
317
|
+
- Image upload (base64) with in-editor resize (25/50/75/100% presets + custom width)
|
|
318
|
+
- Tables with Google Docs-style grid picker, row/column operations, merge/split cells, header toggle, cell background color, column resize
|
|
319
|
+
- Video embed (YouTube/Vimeo URL)
|
|
320
|
+
- Save/load templates (full documents and snippets)
|
|
321
|
+
- Readonly mode
|
|
322
|
+
- Tailwind CSS theming via class string props
|