@grandaniel/vue-markdown-editor 1.1.3-dev.79b1d91 → 1.1.3-dev.8113832

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.
Files changed (34) hide show
  1. package/README.md +588 -6
  2. package/dist/components/MarkdownEditor/Composable/parseMarkdown.d.ts +2 -0
  3. package/dist/components/MarkdownEditor/Composable/serializeMarkdown.d.ts +2 -0
  4. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenu.vue.d.ts +3 -1
  5. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorFileContextMenu.vue.d.ts +19 -0
  6. package/dist/components/MarkdownEditor/ContextMenu/MarkdownEditorImageContextMenu.vue.d.ts +2 -0
  7. package/dist/components/MarkdownEditor/Factory/MarkdownNodeFactory.d.ts +2 -0
  8. package/dist/components/MarkdownEditor/MarkdownComponentRegistry.d.ts +2 -1
  9. package/dist/components/MarkdownEditor/MarkdownEditor.vue.d.ts +8 -0
  10. package/dist/components/MarkdownEditor/MarkdownEditorModule.vue.d.ts +2 -2
  11. package/dist/components/MarkdownEditor/Modules/MarkdownModuleFile.vue.d.ts +16 -0
  12. package/dist/components/MarkdownEditor/Modules/MarkdownModuleFileState.d.ts +8 -0
  13. package/dist/components/MarkdownEditor/Modules/MarkdownModuleHeadline1.vue.d.ts +2 -2
  14. package/dist/components/MarkdownEditor/Modules/MarkdownModuleHeadline2.vue.d.ts +2 -2
  15. package/dist/components/MarkdownEditor/Modules/MarkdownModuleHeadline3.vue.d.ts +2 -2
  16. package/dist/components/MarkdownEditor/Modules/MarkdownModuleParagraph.vue.d.ts +2 -2
  17. package/dist/components/MarkdownEditor/Types/MarkdownAstNode.d.ts +2 -0
  18. package/dist/components/MarkdownEditor/Types/MarkdownAstNodeType.d.ts +2 -1
  19. package/dist/components/MarkdownEditor/index.d.ts +2 -1
  20. package/dist/components/MarkdownRenderer/Composable/useMarkdownRenderer.d.ts +1189 -0
  21. package/dist/components/MarkdownRenderer/MarkdownRenderer.vue.d.ts +1170 -0
  22. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleFileRender.vue.d.ts +7 -0
  23. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleHeadline1Render.vue.d.ts +7 -0
  24. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleHeadline2Render.vue.d.ts +7 -0
  25. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleHeadline3Render.vue.d.ts +7 -0
  26. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleImageRender.vue.d.ts +7 -0
  27. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleListRender.vue.d.ts +7 -0
  28. package/dist/components/MarkdownRenderer/RenderModules/MarkdownModuleParagraphRender.vue.d.ts +7 -0
  29. package/dist/components/MarkdownRenderer/defaultRenderComponentRegistry.d.ts +10 -0
  30. package/dist/components/MarkdownRenderer/index.d.ts +2 -0
  31. package/dist/components/index.d.ts +1 -0
  32. package/dist/vue-markdown-editor.css +1 -1
  33. package/dist/vue-markdown-editor.mjs +4373 -3977
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,51 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="docs/img/editor-1.png">
3
+ <img alt="vue-markdown-editor — A block-based, Notion-like markdown editor for Vue 3" src="docs/img/editor-1.png">
4
+ </picture>
5
+
1
6
  # @grandaniel/vue-markdown-editor
2
7
 
3
- Vue 3 markdown editor component library.
8
+ > A block-based, Notion-like Markdown editor **and** SSR-safe renderer for Vue 3. UI-first. Powered by [TipTap](https://tiptap.dev/).
9
+
10
+ **@grandaniel/vue-markdown-editor** provides two complementary packages:
11
+
12
+ - **Editor** — A rich block-editing experience where every Markdown element — headlines, paragraphs, lists, images — is its own independently editable, draggable block. Built for content-first workflows: write, reorder, and format with keyboard shortcuts and an always-visible drag handle.
13
+
14
+ - **Renderer** — A lightweight, SSR-safe component that renders Markdown strings to semantic HTML. Uses the same `remark-parse` pipeline as the editor, so custom module blocks render identically. Fully customizable: override any default render component with your own Vue components.
15
+
16
+ Images are first-class citizens: paste an image to auto-upload, then edit its **src**, **alt text**, and **caption** in a dedicated modal. The editor serializes back to clean Markdown automatically.
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ - [Who uses it](#who-uses-it)
23
+ - [Installation](#installation)
24
+ - [Editor](#editor)
25
+ - [Quick Start](#editor-quick-start)
26
+ - [Keyboard Shortcuts](#keyboard-shortcuts)
27
+ - [Image Upload](#image-upload)
28
+ - [API Reference](#editor-api-reference)
29
+ - [Renderer](#renderer)
30
+ - [Quick Start](#renderer-quick-start)
31
+ - [Custom Render Components](#custom-render-components)
32
+ - [API Reference](#renderer-api-reference)
33
+ - [Exported Types & Utilities](#exported-types--utilities)
34
+ - [Custom Styling](#custom-styling)
35
+ - [Supply Chain Security](#supply-chain-security)
36
+ - [Contributing](#contributing)
37
+ - [License](#license)
38
+
39
+ ---
40
+
41
+ ## Who uses it
42
+
43
+ | Project | How |
44
+ |---|---|
45
+ | **[heartbeat.systems](https://heartbeat.systems)** | Admin utility — content editors manage help articles, release notes, and in-app documentation through the block editor. |
46
+ | **[markdownstud.io](https://markdownstud.io)** | AI writing assistant — the editor serves as the primary composition surface where users draft, review, and polish AI-generated content. |
47
+
48
+ ---
4
49
 
5
50
  ## Installation
6
51
 
@@ -8,16 +53,36 @@ Vue 3 markdown editor component library.
8
53
  npm install @grandaniel/vue-markdown-editor
9
54
  ```
10
55
 
11
- ## Quick Start
56
+ > **Peer dependency:** Vue `^3.5.0`
57
+ > **Node:** `>=22`
58
+
59
+ Import the components **and** the stylesheet:
60
+
61
+ ```ts
62
+ import { MarkdownEditor, MarkdownRenderer, useMarkdownEditor, useMarkdownRenderer } from "@grandaniel/vue-markdown-editor";
63
+ import "@grandaniel/vue-markdown-editor/style.css";
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Editor
69
+
70
+ The editor is driven by a composable: `useMarkdownEditor()` creates the reactive state, and `<MarkdownEditor>` renders it.
71
+
72
+ ### Editor Quick Start
12
73
 
13
74
  ```vue
14
75
  <script setup lang="ts">
15
76
  import { ref } from "vue";
16
- import { MarkdownEditor, useMarkdownEditor } from "@grandaniel/vue-markdown-editor";
17
- import "@grandaniel/vue-markdown-editor/dist/vue-markdown-editor.css";
77
+ import {
78
+ MarkdownEditor,
79
+ useMarkdownEditor,
80
+ type MarkdownAstNode,
81
+ } from "@grandaniel/vue-markdown-editor";
82
+ import "@grandaniel/vue-markdown-editor/style.css";
18
83
 
19
- const editor = useMarkdownEditor("# Hello\n\nStart writing...");
20
- const focusedNode = ref(null);
84
+ const editor = useMarkdownEditor("# Hello, world!\n\nStart writing here…");
85
+ const focusedNode = ref<MarkdownAstNode | null>(null);
21
86
  </script>
22
87
 
23
88
  <template>
@@ -27,3 +92,520 @@ const focusedNode = ref(null);
27
92
  />
28
93
  </template>
29
94
  ```
95
+
96
+ #### Reading the output
97
+
98
+ The composable keeps the raw Markdown in sync automatically. Read it at any time:
99
+
100
+ ```ts
101
+ console.log(editor.markdownContent.value);
102
+ // "# Hello, world!\n\nStart writing here…"
103
+ ```
104
+
105
+ You can also **programmatically set** the content:
106
+
107
+ ```ts
108
+ editor.markdownContent.value = "## New heading\n\nFresh content.";
109
+ ```
110
+
111
+ ---
112
+
113
+ ### Keyboard Shortcuts
114
+
115
+ | Key | Action |
116
+ |---|---|
117
+ | <kbd>↑</kbd> / <kbd>↓</kbd> | Move focus between blocks |
118
+ | <kbd>Enter</kbd> | Split current block → insert new paragraph below |
119
+ | <kbd>Backspace</kbd> (empty block) | Delete the block, focus moves up |
120
+ | <kbd>Delete</kbd> (empty block) | Delete the block, focus stays at same index |
121
+ | Click blank area | Append a new empty paragraph at the bottom |
122
+
123
+ #### Auto type‑detection
124
+
125
+ Type `# `, `## `, or `### ` at the start of a paragraph and the block auto‑converts to the matching heading level.
126
+
127
+ ---
128
+
129
+ ### Image Upload
130
+
131
+ Images are first-class blocks with **src**, **alt text**, and **caption** fields. Right‑click any image → **Edit Attributes** to open the editing modal.
132
+
133
+ #### Paste‑to‑upload
134
+
135
+ Pass an `imageUploadFunction` prop — any pasted image (`Ctrl+V`) from the clipboard will be sent through your upload handler and inserted as a new image block:
136
+
137
+ ```vue
138
+ <script setup lang="ts">
139
+ async function uploadImage(file: File): Promise<string> {
140
+ const formData = new FormData();
141
+ formData.append("image", file);
142
+
143
+ const res = await fetch("/api/upload", { method: "POST", body: formData });
144
+ const { url } = await res.json();
145
+ return url;
146
+ }
147
+ </script>
148
+
149
+ <template>
150
+ <MarkdownEditor
151
+ :editor="editor"
152
+ :image-upload-function="uploadImage"
153
+ />
154
+ </template>
155
+ ```
156
+
157
+ The serialized Markdown uses a custom block syntax for images:
158
+
159
+ ```markdown
160
+ """MarkdownModuleImage
161
+ src: https://example.com/photo.jpg
162
+ alt: A scenic mountain view
163
+ caption: Photo taken during the 2026 summit
164
+ """
165
+ ```
166
+
167
+ > **Note:** `imageUploadFunction` is optional. Without it, pasted images from the clipboard are ignored.
168
+
169
+ ---
170
+
171
+ ### Editor API Reference
172
+
173
+ #### `useMarkdownEditor(initialContent?: string)`
174
+
175
+ Returns a reactive editor instance:
176
+
177
+ | Member | Type | Description |
178
+ |---|---|---|
179
+ | `markdownContent` | `Ref<string>` | Reactive raw Markdown. Read to serialize, write to load content. |
180
+ | `markdownNodes` | `Ref<MarkdownAstNode[]>` | Reactive array of AST nodes. |
181
+ | `deleteNode(index)` | `(index: number) => void` | Remove the node at `index`. |
182
+ | `addBlankNode(index?)` | `(index?: number) => number` | Insert an empty paragraph at `index` (or end). Returns the new index. |
183
+ | `addNodeWithType(index, type, content?)` | `(index: number, type: MarkdownNodeType, content?: string) => number` | Insert a typed node. Returns the new index. |
184
+ | `replaceNodeType(node, newType)` | `(node: MarkdownAstNode, type: MarkdownNodeType) => { newNode, index } \| null` | Convert between block types (e.g. paragraph → heading). |
185
+ | `moveNode(from, to)` | `(fromIndex: number, toIndex: number) => void` | Programmatically reorder a block. |
186
+
187
+ #### `MarkdownEditor` props
188
+
189
+ | Prop | Type | Required | Description |
190
+ |---|---|---|---|
191
+ | `editor` | `MarkdownEditorInstance` | ✓ | Instance from `useMarkdownEditor()`. |
192
+ | `focusedNode` | `MarkdownAstNode \| null` | — | For `v-model:focused-node` tracking. |
193
+ | `imageUploadFunction` | `(file: File) => Promise<string>` | — | Async callback for paste‑to‑upload. |
194
+
195
+ #### `MarkdownEditor` emits
196
+
197
+ | Event | Payload | Description |
198
+ |---|---|---|
199
+ | `update:focused-node` | `MarkdownAstNode \| null` | Fires when focus moves to a new block. |
200
+
201
+ #### `MarkdownEditor` slots
202
+
203
+ | Slot | Description |
204
+ |---|---|
205
+ | `after-controls` | Injected inside every block, after the drag‑handle / add / delete controls. |
206
+
207
+ ---
208
+
209
+ ## Renderer
210
+
211
+ `<MarkdownRenderer>` converts a Markdown string to semantic HTML. It uses the same `remark-parse` pipeline as the editor, so custom module blocks (like `"""MarkdownModuleImage"""`) render as rich `<figure>` elements automatically.
212
+
213
+ The renderer is **SSR‑safe** — no browser APIs, no TipTap. It works in `nuxt generate`, `vite-ssg`, and any server‑side rendering context.
214
+
215
+ ### Renderer Quick Start
216
+
217
+ ```vue
218
+ <script setup lang="ts">
219
+ import { MarkdownRenderer } from "@grandaniel/vue-markdown-editor";
220
+
221
+ const markdown = `# Hello World
222
+
223
+ This is a **paragraph** with *inline* formatting.
224
+
225
+ - List item 1
226
+ - List item 2
227
+
228
+ """MarkdownModuleImage
229
+ src: https://example.com/photo.jpg
230
+ alt: A scenic view
231
+ caption: Photo caption
232
+ """
233
+ `;
234
+ </script>
235
+
236
+ <template>
237
+ <MarkdownRenderer :markdown="markdown" />
238
+ </template>
239
+ ```
240
+
241
+ #### Live preview alongside the editor
242
+
243
+ Bind the editor's reactive `markdownContent` to the renderer:
244
+
245
+ ```vue
246
+ <script setup lang="ts">
247
+ import { MarkdownEditor, MarkdownRenderer, useMarkdownEditor } from "@grandaniel/vue-markdown-editor";
248
+
249
+ const editor = useMarkdownEditor("# Start writing…");
250
+ </script>
251
+
252
+ <template>
253
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
254
+ <MarkdownEditor :editor="editor" />
255
+ <MarkdownRenderer :markdown="editor.markdownContent.value" />
256
+ </div>
257
+ </template>
258
+ ```
259
+
260
+ ---
261
+
262
+ ### Custom Render Components
263
+
264
+ You can override any default render component with your own Vue component. Use `useMarkdownRenderer()` to create a renderer instance, then call `overrideComponent()` to swap in your custom component for a specific node type.
265
+
266
+ Each override is **strictly typed** — the replacement component must accept a `state` prop matching the correct state class for that node type (e.g. `MarkdownModuleImageState` for images, `MarkdownModuleTextState` for paragraphs and headings).
267
+
268
+ #### Overriding the image render component
269
+
270
+ ```vue
271
+ <script setup lang="ts">
272
+ import {
273
+ MarkdownRenderer,
274
+ useMarkdownRenderer,
275
+ MarkdownAstNodeType,
276
+ type MarkdownModuleImageState,
277
+ } from "@grandaniel/vue-markdown-editor";
278
+ import CustomImageRender from "./CustomImageRender.vue";
279
+
280
+ // Create a renderer instance and override the image component
281
+ const renderer = useMarkdownRenderer();
282
+ renderer.overrideComponent(MarkdownAstNodeType.IMAGE, CustomImageRender);
283
+
284
+ const markdown = `"""MarkdownModuleImage
285
+ src: https://example.com/photo.jpg
286
+ alt: A scenic view
287
+ caption: My custom caption
288
+ """
289
+ `;
290
+ </script>
291
+
292
+ <template>
293
+ <MarkdownRenderer
294
+ :markdown="markdown"
295
+ :renderer="renderer"
296
+ />
297
+ </template>
298
+ ```
299
+
300
+ Your custom component receives the state as a typed prop:
301
+
302
+ ```vue
303
+ <!-- CustomImageRender.vue -->
304
+ <script setup lang="ts">
305
+ import { type MarkdownModuleImageState } from "@grandaniel/vue-markdown-editor";
306
+
307
+ defineProps<{ state: MarkdownModuleImageState }>();
308
+ </script>
309
+
310
+ <template>
311
+ <div class="my-custom-image-wrapper">
312
+ <img :src="state.src" :alt="state.alt" />
313
+ <span class="my-caption">{{ state.caption }}</span>
314
+ </div>
315
+ </template>
316
+ ```
317
+
318
+ #### Available node types and their state classes
319
+
320
+ | Node Type | State Class | Prop Interface |
321
+ |---|---|---|
322
+ | `MarkdownAstNodeType.PARAGRAPH` | `MarkdownModuleTextState` | `{ text: string }` |
323
+ | `MarkdownAstNodeType.HEADLINE1` | `MarkdownModuleTextState` | `{ text: string }` |
324
+ | `MarkdownAstNodeType.HEADLINE2` | `MarkdownModuleTextState` | `{ text: string }` |
325
+ | `MarkdownAstNodeType.HEADLINE3` | `MarkdownModuleTextState` | `{ text: string }` |
326
+ | `MarkdownAstNodeType.LIST` | `MarkdownModuleListState` | `{ items: MarkdownModuleTextState[] }` |
327
+ | `MarkdownAstNodeType.IMAGE` | `MarkdownModuleImageState` | `{ src: string; alt: string; caption: string }` |
328
+ | `MarkdownAstNodeType.FILE` | `MarkdownModuleFileState` | `{ url: string; fileName: string; fileSize: number; mimeType: string; uploadError: string }` |
329
+
330
+ ---
331
+
332
+ ### Renderer API Reference
333
+
334
+ #### `useMarkdownRenderer()`
335
+
336
+ Returns a reactive renderer instance with a customizable component registry:
337
+
338
+ | Member | Type | Description |
339
+ |---|---|---|
340
+ | `componentRegistry` | `Reactive<{ [K in MarkdownNodeType]: RenderComponent<RenderStateMap[K]> }>` | Reactive map of node types to their current render components. |
341
+ | `overrideComponent(type, component)` | `<K extends MarkdownNodeType>(type: K, component: RenderComponent<RenderStateMap[K]>) => void` | Replace the render component for a given node type. Strictly typed — the component must accept the correct `state` prop. |
342
+
343
+ #### `MarkdownRenderer` props
344
+
345
+ | Prop | Type | Required | Description |
346
+ |---|---|---|---|
347
+ | `markdown` | `string` | ✓ | Raw Markdown string to render as HTML. |
348
+ | `renderer` | `MarkdownRendererInstance` | — | Instance from `useMarkdownRenderer()`. Optional — when omitted, the built-in default render components are used. Provide this to supply custom render components via `overrideComponent()`. |
349
+
350
+ ---
351
+
352
+ ## Exported Types & Utilities
353
+
354
+ | Export | Kind |
355
+ |---|---|
356
+ | `MarkdownEditorInstance` | Type — return type of `useMarkdownEditor()`. |
357
+ | `MarkdownRendererInstance` | Type — return type of `useMarkdownRenderer()`. |
358
+ | `MarkdownAstNode` | Class — AST node with `id`, `type`, `componentState`, `editingState`. |
359
+ | `MarkdownAstNodeType` | Enum — `PARAGRAPH`, `HEADLINE1`, `HEADLINE2`, `HEADLINE3`, `IMAGE`, `LIST`, `FILE`. |
360
+ | `ImageNode` | Type alias — `MarkdownAstNode<MarkdownModuleImageState>`. |
361
+ | `TextNode` | Type alias — `MarkdownAstNode<MarkdownModuleTextState>`. |
362
+ | `FileNode` | Type alias — `MarkdownAstNode<MarkdownModuleFileState>`. |
363
+ | `TextishNodeType` | Type — union of `PARAGRAPH \| HEADLINE1 \| HEADLINE2 \| HEADLINE3 \| LIST`. |
364
+ | `isTextNodeState(node)` | Type guard for text‑based nodes. |
365
+ | `isTextNodeType(type)` | Type guard for text‑based node types. |
366
+ | `RenderComponent<TState>` | Type — a Vue component that accepts `{ state: TState }` as props. |
367
+ | `RenderStateMap` | Interface — maps each `MarkdownNodeType` to its state class for strict typing. |
368
+ | `MarkdownModuleImageState` | Class — state for image nodes (`src`, `alt`, `caption`). |
369
+ | `MarkdownModuleFileState` | Class — state for file nodes (`url`, `fileName`, `fileSize`, `mimeType`, `uploadError`). |
370
+
371
+ ---
372
+
373
+ ## Custom Styling
374
+
375
+ All components use scoped SCSS. To override styles, use **global CSS** with higher specificity, or Vue's `:deep()` combinator from a parent component.
376
+
377
+ ### CSS class reference
378
+
379
+ | Class | Applies to |
380
+ |---|---|
381
+ | `.markdown-editor` | Root editor container |
382
+ | `.markdown-editor-module` | Individual block wrapper — `.is-focused` when active |
383
+ | `.markdown-editor-module-controls` | Left control bar (drag handle + add/delete buttons) |
384
+ | `.markdown-editor-module-content` | Content area inside a block |
385
+ | `.markdown-editor-module-content-focused` | Content area when the block is focused |
386
+ | `.markdown-editor-focus-controls` | Row containing drag‑handle, delete, and add buttons |
387
+ | `.drag-handle` | SortableJS drag handle (⠿) |
388
+ | `.focus-control-btn` | Delete / Add buttons in the control bar |
389
+ | `.markdown-editor-context-menu` | Floating block context menu (`z-index: 1000`) |
390
+ | `.markdown-editor-context-menu-block-item` | Full‑width context menu button |
391
+ | `.markdown-editor-context-menu-inline-item` | Inline toolbar button (`.is-active` when toggled) |
392
+ | `.markdown-editor-modal-overlay` | Modal backdrop (`z-index: 9999`) |
393
+ | `.markdown-editor-modal` | Modal container |
394
+ | `.markdown-editor-modal-header` | Modal title bar |
395
+ | `.markdown-editor-modal-title` | Modal heading text |
396
+ | `.markdown-editor-modal-close` | Close (✕) button |
397
+ | `.markdown-editor-modal-body` | Modal content area |
398
+ | `.markdown-editor-modal-footer` | Modal action bar |
399
+ | `.markdown-editor-modal-button` | Base modal button |
400
+ | `.markdown-editor-modal-button-primary` | Primary (Save) button — blue |
401
+ | `.markdown-editor-modal-button-secondary` | Secondary (Cancel) button — gray |
402
+ | `.markdown-module-image` | Image block wrapper |
403
+ | `.markdown-module-image-form` | Image edit form inside the modal |
404
+ | `.markdown-module-image-form-field` | Form field group (label + input) |
405
+
406
+ ### Styling TipTap content
407
+
408
+ Each text‑based block hosts its own TinyMCE‑style TipTap editor. Target `.tiptap` inside a block's content area:
409
+
410
+ ```css
411
+ /* Make all TipTap editors use your font */
412
+ .markdown-editor-module-content .tiptap {
413
+ font-family: "Georgia", serif;
414
+ font-size: 1.1rem;
415
+ line-height: 1.8;
416
+ }
417
+ ```
418
+
419
+ ### Do's
420
+
421
+ - ✅ Import the stylesheet: `import "@grandaniel/vue-markdown-editor/style.css"`
422
+ - ✅ Use **global** (unscoped) CSS or `:deep()` from a parent to override styles
423
+ - ✅ Target `.tiptap` inside `.markdown-editor-module-content` for editor typography
424
+ - ✅ Use `z-index` values above `1000` / `9999` for anything that must layer **above** context menus and modals
425
+
426
+ ### Don'ts
427
+
428
+ - ❌ Don't rely on CSS custom properties — the editor uses hard‑coded Tailwind‑scale colors (grays and blues)
429
+ - ❌ Don't override `z-index` on `.markdown-editor-context-menu` or `.markdown-editor-modal-overlay` — it will break layering
430
+ - ❌ Don't use `display: contents` on `.markdown-editor-module` — it interferes with SortableJS drag logic
431
+ - ❌ Don't set `outline: none` on `.markdown-editor-module-content` globally — the focus ring is intentional for keyboard navigation
432
+
433
+ ---
434
+
435
+ ## Supply Chain Security
436
+
437
+ We take package integrity seriously.
438
+
439
+ | Measure | Status |
440
+ |---|---|
441
+ | **npm package provenance** | ✅ Enabled — every publish includes [provenance attestations](https://docs.npmjs.com/generating-provenance-statements) via GitHub Actions and Sigstore. |
442
+ | **CI/CD** | ✅ GitHub Actions runs `npm ci` → `npm test` → `npm run build` → publish on every push to `dev` and `main`. |
443
+ | **Prerelease tags** | ✅ Non‑main branches publish with a `dev` dist‑tag (e.g. `1.1.3-dev.abc1234`). |
444
+ | **Dependabot** | 🔜 Planned — automated dependency update PRs will be enabled via `.github/dependabot.yml`. |
445
+
446
+ To verify provenance locally:
447
+
448
+ ```bash
449
+ npm audit signatures
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Contributing
455
+
456
+ We welcome contributions! Please follow the guidelines below.
457
+
458
+ ### PR Policy
459
+
460
+ 1. **Fork** the repository and create a feature branch off `dev`.
461
+ 2. **Keep changes focused** — one feature or fix per PR.
462
+ 3. **Add tests** for any new functionality. The project uses [Vitest](https://vitest.dev/) + [`@vue/test-utils`](https://test-utils.vuejs.org/).
463
+ 4. **Run the full check** before pushing:
464
+
465
+ ```bash
466
+ npm ci
467
+ npm run test
468
+ npm run type-check
469
+ npm run build
470
+ ```
471
+
472
+ 5. **Open a PR** against the `dev` branch with a clear description of what changed and why.
473
+
474
+ ### Dev setup
475
+
476
+ ```bash
477
+ # Clone and install
478
+ git clone https://github.com/danielgran/vue-markdown-editor.git
479
+ cd vue-markdown-editor
480
+ npm ci
481
+
482
+ # Start the dev server
483
+ npm run dev
484
+ ```
485
+
486
+ The dev server launches at `http://localhost:4010`. The entry point is `dev/App.vue` — a full showcase that demonstrates every editor feature: block types, drag & drop, image upload, context menus, keyboard navigation, and Markdown output serialization. Use it as a playground while developing.
487
+
488
+ ---
489
+
490
+ ## License
491
+
492
+ [ISC](LICENSE) © 2026 [danielgran](https://github.com/danielgran)
493
+
494
+ All components use scoped SCSS. To override styles, use **global CSS** with higher specificity, or Vue's `:deep()` combinator from a parent component.
495
+
496
+ ### CSS class reference
497
+
498
+ | Class | Applies to |
499
+ |---|---|
500
+ | `.markdown-editor` | Root editor container |
501
+ | `.markdown-editor-module` | Individual block wrapper — `.is-focused` when active |
502
+ | `.markdown-editor-module-controls` | Left control bar (drag handle + add/delete buttons) |
503
+ | `.markdown-editor-module-content` | Content area inside a block |
504
+ | `.markdown-editor-module-content-focused` | Content area when the block is focused |
505
+ | `.markdown-editor-focus-controls` | Row containing drag‑handle, delete, and add buttons |
506
+ | `.drag-handle` | SortableJS drag handle (⠿) |
507
+ | `.focus-control-btn` | Delete / Add buttons in the control bar |
508
+ | `.markdown-editor-context-menu` | Floating block context menu (`z-index: 1000`) |
509
+ | `.markdown-editor-context-menu-block-item` | Full‑width context menu button |
510
+ | `.markdown-editor-context-menu-inline-item` | Inline toolbar button (`.is-active` when toggled) |
511
+ | `.markdown-editor-modal-overlay` | Modal backdrop (`z-index: 9999`) |
512
+ | `.markdown-editor-modal` | Modal container |
513
+ | `.markdown-editor-modal-header` | Modal title bar |
514
+ | `.markdown-editor-modal-title` | Modal heading text |
515
+ | `.markdown-editor-modal-close` | Close (✕) button |
516
+ | `.markdown-editor-modal-body` | Modal content area |
517
+ | `.markdown-editor-modal-footer` | Modal action bar |
518
+ | `.markdown-editor-modal-button` | Base modal button |
519
+ | `.markdown-editor-modal-button-primary` | Primary (Save) button — blue |
520
+ | `.markdown-editor-modal-button-secondary` | Secondary (Cancel) button — gray |
521
+ | `.markdown-module-image` | Image block wrapper |
522
+ | `.markdown-module-image-form` | Image edit form inside the modal |
523
+ | `.markdown-module-image-form-field` | Form field group (label + input) |
524
+
525
+ ### Styling TipTap content
526
+
527
+ Each text‑based block hosts its own TinyMCE‑style TipTap editor. Target `.tiptap` inside a block's content area:
528
+
529
+ ```css
530
+ /* Make all TipTap editors use your font */
531
+ .markdown-editor-module-content .tiptap {
532
+ font-family: "Georgia", serif;
533
+ font-size: 1.1rem;
534
+ line-height: 1.8;
535
+ }
536
+ ```
537
+
538
+ ### Do's
539
+
540
+ - ✅ Import the stylesheet: `import "@grandaniel/vue-markdown-editor/style.css"`
541
+ - ✅ Use **global** (unscoped) CSS or `:deep()` from a parent to override styles
542
+ - ✅ Target `.tiptap` inside `.markdown-editor-module-content` for editor typography
543
+ - ✅ Use `z-index` values above `1000` / `9999` for anything that must layer **above** context menus and modals
544
+
545
+ ### Don'ts
546
+
547
+ - ❌ Don't rely on CSS custom properties — the editor uses hard‑coded Tailwind‑scale colors (grays and blues)
548
+ - ❌ Don't override `z-index` on `.markdown-editor-context-menu` or `.markdown-editor-modal-overlay` — it will break layering
549
+ - ❌ Don't use `display: contents` on `.markdown-editor-module` — it interferes with SortableJS drag logic
550
+ - ❌ Don't set `outline: none` on `.markdown-editor-module-content` globally — the focus ring is intentional for keyboard navigation
551
+
552
+ ---
553
+
554
+ ## Supply Chain Security
555
+
556
+ We take package integrity seriously.
557
+
558
+ | Measure | Status |
559
+ |---|---|
560
+ | **npm package provenance** | ✅ Enabled — every publish includes [provenance attestations](https://docs.npmjs.com/generating-provenance-statements) via GitHub Actions and Sigstore. |
561
+ | **CI/CD** | ✅ GitHub Actions runs `npm ci` → `npm test` → `npm run build` → publish on every push to `dev` and `main`. |
562
+ | **Prerelease tags** | ✅ Non‑main branches publish with a `dev` dist‑tag (e.g. `1.1.3-dev.abc1234`). |
563
+ | **Dependabot** | 🔜 Planned — automated dependency update PRs will be enabled via `.github/dependabot.yml`. |
564
+
565
+ To verify provenance locally:
566
+
567
+ ```bash
568
+ npm audit signatures
569
+ ```
570
+
571
+ ---
572
+
573
+ ## Contributing
574
+
575
+ We welcome contributions! Please follow the guidelines below.
576
+
577
+ ### PR Policy
578
+
579
+ 1. **Fork** the repository and create a feature branch off `dev`.
580
+ 2. **Keep changes focused** — one feature or fix per PR.
581
+ 3. **Add tests** for any new functionality. The project uses [Vitest](https://vitest.dev/) + [`@vue/test-utils`](https://test-utils.vuejs.org/).
582
+ 4. **Run the full check** before pushing:
583
+
584
+ ```bash
585
+ npm ci
586
+ npm run test
587
+ npm run type-check
588
+ npm run build
589
+ ```
590
+
591
+ 5. **Open a PR** against the `dev` branch with a clear description of what changed and why.
592
+
593
+ ### Dev setup
594
+
595
+ ```bash
596
+ # Clone and install
597
+ git clone https://github.com/danielgran/vue-markdown-editor.git
598
+ cd vue-markdown-editor
599
+ npm ci
600
+
601
+ # Start the dev server
602
+ npm run dev
603
+ ```
604
+
605
+ The dev server launches at `http://localhost:4010`. The entry point is `dev/App.vue` — a full showcase that demonstrates every editor feature: block types, drag & drop, image upload, context menus, keyboard navigation, and Markdown output serialization. Use it as a playground while developing.
606
+
607
+ ---
608
+
609
+ ## License
610
+
611
+ [ISC](LICENSE) © 2026 [danielgran](https://github.com/danielgran)
@@ -0,0 +1,2 @@
1
+ import type { MarkdownAstNode } from "../Types/MarkdownAstNode";
2
+ export declare function parseMarkdown(markdown: string): MarkdownAstNode[];
@@ -0,0 +1,2 @@
1
+ import type { MarkdownAstNode } from "../Types/MarkdownAstNode";
2
+ export declare function serializeMarkdown(nodes: MarkdownAstNode[]): string;
@@ -7,7 +7,9 @@ declare var __VLS_7: {};
7
7
  type __VLS_Slots = {} & {
8
8
  default?: (props: typeof __VLS_7) => any;
9
9
  };
10
- declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
11
+ rootEl: import("vue").Ref<HTMLElement | null, HTMLElement | null>;
12
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
13
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
14
  declare const _default: typeof __VLS_export;
13
15
  export default _default;
@@ -0,0 +1,19 @@
1
+ type __VLS_Props = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ click: () => any;
7
+ editAttributes: () => any;
8
+ download: () => any;
9
+ retry: () => any;
10
+ close: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onClick?: (() => any) | undefined;
13
+ onEditAttributes?: (() => any) | undefined;
14
+ onDownload?: (() => any) | undefined;
15
+ onRetry?: (() => any) | undefined;
16
+ onClose?: (() => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -3,9 +3,11 @@ type __VLS_Props = {
3
3
  y: number;
4
4
  };
5
5
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ click: () => any;
6
7
  editAttributes: () => any;
7
8
  close: () => any;
8
9
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ onClick?: (() => any) | undefined;
9
11
  onEditAttributes?: (() => any) | undefined;
10
12
  onClose?: (() => any) | undefined;
11
13
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,3 +1,4 @@
1
+ import type MarkdownModuleFileState from "../Modules/MarkdownModuleFileState";
1
2
  import type MarkdownModuleImageState from "../Modules/MarkdownModuleImageState";
2
3
  import type MarkdownModuleListState from "../Modules/MarkdownModuleListState";
3
4
  import MarkdownModuleTextState from "../Modules/MarkdownModuleTextState";
@@ -9,6 +10,7 @@ declare class MarkdownNodeFactory {
9
10
  createImageNode(src: string, alt: string, caption?: string): MarkdownAstNode<MarkdownModuleImageState>;
10
11
  createListNode(items: string[]): MarkdownAstNode<MarkdownModuleListState>;
11
12
  createBlankParagraph(): MarkdownAstNode<MarkdownModuleTextState>;
13
+ createFileNode(url: string, fileName: string, fileSize: number, mimeType: string, uploadError?: string): MarkdownAstNode<MarkdownModuleFileState>;
12
14
  }
13
15
  declare const _default: MarkdownNodeFactory;
14
16
  export default _default;