@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