jotterjs 0.1.3 → 0.3.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 CHANGED
@@ -1,122 +1,253 @@
1
- # JotterJS
2
-
3
- A lightweight, vanilla JS rich-text editor component built on `contenteditable`. No dependencies at runtime.
4
-
5
- ## Features
6
-
7
- - Full formatting toolbar (bold, italic, underline, strikethrough, subscript/superscript, inline code)
8
- - Block format, font family, font size, and colour pickers
9
- - Lists, indentation, alignment, tables, links, and image insertion
10
- - Source (raw HTML) toggle
11
- - Content themes: `default`, `warm`, `ink`, `forest`
12
- - Pre-built toolbar presets (`minimal`, `writing`) and fully custom toolbar support
13
- - Custom toolbar buttons with `onClick` callbacks
14
- - Event system (`change`, `focus`, `blur`)
15
- - Simple chainable API
16
-
17
- ## Installation
18
-
19
- ```bash
20
- npm install jotterjs
21
- ```
22
-
23
- Or use the built files from `dist/` directly in a `<script>` tag.
24
-
25
- ## Usage
26
-
27
- ### ES Module
28
-
29
- ```js
30
- import JotterJS from 'jotterjs';
31
-
32
- const editor = new JotterJS('#my-editor', {
33
- height: '320px',
34
- placeholder: 'Start typing…',
35
- onChange: (html) => console.log(html),
36
- });
37
- ```
38
-
39
- ### IIFE (script tag)
40
-
41
- ```html
42
- <link rel="stylesheet" href="dist/jotter.min.css" />
43
- <script src="dist/jotter.iife.min.js"></script>
44
- <script>
45
- const editor = new JotterJS('#my-editor');
46
- </script>
47
- ```
48
-
49
- ## Options
50
-
51
- | Option | Type | Default | Description |
52
- |---------------|------------|------------------|--------------------------------------------------|
53
- | `placeholder` | `string` | `'Start typing…'`| Placeholder text shown when the editor is empty |
54
- | `height` | `string` | `'320px'` | Min-height of the editable area |
55
- | `theme` | `string` | `'default'` | Content theme: `default`, `warm`, `ink`, `forest`|
56
- | `toolbar` | `Array` | Full toolbar | Array of action descriptors |
57
- | `onChange` | `Function` | — | Callback `(html)` fired on every content change |
58
- | `onFocus` | `Function` | — | Callback fired on editor focus |
59
- | `onBlur` | `Function` | — | Callback fired on editor blur |
60
-
61
- ## API
62
-
63
- ```js
64
- editor.getHTML() // → string — raw innerHTML
65
- editor.getText() // → string — plain text
66
- editor.setHTML(html) // replace content
67
- editor.insertHTML(html) // insert HTML at caret
68
- editor.insertText(text) // insert plain text at caret
69
- editor.clear() // empty the editor
70
- editor.focus() // focus the editable area
71
- editor.setTheme(name) // change theme at runtime
72
- editor.setEnabled(bool) // toggle contenteditable
73
- editor.toggleSource() // switch rich-text ↔ HTML source view
74
- editor.isSourceMode() // → boolean
75
- editor.on(event, fn) // subscribe to 'change' | 'focus' | 'blur'
76
- editor.off(event, fn) // unsubscribe
77
- editor.destroy() // unmount and return final HTML
78
- ```
79
-
80
- Methods return `this` for chaining (except `getHTML`, `getText`, `isSourceMode`, and `destroy`).
81
-
82
- ## Toolbar Presets
83
-
84
- ```js
85
- new JotterJS('#el', { toolbar: JotterJS.presets.minimal });
86
- new JotterJS('#el', { toolbar: JotterJS.presets.writing });
87
- ```
88
-
89
- ## Custom Toolbar
90
-
91
- ```js
92
- const { actions } = JotterJS;
93
-
94
- new JotterJS('#el', {
95
- toolbar: [
96
- actions.bold,
97
- actions.italic,
98
- actions.sep,
99
- {
100
- icon: 'star',
101
- title: 'Insert signature',
102
- onClick: (editor) => editor.insertHTML('<p><em>— Sent with JotterJS</em></p>'),
103
- },
104
- {
105
- label: 'Clear',
106
- title: 'Clear content',
107
- onClick: (editor) => editor.clear(),
108
- },
109
- ],
110
- });
111
- ```
112
-
113
- ## Development
114
-
115
- ```bash
116
- npm run dev # start dev server
117
- npm run build # build to dist/
118
- ```
119
-
120
- ## License
121
-
122
- MIT
1
+ # JotterJS
2
+
3
+ A lightweight, vanilla JS rich-text editor component built on `contenteditable`. No dependencies at runtime.
4
+
5
+ ## Features
6
+
7
+ - Full formatting toolbar (bold, italic, underline, strikethrough, subscript/superscript, inline code)
8
+ - Block format, font family, font size, and colour pickers
9
+ - Lists, indentation, alignment, tables, links, and image insertion
10
+ - Source (raw HTML) toggle
11
+ - Content themes: `default`, `warm`, `ink`, `forest`
12
+ - Pre-built toolbar presets (`minimal`, `writing`, `full`) and fully custom toolbar support
13
+ - Custom toolbar buttons with `onClick` callbacks
14
+ - Replaceable insert dialogs — swap the built-in image/link/video/embed popups for your own UI
15
+ - Selection bookmarks that survive async host UI and DOM mutation
16
+ - Event system (`change`, `focus`, `blur`)
17
+ - Simple chainable API
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install jotterjs
23
+ ```
24
+
25
+ Or use the built files from `dist/` directly in a `<script>` tag.
26
+
27
+ ## Usage
28
+
29
+ ### ES Module
30
+
31
+ ```js
32
+ import JotterJS from 'jotterjs';
33
+
34
+ const editor = new JotterJS('#my-editor', {
35
+ height: '320px',
36
+ placeholder: 'Start typing…',
37
+ onChange: (html) => console.log(html),
38
+ });
39
+ ```
40
+
41
+ ### IIFE (script tag)
42
+
43
+ ```html
44
+ <link rel="stylesheet" href="dist/jotter.min.css" />
45
+ <script src="dist/jotter.iife.min.js"></script>
46
+ <script>
47
+ const editor = new JotterJS('#my-editor');
48
+ </script>
49
+ ```
50
+
51
+ ## Options
52
+
53
+ | Option | Type | Default | Description |
54
+ |---------------|------------|------------------|--------------------------------------------------|
55
+ | `placeholder` | `string` | `'Start typing…'`| Placeholder text shown when the editor is empty |
56
+ | `height` | `string` | `'320px'` | Min-height of the editable area |
57
+ | `theme` | `string` | `'default'` | Content theme: `default`, `warm`, `ink`, `forest`|
58
+ | `toolbar` | `Array` | Full toolbar | Array of action descriptors |
59
+ | `onChange` | `Function` | — | Callback `(html)` fired on every content change |
60
+ | `onFocus` | `Function` | — | Callback fired on editor focus |
61
+ | `onBlur` | `Function` | — | Callback fired on editor blur |
62
+ | `onRequestImage` | `Function` | — | Replaces the Insert Image popup — see [Replacing a built-in popup](#replacing-a-built-in-popup) |
63
+ | `onRequestLink` | `Function` | — | Replaces the Insert Link popup |
64
+ | `onRequestVideo` | `Function` | — | Replaces the Insert Video popup |
65
+ | `onRequestEmbed` | `Function` | — | Replaces the Insert Embed popup |
66
+
67
+ `change` fires once per edit. `focus` and `blur` describe the editor as a whole:
68
+ moving into the editor's own popup is not a blur, and neither is host UI opened
69
+ between `beginExternalUI()` and `endExternalUI()`.
70
+
71
+ ## API
72
+
73
+ ```js
74
+ editor.getHTML() // → string — raw innerHTML
75
+ editor.getText() // → string — plain text
76
+ editor.setHTML(html) // replace content
77
+ editor.insertHTML(html) // insert HTML at caret
78
+ editor.insertText(text) // insert plain text at caret
79
+ editor.clear() // empty the editor
80
+ editor.focus() // focus the editable area
81
+ editor.setTheme(name) // change theme at runtime
82
+ editor.setEnabled(bool) // toggle contenteditable
83
+ editor.toggleSource() // switch rich-text ↔ HTML source view
84
+ editor.isSourceMode() // → boolean
85
+ editor.on(event, fn) // subscribe to 'change' | 'focus' | 'blur'
86
+ editor.off(event, fn) // unsubscribe
87
+ editor.destroy() // unmount and return final HTML
88
+
89
+ // Working across async host UI
90
+ editor.saveSelection() // → token — bookmark the caret
91
+ editor.restoreSelection(token) // put the caret back (consumes the token)
92
+ editor.releaseSelection(token) // discard a bookmark instead
93
+ editor.insertHTML(html, { at: token }) // restore, then insert
94
+ editor.insertText(text, { at: token })
95
+ editor.beginExternalUI() // your UI is taking over: hold the caret, hush focus/blur
96
+ editor.endExternalUI() // your UI is done: caret back, events resume
97
+ ```
98
+
99
+ Methods return `this` for chaining (except `getHTML`, `getText`, `saveSelection`,
100
+ `isSourceMode`, and `destroy`).
101
+
102
+ ## Toolbar Presets
103
+
104
+ ```js
105
+ new JotterJS('#el', { toolbar: JotterJS.presets.minimal });
106
+ new JotterJS('#el', { toolbar: JotterJS.presets.writing });
107
+ new JotterJS('#el', { toolbar: JotterJS.presets.full }); // the default toolbar
108
+ ```
109
+
110
+ Every preset is composed from `JotterJS.actions`, so a given command has the
111
+ same icon and tooltip whichever toolbar it appears in. Extend one by spreading:
112
+
113
+ ```js
114
+ new JotterJS('#el', {
115
+ toolbar: [
116
+ ...JotterJS.presets.minimal,
117
+ JotterJS.actions.sep,
118
+ JotterJS.actions.image,
119
+ ],
120
+ });
121
+ ```
122
+
123
+ ## Custom Toolbar
124
+
125
+ Action descriptors and preset arrays are frozen and shared between presets, so
126
+ customise by copying rather than mutating in place:
127
+
128
+ ```js
129
+ { ...JotterJS.actions.image, onClick: fn } // ✓
130
+ JotterJS.actions.image.onClick = fn // ✗ throws — would leak everywhere
131
+ ```
132
+
133
+ ```js
134
+ const { actions } = JotterJS;
135
+
136
+ new JotterJS('#el', {
137
+ toolbar: [
138
+ actions.bold,
139
+ actions.italic,
140
+ actions.sep,
141
+ {
142
+ icon: 'star',
143
+ title: 'Insert signature',
144
+ onClick: (editor) => editor.insertHTML('<p><em>— Sent with JotterJS</em></p>'),
145
+ },
146
+ {
147
+ label: 'Clear',
148
+ title: 'Clear content',
149
+ onClick: (editor) => editor.clear(),
150
+ },
151
+ ],
152
+ });
153
+ ```
154
+
155
+ ## Replacing a built-in popup
156
+
157
+ The insert dialogs are defaults, not fixtures. An app with its own asset library
158
+ wants "pick from files uploaded to this course", not a URL field. There are two
159
+ ways in, depending on how much you want to own.
160
+
161
+ ### Resolver hooks — keep the button, replace the dialog
162
+
163
+ Pass an `onRequest*` option and the toolbar button awaits it instead of opening
164
+ the popup. You answer *which image*; the editor still bookmarks the caret,
165
+ restores it afterwards, builds the markup and emits `change`:
166
+
167
+ ```js
168
+ new JotterJS('#el', {
169
+ onRequestImage: async ({ src, alt, width }) => {
170
+ const file = await myAssetLibrary.pick(); // your modal, focus trap and all
171
+ if (!file) return null; // null (or a throw) = cancelled
172
+ return { src: file.url, alt: file.title, width: '480px' };
173
+ },
174
+ });
175
+ ```
176
+
177
+ | Hook | Receives | Return |
178
+ |------------------|---------------------------------------------------|-----------------------------------------------|
179
+ | `onRequestImage` | `{ src, alt, width, selection }` | `{ src, alt, width }` or a `src` string |
180
+ | `onRequestLink` | `{ href, text, title, target, selection, isEdit }`| `{ href, text, title, target }` or an `href` string |
181
+ | `onRequestVideo` | `{ url, selection }` | `{ url }` / `{ id }` or a URL string |
182
+ | `onRequestEmbed` | `{ html, selection }` | `{ html }` or an HTML string |
183
+
184
+ `onRequestLink` doubles as edit mode: when the caret sits inside an `<a>`, the
185
+ context arrives pre-filled with `isEdit: true`, and what you return replaces that
186
+ anchor. Take as long as you like — an upload with a progress bar, a re-render,
187
+ anything: the insertion point is bookmarked, not merely remembered.
188
+
189
+ ### `onClick` — replace the button outright
190
+
191
+ `onClick` outranks everything else on a descriptor, popup actions included:
192
+
193
+ ```js
194
+ const { actions } = JotterJS;
195
+
196
+ new JotterJS('#el', {
197
+ toolbar: [
198
+ actions.bold, actions.italic, actions.sep,
199
+ { ...actions.image, onClick: (editor) => openMyPicker(editor) },
200
+ ],
201
+ });
202
+ ```
203
+
204
+ You now own the whole interaction, including the caret. Wrap the async part so
205
+ the editor knows host UI has the floor:
206
+
207
+ ```js
208
+ async function openMyPicker(editor) {
209
+ editor.beginExternalUI(); // hold the caret, stop emitting focus/blur
210
+ try {
211
+ const file = await myAssetLibrary.pick();
212
+ editor.endExternalUI(); // caret comes back before we insert
213
+ if (file) editor.insertHTML(`<img src="${file.url}" alt="">`);
214
+ } catch (err) {
215
+ editor.endExternalUI();
216
+ }
217
+ }
218
+ ```
219
+
220
+ Or bookmark explicitly, if the caret must outlive several steps:
221
+
222
+ ```js
223
+ const bookmark = editor.saveSelection();
224
+ const file = await upload(blob); // DOM churns meanwhile
225
+ editor.insertHTML(`<img src="${file.url}">`, { at: bookmark });
226
+ ```
227
+
228
+ Bookmarks are marker nodes, not cloned ranges, so they still point at the right
229
+ spot after the surrounding DOM has changed. Each token is consumed exactly once —
230
+ by `restoreSelection`, by `insertHTML(..., { at })`, or by `releaseSelection` if
231
+ the user cancels. They never appear in `getHTML()`.
232
+
233
+ ### Why `beginExternalUI` matters
234
+
235
+ A `contenteditable` blurs the moment your modal takes focus. Hosts that save on
236
+ blur then re-render, and a re-render remounts the editor — with your modal still
237
+ open on top of it. Between `beginExternalUI()` and `endExternalUI()`, `blur` and
238
+ `focus` are not emitted, so that chain never starts. The `onRequest*` hooks wrap
239
+ this for you.
240
+
241
+ ## Development
242
+
243
+ ```bash
244
+ npm run dev # start dev server
245
+ npm run build # build to dist/
246
+ ```
247
+
248
+ `test/index.html` is the manual playground; `test/spec.html` is a self-checking
249
+ behaviour spec — open it and read the pass/fail list.
250
+
251
+ ## License
252
+
253
+ MIT
@@ -1,5 +1,5 @@
1
- var JotterJS=(()=>{var _=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var v=(p,t)=>{for(var e in t)_(p,e,{get:t[e],enumerable:!0})},E=(p,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of L(t))!y.call(p,o)&&o!==e&&_(p,o,{get:()=>t[o],enumerable:!(i=C(t,o))||i.enumerable});return p};var T=p=>E(_({},"__esModule",{value:!0}),p);var H={};v(H,{JotterJS:()=>u,default:()=>j});var b=[{custom:"toggleSource",label:"Source",title:"Edit HTML Source"},{type:"sep"},{cmd:"undo",icon:"undo",title:"Undo (Ctrl+Z)"},{cmd:"redo",icon:"redo",title:"Redo (Ctrl+Y)"},{type:"sep"},{cmd:"copy",icon:"content_copy",title:"Copy"},{cmd:"cut",icon:"content_cut",title:"Cut"},{cmd:"paste",icon:"content_paste",title:"Paste"},{type:"sep"},{cmd:"removeFormat",icon:"format_clear",title:"Clear Formatting"},{type:"sep"},{type:"blockformat"},{type:"fontfamily"},{type:"fontsize"},{type:"sep"},{cmd:"bold",icon:"format_bold",title:"Bold (Ctrl+B)"},{cmd:"italic",icon:"format_italic",title:"Italic (Ctrl+I)"},{cmd:"underline",icon:"format_underlined",title:"Underline (Ctrl+U)"},{cmd:"strikeThrough",icon:"strikethrough_s",title:"Strikethrough"},{cmd:"subscript",icon:"subscript",title:"Subscript"},{cmd:"superscript",icon:"superscript",title:"Superscript"},{custom:"code",icon:"code",title:"Inline Code"},{type:"sep"},{type:"color",cmd:"foreColor",icon:"format_color_text",title:"Text Color"},{type:"color",cmd:"hiliteColor",icon:"format_color_fill",title:"Background Color"},{type:"sep"},{cmd:"justifyLeft",icon:"format_align_left",title:"Align Left"},{cmd:"justifyCenter",icon:"format_align_center",title:"Align Center"},{cmd:"justifyRight",icon:"format_align_right",title:"Align Right"},{type:"sep"},{cmd:"insertUnorderedList",icon:"format_list_bulleted",title:"Bullet List"},{cmd:"insertOrderedList",icon:"format_list_numbered",title:"Numbered List"},{type:"sep"},{type:"popup",id:"link",icon:"insert_link",title:"Insert Link"},{cmd:"unlink",icon:"link_off",title:"Remove Link"},{type:"sep"},{type:"popup",id:"image",icon:"image",title:"Insert Image"},{type:"popup",id:"video",icon:"smart_display",title:"Insert YouTube Video"},{type:"popup",id:"table",icon:"table_chart",title:"Insert Table"},{type:"popup",id:"embed",icon:"html",title:"Insert Embed"},{type:"popup",id:"symbol",icon:"emoji_symbols",title:"Insert Symbol"},{type:"popup",id:"specialchar",icon:"format_shapes",title:"Special Characters"},{type:"popup",id:"lorem",icon:"script",title:"Insert Lorem Ipsum"},{type:"sep"},{type:"theme"},{type:"sep"},{type:"theme"}],g=[{label:"Paragraph",tag:"p"},{label:"Heading 1",tag:"h1"},{label:"Heading 2",tag:"h2"},{label:"Heading 3",tag:"h3"},{label:"Heading 4",tag:"h4"},{label:"Pre / Code",tag:"pre"},{label:"Blockquote",tag:"blockquote"}],S=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Lucida Console","Palatino Linotype","Tahoma","Times New Roman","Trebuchet MS","Verdana"],x=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],k=["\u2190","\u2192","\u2191","\u2193","\u2194","\u2195","\u21D0","\u21D2","\u21D1","\u21D3","\u21D4","\u2022","\xB7","\u25E6","\u25CB","\u25CF","\u25A1","\u25A0","\u25C6","\u25C7","\u25B2","\u25BC","\u2605","\u2606","\u2660","\u2663","\u2665","\u2666","\u2713","\u2717","\u2715","\u2718","\u2248","\u2260","\u2261","\u2264","\u2265","\xF7","\xD7","\xB1","\u221E","\u221A","\u2211","\u220F","\u222B","\u2202","\u2206","\u2207","\u03C0","\u03A9","\u03BC","\u03B1","\u03B2","\u03B3","\xA9","\xAE","\u2122","\xA7","\xB6","\u2020","\u2021","\xB0","\u2032","\u2033","\u2030","\u201C","\u201D","\u2018","\u2019","\xAB","\xBB","\u2039","\u203A","\u2014","\u2013","\u2026","\xBF","\xA1","\u20AC","\xA3","\xA5","\xA2","\u20B9","\u20BD","\u20BF"],M=["\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6","\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE","\xCF","\xD0","\xD1","\xD2","\xD3","\xD4","\xD5","\xD6","\xD8","\xD9","\xDA","\xDB","\xDC","\xDD","\xDE","\xDF","\xE0","\xE1","\xE2","\xE3","\xE4","\xE5","\xE6","\xE7","\xE8","\xE9","\xEA","\xEB","\xEC","\xED","\xEE","\xEF","\xF0","\xF1","\xF2","\xF3","\xF4","\xF5","\xF6","\xF8","\xF9","\xFA","\xFB","\xFC","\xFD","\xFE","\xFF","\u0152","\u0153","\u0160","\u0161","\u0178","\u017D","\u017E"],f=[{id:"default",label:"Default"},{id:"warm",label:"Warm"},{id:"ink",label:"Ink / Navy"},{id:"forest",label:"Forest"}],w=[{label:"Short \u2014 1 sentence",text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit."},{label:"Medium \u2014 1 paragraph",text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."},{label:"Long \u2014 3 paragraphs",isHTML:!0,text:"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p><p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p><p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>"}],u=class{constructor(t,e={}){if(this._target=typeof t=="string"?document.querySelector(t):t,!this._target)throw new Error("[JotterJS] Target element not found.");this._options=Object.assign({placeholder:"Start typing\u2026",height:"320px",theme:"default",onChange:null,onFocus:null,onBlur:null},e),this._listeners={},this._savedRange=null,this._lastForeColor="#e8e4d8",this._lastHiliteColor="#c8a96e",this._init()}_init(){let t=this._target.innerHTML||"";this._target.innerHTML="",this._target.classList.add("jotter-host"),this._root=document.createElement("div"),this._root.className="htmled",this._toolbar=this._buildToolbar(),this._editorWrap=document.createElement("div"),this._editorWrap.className="jotter-editor-wrap",this._editor=document.createElement("div"),this._editor.className="jotter-editor",this._editor.contentEditable="true",this._editor.setAttribute("data-placeholder",this._options.placeholder),this._editor.style.minHeight=this._options.height,this._editor.innerHTML=this._sanitize(t),this._editor.spellcheck=!0,document.execCommand("defaultParagraphSeparator",!1,"p"),this._source=document.createElement("textarea"),this._source.className="jotter-source",this._source.setAttribute("aria-label","HTML source"),this._source.setAttribute("spellcheck","false"),this._source.style.minHeight=this._options.height,this._sourceMode=!1,this._statusBar=this._buildStatusBar(),this._editorWrap.appendChild(this._editor),this._editorWrap.appendChild(this._source),this._root.appendChild(this._toolbar),this._root.appendChild(this._editorWrap),this._root.appendChild(this._statusBar),this._target.appendChild(this._root),this._popup=this._buildPopupContainer(),document.body.appendChild(this._popup),this._bindEvents(),this._updateToolbarState(),this._updateStatus(),this.setTheme(this._options.theme)}_buildToolbar(){let t=document.createElement("div");return t.className="jotter-toolbar",this._toolbarEl=t,(this._options.toolbar||b).forEach(e=>{let i=this._buildAction(e);i&&t.appendChild(i)}),t}_buildAction(t){switch(t.type){case"sep":return this._makeSep();case"blockformat":return this._buildBlockFormatSelect();case"fontfamily":return this._buildFontFamilySelect();case"fontsize":return this._buildFontSizeSelect();case"color":return this._buildColorBtn(t);case"popup":return this._buildPopupBtn(t);case"theme":return this._buildThemeSelect();default:return this._buildBtn(t)}}_makeSep(){let t=document.createElement("span");return t.className="jotter-sep",t}_buildBtn(t){let e=document.createElement("button");if(e.type="button",e.className="jotter-btn",t.cmd&&(e.dataset.cmd=t.cmd),t.custom&&(e.dataset.custom=t.custom),e.title=t.title,e.setAttribute("aria-label",t.title),t.label)e.classList.add("jotter-btn--text"),e.appendChild(document.createTextNode(t.label));else{let i=document.createElement("span");i.className="material-icons",i.textContent=t.icon,e.appendChild(i)}return e.addEventListener("mousedown",i=>{if(i.preventDefault(),this._editor.focus(),t.onClick)t.onClick(this);else if(t.custom==="toggleSource")this._toggleSourceMode();else if(t.custom==="code")this._toggleInlineCode();else if(t.cmd==="copy")document.execCommand("copy");else if(t.cmd==="cut")document.execCommand("cut");else if(t.cmd==="paste")this._pasteFromClipboard();else if(t.prompt){let o=window.prompt(t.prompt);o&&document.execCommand(t.cmd,!1,o)}else document.execCommand(t.cmd,!1,null);this._updateToolbarState(),this._updateStatus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),e}_buildBlockFormatSelect(){let t=document.createElement("select");return t.className="jotter-select",t.title="Block format",t.dataset.id="blockformat",g.forEach(({label:e,tag:i})=>{let o=document.createElement("option");o.value=i,o.textContent=e,t.appendChild(o)}),t.addEventListener("mousedown",()=>{this._savedRange=this._saveRange()}),t.addEventListener("change",()=>{this._restoreRange(this._savedRange),document.execCommand("formatBlock",!1,t.value),this._editor.focus(),this._updateStatus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),t}_buildFontFamilySelect(){let t=document.createElement("select");t.className="jotter-select jotter-select--font",t.title="Font family",t.dataset.id="fontfamily";let e=document.createElement("option");return e.value="",e.textContent="Font",t.appendChild(e),S.forEach(i=>{let o=document.createElement("option");o.value=i,o.textContent=i,o.style.fontFamily=i,t.appendChild(o)}),t.addEventListener("mousedown",()=>{this._savedRange=this._saveRange()}),t.addEventListener("change",()=>{t.value&&(this._restoreRange(this._savedRange),document.execCommand("fontName",!1,t.value),this._editor.focus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML()))}),t}_buildFontSizeSelect(){let t=document.createElement("select");t.className="jotter-select jotter-select--size",t.title="Font size",t.dataset.id="fontsize";let e=document.createElement("option");return e.value="",e.textContent="Size",t.appendChild(e),x.forEach(i=>{let o=document.createElement("option");o.value=i,o.textContent=`${i}px`,t.appendChild(o)}),t.addEventListener("mousedown",()=>{this._savedRange=this._saveRange()}),t.addEventListener("change",()=>{t.value&&(this._restoreRange(this._savedRange),this._applyFontSize(t.value),this._editor.focus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML()))}),t}_buildThemeSelect(){let t=document.createElement("select");return t.className="jotter-select jotter-select--theme",t.title="Editor theme",t.dataset.id="theme",f.forEach(({id:e,label:i})=>{let o=document.createElement("option");o.value=e,o.textContent=i,t.appendChild(o)}),t.value=this._options.theme,t.addEventListener("change",()=>this.setTheme(t.value)),this._themeSelect=t,t}_buildColorBtn(t){let e=document.createElement("span");e.className="jotter-color-wrap";let i=document.createElement("button");i.type="button",i.className="jotter-btn jotter-color-btn",i.dataset.cmd=t.cmd,i.title=t.title,i.setAttribute("aria-label",t.title);let o=document.createElement("span");o.className="material-icons",o.textContent=t.icon,i.appendChild(o);let s=document.createElement("span");s.className="jotter-color-swatch";let n=t.cmd==="foreColor"?this._lastForeColor:this._lastHiliteColor;s.style.background=n,i.appendChild(s);let a=document.createElement("input");return a.type="color",a.className="jotter-color-input",a.value=n,a.tabIndex=-1,a.addEventListener("change",()=>{let r=a.value;s.style.background=r,t.cmd==="foreColor"?this._lastForeColor=r:this._lastHiliteColor=r,this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand(t.cmd,!1,r),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),i.addEventListener("mousedown",r=>{r.preventDefault(),this._savedRange=this._saveRange(),a.click()}),e.appendChild(i),e.appendChild(a),e}_buildPopupBtn(t){let e=document.createElement("button");e.type="button",e.className="jotter-btn",e.title=t.title,e.setAttribute("aria-label",t.title);let i=document.createElement("span");return i.className="material-icons",i.textContent=t.icon,e.appendChild(i),e.addEventListener("mousedown",o=>{if(o.preventDefault(),this._savedRange=this._saveRange(),this._popup.classList.contains("jotter-popup--visible")&&this._popup.dataset.popupId===t.id){this._hidePopup();return}this._showPopup(e,this._buildPopupContent(t.id),t.id)}),e}_buildPopupContainer(){let t=document.createElement("div");return t.className="jotter-popup",t.setAttribute("role","dialog"),t}_showPopup(t,e,i){this._popup.innerHTML="",this._popup.appendChild(e),this._popup.dataset.popupId=i,this._popup.classList.add("jotter-popup--visible");let o=t.getBoundingClientRect();this._popup.style.top=o.bottom+6+"px",this._popup.style.left=o.left+"px",this._popup.style.right="auto",requestAnimationFrame(()=>{let s=this._popup.getBoundingClientRect();s.right>window.innerWidth-8&&(this._popup.style.left=Math.max(8,o.left-(s.right-window.innerWidth+8))+"px")})}_hidePopup(){this._popup.classList.remove("jotter-popup--visible"),this._popup.dataset.popupId=""}_buildPopupContent(t){switch(t){case"link":return this._popupLink();case"table":return this._popupTable();case"image":return this._popupImage();case"video":return this._popupVideo();case"embed":return this._popupEmbed();case"symbol":return this._popupSymbol();case"specialchar":return this._popupSpecialChar();case"lorem":return this._popupLorem();default:{let e=document.createElement("div");return e.className="jotter-popup-inner",e.textContent="Unknown: "+t,e}}}_popupTable(){let t=document.createElement("div");t.className="jotter-popup-inner";let e=this._popupTitle("Insert Table");t.appendChild(e);let i=10,o=8,s=document.createElement("div");s.className="jotter-table-grid",s.style.gridTemplateColumns=`repeat(${i}, 1fr)`;let n=document.createElement("div");n.className="jotter-popup-hint",n.textContent="Hover to select size";let a=[];for(let r=0;r<o;r++)for(let c=0;c<i;c++){let l=document.createElement("span");l.className="jotter-table-cell",l.dataset.r=r,l.dataset.c=c,l.addEventListener("mouseenter",()=>{n.textContent=`${r+1} \xD7 ${c+1} table`,a.forEach(d=>{d.classList.toggle("jotter-table-cell--active",+d.dataset.r<=r&&+d.dataset.c<=c)})}),l.addEventListener("click",()=>{this._insertTable(r+1,c+1),this._hidePopup()}),a.push(l),s.appendChild(l)}return t.appendChild(s),t.appendChild(n),t}_insertTable(t,e){this._restoreRange(this._savedRange),this._editor.focus();let i="<table><tbody>";for(let o=0;o<t;o++){i+="<tr>";for(let s=0;s<e;s++)i+=o===0?"<th><br></th>":"<td><br></td>";i+="</tr>"}i+="</tbody></table><p><br></p>",document.execCommand("insertHTML",!1,i),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}_popupLink(){let t=document.createElement("div");t.className="jotter-popup-inner jotter-popup-form",t.appendChild(this._popupTitle("Insert Link"));let e=null,i="";if(this._savedRange){let c=window.getSelection();if(c&&c.rangeCount){i=c.toString();let l=c.anchorNode;for(;l&&l!==this._editor;){if(l.nodeName==="A"){e=l;break}l=l.parentNode}}}let o=this._makeField(t,"URL","url","https://"),s=this._makeField(t,"Link text (leave blank to keep selection)","text",""),n=this._makeField(t,"Title / tooltip","text",""),a=document.createElement("label");a.className="jotter-popup-label",a.textContent="Open in";let r=document.createElement("select");return r.className="jotter-popup-select",[["(same window)",""],["New tab (_blank)","_blank"],["Parent frame (_parent)","_parent"],["Top frame (_top)","_top"]].forEach(([c,l])=>{let d=document.createElement("option");d.value=l,d.textContent=c,r.appendChild(d)}),t.appendChild(a),t.appendChild(r),e?(o.value=e.getAttribute("href")||"",s.value=e.textContent||"",n.value=e.getAttribute("title")||"",r.value=e.getAttribute("target")||""):i&&(s.value=i),t.appendChild(this._makeSubmitBtn(e?"Update Link":"Insert Link",()=>{let c=o.value.trim();if(!c)return;let l=s.value.trim()||i||c,d=n.value.trim(),h=r.value,m=`href="${this._esc(c)}"`;h&&(m+=` target="${this._esc(h)}"`),d&&(m+=` title="${this._esc(d)}"`),this._restoreRange(this._savedRange),this._editor.focus(),e?(e.href=c,h?e.target=h:e.removeAttribute("target"),d?e.title=d:e.removeAttribute("title"),e.textContent=l):document.execCommand("insertHTML",!1,`<a ${m}>${this._esc(l)}</a>`),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())})),t}_popupImage(){let t=document.createElement("div");t.className="jotter-popup-inner jotter-popup-form",t.appendChild(this._popupTitle("Insert Image"));let e=this._makeField(t,"Image URL","text","https://example.com/image.jpg"),i=this._makeField(t,"Alt text","text","Descriptive text"),o=this._makeField(t,"Width (e.g. 400px or 50%)","text","");return t.appendChild(this._makeSubmitBtn("Insert Image",()=>{let s=e.value.trim();if(!s)return;let n=i.value.trim(),a=o.value.trim(),r=a?`max-width:${a}`:"max-width:100%";this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertHTML",!1,`<img src="${this._esc(s)}" alt="${this._esc(n)}" style="${r}">`),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())})),t}_popupVideo(){let t=document.createElement("div");t.className="jotter-popup-inner jotter-popup-form",t.appendChild(this._popupTitle("Insert YouTube Video"));let e=this._makeField(t,"YouTube URL","text","https://www.youtube.com/watch?v=...");return t.appendChild(this._makeSubmitBtn("Embed Video",()=>{let i=this._ytId(e.value.trim());if(!i){e.classList.add("jotter-input--error");return}e.classList.remove("jotter-input--error");let o=`<div class="jotter-video-wrap"><iframe src="https://www.youtube.com/embed/${i}" frameborder="0" allowfullscreen loading="lazy" title="YouTube video"></iframe></div><p><br></p>`;this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertHTML",!1,o),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())})),t}_ytId(t){for(let e of[/[?&]v=([A-Za-z0-9_-]{11})/,/youtu\.be\/([A-Za-z0-9_-]{11})/,/embed\/([A-Za-z0-9_-]{11})/]){let i=t.match(e);if(i)return i[1]}return null}_popupEmbed(){let t=document.createElement("div");t.className="jotter-popup-inner jotter-popup-form",t.appendChild(this._popupTitle("Insert Embed"));let e=document.createElement("label");e.className="jotter-popup-label",e.textContent="Paste HTML / embed code";let i=document.createElement("textarea");return i.className="jotter-popup-textarea",i.placeholder='<iframe src="..." ...></iframe>',i.rows=4,t.appendChild(e),t.appendChild(i),t.appendChild(this._makeSubmitBtn("Insert",()=>{let o=i.value.trim();o&&(this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertHTML",!1,o+"<p><br></p>"),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML()))})),t}_popupSymbol(){let t=document.createElement("div");return t.className="jotter-popup-inner",t.appendChild(this._popupTitle("Insert Symbol")),t.appendChild(this._charGrid(k)),t}_popupSpecialChar(){let t=document.createElement("div");return t.className="jotter-popup-inner",t.appendChild(this._popupTitle("Special Characters")),t.appendChild(this._charGrid(M)),t}_charGrid(t){let e=document.createElement("div");return e.className="jotter-char-grid",t.forEach(i=>{let o=document.createElement("button");o.type="button",o.className="jotter-char-btn",o.textContent=i,o.title=`U+${i.codePointAt(0).toString(16).toUpperCase().padStart(4,"0")}`,o.addEventListener("mousedown",s=>{s.preventDefault(),this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertText",!1,i),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),e.appendChild(o)}),e}_popupLorem(){let t=document.createElement("div");return t.className="jotter-popup-inner",t.appendChild(this._popupTitle("Insert Lorem Ipsum")),w.forEach(e=>{let i=document.createElement("button");i.type="button",i.className="jotter-lorem-btn",i.textContent=e.label,i.addEventListener("mousedown",o=>{o.preventDefault(),this._restoreRange(this._savedRange),this._editor.focus(),e.isHTML?document.execCommand("insertHTML",!1,e.text):document.execCommand("insertText",!1,e.text),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),t.appendChild(i)}),t}_popupTitle(t){let e=document.createElement("div");return e.className="jotter-popup-title",e.textContent=t,e}_makeField(t,e,i,o){let s=document.createElement("label");s.className="jotter-popup-label",s.textContent=e;let n=document.createElement("input");return n.type=i,n.className="jotter-popup-input",n.placeholder=o,t.appendChild(s),t.appendChild(n),n}_makeSubmitBtn(t,e){let i=document.createElement("button");return i.type="button",i.className="jotter-popup-submit",i.textContent=t,i.addEventListener("click",e),i}_esc(t){return t.replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_buildStatusBar(){let t=document.createElement("div");t.className="jotter-status",this._wordCountEl=document.createElement("span"),this._wordCountEl.className="jotter-status-words",this._charCountEl=document.createElement("span"),this._charCountEl.className="jotter-status-chars";let e=document.createElement("span");return e.className="jotter-status-mode",e.textContent="HTML",t.appendChild(this._wordCountEl),t.appendChild(this._charCountEl),t.appendChild(e),t}_bindEvents(){this._editor.addEventListener("input",()=>{this._editor.querySelectorAll(":scope > div").forEach(t=>{if(t.attributes.length>0)return;let e=document.createElement("p");e.innerHTML=t.innerHTML,t.replaceWith(e)}),Array.from(this._editor.childNodes).forEach(t=>{if(t.nodeType===Node.TEXT_NODE&&t.textContent.trim()!==""){let e=window.getSelection(),i=null;if(e&&e.rangeCount){let s=e.getRangeAt(0);s.startContainer===t&&(i=s.startOffset)}let o=document.createElement("p");if(t.replaceWith(o),o.appendChild(t),i!==null){let s=document.createRange();s.setStart(t,i),s.collapse(!0),e.removeAllRanges(),e.addRange(s)}}}),this._updateStatus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),this._editor.addEventListener("keyup",()=>this._updateToolbarState()),this._editor.addEventListener("mouseup",()=>this._updateToolbarState()),this._editor.addEventListener("focus",()=>{this._root.classList.add("jotter--focused"),this._emit("focus"),this._options.onFocus&&this._options.onFocus()}),this._editor.addEventListener("blur",()=>{this._root.classList.remove("jotter--focused"),this._emit("blur"),this._options.onBlur&&this._options.onBlur()}),this._editor.addEventListener("keydown",t=>{t.key==="Tab"&&(t.preventDefault(),document.execCommand("insertHTML",!1,"&nbsp;&nbsp;&nbsp;&nbsp;"))}),document.addEventListener("mousedown",t=>{this._popup.classList.contains("jotter-popup--visible")&&!this._popup.contains(t.target)&&!this._toolbarEl.contains(t.target)&&this._hidePopup()}),document.addEventListener("keydown",t=>{t.key==="Escape"&&this._popup.classList.contains("jotter-popup--visible")&&(this._hidePopup(),this._editor.focus())})}_toggleSourceMode(){this._sourceMode=!this._sourceMode,this._sourceMode?(this._source.value=this._prettyHTML(this._editor.innerHTML),this._editor.style.display="none",this._source.style.display="block"):(this._editor.innerHTML=this._sanitize(this._source.value),this._source.style.display="none",this._editor.style.display="",this._updateStatus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())),this._root.classList.toggle("jotter--source-mode",this._sourceMode);let t=this._toolbarEl.querySelector('[data-custom="toggleSource"]');t&&t.classList.toggle("jotter-btn--active",this._sourceMode)}_prettyHTML(t){let e=0,i=" ",o=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),s=new Set(["a","abbr","acronym","b","bdo","big","br","button","cite","code","dfn","em","i","img","input","kbd","label","map","object","output","q","samp","select","small","span","strong","sub","sup","textarea","time","tt","u","var"]);return t.replace(/>\s+</g,"><").replace(/(<[^>]+>)/g,`
1
+ var JotterJS=(()=>{var b=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var S=(p,e)=>{for(var t in e)b(p,t,{get:e[t],enumerable:!0})},y=(p,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of v(e))!E.call(p,i)&&i!==t&&b(p,i,{get:()=>e[i],enumerable:!(o=k(e,i))||o.enumerable});return p};var x=p=>y(b({},"__esModule",{value:!0}),p);var H={};S(H,{JotterJS:()=>m,default:()=>B});var f={source:{custom:"toggleSource",label:"Source",title:"Edit HTML Source"},sep:{type:"sep"},blockformat:{type:"blockformat"},fontfamily:{type:"fontfamily"},fontsize:{type:"fontsize"},theme:{type:"theme"},undo:{cmd:"undo",icon:"undo",title:"Undo (Ctrl+Z)"},redo:{cmd:"redo",icon:"redo",title:"Redo (Ctrl+Y)"},bold:{cmd:"bold",icon:"format_bold",title:"Bold (Ctrl+B)"},italic:{cmd:"italic",icon:"format_italic",title:"Italic (Ctrl+I)"},underline:{cmd:"underline",icon:"format_underlined",title:"Underline (Ctrl+U)"},strike:{cmd:"strikeThrough",icon:"strikethrough_s",title:"Strikethrough"},subscript:{cmd:"subscript",icon:"subscript",title:"Subscript"},superscript:{cmd:"superscript",icon:"superscript",title:"Superscript"},code:{custom:"code",icon:"code",title:"Inline Code"},copy:{cmd:"copy",icon:"content_copy",title:"Copy"},cut:{cmd:"cut",icon:"content_cut",title:"Cut"},paste:{cmd:"paste",icon:"content_paste",title:"Paste"},clearFormat:{cmd:"removeFormat",icon:"format_clear",title:"Clear Formatting"},alignLeft:{cmd:"justifyLeft",icon:"format_align_left",title:"Align Left"},alignCenter:{cmd:"justifyCenter",icon:"format_align_center",title:"Align Center"},alignRight:{cmd:"justifyRight",icon:"format_align_right",title:"Align Right"},bullets:{cmd:"insertUnorderedList",icon:"format_list_bulleted",title:"Bullet List"},numbered:{cmd:"insertOrderedList",icon:"format_list_numbered",title:"Numbered List"},link:{type:"popup",id:"link",icon:"insert_link",title:"Insert Link"},unlink:{cmd:"unlink",icon:"link_off",title:"Remove Link"},foreColor:{type:"color",cmd:"foreColor",icon:"format_color_text",title:"Text Color"},hiliteColor:{type:"color",cmd:"hiliteColor",icon:"format_color_fill",title:"Background Color"},image:{type:"popup",id:"image",icon:"image",title:"Insert Image"},video:{type:"popup",id:"video",icon:"smart_display",title:"Insert YouTube Video"},table:{type:"popup",id:"table",icon:"table_chart",title:"Insert Table"},embed:{type:"popup",id:"embed",icon:"html",title:"Insert Embed"},symbol:{type:"popup",id:"symbol",icon:"emoji_symbols",title:"Insert Symbol"},specialChar:{type:"popup",id:"specialchar",icon:"format_shapes",title:"Special Characters"},lorem:{type:"popup",id:"lorem",icon:"history_edu",title:"Insert Lorem Ipsum"}};Object.values(f).forEach(Object.freeze);Object.freeze(f);var n=f,_={minimal:[n.source,n.sep,n.bold,n.italic,n.underline,n.sep,n.link,n.unlink],writing:[n.source,n.sep,n.undo,n.redo,n.sep,n.blockformat,n.sep,n.bold,n.italic,n.underline,n.strike,n.sep,n.bullets,n.numbered,n.sep,n.link,n.unlink,n.sep,n.image],full:[n.source,n.sep,n.undo,n.redo,n.sep,n.copy,n.cut,n.paste,n.sep,n.clearFormat,n.sep,n.blockformat,n.fontfamily,n.fontsize,n.sep,n.bold,n.italic,n.underline,n.strike,n.subscript,n.superscript,n.code,n.sep,n.foreColor,n.hiliteColor,n.sep,n.alignLeft,n.alignCenter,n.alignRight,n.sep,n.bullets,n.numbered,n.sep,n.link,n.unlink,n.sep,n.image,n.video,n.table,n.embed,n.symbol,n.specialChar,n.lorem,n.sep,n.theme]};Object.values(_).forEach(Object.freeze);Object.freeze(_);var L=_.full,T={image:"onRequestImage",link:"onRequestLink",video:"onRequestVideo",embed:"onRequestEmbed"},g=[{label:"Paragraph",tag:"p"},{label:"Heading 1",tag:"h1"},{label:"Heading 2",tag:"h2"},{label:"Heading 3",tag:"h3"},{label:"Heading 4",tag:"h4"},{label:"Pre / Code",tag:"pre"},{label:"Blockquote",tag:"blockquote"}],w=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Lucida Console","Palatino Linotype","Tahoma","Times New Roman","Trebuchet MS","Verdana"],j=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],M=["\u2190","\u2192","\u2191","\u2193","\u2194","\u2195","\u21D0","\u21D2","\u21D1","\u21D3","\u21D4","\u2022","\xB7","\u25E6","\u25CB","\u25CF","\u25A1","\u25A0","\u25C6","\u25C7","\u25B2","\u25BC","\u2605","\u2606","\u2660","\u2663","\u2665","\u2666","\u2713","\u2717","\u2715","\u2718","\u2248","\u2260","\u2261","\u2264","\u2265","\xF7","\xD7","\xB1","\u221E","\u221A","\u2211","\u220F","\u222B","\u2202","\u2206","\u2207","\u03C0","\u03A9","\u03BC","\u03B1","\u03B2","\u03B3","\xA9","\xAE","\u2122","\xA7","\xB6","\u2020","\u2021","\xB0","\u2032","\u2033","\u2030","\u201C","\u201D","\u2018","\u2019","\xAB","\xBB","\u2039","\u203A","\u2014","\u2013","\u2026","\xBF","\xA1","\u20AC","\xA3","\xA5","\xA2","\u20B9","\u20BD","\u20BF"],N=["\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6","\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE","\xCF","\xD0","\xD1","\xD2","\xD3","\xD4","\xD5","\xD6","\xD8","\xD9","\xDA","\xDB","\xDC","\xDD","\xDE","\xDF","\xE0","\xE1","\xE2","\xE3","\xE4","\xE5","\xE6","\xE7","\xE8","\xE9","\xEA","\xEB","\xEC","\xED","\xEE","\xEF","\xF0","\xF1","\xF2","\xF3","\xF4","\xF5","\xF6","\xF8","\xF9","\xFA","\xFB","\xFC","\xFD","\xFE","\xFF","\u0152","\u0153","\u0160","\u0161","\u0178","\u017D","\u017E"],C=[{id:"default",label:"Default"},{id:"warm",label:"Warm"},{id:"ink",label:"Ink / Navy"},{id:"forest",label:"Forest"}],I=[{label:"Short \u2014 1 sentence",text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit."},{label:"Medium \u2014 1 paragraph",text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."},{label:"Long \u2014 3 paragraphs",isHTML:!0,text:"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p><p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p><p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>"}],m=class{constructor(e,t={}){if(this._target=typeof e=="string"?document.querySelector(e):e,!this._target)throw new Error("[JotterJS] Target element not found.");this._options=Object.assign({placeholder:"Start typing\u2026",height:"320px",theme:"default",onChange:null,onFocus:null,onBlur:null,onRequestImage:null,onRequestLink:null,onRequestVideo:null,onRequestEmbed:null},t),this._listeners={},this._bookmarks=new Map,this._bmSeq=0,this._savedBookmark=null,this._externalBookmark=null,this._externalDepth=0,this._changeCount=0,this._destroyed=!1,this._lastForeColor="#e8e4d8",this._lastHiliteColor="#c8a96e",this._init()}_init(){let e=this._target.innerHTML||"";this._target.innerHTML="",this._target.classList.add("jotter-host"),this._root=document.createElement("div"),this._root.className="htmled",this._toolbar=this._buildToolbar(),this._editorWrap=document.createElement("div"),this._editorWrap.className="jotter-editor-wrap",this._editor=document.createElement("div"),this._editor.className="jotter-editor",this._editor.contentEditable="true",this._editor.setAttribute("data-placeholder",this._options.placeholder),this._editor.style.minHeight=this._options.height,this._editor.innerHTML=this._sanitize(e),this._editor.spellcheck=!0,document.execCommand("defaultParagraphSeparator",!1,"p"),this._source=document.createElement("textarea"),this._source.className="jotter-source",this._source.setAttribute("aria-label","HTML source"),this._source.setAttribute("spellcheck","false"),this._source.style.minHeight=this._options.height,this._sourceMode=!1,this._statusBar=this._buildStatusBar(),this._editorWrap.appendChild(this._editor),this._editorWrap.appendChild(this._source),this._root.appendChild(this._toolbar),this._root.appendChild(this._editorWrap),this._root.appendChild(this._statusBar),this._target.appendChild(this._root),this._popup=this._buildPopupContainer(),document.body.appendChild(this._popup),this._bindEvents(),this._updateToolbarState(),this._updateStatus(),this.setTheme(this._options.theme)}_buildToolbar(){let e=document.createElement("div");return e.className="jotter-toolbar",this._toolbarEl=e,(this._options.toolbar||L).forEach(t=>{let o=this._buildAction(t);o&&e.appendChild(o)}),e}_buildAction(e){if(typeof e.onClick=="function")return this._buildBtn(e);switch(e.type){case"sep":return this._makeSep();case"blockformat":return this._buildBlockFormatSelect();case"fontfamily":return this._buildFontFamilySelect();case"fontsize":return this._buildFontSizeSelect();case"color":return this._buildColorBtn(e);case"popup":return this._buildPopupBtn(e);case"theme":return this._buildThemeSelect();default:return this._buildBtn(e)}}_makeSep(){let e=document.createElement("span");return e.className="jotter-sep",e}_buildBtn(e){let t=document.createElement("button");if(t.type="button",t.className="jotter-btn",e.cmd&&(t.dataset.cmd=e.cmd),e.custom&&(t.dataset.custom=e.custom),t.title=e.title,t.setAttribute("aria-label",e.title),e.label)t.classList.add("jotter-btn--text"),t.appendChild(document.createTextNode(e.label));else{let o=document.createElement("span");o.className="material-icons",o.textContent=e.icon,t.appendChild(o)}return t.addEventListener("mousedown",o=>{o.preventDefault(),this._editor.focus(),this._applyEdit(()=>{if(e.onClick)e.onClick(this);else if(e.custom==="toggleSource")this._toggleSourceMode();else if(e.custom==="code")this._toggleInlineCode();else if(e.cmd==="copy")document.execCommand("copy");else if(e.cmd==="cut")document.execCommand("cut");else if(e.cmd==="paste")this._pasteFromClipboard();else if(e.prompt){let i=window.prompt(e.prompt);i&&document.execCommand(e.cmd,!1,i)}else document.execCommand(e.cmd,!1,null)}),this._updateToolbarState()}),t}_buildBlockFormatSelect(){let e=document.createElement("select");return e.className="jotter-select",e.title="Block format",e.dataset.id="blockformat",g.forEach(({label:t,tag:o})=>{let i=document.createElement("option");i.value=o,i.textContent=t,e.appendChild(i)}),e.addEventListener("mousedown",()=>this._saveBookmark()),e.addEventListener("change",()=>{this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("formatBlock",!1,e.value))}),e}_buildFontFamilySelect(){let e=document.createElement("select");e.className="jotter-select jotter-select--font",e.title="Font family",e.dataset.id="fontfamily";let t=document.createElement("option");return t.value="",t.textContent="Font",e.appendChild(t),w.forEach(o=>{let i=document.createElement("option");i.value=o,i.textContent=o,i.style.fontFamily=o,e.appendChild(i)}),e.addEventListener("mousedown",()=>this._saveBookmark()),e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("fontName",!1,e.value)))}),e}_buildFontSizeSelect(){let e=document.createElement("select");e.className="jotter-select jotter-select--size",e.title="Font size",e.dataset.id="fontsize";let t=document.createElement("option");return t.value="",t.textContent="Size",e.appendChild(t),j.forEach(o=>{let i=document.createElement("option");i.value=o,i.textContent=`${o}px`,e.appendChild(i)}),e.addEventListener("mousedown",()=>this._saveBookmark()),e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>this._applyFontSize(e.value)))}),e}_buildThemeSelect(){let e=document.createElement("select");return e.className="jotter-select jotter-select--theme",e.title="Editor theme",e.dataset.id="theme",C.forEach(({id:t,label:o})=>{let i=document.createElement("option");i.value=t,i.textContent=o,e.appendChild(i)}),e.value=this._options.theme,e.addEventListener("change",()=>this.setTheme(e.value)),this._themeSelect=e,e}_buildColorBtn(e){let t=document.createElement("span");t.className="jotter-color-wrap";let o=document.createElement("button");o.type="button",o.className="jotter-btn jotter-color-btn",o.dataset.cmd=e.cmd,o.title=e.title,o.setAttribute("aria-label",e.title);let i=document.createElement("span");i.className="material-icons",i.textContent=e.icon,o.appendChild(i);let s=document.createElement("span");s.className="jotter-color-swatch";let r=e.cmd==="foreColor"?this._lastForeColor:this._lastHiliteColor;s.style.background=r,o.appendChild(s);let a=document.createElement("input");return a.type="color",a.className="jotter-color-input",a.value=r,a.tabIndex=-1,a.addEventListener("change",()=>{let l=a.value;s.style.background=l,e.cmd==="foreColor"?this._lastForeColor=l:this._lastHiliteColor=l,this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand(e.cmd,!1,l))}),o.addEventListener("mousedown",l=>{l.preventDefault(),this._saveBookmark(),a.click()}),t.appendChild(o),t.appendChild(a),t}_buildPopupBtn(e){let t=document.createElement("button");t.type="button",t.className="jotter-btn",t.title=e.title,t.setAttribute("aria-label",e.title);let o=document.createElement("span");return o.className="material-icons",o.textContent=e.icon,t.appendChild(o),t.addEventListener("mousedown",i=>{i.preventDefault();let s=this._resolverFor(e.id);if(s){this._hidePopup(),this._runResolver(e.id,s);return}if(this._saveBookmark(),this._popupVisible()&&this._popup.dataset.popupId===e.id){this._hidePopup();return}this._showPopup(t,this._buildPopupContent(e.id),e.id)}),t}_buildPopupContainer(){let e=document.createElement("div");return e.className="jotter-popup",e.setAttribute("role","dialog"),e}_showPopup(e,t,o){this._popup.innerHTML="",this._popup.appendChild(t),this._popup.dataset.popupId=o,this._popup.classList.add("jotter-popup--visible");let i=e.getBoundingClientRect();this._popup.style.top=i.bottom+6+"px",this._popup.style.left=i.left+"px",this._popup.style.right="auto",requestAnimationFrame(()=>{let s=this._popup.getBoundingClientRect();s.right>window.innerWidth-8&&(this._popup.style.left=Math.max(8,i.left-(s.right-window.innerWidth+8))+"px")})}_popupVisible(){return this._popup.classList.contains("jotter-popup--visible")}_hidePopup(){this._popup.classList.remove("jotter-popup--visible"),this._popup.dataset.popupId="",this._releaseSavedBookmark()}_buildPopupContent(e){switch(e){case"link":return this._popupLink();case"table":return this._popupTable();case"image":return this._popupImage();case"video":return this._popupVideo();case"embed":return this._popupEmbed();case"symbol":return this._popupSymbol();case"specialchar":return this._popupSpecialChar();case"lorem":return this._popupLorem();default:{let t=document.createElement("div");return t.className="jotter-popup-inner",t.textContent="Unknown: "+e,t}}}_popupTable(){let e=document.createElement("div");e.className="jotter-popup-inner";let t=this._popupTitle("Insert Table");e.appendChild(t);let o=10,i=8,s=document.createElement("div");s.className="jotter-table-grid",s.style.gridTemplateColumns=`repeat(${o}, 1fr)`;let r=document.createElement("div");r.className="jotter-popup-hint",r.textContent="Hover to select size";let a=[];for(let l=0;l<i;l++)for(let d=0;d<o;d++){let c=document.createElement("span");c.className="jotter-table-cell",c.dataset.r=l,c.dataset.c=d,c.addEventListener("mouseenter",()=>{r.textContent=`${l+1} \xD7 ${d+1} table`,a.forEach(u=>{u.classList.toggle("jotter-table-cell--active",+u.dataset.r<=l&&+u.dataset.c<=d)})}),c.addEventListener("click",()=>this._insertTable(l+1,d+1)),a.push(c),s.appendChild(c)}return e.appendChild(s),e.appendChild(r),e}_commitPopup(e){this._restoreSavedBookmark(),this._hidePopup(),this._applyEdit(e)}_insertTable(e,t){let o="<table><tbody>";for(let i=0;i<e;i++){o+="<tr>";for(let s=0;s<t;s++)o+=i===0?"<th><br></th>":"<td><br></td>";o+="</tr>"}o+="</tbody></table><p><br></p>",this._commitPopup(()=>document.execCommand("insertHTML",!1,o))}_popupLink(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Link"));let t=this._anchorInSelection(),o=this._selectedText(),i=this._makeField(e,"URL","url","https://"),s=this._makeField(e,"Link text (leave blank to keep selection)","text",""),r=this._makeField(e,"Title / tooltip","text",""),a=document.createElement("label");a.className="jotter-popup-label",a.textContent="Open in";let l=document.createElement("select");return l.className="jotter-popup-select",[["(same window)",""],["New tab (_blank)","_blank"],["Parent frame (_parent)","_parent"],["Top frame (_top)","_top"]].forEach(([d,c])=>{let u=document.createElement("option");u.value=c,u.textContent=d,l.appendChild(u)}),e.appendChild(a),e.appendChild(l),t?(i.value=t.getAttribute("href")||"",s.value=t.textContent||"",r.value=t.getAttribute("title")||"",l.value=t.getAttribute("target")||""):o&&(s.value=o),e.appendChild(this._makeSubmitBtn(t?"Update Link":"Insert Link",()=>{let d=i.value.trim();if(!d)return;let c=s.value.trim()||o||d,u=r.value.trim(),h=l.value;this._commitPopup(()=>{t?(t.href=d,h?t.target=h:t.removeAttribute("target"),u?t.title=u:t.removeAttribute("title"),t.textContent=c):document.execCommand("insertHTML",!1,this._linkHTML({href:d,text:c,title:u,target:h}))})})),e}_popupImage(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Image"));let t=this._makeField(e,"Image URL","text","https://example.com/image.jpg"),o=this._makeField(e,"Alt text","text","Descriptive text"),i=this._makeField(e,"Width (e.g. 400px or 50%)","text","");return e.appendChild(this._makeSubmitBtn("Insert Image",()=>{let s=t.value.trim();if(!s)return;let r=this._imageHTML({src:s,alt:o.value.trim(),width:i.value.trim()});this._commitPopup(()=>document.execCommand("insertHTML",!1,r))})),e}_popupVideo(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert YouTube Video"));let t=this._makeField(e,"YouTube URL","text","https://www.youtube.com/watch?v=...");return e.appendChild(this._makeSubmitBtn("Embed Video",()=>{let o=this._ytId(t.value.trim());if(!o){t.classList.add("jotter-input--error");return}t.classList.remove("jotter-input--error"),this._commitPopup(()=>document.execCommand("insertHTML",!1,this._videoHTML(o)))})),e}_ytId(e){for(let t of[/[?&]v=([A-Za-z0-9_-]{11})/,/youtu\.be\/([A-Za-z0-9_-]{11})/,/embed\/([A-Za-z0-9_-]{11})/]){let o=e.match(t);if(o)return o[1]}return null}_popupEmbed(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Embed"));let t=document.createElement("label");t.className="jotter-popup-label",t.textContent="Paste HTML / embed code";let o=document.createElement("textarea");return o.className="jotter-popup-textarea",o.placeholder='<iframe src="..." ...></iframe>',o.rows=4,e.appendChild(t),e.appendChild(o),e.appendChild(this._makeSubmitBtn("Insert",()=>{let i=o.value.trim();i&&this._commitPopup(()=>document.execCommand("insertHTML",!1,this._embedHTML(i)))})),e}_popupSymbol(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Symbol")),e.appendChild(this._charGrid(M)),e}_popupSpecialChar(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Special Characters")),e.appendChild(this._charGrid(N)),e}_charGrid(e){let t=document.createElement("div");return t.className="jotter-char-grid",e.forEach(o=>{let i=document.createElement("button");i.type="button",i.className="jotter-char-btn",i.textContent=o,i.title=`U+${o.codePointAt(0).toString(16).toUpperCase().padStart(4,"0")}`,i.addEventListener("mousedown",s=>{s.preventDefault(),this._commitPopup(()=>document.execCommand("insertText",!1,o))}),t.appendChild(i)}),t}_popupLorem(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Lorem Ipsum")),I.forEach(t=>{let o=document.createElement("button");o.type="button",o.className="jotter-lorem-btn",o.textContent=t.label,o.addEventListener("mousedown",i=>{i.preventDefault(),this._commitPopup(()=>document.execCommand(t.isHTML?"insertHTML":"insertText",!1,t.text))}),e.appendChild(o)}),e}_popupTitle(e){let t=document.createElement("div");return t.className="jotter-popup-title",t.textContent=e,t}_makeField(e,t,o,i){let s=document.createElement("label");s.className="jotter-popup-label",s.textContent=t;let r=document.createElement("input");return r.type=o,r.className="jotter-popup-input",r.placeholder=i,e.appendChild(s),e.appendChild(r),r}_makeSubmitBtn(e,t){let o=document.createElement("button");return o.type="button",o.className="jotter-popup-submit",o.textContent=e,o.addEventListener("click",t),o}_esc(e){return String(e).replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_imageHTML({src:e,alt:t,width:o}){let i=o?`max-width:${o}`:"max-width:100%";return`<img src="${this._esc(e)}" alt="${this._esc(t||"")}" style="${this._esc(i)}">`}_linkHTML({href:e,text:t,title:o,target:i}){let s=`href="${this._esc(e)}"`;return i&&(s+=` target="${this._esc(i)}"`),o&&(s+=` title="${this._esc(o)}"`),`<a ${s}>${this._esc(t||e)}</a>`}_videoHTML(e){return/^[A-Za-z0-9_-]{11}$/.test(e)?`<div class="jotter-video-wrap"><iframe src="https://www.youtube.com/embed/${e}" frameborder="0" allowfullscreen loading="lazy" title="YouTube video"></iframe></div><p><br></p>`:null}_embedHTML(e){return e+"<p><br></p>"}_resolverFor(e){let t=T[e],o=t?this._options[t]:null;return typeof o=="function"?o:null}async _runResolver(e,t){let o=this._resolverContext(e);this.beginExternalUI();let i=null;try{i=await t(o)}catch{i=null}if(this._destroyed)return;let s=i==null||i===!1?null:this._resolvedHTML(e,i,o);this.endExternalUI(),s&&(this._applyEdit(()=>document.execCommand("insertHTML",!1,s)),this._updateToolbarState())}_resolverContext(e){switch(e){case"image":return{src:"",alt:"",width:"",selection:this._selectedText()};case"video":return{url:"",selection:this._selectedText()};case"embed":return{html:"",selection:this._selectedText()};case"link":return this._linkContext();default:return{selection:this._selectedText()}}}_linkContext(){let e=this._anchorInSelection();if(!e){let o=this._selectedText();return{href:"",text:o,title:"",target:"",selection:o,isEdit:!1}}let t=window.getSelection();if(t){let o=document.createRange();o.selectNode(e),t.removeAllRanges(),t.addRange(o)}return{href:e.getAttribute("href")||"",text:e.textContent||"",title:e.getAttribute("title")||"",target:e.getAttribute("target")||"",selection:e.textContent||"",isEdit:!0}}_resolvedHTML(e,t,o){switch(e){case"image":{let i=typeof t=="string"?{src:t}:t,s=String(i.src||i.url||"").trim();return s?this._imageHTML({...i,src:s}):null}case"link":{let i=typeof t=="string"?{href:t}:t,s=String(i.href||i.url||"").trim();if(!s)return null;let r=String(i.text!=null?i.text:o.text||"").trim();return this._linkHTML({...i,href:s,text:r})}case"video":{let i=typeof t=="string"?{url:t}:t,s=i.id?String(i.id).trim():this._ytId(String(i.url||"").trim());return s?this._videoHTML(s):null}case"embed":{let i=String(typeof t=="string"?t:t.html||"").trim();return i?this._embedHTML(i):null}default:return null}}_selectedText(){let e=window.getSelection();return!e||!e.rangeCount?"":this._editor.contains(e.getRangeAt(0).commonAncestorContainer)?e.toString():""}_anchorInSelection(){let e=window.getSelection();if(!e||!e.rangeCount)return null;let t=e.anchorNode;if(!t||!this._editor.contains(t))return null;for(;t&&t!==this._editor;){if(t.nodeName==="A")return t;t=t.parentNode}return null}_buildStatusBar(){let e=document.createElement("div");e.className="jotter-status",this._wordCountEl=document.createElement("span"),this._wordCountEl.className="jotter-status-words",this._charCountEl=document.createElement("span"),this._charCountEl.className="jotter-status-chars";let t=document.createElement("span");return t.className="jotter-status-mode",t.textContent="HTML",e.appendChild(this._wordCountEl),e.appendChild(this._charCountEl),e.appendChild(t),e}_bindEvents(){this._editor.addEventListener("input",()=>{this._editor.querySelectorAll(":scope > div").forEach(e=>{if(e.attributes.length>0)return;let t=document.createElement("p");t.innerHTML=e.innerHTML,e.replaceWith(t)}),Array.from(this._editor.childNodes).forEach(e=>{if(e.nodeType===Node.TEXT_NODE&&e.textContent.trim()!==""){let t=window.getSelection(),o=null;if(t&&t.rangeCount){let s=t.getRangeAt(0);s.startContainer===e&&(o=s.startOffset)}let i=document.createElement("p");if(e.replaceWith(i),i.appendChild(e),o!==null){let s=document.createRange();s.setStart(e,o),s.collapse(!0),t.removeAllRanges(),t.addRange(s)}}}),this._updateStatus(),this._emitChange()}),this._editor.addEventListener("keyup",()=>this._updateToolbarState()),this._editor.addEventListener("mouseup",()=>this._updateToolbarState()),this._editor.addEventListener("focusin",e=>{this._root.classList.add("jotter--focused"),!(this._externalDepth>0)&&(this._isInternalTarget(e.relatedTarget)||(this._emit("focus"),this._options.onFocus&&this._options.onFocus()))}),this._editor.addEventListener("focusout",e=>{this._externalDepth>0||this._isInternalTarget(e.relatedTarget)||setTimeout(()=>{this._destroyed||this._externalDepth>0||this._isInternalTarget(document.activeElement)||this._popupVisible()||(this._root.classList.remove("jotter--focused"),this._emit("blur"),this._options.onBlur&&this._options.onBlur())},0)}),this._editor.addEventListener("keydown",e=>{e.key==="Tab"&&(e.preventDefault(),document.execCommand("insertHTML",!1,"&nbsp;&nbsp;&nbsp;&nbsp;"))}),this._onDocMouseDown=e=>{this._popupVisible()&&!this._popup.contains(e.target)&&!this._toolbarEl.contains(e.target)&&this._hidePopup()},document.addEventListener("mousedown",this._onDocMouseDown),this._onDocKeyDown=e=>{e.key==="Escape"&&this._popupVisible()&&(this._restoreSavedBookmark(),this._hidePopup())},document.addEventListener("keydown",this._onDocKeyDown)}_isInternalTarget(e){return!!e&&(this._root.contains(e)||this._popup.contains(e))}_toggleSourceMode(){this._sourceMode=!this._sourceMode,this._sourceMode?(this._dropBookmarks(),this._source.value=this._prettyHTML(this._richHTML()),this._editor.style.display="none",this._source.style.display="block"):(this._editor.innerHTML=this._sanitize(this._source.value),this._source.style.display="none",this._editor.style.display="",this._updateStatus(),this._emitChange()),this._root.classList.toggle("jotter--source-mode",this._sourceMode);let e=this._toolbarEl.querySelector('[data-custom="toggleSource"]');e&&e.classList.toggle("jotter-btn--active",this._sourceMode)}_prettyHTML(e){let t=0,o=" ",i=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),s=new Set(["a","abbr","acronym","b","bdo","big","br","button","cite","code","dfn","em","i","img","input","kbd","label","map","object","output","q","samp","select","small","span","strong","sub","sup","textarea","time","tt","u","var"]);return e.replace(/>\s+</g,"><").replace(/(<[^>]+>)/g,`
2
2
  $1
3
3
  `).split(`
4
- `).map(n=>n.trim()).filter(n=>n.length>0).map(n=>{let a=n.match(/^<\/(\w+)/),r=n.match(/^<(\w+)/),c=n.endsWith("/>"),l=r?r[1].toLowerCase():null,d=a?a[1].toLowerCase():null;d&&!s.has(d)&&(e=Math.max(0,e-1));let h=i.repeat(e)+n;return l&&!c&&!o.has(l)&&!d&&!s.has(l)&&e++,h}).join(`
5
- `)}_sanitize(t){let e=new DOMParser().parseFromString(t,"text/html");return e.querySelectorAll("script").forEach(i=>i.remove()),e.querySelectorAll("*").forEach(i=>{Array.from(i.attributes).forEach(o=>{(o.name.startsWith("on")||["href","src","action","formaction","data"].includes(o.name)&&/^\s*javascript:/i.test(o.value))&&i.removeAttribute(o.name)})}),e.body.innerHTML}_toggleInlineCode(){let t=window.getSelection();if(!t||t.rangeCount===0)return;let e=t.getRangeAt(0),i=e.commonAncestorContainer;i.nodeType===3&&(i=i.parentNode);let o=i.closest?i.closest("code"):null;if(o){let s=o.parentNode;for(;o.firstChild;)s.insertBefore(o.firstChild,o);s.removeChild(o)}else if(e.collapsed){let s=document.createElement("code");s.innerHTML="&#8203;",e.insertNode(s);let n=document.createRange();n.setStart(s,0),n.setEnd(s,s.childNodes.length),t.removeAllRanges(),t.addRange(n)}else{let s=document.createElement("code");try{e.surroundContents(s)}catch{let a=e.extractContents();s.appendChild(a),e.insertNode(s)}}}_applyFontSize(t){let e=window.getSelection();if(!e||e.rangeCount===0)return;let i=e.getRangeAt(0);if(i.collapsed)return;let o=document.createElement("span");o.style.fontSize=t+"px";try{i.surroundContents(o)}catch{let n=i.extractContents();o.appendChild(n),i.insertNode(o)}}async _pasteFromClipboard(){try{if(navigator.clipboard&&navigator.clipboard.readText){let t=await navigator.clipboard.readText();document.execCommand("insertText",!1,t)}else document.execCommand("paste")}catch{}}_updateToolbarState(){this._toolbarEl.querySelectorAll(".jotter-btn[data-cmd]").forEach(e=>{try{e.classList.toggle("jotter-btn--active",document.queryCommandState(e.dataset.cmd))}catch{}});let t=this._toolbarEl.querySelector('[data-id="blockformat"]');if(t){let e=document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g,""),i=g.find(o=>o.tag===e);i&&(t.value=i.tag)}}_updateStatus(){let t=this._editor.innerText||"",e=t.trim()===""?0:t.trim().split(/\s+/).length,i=t.replace(/\n/g,"").length;this._wordCountEl.textContent=`${e} word${e!==1?"s":""}`,this._charCountEl.textContent=`${i} char${i!==1?"s":""}`}_saveRange(){let t=window.getSelection();return t&&t.rangeCount>0?t.getRangeAt(0).cloneRange():null}_restoreRange(t){if(!t)return;let e=window.getSelection();e.removeAllRanges(),e.addRange(t)}_emit(t,e){(this._listeners[t]||[]).forEach(i=>i(e))}getHTML(){return this._sourceMode?this._source.value:this._editor.innerHTML}getText(){return this._editor.innerText}isSourceMode(){return this._sourceMode}toggleSource(){return this._toggleSourceMode(),this}clear(){return this._editor.innerHTML="",this._updateStatus(),this}focus(){return this._editor.focus(),this}insertHTML(t){return this._editor.focus(),document.execCommand("insertHTML",!1,this._sanitize(t)),this._updateStatus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML()),this}insertText(t){return this._editor.focus(),document.execCommand("insertText",!1,t),this._updateStatus(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML()),this}setHTML(t){return this._editor.innerHTML=this._sanitize(t),this._updateStatus(),this}setTheme(t){let i=f.find(o=>o.id===t)?t:"default";return this._editor.dataset.theme=i,this._options.theme=i,this._themeSelect&&(this._themeSelect.value=i),this}setEnabled(t){return this._editor.contentEditable=String(t),this._root.classList.toggle("jotter--disabled",!t),this}on(t,e){return this._listeners[t]||(this._listeners[t]=[]),this._listeners[t].push(e),this}off(t,e){return this._listeners[t]&&(this._listeners[t]=this._listeners[t].filter(i=>i!==e)),this}destroy(){let t=this.getHTML();return this._hidePopup(),this._popup.parentNode&&this._popup.parentNode.removeChild(this._popup),this._target.classList.remove("jotter-host"),this._target.innerHTML=t,this._listeners={},t}};u.toolbar=b;u.actions={source:{custom:"toggleSource",label:"Source",title:"Edit HTML Source"},sep:{type:"sep"},blockformat:{type:"blockformat"},fontfamily:{type:"fontfamily"},fontsize:{type:"fontsize"},theme:{type:"theme"},undo:{cmd:"undo",icon:"undo",title:"Undo (Ctrl+Z)"},redo:{cmd:"redo",icon:"redo",title:"Redo (Ctrl+Y)"},bold:{cmd:"bold",icon:"format_bold",title:"Bold (Ctrl+B)"},italic:{cmd:"italic",icon:"format_italic",title:"Italic (Ctrl+I)"},underline:{cmd:"underline",icon:"format_underlined",title:"Underline (Ctrl+U)"},strike:{cmd:"strikeThrough",icon:"strikethrough_s",title:"Strikethrough"},subscript:{cmd:"subscript",icon:"subscript",title:"Subscript"},superscript:{cmd:"superscript",icon:"superscript",title:"Superscript"},code:{custom:"code",icon:"code",title:"Inline Code"},copy:{cmd:"copy",icon:"content_copy",title:"Copy"},cut:{cmd:"cut",icon:"content_cut",title:"Cut"},paste:{cmd:"paste",icon:"content_paste",title:"Paste"},clearFormat:{cmd:"removeFormat",icon:"format_clear",title:"Clear Formatting"},alignLeft:{cmd:"justifyLeft",icon:"format_align_left",title:"Align Left"},alignCenter:{cmd:"justifyCenter",icon:"format_align_center",title:"Align Center"},alignRight:{cmd:"justifyRight",icon:"format_align_right",title:"Align Right"},bullets:{cmd:"insertUnorderedList",icon:"format_list_bulleted",title:"Bullet List"},numbered:{cmd:"insertOrderedList",icon:"format_list_numbered",title:"Numbered List"},link:{type:"popup",id:"link",icon:"insert_link",title:"Insert Link"},unlink:{cmd:"unlink",icon:"link_off",title:"Remove Link"},foreColor:{type:"color",cmd:"foreColor",icon:"format_color_text",title:"Text Color"},hiliteColor:{type:"color",cmd:"hiliteColor",icon:"format_color_fill",title:"Background Color"},image:{type:"popup",id:"image",icon:"image",title:"Insert Image"},video:{type:"popup",id:"video",icon:"smart_display",title:"Insert YouTube Video"},table:{type:"popup",id:"table",icon:"table_chart",title:"Insert Table"},embed:{type:"popup",id:"embed",icon:"html",title:"Insert Embed"},symbol:{type:"popup",id:"symbol",icon:"emoji_symbols",title:"Insert Symbol"},specialChar:{type:"popup",id:"specialchar",icon:"format_shapes",title:"Special Characters"},lorem:{type:"popup",id:"lorem",icon:"article",title:"Insert Lorem Ipsum"}};u.presets={minimal:[{custom:"toggleSource",label:"Source",title:"Edit HTML Source"},{type:"sep"},{cmd:"bold",icon:"format_bold",title:"Bold"},{cmd:"italic",icon:"format_italic",title:"Italic"},{cmd:"underline",icon:"format_underlined",title:"Underline"},{type:"sep"},{type:"popup",id:"link",icon:"insert_link",title:"Insert Link"},{cmd:"unlink",icon:"link_off",title:"Remove Link"}],writing:[{custom:"toggleSource",label:"Source",title:"Edit HTML Source"},{type:"sep"},{cmd:"undo",icon:"undo",title:"Undo"},{cmd:"redo",icon:"redo",title:"Redo"},{type:"sep"},{type:"blockformat"},{type:"sep"},{cmd:"bold",icon:"format_bold",title:"Bold"},{cmd:"italic",icon:"format_italic",title:"Italic"},{cmd:"underline",icon:"format_underlined",title:"Underline"},{cmd:"strikeThrough",icon:"strikethrough_s",title:"Strikethrough"},{type:"sep"},{cmd:"insertUnorderedList",icon:"format_list_bulleted",title:"Bullet List"},{cmd:"insertOrderedList",icon:"format_list_numbered",title:"Ordered List"},{type:"sep"},{type:"popup",id:"link",icon:"insert_link",title:"Insert Link"},{cmd:"unlink",icon:"link_off",title:"Remove Link"},{type:"sep"},{type:"popup",id:"image",icon:"image",title:"Insert Image"}]};var j=u;return T(H);})();
4
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let a=r.match(/^<\/(\w+)/),l=r.match(/^<(\w+)/),d=r.endsWith("/>"),c=l?l[1].toLowerCase():null,u=a?a[1].toLowerCase():null;u&&!s.has(u)&&(t=Math.max(0,t-1));let h=o.repeat(t)+r;return c&&!d&&!i.has(c)&&!u&&!s.has(c)&&t++,h}).join(`
5
+ `)}_sanitize(e){let t=new DOMParser().parseFromString(e,"text/html");return t.querySelectorAll("script").forEach(o=>o.remove()),t.querySelectorAll("[data-jotter-bookmark]").forEach(o=>o.remove()),t.querySelectorAll("*").forEach(o=>{Array.from(o.attributes).forEach(i=>{(i.name.startsWith("on")||["href","src","action","formaction","data"].includes(i.name)&&/^\s*javascript:/i.test(i.value))&&o.removeAttribute(i.name)})}),t.body.innerHTML}_toggleInlineCode(){let e=window.getSelection();if(!e||e.rangeCount===0)return;let t=e.getRangeAt(0),o=t.commonAncestorContainer;o.nodeType===3&&(o=o.parentNode);let i=o.closest?o.closest("code"):null;if(i){let s=i.parentNode;for(;i.firstChild;)s.insertBefore(i.firstChild,i);s.removeChild(i)}else if(t.collapsed){let s=document.createElement("code");s.innerHTML="&#8203;",t.insertNode(s);let r=document.createRange();r.setStart(s,0),r.setEnd(s,s.childNodes.length),e.removeAllRanges(),e.addRange(r)}else{let s=document.createElement("code");try{t.surroundContents(s)}catch{let a=t.extractContents();s.appendChild(a),t.insertNode(s)}}}_applyFontSize(e){let t=window.getSelection();if(!t||t.rangeCount===0)return;let o=t.getRangeAt(0);if(o.collapsed)return;let i=document.createElement("span");i.style.fontSize=e+"px";try{o.surroundContents(i)}catch{let r=o.extractContents();i.appendChild(r),o.insertNode(i)}}async _pasteFromClipboard(){try{if(navigator.clipboard&&navigator.clipboard.readText){let e=await navigator.clipboard.readText();document.execCommand("insertText",!1,e)}else document.execCommand("paste")}catch{}}_updateToolbarState(){this._toolbarEl.querySelectorAll(".jotter-btn[data-cmd]").forEach(t=>{try{t.classList.toggle("jotter-btn--active",document.queryCommandState(t.dataset.cmd))}catch{}});let e=this._toolbarEl.querySelector('[data-id="blockformat"]');if(e){let t=document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g,""),o=g.find(i=>i.tag===t);o&&(e.value=o.tag)}}_updateStatus(){let e=this._editor.innerText||"",t=e.trim()===""?0:e.trim().split(/\s+/).length,o=e.replace(/\n/g,"").length;this._wordCountEl.textContent=`${t} word${t!==1?"s":""}`,this._charCountEl.textContent=`${o} char${o!==1?"s":""}`}_richHTML(){if(this._bookmarks.size===0)return this._editor.innerHTML;let e=this._editor.cloneNode(!0);return e.querySelectorAll("[data-jotter-bookmark]").forEach(t=>t.remove()),e.innerHTML}_makeMarker(e){let t=document.createElement("span");return t.className="jotter-bookmark",t.dataset.jotterBookmark=e,t}_takeRange(e){let t=e!=null?this._bookmarks.get(e):null;if(!t)return null;if(this._bookmarks.delete(e),t.atStart){let a=document.createRange();return a.setStart(this._editor,0),a.collapse(!0),a}let{start:o,end:i}=t,s=this._editor.contains(o)&&(!i||this._editor.contains(i)),r=null;return s&&(r=document.createRange(),r.setStartAfter(o),i?r.setEndBefore(i):r.collapse(!0)),o.parentNode&&o.remove(),i&&i.parentNode&&i.remove(),r}_saveBookmark(){this._releaseSavedBookmark(),this._savedBookmark=this.saveSelection()}_restoreSavedBookmark(){this.restoreSelection(this._savedBookmark),this._savedBookmark=null,this._editor.focus()}_releaseSavedBookmark(){this.releaseSelection(this._savedBookmark),this._savedBookmark=null}_dropBookmarks(){Array.from(this._bookmarks.keys()).forEach(e=>this.releaseSelection(e)),this._savedBookmark=null}_emit(e,t){(this._listeners[e]||[]).forEach(o=>o(t))}_emitChange(){let e=this.getHTML();this._changeCount++,this._emit("change",e),this._options.onChange&&this._options.onChange(e)}_applyEdit(e){let t=this._changeCount,o=this._richHTML();e(),this._updateStatus(),this._changeCount===t&&this._richHTML()!==o&&this._emitChange()}getHTML(){return this._sourceMode?this._source.value:this._richHTML()}getText(){return this._editor.innerText}isSourceMode(){return this._sourceMode}toggleSource(){return this._toggleSourceMode(),this}clear(){return this._dropBookmarks(),this._editor.innerHTML="",this._updateStatus(),this}focus(){return this._editor.focus(),this}insertHTML(e,t={}){return t.at!=null&&this.restoreSelection(t.at),this._editor.focus(),this._applyEdit(()=>document.execCommand("insertHTML",!1,this._sanitize(e))),this}insertText(e,t={}){return t.at!=null&&this.restoreSelection(t.at),this._editor.focus(),this._applyEdit(()=>document.execCommand("insertText",!1,e)),this}saveSelection(){let e=window.getSelection();if(!e||e.rangeCount===0)return null;let t=e.getRangeAt(0);if(!this._editor.contains(t.startContainer)||!this._editor.contains(t.endContainer))return null;let o="jbm"+ ++this._bmSeq;if(this._editor.childNodes.length===0)return this._bookmarks.set(o,{atStart:!0}),o;let i=this._makeMarker(o),s=t.collapsed?null:this._makeMarker(o);if(s){let l=t.cloneRange();l.collapse(!1),l.insertNode(s)}let r=t.cloneRange();r.collapse(!0),r.insertNode(i),this._bookmarks.set(o,{start:i,end:s});let a=document.createRange();return a.setStartAfter(i),s?a.setEndBefore(s):a.collapse(!0),e.removeAllRanges(),e.addRange(a),o}restoreSelection(e){let t=this._takeRange(e);if(!t)return this;this._sourceMode||this._editor.focus();let o=window.getSelection();return o.removeAllRanges(),o.addRange(t),this}releaseSelection(e){return this._takeRange(e),this}beginExternalUI(){return this._externalDepth===0&&(this._externalBookmark=this.saveSelection()),this._externalDepth++,this}endExternalUI(e={}){if(this._externalDepth===0)return this;if(this._externalDepth===1){let t=this._externalBookmark;this._externalBookmark=null,e.restore===!1?this.releaseSelection(t):this.restoreSelection(t)}return this._externalDepth--,this}setHTML(e){return this._dropBookmarks(),this._editor.innerHTML=this._sanitize(e),this._updateStatus(),this}setTheme(e){let o=C.find(i=>i.id===e)?e:"default";return this._editor.dataset.theme=o,this._options.theme=o,this._themeSelect&&(this._themeSelect.value=o),this}setEnabled(e){return this._editor.contentEditable=String(e),this._root.classList.toggle("jotter--disabled",!e),this}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}off(e,t){return this._listeners[e]&&(this._listeners[e]=this._listeners[e].filter(o=>o!==t)),this}destroy(){let e=this.getHTML();return this._destroyed=!0,this._hidePopup(),this._dropBookmarks(),this._externalBookmark=null,this._externalDepth=0,document.removeEventListener("mousedown",this._onDocMouseDown),document.removeEventListener("keydown",this._onDocKeyDown),this._popup.parentNode&&this._popup.parentNode.removeChild(this._popup),this._target.classList.remove("jotter-host"),this._target.innerHTML=e,this._listeners={},e}};m.toolbar=_.full;m.actions=f;m.presets=_;var B=m;return x(H);})();