rich-html-editor 0.2.2 → 1.0.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 +3 -1
- package/dist/{chunk-ZDGUOGND.mjs → chunk-W3ULZ2LR.mjs} +23 -1
- package/dist/chunk-W3ULZ2LR.mjs.map +1 -0
- package/dist/index.js +138 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{state-CZIMHTJ3.mjs → state-DA3PGFTN.mjs} +2 -2
- package/package.json +1 -1
- package/dist/chunk-ZDGUOGND.mjs.map +0 -1
- /package/dist/{state-CZIMHTJ3.mjs.map → state-DA3PGFTN.mjs.map} +0 -0
package/README.md
CHANGED
|
@@ -14,9 +14,11 @@ A framework-agnostic, plug-and-play rich HTML editor library for adding WYSIWYG
|
|
|
14
14
|
- 🔒 **Security** - URL sanitization to prevent XSS attacks
|
|
15
15
|
- 📦 **Small Bundle** - ~13KB minified (ESM format)
|
|
16
16
|
|
|
17
|
+
Release: **v1.0.0** — Stable release (2025-12-22) including headings, lists (UL/OL), full toolbar formatting, color pickers, link sanitization, and undo/redo.
|
|
18
|
+
|
|
17
19
|
## Changelog
|
|
18
20
|
|
|
19
|
-
See the full changelog in [CHANGELOG.md](CHANGELOG.md).
|
|
21
|
+
See the full changelog in [CHANGELOG.md](CHANGELOG.md). Latest release: **v1.0.0** (2025-12-22).
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
@@ -74,6 +74,26 @@ var LABEL_STRIKETHROUGH = "<s>S</s>";
|
|
|
74
74
|
var LABEL_UNDO = "\u21BA";
|
|
75
75
|
var LABEL_REDO = "\u21BB";
|
|
76
76
|
var LABEL_LINK = "\u{1F517}";
|
|
77
|
+
var LABEL_UNORDERED_LIST = `
|
|
78
|
+
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
79
|
+
<circle cx="3" cy="4" r="1" fill="currentColor" />
|
|
80
|
+
<rect x="6" y="3" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
81
|
+
<circle cx="3" cy="8" r="1" fill="currentColor" />
|
|
82
|
+
<rect x="6" y="7" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
83
|
+
<circle cx="3" cy="12" r="1" fill="currentColor" />
|
|
84
|
+
<rect x="6" y="11" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
85
|
+
</svg>
|
|
86
|
+
`;
|
|
87
|
+
var LABEL_ORDERED_LIST = `
|
|
88
|
+
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
89
|
+
<text x="1" y="4" font-size="4" fill="currentColor">1.</text>
|
|
90
|
+
<rect x="6" y="3" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
91
|
+
<text x="1" y="8" font-size="4" fill="currentColor">2.</text>
|
|
92
|
+
<rect x="6" y="7" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
93
|
+
<text x="1" y="12" font-size="4" fill="currentColor">3.</text>
|
|
94
|
+
<rect x="6" y="11" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
95
|
+
</svg>
|
|
96
|
+
`;
|
|
77
97
|
var LABEL_ALIGN_LEFT = `
|
|
78
98
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
79
99
|
<rect x="1" y="2" width="10" height="2" rx="0.5" fill="currentColor" />
|
|
@@ -420,6 +440,8 @@ export {
|
|
|
420
440
|
LABEL_UNDO,
|
|
421
441
|
LABEL_REDO,
|
|
422
442
|
LABEL_LINK,
|
|
443
|
+
LABEL_UNORDERED_LIST,
|
|
444
|
+
LABEL_ORDERED_LIST,
|
|
423
445
|
LABEL_ALIGN_LEFT,
|
|
424
446
|
LABEL_ALIGN_CENTER,
|
|
425
447
|
LABEL_ALIGN_RIGHT,
|
|
@@ -438,4 +460,4 @@ export {
|
|
|
438
460
|
pushStandaloneSnapshot,
|
|
439
461
|
setMaxStackSize
|
|
440
462
|
};
|
|
441
|
-
//# sourceMappingURL=chunk-
|
|
463
|
+
//# sourceMappingURL=chunk-W3ULZ2LR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/events.ts","../src/core/constants.ts","../src/utils/sanitize.ts","../src/core/state.ts"],"sourcesContent":["import type { EditorEventType, EditorEvent, EditorEventHandler } from \"./types\";\r\n\r\nexport class EditorEventEmitter {\r\n private listeners: Map<EditorEventType, Set<EditorEventHandler>> = new Map();\r\n\r\n on(type: EditorEventType, handler: EditorEventHandler): () => void {\r\n if (!this.listeners.has(type)) {\r\n this.listeners.set(type, new Set());\r\n }\r\n this.listeners.get(type)!.add(handler);\r\n return () => this.off(type, handler);\r\n }\r\n\r\n once(type: EditorEventType, handler: EditorEventHandler): void {\r\n const unsubscribe = this.on(type, (event) => {\r\n handler(event);\r\n unsubscribe();\r\n });\r\n }\r\n\r\n off(type: EditorEventType, handler: EditorEventHandler): void {\r\n const handlers = this.listeners.get(type);\r\n if (handlers) {\r\n handlers.delete(handler);\r\n if (handlers.size === 0) this.listeners.delete(type);\r\n }\r\n }\r\n\r\n emit(event: EditorEvent): void {\r\n const handlers = this.listeners.get(event.type);\r\n if (handlers) {\r\n handlers.forEach((handler) => {\r\n try {\r\n handler(event);\r\n } catch (error) {\r\n console.error(\r\n `[rich-html-editor] Error in event handler for ${event.type}:`,\r\n error\r\n );\r\n }\r\n });\r\n }\r\n }\r\n\r\n removeAllListeners(type?: EditorEventType): void {\r\n if (type) this.listeners.delete(type);\r\n else this.listeners.clear();\r\n }\r\n\r\n listenerCount(type: EditorEventType): number {\r\n return this.listeners.get(type)?.size ?? 0;\r\n }\r\n}\r\n\r\nexport const editorEventEmitter = new EditorEventEmitter();\r\n\r\nexport function getEditorEventEmitter(): EditorEventEmitter {\r\n return editorEventEmitter;\r\n}\r\n","// Shared constants for the rich-html-editor (moved to core)\r\nexport const TOOLBAR_ID = \"editor-toolbar\";\r\nexport const STYLE_ID = \"editor-styles\";\r\nexport const CLASS_EDITABLE = \"editor-editable-element\";\r\nexport const CLASS_ACTIVE = \"editor-active-element\";\r\n\r\n// Default undo/redo stack size\r\nexport const DEFAULT_MAX_STACK = 60;\r\n\r\n// Toolbar styling constants\r\nexport const TOOLBAR_BG = \"#f8fafc\";\r\nexport const TOOLBAR_BORDER = \"#e5e7eb\";\r\nexport const BUTTON_BORDER = \"#d1d5db\";\r\nexport const BUTTON_ACTIVE_BG = \"#e0e7ff\";\r\nexport const BUTTON_BG = \"#fff\";\r\nexport const BUTTON_COLOR = \"#222\";\r\nexport const INFO_COLOR = \"#888\";\r\n\r\n// Outline colors used by injected styles\r\nexport const HOVER_OUTLINE = \"#2563eb\";\r\nexport const ACTIVE_OUTLINE = \"#16a34a\";\r\n\r\n// Toolbar label/icon constants (kept in core for reuse)\r\nexport const LABEL_BOLD = \"<b>B</b>\";\r\nexport const LABEL_ITALIC = \"<i>I</i>\";\r\nexport const LABEL_UNDERLINE = \"<u>U</u>\";\r\nexport const LABEL_STRIKETHROUGH = \"<s>S</s>\";\r\nexport const LABEL_UNDO = \"↺\";\r\nexport const LABEL_REDO = \"↻\";\r\nexport const LABEL_LINK = \"🔗\";\r\nexport const LABEL_UNORDERED_LIST = `\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\r\n <circle cx=\"3\" cy=\"4\" r=\"1\" fill=\"currentColor\" />\r\n <rect x=\"6\" y=\"3\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n <circle cx=\"3\" cy=\"8\" r=\"1\" fill=\"currentColor\" />\r\n <rect x=\"6\" y=\"7\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n <circle cx=\"3\" cy=\"12\" r=\"1\" fill=\"currentColor\" />\r\n <rect x=\"6\" y=\"11\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n </svg>\r\n`;\r\nexport const LABEL_ORDERED_LIST = `\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\r\n <text x=\"1\" y=\"4\" font-size=\"4\" fill=\"currentColor\">1.</text>\r\n <rect x=\"6\" y=\"3\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n <text x=\"1\" y=\"8\" font-size=\"4\" fill=\"currentColor\">2.</text>\r\n <rect x=\"6\" y=\"7\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n <text x=\"1\" y=\"12\" font-size=\"4\" fill=\"currentColor\">3.</text>\r\n <rect x=\"6\" y=\"11\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n </svg>\r\n`;\r\nexport const LABEL_ALIGN_LEFT = `\r\n\t<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\r\n\t\t<rect x=\"1\" y=\"2\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"6\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"10\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"14\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t</svg>\r\n`;\r\nexport const LABEL_ALIGN_CENTER = `\r\n\t<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\r\n\t\t<rect x=\"3\" y=\"2\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"6\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"3\" y=\"10\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"14\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t</svg>\r\n`;\r\nexport const LABEL_ALIGN_RIGHT = `\r\n\t<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\r\n\t\t<rect x=\"5\" y=\"2\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"6\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"5\" y=\"10\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t\t<rect x=\"1\" y=\"14\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\r\n\t</svg>\r\n`;\r\n\r\n// Font and size option lists used by the toolbar\r\nexport const FONT_OPTIONS: { label: string; value: string }[] = [\r\n { label: \"Arial\", value: \"Arial\" },\r\n { label: \"Helvetica\", value: \"Helvetica, Arial, sans-serif\" },\r\n { label: \"Verdana\", value: \"Verdana, Geneva, sans-serif\" },\r\n { label: \"Tahoma\", value: \"Tahoma, Geneva, sans-serif\" },\r\n { label: \"Trebuchet MS\", value: \"Trebuchet MS, Helvetica, sans-serif\" },\r\n { label: \"Georgia\", value: \"Georgia, serif\" },\r\n { label: \"Times New Roman\", value: \"Times New Roman, Times, serif\" },\r\n { label: \"Palatino\", value: \"Palatino, 'Palatino Linotype', serif\" },\r\n { label: \"Garamond\", value: \"Garamond, serif\" },\r\n { label: \"Book Antiqua\", value: \"'Book Antiqua', Palatino, serif\" },\r\n { label: \"Courier New\", value: \"'Courier New', Courier, monospace\" },\r\n { label: \"Lucida Console\", value: \"'Lucida Console', Monaco, monospace\" },\r\n { label: \"Impact\", value: \"Impact, Charcoal, sans-serif\" },\r\n { label: \"Comic Sans MS\", value: \"'Comic Sans MS', 'Comic Sans', cursive\" },\r\n { label: \"Segoe UI\", value: \"'Segoe UI', Tahoma, Geneva, sans-serif\" },\r\n {\r\n label: \"Roboto\",\r\n value: \"Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif\",\r\n },\r\n { label: \"Open Sans\", value: \"'Open Sans', Arial, sans-serif\" },\r\n { label: \"Lato\", value: \"Lato, 'Helvetica Neue', Arial, sans-serif\" },\r\n { label: \"Montserrat\", value: \"Montserrat, Arial, sans-serif\" },\r\n { label: \"Source Sans Pro\", value: \"'Source Sans Pro', Arial, sans-serif\" },\r\n { label: \"Fira Sans\", value: \"'Fira Sans', Arial, sans-serif\" },\r\n { label: \"Ubuntu\", value: \"Ubuntu, Arial, sans-serif\" },\r\n { label: \"Noto Sans\", value: \"'Noto Sans', Arial, sans-serif\" },\r\n { label: \"Droid Sans\", value: \"'Droid Sans', Arial, sans-serif\" },\r\n {\r\n label: \"Helvetica Neue\",\r\n value: \"'Helvetica Neue', Helvetica, Arial, sans-serif\",\r\n },\r\n {\r\n label: \"System UI\",\r\n value:\r\n \"system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\",\r\n },\r\n];\r\n\r\nexport const SIZE_OPTIONS: { label: string; value: string }[] = [\r\n { label: \"8\", value: \"8\" },\r\n { label: \"9\", value: \"9\" },\r\n { label: \"10\", value: \"10\" },\r\n { label: \"11\", value: \"11\" },\r\n { label: \"12\", value: \"12\" },\r\n { label: \"14\", value: \"14\" },\r\n { label: \"16\", value: \"16\" },\r\n { label: \"18\", value: \"18\" },\r\n { label: \"20\", value: \"20\" },\r\n { label: \"22\", value: \"22\" },\r\n { label: \"24\", value: \"24\" },\r\n { label: \"26\", value: \"26\" },\r\n { label: \"28\", value: \"28\" },\r\n { label: \"36\", value: \"36\" },\r\n { label: \"48\", value: \"48\" },\r\n { label: \"72\", value: \"72\" },\r\n];\r\n\r\n// Block format options (Paragraph + Headings)\r\nexport const FORMAT_OPTIONS: { label: string; value: string }[] = [\r\n { label: \"Heading 1\", value: \"h1\" },\r\n { label: \"Heading 2\", value: \"h2\" },\r\n { label: \"Heading 3\", value: \"h3\" },\r\n { label: \"Heading 4\", value: \"h4\" },\r\n { label: \"Heading 5\", value: \"h5\" },\r\n { label: \"Heading 6\", value: \"h6\" },\r\n];\r\n\r\n// Shared SVG used by toolbar color inputs (palette icon)\r\nexport const PALETTE_SVG = `\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\r\n <path d=\"M12 2C7 2 3 6 3 11c0 2.8 1.4 5.3 3.7 6.8.9.6 2 .9 3.3.9 1.6 0 3.1-.5 4.3-1.4.6-.4.9-1.1.6-1.8-.3-.7-1-1-1.7-.8-1.1.3-2.3.2-3.4-.3C10.3 14.6 9 13.6 9 12c0-1.7 1.3-3 3-3 .8 0 1.5.3 2.1.8.5.4 1.2.3 1.6-.2.9-1.1 1.4-2.4 1.4-3.8C20.9 6 16.9 2 12 2z\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n <circle cx=\"7.5\" cy=\"10.5\" r=\"1\" fill=\"currentColor\"/>\r\n <circle cx=\"11.5\" cy=\"8.5\" r=\"1\" fill=\"currentColor\"/>\r\n <circle cx=\"14.5\" cy=\"11.5\" r=\"1\" fill=\"currentColor\"/>\r\n </svg>\r\n`;\r\n\r\n// MS Word-like icons for color controls\r\nexport const TEXT_COLOR_ICON_HTML = `\r\n <span class=\"text-color-wrapper\" aria-hidden=\"true\">\r\n <span class=\"text-A\">A</span>\r\n </span>\r\n`;\r\n\r\nexport const HIGHLIGHT_ICON_HTML = `\r\n <span class=\"highlight-wrapper\" aria-hidden=\"true\">\r\n <span class=\"highlight-bar\" data-role=\"highlight\"></span>\r\n <span class=\"text-A\">A</span>\r\n </span>\r\n`;\r\n","import createDOMPurify from \"dompurify\";\r\n\r\n/**\r\n * Sanitize HTML using DOMPurify.\r\n *\r\n * Works in browser and in jsdom (tests) by accepting either a `Window`\r\n * or a `Document` (from which `defaultView` is used).\r\n */\r\nexport function sanitizeHtml(\r\n html: string,\r\n ctx?: Window | Document | null\r\n): string {\r\n if (!html) return \"\";\r\n // Try to get a Window reference\r\n let win: Window | null = null;\r\n if (ctx && (ctx as Document).defaultView) {\r\n win = (ctx as Document).defaultView as Window;\r\n } else if (ctx && (ctx as Window).document) {\r\n win = ctx as Window;\r\n } else if (typeof window !== \"undefined\") {\r\n win = window as Window;\r\n }\r\n\r\n // If we have a window, use DOMPurify\r\n if (win) {\r\n try {\r\n const DOMPurify = createDOMPurify(win as any);\r\n // Preserve element ids and data-* attributes so restoring snapshots\r\n // during undo/redo does not break page scripts or CSS that rely on\r\n // those attributes. Still strip <script> and all inline event\r\n // handlers (on*) to avoid executing arbitrary script.\r\n // Use ADD_ATTR to allow `id`, and a hook to preserve any `data-` attrs.\r\n try {\r\n DOMPurify.addHook(\"uponSanitizeAttribute\", (node: any, data: any) => {\r\n try {\r\n if (data && data.attrName && data.attrName.startsWith(\"data-\")) {\r\n // Keep data-* attributes\r\n (data as any).keepAttr = true;\r\n }\r\n } catch (e) {\r\n /* ignore hook errors */\r\n }\r\n });\r\n } catch (e) {\r\n /* addHook may not be available in some environments; ignore */\r\n }\r\n\r\n return DOMPurify.sanitize(html, {\r\n // Use sensible defaults: allow common formatting tags but strip scripts\r\n ALLOWED_TAGS: [\r\n \"a\",\r\n \"b\",\r\n \"i\",\r\n \"em\",\r\n \"strong\",\r\n \"u\",\r\n \"p\",\r\n \"div\",\r\n \"span\",\r\n // Common semantic elements: preserve document structure so undo/redo\r\n // does not flatten header/section/nav into plain content.\r\n \"header\",\r\n \"nav\",\r\n \"section\",\r\n \"main\",\r\n \"footer\",\r\n \"article\",\r\n \"aside\",\r\n \"figure\",\r\n \"figcaption\",\r\n \"time\",\r\n // Interactive / form elements that may appear in content\r\n \"button\",\r\n \"input\",\r\n \"label\",\r\n \"select\",\r\n \"option\",\r\n \"textarea\",\r\n \"details\",\r\n \"summary\",\r\n // Allow <style> tags so user/content-provided CSS is preserved\r\n // when taking snapshots and during undo/redo operations.\r\n // DOMPurify will still sanitize the contents of style blocks.\r\n \"style\",\r\n // Preserve linked stylesheets so page/editor styling isn't lost\r\n \"link\",\r\n \"ul\",\r\n \"ol\",\r\n \"li\",\r\n \"br\",\r\n \"hr\",\r\n \"blockquote\",\r\n \"pre\",\r\n \"code\",\r\n \"h1\",\r\n \"h2\",\r\n \"h3\",\r\n \"h4\",\r\n \"h5\",\r\n \"h6\",\r\n \"img\",\r\n ],\r\n ALLOWED_ATTR: [\r\n \"href\",\r\n \"title\",\r\n \"alt\",\r\n \"src\",\r\n \"class\",\r\n \"style\",\r\n // Attributes used by <link> tags\r\n \"rel\",\r\n \"type\",\r\n \"media\",\r\n ],\r\n // Also allow `id` attributes so element ids survive sanitization.\r\n ADD_ATTR: [\"id\"],\r\n });\r\n } catch (e) {\r\n // If DOMPurify initialization fails, fall through to minimal stripping\r\n }\r\n }\r\n\r\n // Minimal fallback: remove <script> tags and on* attributes\r\n return html\r\n .replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, \"\")\r\n .replace(/on[a-z]+=\\\"[^\"]*\\\"/gi, \"\");\r\n}\r\n\r\nexport default sanitizeHtml;\r\n","import { editorEventEmitter } from \"./events\";\r\nimport {\r\n DEFAULT_MAX_STACK,\r\n TOOLBAR_ID,\r\n STYLE_ID,\r\n CLASS_EDITABLE,\r\n CLASS_ACTIVE,\r\n} from \"./constants\";\r\nimport { sanitizeHtml } from \"../utils/sanitize\";\r\n\r\nlet _doc: Document | null = null;\r\nlet _undoStack: string[] = [];\r\nlet _redoStack: string[] = [];\r\nlet _currentEditable: HTMLElement | null = null;\r\nlet _maxStackSize = DEFAULT_MAX_STACK;\r\n\r\nexport function _setDoc(doc: Document | null) {\r\n _doc = doc;\r\n}\r\nexport function _getDoc() {\r\n return _doc;\r\n}\r\nexport function _setUndoStack(stack: string[]) {\r\n _undoStack = stack;\r\n editorEventEmitter.emit({\r\n type: \"undoStateChanged\",\r\n timestamp: Date.now(),\r\n data: { canUndo: _undoStack.length > 1 },\r\n });\r\n}\r\nexport function _getUndoStack() {\r\n return _undoStack;\r\n}\r\nexport function _setRedoStack(stack: string[]) {\r\n _redoStack = stack;\r\n editorEventEmitter.emit({\r\n type: \"redoStateChanged\",\r\n timestamp: Date.now(),\r\n data: { canRedo: _redoStack.length > 0 },\r\n });\r\n}\r\nexport function _getRedoStack() {\r\n return _redoStack;\r\n}\r\nexport function _setCurrentEditable(el: HTMLElement | null) {\r\n _currentEditable = el;\r\n editorEventEmitter.emit({\r\n type: \"selectionChanged\",\r\n timestamp: Date.now(),\r\n data: { element: el?.tagName },\r\n });\r\n}\r\nexport function _getCurrentEditable() {\r\n return _currentEditable;\r\n}\r\nexport function pushStandaloneSnapshot(clearRedo = true) {\r\n if (!_doc) return;\r\n // Clone the documentElement and remove injected UI (toolbar/style)\r\n // so snapshots capture only the user's content.\r\n const clone = _doc.documentElement.cloneNode(true) as HTMLElement;\r\n const toolbarNode = clone.querySelector(`#${TOOLBAR_ID}`);\r\n if (toolbarNode && toolbarNode.parentNode)\r\n toolbarNode.parentNode.removeChild(toolbarNode);\r\n const styleNode = clone.querySelector(`#${STYLE_ID}`);\r\n if (styleNode && styleNode.parentNode)\r\n styleNode.parentNode.removeChild(styleNode);\r\n // Remove editor-specific attributes/classes so snapshots don't persist\r\n // transient editing state (contenteditable, toolbar classes, tabindex).\r\n try {\r\n const editableNodes = clone.querySelectorAll(\r\n \"[contenteditable], .\" + CLASS_EDITABLE + \", .\" + CLASS_ACTIVE\r\n );\r\n editableNodes.forEach((el) => {\r\n try {\r\n if (el instanceof Element) {\r\n if (el.hasAttribute(\"contenteditable\"))\r\n el.removeAttribute(\"contenteditable\");\r\n if (el.hasAttribute(\"tabindex\")) el.removeAttribute(\"tabindex\");\r\n el.classList.remove(CLASS_EDITABLE, CLASS_ACTIVE);\r\n }\r\n } catch (e) {\r\n /* ignore */\r\n }\r\n });\r\n } catch (e) {\r\n /* ignore */\r\n }\r\n // Preserve scripts by replacing them with a harmless placeholder\r\n // that contains the encoded script source/attributes. This allows the\r\n // sanitizer to run (which strips <script> tags) while keeping the\r\n // script content available to re-insert and execute on restore.\r\n try {\r\n const scripts = Array.from(\r\n clone.querySelectorAll<HTMLScriptElement>(\"script\")\r\n );\r\n scripts.forEach((s) => {\r\n try {\r\n const code = s.textContent || \"\";\r\n const attrs: Record<string, string> = {};\r\n Array.from(s.attributes).forEach((a) => (attrs[a.name] = a.value));\r\n const placeholder = clone.ownerDocument!.createElement(\"span\");\r\n // encode script body in base64 to survive sanitization\r\n try {\r\n // btoa may throw on Unicode; encodeURIComponent first\r\n const safe =\r\n typeof btoa !== \"undefined\"\r\n ? btoa(unescape(encodeURIComponent(code)))\r\n : encodeURIComponent(code);\r\n placeholder.setAttribute(\"data-rhe-script\", safe);\r\n } catch (e) {\r\n placeholder.setAttribute(\"data-rhe-script\", encodeURIComponent(code));\r\n }\r\n if (Object.keys(attrs).length) {\r\n placeholder.setAttribute(\r\n \"data-rhe-script-attrs\",\r\n encodeURIComponent(JSON.stringify(attrs))\r\n );\r\n }\r\n // mark parent editable region if present so we can reinsert in-place\r\n const parentMarker = s.closest(\"[data-rhe-id]\") as HTMLElement | null;\r\n if (parentMarker && parentMarker.getAttribute(\"data-rhe-id\")) {\r\n placeholder.setAttribute(\r\n \"data-rhe-script-parent\",\r\n parentMarker.getAttribute(\"data-rhe-id\")!\r\n );\r\n } else {\r\n placeholder.setAttribute(\"data-rhe-script-parent\", \"head\");\r\n }\r\n s.parentNode?.replaceChild(placeholder, s);\r\n } catch (e) {\r\n /* ignore per-script errors */\r\n }\r\n });\r\n } catch (e) {\r\n /* ignore script extraction errors */\r\n }\r\n const snapRaw = clone.outerHTML;\r\n const snap = sanitizeHtml(snapRaw, _doc);\r\n if (!_undoStack.length || _undoStack[_undoStack.length - 1] !== snap) {\r\n _undoStack.push(snap);\r\n if (_undoStack.length > _maxStackSize) _undoStack.shift();\r\n editorEventEmitter.emit({\r\n type: \"contentChanged\",\r\n timestamp: Date.now(),\r\n data: { htmlLength: snap.length },\r\n });\r\n }\r\n if (clearRedo) {\r\n _redoStack = [];\r\n editorEventEmitter.emit({\r\n type: \"redoStateChanged\",\r\n timestamp: Date.now(),\r\n data: { canRedo: false },\r\n });\r\n }\r\n}\r\nexport function setMaxStackSize(size: number) {\r\n _maxStackSize = Math.max(1, size);\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -91,7 +91,7 @@ var init_events = __esm({
|
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
// src/core/constants.ts
|
|
94
|
-
var TOOLBAR_ID, STYLE_ID, CLASS_EDITABLE, CLASS_ACTIVE, DEFAULT_MAX_STACK, TOOLBAR_BG, TOOLBAR_BORDER, BUTTON_BORDER, BUTTON_ACTIVE_BG, BUTTON_BG, BUTTON_COLOR, INFO_COLOR, HOVER_OUTLINE, ACTIVE_OUTLINE, LABEL_BOLD, LABEL_ITALIC, LABEL_UNDERLINE, LABEL_STRIKETHROUGH, LABEL_UNDO, LABEL_REDO, LABEL_LINK, LABEL_ALIGN_LEFT, LABEL_ALIGN_CENTER, LABEL_ALIGN_RIGHT, FONT_OPTIONS, SIZE_OPTIONS, FORMAT_OPTIONS;
|
|
94
|
+
var TOOLBAR_ID, STYLE_ID, CLASS_EDITABLE, CLASS_ACTIVE, DEFAULT_MAX_STACK, TOOLBAR_BG, TOOLBAR_BORDER, BUTTON_BORDER, BUTTON_ACTIVE_BG, BUTTON_BG, BUTTON_COLOR, INFO_COLOR, HOVER_OUTLINE, ACTIVE_OUTLINE, LABEL_BOLD, LABEL_ITALIC, LABEL_UNDERLINE, LABEL_STRIKETHROUGH, LABEL_UNDO, LABEL_REDO, LABEL_LINK, LABEL_UNORDERED_LIST, LABEL_ORDERED_LIST, LABEL_ALIGN_LEFT, LABEL_ALIGN_CENTER, LABEL_ALIGN_RIGHT, FONT_OPTIONS, SIZE_OPTIONS, FORMAT_OPTIONS;
|
|
95
95
|
var init_constants = __esm({
|
|
96
96
|
"src/core/constants.ts"() {
|
|
97
97
|
"use strict";
|
|
@@ -116,6 +116,26 @@ var init_constants = __esm({
|
|
|
116
116
|
LABEL_UNDO = "\u21BA";
|
|
117
117
|
LABEL_REDO = "\u21BB";
|
|
118
118
|
LABEL_LINK = "\u{1F517}";
|
|
119
|
+
LABEL_UNORDERED_LIST = `
|
|
120
|
+
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
121
|
+
<circle cx="3" cy="4" r="1" fill="currentColor" />
|
|
122
|
+
<rect x="6" y="3" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
123
|
+
<circle cx="3" cy="8" r="1" fill="currentColor" />
|
|
124
|
+
<rect x="6" y="7" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
125
|
+
<circle cx="3" cy="12" r="1" fill="currentColor" />
|
|
126
|
+
<rect x="6" y="11" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
127
|
+
</svg>
|
|
128
|
+
`;
|
|
129
|
+
LABEL_ORDERED_LIST = `
|
|
130
|
+
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
131
|
+
<text x="1" y="4" font-size="4" fill="currentColor">1.</text>
|
|
132
|
+
<rect x="6" y="3" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
133
|
+
<text x="1" y="8" font-size="4" fill="currentColor">2.</text>
|
|
134
|
+
<rect x="6" y="7" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
135
|
+
<text x="1" y="12" font-size="4" fill="currentColor">3.</text>
|
|
136
|
+
<rect x="6" y="11" width="9" height="2" rx="0.5" fill="currentColor" />
|
|
137
|
+
</svg>
|
|
138
|
+
`;
|
|
119
139
|
LABEL_ALIGN_LEFT = `
|
|
120
140
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
121
141
|
<rect x="1" y="2" width="10" height="2" rx="0.5" fill="currentColor" />
|
|
@@ -1094,6 +1114,24 @@ function injectToolbar(doc, options) {
|
|
|
1094
1114
|
)
|
|
1095
1115
|
);
|
|
1096
1116
|
grp3.appendChild(makeButton2(LABEL_STRIKETHROUGH, "Strikethrough", "strike"));
|
|
1117
|
+
grp3.appendChild(
|
|
1118
|
+
makeButton2(
|
|
1119
|
+
LABEL_UNORDERED_LIST,
|
|
1120
|
+
"Unordered list",
|
|
1121
|
+
"unorderedList",
|
|
1122
|
+
void 0,
|
|
1123
|
+
format.listType === "ul"
|
|
1124
|
+
)
|
|
1125
|
+
);
|
|
1126
|
+
grp3.appendChild(
|
|
1127
|
+
makeButton2(
|
|
1128
|
+
LABEL_ORDERED_LIST,
|
|
1129
|
+
"Ordered list",
|
|
1130
|
+
"orderedList",
|
|
1131
|
+
void 0,
|
|
1132
|
+
format.listType === "ol"
|
|
1133
|
+
)
|
|
1134
|
+
);
|
|
1097
1135
|
toolbar.appendChild(grp3);
|
|
1098
1136
|
toolbar.appendChild(makeSep2());
|
|
1099
1137
|
const grp4 = makeGroup2();
|
|
@@ -1181,7 +1219,8 @@ function computeFormatState(doc) {
|
|
|
1181
1219
|
hiliteColor: null,
|
|
1182
1220
|
fontName: null,
|
|
1183
1221
|
fontSize: null,
|
|
1184
|
-
formatBlock: null
|
|
1222
|
+
formatBlock: null,
|
|
1223
|
+
listType: null
|
|
1185
1224
|
};
|
|
1186
1225
|
const computed = (_a = doc.defaultView) == null ? void 0 : _a.getComputedStyle(el);
|
|
1187
1226
|
const bold = !!(el.closest("strong, b") || computed && (computed.fontWeight === "700" || Number(computed.fontWeight) >= 700));
|
|
@@ -1217,6 +1256,18 @@ function computeFormatState(doc) {
|
|
|
1217
1256
|
blockEl = blockEl.parentElement;
|
|
1218
1257
|
}
|
|
1219
1258
|
const formatBlock = blockEl ? blockEl.tagName.toLowerCase() : null;
|
|
1259
|
+
let listType = null;
|
|
1260
|
+
try {
|
|
1261
|
+
if (blockEl && blockEl.tagName === "LI") {
|
|
1262
|
+
const list = blockEl.closest("ul,ol");
|
|
1263
|
+
if (list) listType = list.tagName.toLowerCase();
|
|
1264
|
+
} else {
|
|
1265
|
+
const possible = el.closest("ul,ol");
|
|
1266
|
+
if (possible) listType = possible.tagName.toLowerCase();
|
|
1267
|
+
}
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
listType = null;
|
|
1270
|
+
}
|
|
1220
1271
|
return {
|
|
1221
1272
|
bold,
|
|
1222
1273
|
italic,
|
|
@@ -1225,7 +1276,8 @@ function computeFormatState(doc) {
|
|
|
1225
1276
|
hiliteColor,
|
|
1226
1277
|
fontName,
|
|
1227
1278
|
fontSize,
|
|
1228
|
-
formatBlock
|
|
1279
|
+
formatBlock,
|
|
1280
|
+
listType
|
|
1229
1281
|
};
|
|
1230
1282
|
} catch (err) {
|
|
1231
1283
|
return {
|
|
@@ -1236,7 +1288,8 @@ function computeFormatState(doc) {
|
|
|
1236
1288
|
hiliteColor: null,
|
|
1237
1289
|
fontName: null,
|
|
1238
1290
|
fontSize: null,
|
|
1239
|
-
formatBlock: null
|
|
1291
|
+
formatBlock: null,
|
|
1292
|
+
listType: null
|
|
1240
1293
|
};
|
|
1241
1294
|
}
|
|
1242
1295
|
}
|
|
@@ -1632,6 +1685,9 @@ function applyStandaloneCommand(command, value) {
|
|
|
1632
1685
|
} else {
|
|
1633
1686
|
wrapSelectionWithElement(doc, tag);
|
|
1634
1687
|
}
|
|
1688
|
+
} else if (command === "unorderedList" || command === "orderedList") {
|
|
1689
|
+
const tag = command === "unorderedList" ? "ul" : "ol";
|
|
1690
|
+
toggleList(doc, tag);
|
|
1635
1691
|
}
|
|
1636
1692
|
pushStandaloneSnapshot();
|
|
1637
1693
|
} catch (error) {
|
|
@@ -1667,6 +1723,57 @@ function wrapSelectionWithElement(doc, tagName, style) {
|
|
|
1667
1723
|
newRange.selectNodeContents(wrapper);
|
|
1668
1724
|
sel.addRange(newRange);
|
|
1669
1725
|
}
|
|
1726
|
+
function toggleList(doc, listTag) {
|
|
1727
|
+
var _a, _b;
|
|
1728
|
+
const sel = doc.getSelection();
|
|
1729
|
+
if (!sel || !sel.rangeCount) return;
|
|
1730
|
+
const node = sel.anchorNode || null;
|
|
1731
|
+
const block = findBlockAncestor(node);
|
|
1732
|
+
if (block && block.tagName === "LI" && block.parentElement) {
|
|
1733
|
+
const parentList = block.parentElement;
|
|
1734
|
+
const parentTag = parentList.tagName.toLowerCase();
|
|
1735
|
+
if (parentTag === listTag) {
|
|
1736
|
+
const frag = doc.createDocumentFragment();
|
|
1737
|
+
Array.from(parentList.children).forEach((li2) => {
|
|
1738
|
+
const p = doc.createElement("p");
|
|
1739
|
+
while (li2.firstChild) p.appendChild(li2.firstChild);
|
|
1740
|
+
frag.appendChild(p);
|
|
1741
|
+
});
|
|
1742
|
+
(_a = parentList.parentElement) == null ? void 0 : _a.replaceChild(frag, parentList);
|
|
1743
|
+
return;
|
|
1744
|
+
} else {
|
|
1745
|
+
const newList = doc.createElement(listTag);
|
|
1746
|
+
while (parentList.firstChild) newList.appendChild(parentList.firstChild);
|
|
1747
|
+
(_b = parentList.parentElement) == null ? void 0 : _b.replaceChild(newList, parentList);
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const range = sel.getRangeAt(0);
|
|
1752
|
+
if (range.collapsed) {
|
|
1753
|
+
const list2 = doc.createElement(listTag);
|
|
1754
|
+
const li2 = doc.createElement("li");
|
|
1755
|
+
const zw = doc.createTextNode("\u200B");
|
|
1756
|
+
li2.appendChild(zw);
|
|
1757
|
+
list2.appendChild(li2);
|
|
1758
|
+
range.insertNode(list2);
|
|
1759
|
+
const newRange2 = doc.createRange();
|
|
1760
|
+
newRange2.setStart(zw, 1);
|
|
1761
|
+
newRange2.collapse(true);
|
|
1762
|
+
sel.removeAllRanges();
|
|
1763
|
+
sel.addRange(newRange2);
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const content = range.extractContents();
|
|
1767
|
+
const list = doc.createElement(listTag);
|
|
1768
|
+
const li = doc.createElement("li");
|
|
1769
|
+
li.appendChild(content);
|
|
1770
|
+
list.appendChild(li);
|
|
1771
|
+
range.insertNode(list);
|
|
1772
|
+
sel.removeAllRanges();
|
|
1773
|
+
const newRange = doc.createRange();
|
|
1774
|
+
newRange.selectNodeContents(li);
|
|
1775
|
+
sel.addRange(newRange);
|
|
1776
|
+
}
|
|
1670
1777
|
function findBlockAncestor(node) {
|
|
1671
1778
|
let n = node;
|
|
1672
1779
|
const BLOCKS = [
|
|
@@ -1809,6 +1916,33 @@ function attachStandaloneHandlers(doc) {
|
|
|
1809
1916
|
},
|
|
1810
1917
|
true
|
|
1811
1918
|
);
|
|
1919
|
+
doc.addEventListener(
|
|
1920
|
+
"keydown",
|
|
1921
|
+
(e) => {
|
|
1922
|
+
if (e.key !== "Enter") return;
|
|
1923
|
+
if (e.shiftKey) return;
|
|
1924
|
+
const sel = doc.getSelection();
|
|
1925
|
+
if (!sel || !sel.rangeCount) return;
|
|
1926
|
+
const node = sel.anchorNode;
|
|
1927
|
+
const el = node && node.nodeType === Node.ELEMENT_NODE ? node : node && node.parentElement || null;
|
|
1928
|
+
if (!el) return;
|
|
1929
|
+
const li = el.closest("li");
|
|
1930
|
+
if (!li || !li.parentElement) return;
|
|
1931
|
+
e.preventDefault();
|
|
1932
|
+
const list = li.parentElement;
|
|
1933
|
+
const newLi = doc.createElement("li");
|
|
1934
|
+
const zw = doc.createTextNode("\u200B");
|
|
1935
|
+
newLi.appendChild(zw);
|
|
1936
|
+
if (li.nextSibling) list.insertBefore(newLi, li.nextSibling);
|
|
1937
|
+
else list.appendChild(newLi);
|
|
1938
|
+
const range = doc.createRange();
|
|
1939
|
+
range.setStart(zw, 1);
|
|
1940
|
+
range.collapse(true);
|
|
1941
|
+
sel.removeAllRanges();
|
|
1942
|
+
sel.addRange(range);
|
|
1943
|
+
},
|
|
1944
|
+
true
|
|
1945
|
+
);
|
|
1812
1946
|
}
|
|
1813
1947
|
|
|
1814
1948
|
// src/core/editor.ts
|