markdown-text-editor 1.7.0 → 1.9.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 nezanuha
3
+ Copyright (c) 2026 nezanuha
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # MarkdownEditor — Lightweight JavaScript Markdown Editor with WYSIWYG & Plain Mode
1
+ # Markdown Text Editor
2
2
 
3
- ### The Native-First JavaScript Markdown Editor
3
+ ### Toolbar, live preview and WYSIWYG on the textarea you already have
4
4
 
5
5
  [![npm installs][npm_installs]](https://www.npmjs.com/package/markdown-text-editor)
6
6
  [![Jsdelivr hits][jsdelivr]](https://cdn.jsdelivr.net/npm/markdown-text-editor)
@@ -13,27 +13,34 @@ A lightweight, embeddable JavaScript Markdown editor that transforms a standard
13
13
 
14
14
  A native-first Markdown editor built on a standard textarea. No data binding, no API — just drop it in and your forms keep working as-is.
15
15
 
16
+ **Works standalone.** No Frutjam, no Tailwind, no framework required, the styles are bundled.
17
+
18
+ ![Hybrid mode: headings, bold text and list markers styled live inside the textarea while the Markdown syntax stays visible](https://cdn.frutjam.com/media/plugins/hybrid-mode-markdown-editor.webp)
19
+
20
+ <sub>Hybrid mode. Text is styled as you type and the Markdown stays where it is, because this is your `<textarea>`, not a copy of it.</sub>
21
+
16
22
  > **No complex APIs. No data binding. No JSON schemas.** Just a `<textarea>` that types Markdown and submits like any normal form field — enhanced with a rich toolbar, live preview, and WYSIWYG hybrid mode.
17
23
 
18
24
  ## ⭐ Why developers choose this over EasyMDE / SimpleMDE
19
25
 
20
- Most JavaScript markdown editors (EasyMDE, SimpleMDE, CodeMirror-based editors) replace your `<textarea>` with a custom element — which means you have to write extra code to extract the value before form submission, sync state manually, and learn a new API just to read or set content.
26
+ Most JavaScript markdown editors (EasyMDE, SimpleMDE, CodeMirror-based editors) hide your `<textarea>` and edit a copy, writing the value back when the form is submitted. That holds up until something else needs the field: a `required` input the browser cannot focus blocks the submit entirely, and `.value` or `FormData` read before submit returns an empty string, which breaks htmx, Turbo, Livewire, autosave and unsaved-changes guards.
21
27
 
22
28
  **MarkdownEditor is different.** It sits transparently on top of your existing `<textarea>`:
23
29
 
24
30
  | Feature | MarkdownEditor | EasyMDE / SimpleMDE |
25
31
  |---|---|---|
26
- | Native `<textarea>` preserved | ✅ | ❌ Replaced |
27
- | Form submission works as-is | ✅ | ❌ Requires extra JS |
28
- | Get/set value via `.value` | ✅ | ❌ Custom API needed |
32
+ | Native `<textarea>` preserved | ✅ | ❌ Hidden, edited as a copy |
33
+ | Works with `required` fields | ✅ | ❌ Browser blocks the submit |
34
+ | `.value` correct before submit | ✅ | ❌ Empty until the form submits |
35
+ | Serialises with `FormData`, htmx, Turbo | ✅ | ❌ Needs the editor's own API |
29
36
  | WYSIWYG hybrid mode | ✅ | ❌ |
30
- | CSP-compatible (no inline JS) | ✅ | ❌ |
31
- | Zero CSS conflicts | ✅ | ❌ |
32
- | RTL support | ✅ | ❌ |
37
+ | Inline event handlers (CSP) | ✅ None | Some |
38
+ | RTL support | ✅ Built in | Via CodeMirror's `direction` option |
33
39
  | Built-in Find & Replace | ✅ | ❌ |
34
40
  | Swap the markdown parser | ✅ | ❌ |
35
41
  | Keyboard shortcuts | ✅ | Partial |
36
42
  | Dark mode / theming | ✅ | Limited |
43
+ | Bundle size, gzipped | 54 KB | 107 KB (JS + CSS) |
37
44
 
38
45
  ## 🚀 Quick Start
39
46
 
@@ -89,13 +96,14 @@ That's it. Form submission, `.value` access, and all native textarea behaviour w
89
96
  - 🔄 **Undo / Redo** — Full diff-based history with exact cursor restoration. Works with `Ctrl+Z`, `Ctrl+Y`, `Ctrl+Shift+Z`
90
97
  - ♿ **Accessible by Default** — `role="toolbar"`, `aria-pressed`, `aria-disabled`, `disabled`, screen-reader-friendly SVGs, and correct focus restoration on modal close
91
98
  - 🛡️ **XSS Safe** — Preview output sanitized via [DOMPurify](https://github.com/cure53/DOMPurify) before rendering, including the output of a custom renderer
92
- - 🛡️ **CSP Compatible** — No inline event handlers. Works with strict Content Security Policy headers
99
+ - 🛡️ **No inline scripts** — No inline event handlers and no `eval`, so `script-src` needs no exception. The stylesheet is currently injected as a `<style>` element, which a strict `style-src` blocks ([#42](https://github.com/nezanuha/markdown-text-editor/issues/42))
93
100
  - 🌍 **RTL Support** — Native Right-to-Left support for Arabic, Urdu, Farsi, and other RTL languages
94
101
  - 🌙 **Dark Mode & Theming** — Inherits `data-theme` from any ancestor element. Built-in light, dark, snowberry, and darkberry themes. Fully customizable via CSS variables
102
+ - 🧩 **Custom Tools** — Add your own toolbar button with `{ custom: { title, icon, action } }`, or extend `MarkdownEditor.Tool` when it builds its own markup. Keyboard shortcuts, translation and teardown work exactly as they do for the built-ins. No fork, no patching
95
103
  - 🎛️ **Modular Toolbar** — Pick exactly which tools appear and in what order
96
104
  - 🟦 **TypeScript Ready** — Definitions ship with the package. Options, toolbar entries and variable shapes are all checked, so a mistyped tool name is a compile error rather than a silently missing button
97
105
  - 📦 **Universal Module Support** — ESM, CommonJS, UMD, and IIFE. Works with Vite, webpack, Rollup, or directly via `<script src>` CDN — no configuration needed
98
- - 🚀 **High Performance** — ~52KB gzipped (245KB minified). Debounced preview, cached layout calculations, conflict-free Tab/Enter handling for large documents
106
+ - 🚀 **High Performance** — ~54KB gzipped (253KB minified). Debounced preview, cached layout calculations, conflict-free Tab/Enter handling for large documents
99
107
 
100
108
  ## 🛠 Developer Workflow
101
109
 
@@ -133,6 +141,46 @@ const editor = new MarkdownEditor('#markdown-editor', {
133
141
  });
134
142
  ```
135
143
 
144
+ ### Add your own toolbar button
145
+
146
+ Most tools are just a title, an icon and a function. No class, no fork:
147
+
148
+ ```javascript
149
+ new MarkdownEditor('#markdown-editor', {
150
+ toolbar: ['bold', 'italic', {
151
+ custom: {
152
+ title: 'Callout', // tooltip, and the .callout-btn class
153
+ icon: '<svg>…</svg>',
154
+ shortcut: 'Ctrl+Shift+C', // optional, shown in the tooltip
155
+ action(editor) {
156
+ editor.insertText('> [!NOTE]\n> ');
157
+ }
158
+ }
159
+ }, 'preview']
160
+ });
161
+ ```
162
+
163
+ Use `editor.insertText(text, caretOffset, trailingLength)` rather than writing to the textarea yourself — it replaces the selection, restores focus, moves the caret, re-renders the preview, fires `onChange`, and keeps undo working.
164
+
165
+ A tool that builds its own markup, such as a dropdown or a dialog, extends the base class instead:
166
+
167
+ ```javascript
168
+ class EmbedTool extends MarkdownEditor.Tool {
169
+ constructor(editor) {
170
+ super(editor, 'Embed video');
171
+ this.button = this.createButton('<svg>…</svg>');
172
+ }
173
+ applySyntax(event) {
174
+ // MarkdownEditor.modal is the same dialog the link and image tools use
175
+ const dialog = MarkdownEditor.modal(event, 'fj:max-w-sm', '…', 'Embed');
176
+ }
177
+ }
178
+
179
+ new MarkdownEditor('#markdown-editor', { toolbar: ['bold', EmbedTool, 'preview'] });
180
+ ```
181
+
182
+ Two helpers exist for tools that need them: `editor.renderMarkdown(md)` returns sanitized HTML exactly as the preview does, and `editor.createToolbar(textarea, ['bold', 'italic'])` builds a real toolbar bound to another textarea, for a tool collecting rich text in its own dialog.
183
+
136
184
  ### Tear down in SPAs
137
185
 
138
186
  ```javascript
@@ -149,7 +197,7 @@ useEffect(() => {
149
197
  }, []);
150
198
  ```
151
199
 
152
- Use `defaultValue`, not `value`. The editor writes to the textarea directly, so a controlled binding (or Vue's `:value`) would overwrite what the user is typing. Full React and Vue examples are in the [documentation](https://frutjam.com/community/plugins/markdown-editor).
200
+ Use `defaultValue`, not `value`. The editor writes to the textarea directly, so a controlled binding (or Vue's `:value`) would overwrite what the user is typing. Full React and Vue examples are in the [documentation](https://frutjam.com/plugins/markdown-editor).
153
201
 
154
202
  The same two rules cover Svelte, Angular and anything else: don't bind the value, and call `destroy()` on unmount. In Angular, `onChange` fires outside the zone, so wrap it in `zone.run()` for change detection to notice.
155
203