@grandaniel/vue-markdown-editor 1.1.3-dev.ed12560 → 1.2.0-dev.3b1fd30
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 +261 -37
- package/dist/components/MarkdownRenderer/Composable/useMarkdownRenderer.d.ts +1189 -0
- package/dist/components/MarkdownRenderer/MarkdownRenderer.vue.d.ts +1158 -1
- package/dist/components/MarkdownRenderer/defaultRenderComponentRegistry.d.ts +10 -0
- package/dist/components/MarkdownRenderer/index.d.ts +1 -1
- package/dist/vue-markdown-editor.mjs +3820 -3802
- package/package.json +1 -1
- package/dist/components/MarkdownRenderer/MarkdownRenderComponentRegistry.d.ts +0 -7
package/README.md
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
# @grandaniel/vue-markdown-editor
|
|
7
7
|
|
|
8
|
-
> A block-based, Notion-like Markdown editor for Vue 3. UI-first. Powered by [TipTap](https://tiptap.dev/).
|
|
8
|
+
> A block-based, Notion-like Markdown editor **and** SSR-safe renderer for Vue 3. UI-first. Powered by [TipTap](https://tiptap.dev/).
|
|
9
9
|
|
|
10
|
-
**@grandaniel/vue-markdown-editor**
|
|
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.
|
|
11
15
|
|
|
12
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.
|
|
13
17
|
|
|
@@ -17,10 +21,16 @@ Images are first-class citizens: paste an image to auto-upload, then edit its **
|
|
|
17
21
|
|
|
18
22
|
- [Who uses it](#who-uses-it)
|
|
19
23
|
- [Installation](#installation)
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
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)
|
|
24
34
|
- [Custom Styling](#custom-styling)
|
|
25
35
|
- [Supply Chain Security](#supply-chain-security)
|
|
26
36
|
- [Contributing](#contributing)
|
|
@@ -46,19 +56,21 @@ npm install @grandaniel/vue-markdown-editor
|
|
|
46
56
|
> **Peer dependency:** Vue `^3.5.0`
|
|
47
57
|
> **Node:** `>=22`
|
|
48
58
|
|
|
49
|
-
Import the
|
|
59
|
+
Import the components **and** the stylesheet:
|
|
50
60
|
|
|
51
61
|
```ts
|
|
52
|
-
import { MarkdownEditor, useMarkdownEditor } from "@grandaniel/vue-markdown-editor";
|
|
62
|
+
import { MarkdownEditor, MarkdownRenderer, useMarkdownEditor, useMarkdownRenderer } from "@grandaniel/vue-markdown-editor";
|
|
53
63
|
import "@grandaniel/vue-markdown-editor/style.css";
|
|
54
64
|
```
|
|
55
65
|
|
|
56
66
|
---
|
|
57
67
|
|
|
58
|
-
##
|
|
68
|
+
## Editor
|
|
59
69
|
|
|
60
70
|
The editor is driven by a composable: `useMarkdownEditor()` creates the reactive state, and `<MarkdownEditor>` renders it.
|
|
61
71
|
|
|
72
|
+
### Editor Quick Start
|
|
73
|
+
|
|
62
74
|
```vue
|
|
63
75
|
<script setup lang="ts">
|
|
64
76
|
import { ref } from "vue";
|
|
@@ -81,7 +93,7 @@ const focusedNode = ref<MarkdownAstNode | null>(null);
|
|
|
81
93
|
</template>
|
|
82
94
|
```
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
#### Reading the output
|
|
85
97
|
|
|
86
98
|
The composable keeps the raw Markdown in sync automatically. Read it at any time:
|
|
87
99
|
|
|
@@ -98,7 +110,7 @@ editor.markdownContent.value = "## New heading\n\nFresh content.";
|
|
|
98
110
|
|
|
99
111
|
---
|
|
100
112
|
|
|
101
|
-
|
|
113
|
+
### Keyboard Shortcuts
|
|
102
114
|
|
|
103
115
|
| Key | Action |
|
|
104
116
|
|---|---|
|
|
@@ -108,17 +120,17 @@ editor.markdownContent.value = "## New heading\n\nFresh content.";
|
|
|
108
120
|
| <kbd>Delete</kbd> (empty block) | Delete the block, focus stays at same index |
|
|
109
121
|
| Click blank area | Append a new empty paragraph at the bottom |
|
|
110
122
|
|
|
111
|
-
|
|
123
|
+
#### Auto type‑detection
|
|
112
124
|
|
|
113
125
|
Type `# `, `## `, or `### ` at the start of a paragraph and the block auto‑converts to the matching heading level.
|
|
114
126
|
|
|
115
127
|
---
|
|
116
128
|
|
|
117
|
-
|
|
129
|
+
### Image Upload
|
|
118
130
|
|
|
119
131
|
Images are first-class blocks with **src**, **alt text**, and **caption** fields. Right‑click any image → **Edit Attributes** to open the editing modal.
|
|
120
132
|
|
|
121
|
-
|
|
133
|
+
#### Paste‑to‑upload
|
|
122
134
|
|
|
123
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:
|
|
124
136
|
|
|
@@ -156,9 +168,9 @@ caption: Photo taken during the 2026 summit
|
|
|
156
168
|
|
|
157
169
|
---
|
|
158
170
|
|
|
159
|
-
|
|
171
|
+
### Editor API Reference
|
|
160
172
|
|
|
161
|
-
|
|
173
|
+
#### `useMarkdownEditor(initialContent?: string)`
|
|
162
174
|
|
|
163
175
|
Returns a reactive editor instance:
|
|
164
176
|
|
|
@@ -172,7 +184,7 @@ Returns a reactive editor instance:
|
|
|
172
184
|
| `replaceNodeType(node, newType)` | `(node: MarkdownAstNode, type: MarkdownNodeType) => { newNode, index } \| null` | Convert between block types (e.g. paragraph → heading). |
|
|
173
185
|
| `moveNode(from, to)` | `(fromIndex: number, toIndex: number) => void` | Programmatically reorder a block. |
|
|
174
186
|
|
|
175
|
-
|
|
187
|
+
#### `MarkdownEditor` props
|
|
176
188
|
|
|
177
189
|
| Prop | Type | Required | Description |
|
|
178
190
|
|---|---|---|---|
|
|
@@ -180,40 +192,28 @@ Returns a reactive editor instance:
|
|
|
180
192
|
| `focusedNode` | `MarkdownAstNode \| null` | — | For `v-model:focused-node` tracking. |
|
|
181
193
|
| `imageUploadFunction` | `(file: File) => Promise<string>` | — | Async callback for paste‑to‑upload. |
|
|
182
194
|
|
|
183
|
-
|
|
195
|
+
#### `MarkdownEditor` emits
|
|
184
196
|
|
|
185
197
|
| Event | Payload | Description |
|
|
186
198
|
|---|---|---|
|
|
187
199
|
| `update:focused-node` | `MarkdownAstNode \| null` | Fires when focus moves to a new block. |
|
|
188
200
|
|
|
189
|
-
|
|
201
|
+
#### `MarkdownEditor` slots
|
|
190
202
|
|
|
191
203
|
| Slot | Description |
|
|
192
204
|
|---|---|
|
|
193
205
|
| `after-controls` | Injected inside every block, after the drag‑handle / add / delete controls. |
|
|
194
206
|
|
|
195
|
-
### Exported types & utilities
|
|
196
|
-
|
|
197
|
-
| Export | Kind |
|
|
198
|
-
|---|---|
|
|
199
|
-
| `MarkdownEditorInstance` | Type — return type of `useMarkdownEditor()`. |
|
|
200
|
-
| `MarkdownAstNode` | Class — AST node with `id`, `type`, `componentState`, `editingState`. |
|
|
201
|
-
| `MarkdownAstNodeType` | Enum — `PARAGRAPH`, `HEADLINE1`, `HEADLINE2`, `HEADLINE3`, `IMAGE`, `LIST`. |
|
|
202
|
-
| `ImageNode` | Type alias — `MarkdownAstNode<MarkdownModuleImageState>`. |
|
|
203
|
-
| `TextNode` | Type alias — `MarkdownAstNode<MarkdownModuleTextState>`. |
|
|
204
|
-
| `TextishNodeType` | Type — union of `PARAGRAPH \| HEADLINE1 \| HEADLINE2 \| HEADLINE3 \| LIST`. |
|
|
205
|
-
| `isTextNodeState(node)` | Type guard for text‑based nodes. |
|
|
206
|
-
| `isTextNodeType(type)` | Type guard for text‑based node types. |
|
|
207
|
-
| `MarkdownRenderer` | Component — renders Markdown to plain HTML. SSR‑safe. |
|
|
208
|
-
|
|
209
207
|
---
|
|
210
208
|
|
|
211
|
-
##
|
|
209
|
+
## Renderer
|
|
212
210
|
|
|
213
|
-
`<MarkdownRenderer>` converts a Markdown string to
|
|
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.
|
|
214
212
|
|
|
215
213
|
The renderer is **SSR‑safe** — no browser APIs, no TipTap. It works in `nuxt generate`, `vite-ssg`, and any server‑side rendering context.
|
|
216
214
|
|
|
215
|
+
### Renderer Quick Start
|
|
216
|
+
|
|
217
217
|
```vue
|
|
218
218
|
<script setup lang="ts">
|
|
219
219
|
import { MarkdownRenderer } from "@grandaniel/vue-markdown-editor";
|
|
@@ -238,7 +238,7 @@ caption: Photo caption
|
|
|
238
238
|
</template>
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
#### Live preview alongside the editor
|
|
242
242
|
|
|
243
243
|
Bind the editor's reactive `markdownContent` to the renderer:
|
|
244
244
|
|
|
@@ -257,11 +257,116 @@ const editor = useMarkdownEditor("# Start writing…");
|
|
|
257
257
|
</template>
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
-
|
|
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
|
|
261
344
|
|
|
262
345
|
| Prop | Type | Required | Description |
|
|
263
346
|
|---|---|---|---|
|
|
264
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`). |
|
|
265
370
|
|
|
266
371
|
---
|
|
267
372
|
|
|
@@ -385,3 +490,122 @@ The dev server launches at `http://localhost:4010`. The entry point is `dev/App.
|
|
|
385
490
|
## License
|
|
386
491
|
|
|
387
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)
|