jotterjs 0.2.0 → 0.4.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 +110 -1
- package/dist/jotter.iife.min.js +3 -3
- package/dist/jotter.js +522 -209
- package/dist/jotter.min.css +1 -1
- package/dist/jotter.min.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ A lightweight, vanilla JS rich-text editor component built on `contenteditable`.
|
|
|
11
11
|
- Content themes: `default`, `warm`, `ink`, `forest`
|
|
12
12
|
- Pre-built toolbar presets (`minimal`, `writing`, `full`) and fully custom toolbar support
|
|
13
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
|
|
14
16
|
- Event system (`change`, `focus`, `blur`)
|
|
15
17
|
- Simple chainable API
|
|
16
18
|
|
|
@@ -57,6 +59,14 @@ const editor = new JotterJS('#my-editor', {
|
|
|
57
59
|
| `onChange` | `Function` | — | Callback `(html)` fired on every content change |
|
|
58
60
|
| `onFocus` | `Function` | — | Callback fired on editor focus |
|
|
59
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()`.
|
|
60
70
|
|
|
61
71
|
## API
|
|
62
72
|
|
|
@@ -75,9 +85,19 @@ editor.isSourceMode() // → boolean
|
|
|
75
85
|
editor.on(event, fn) // subscribe to 'change' | 'focus' | 'blur'
|
|
76
86
|
editor.off(event, fn) // unsubscribe
|
|
77
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
|
|
78
97
|
```
|
|
79
98
|
|
|
80
|
-
Methods return `this` for chaining (except `getHTML`, `getText`, `
|
|
99
|
+
Methods return `this` for chaining (except `getHTML`, `getText`, `saveSelection`,
|
|
100
|
+
`isSourceMode`, and `destroy`).
|
|
81
101
|
|
|
82
102
|
## Toolbar Presets
|
|
83
103
|
|
|
@@ -132,6 +152,92 @@ new JotterJS('#el', {
|
|
|
132
152
|
});
|
|
133
153
|
```
|
|
134
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
|
+
|
|
135
241
|
## Development
|
|
136
242
|
|
|
137
243
|
```bash
|
|
@@ -139,6 +245,9 @@ npm run dev # start dev server
|
|
|
139
245
|
npm run build # build to dist/
|
|
140
246
|
```
|
|
141
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
|
+
|
|
142
251
|
## License
|
|
143
252
|
|
|
144
253
|
MIT
|
package/dist/jotter.iife.min.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var JotterJS=(()=>{var b=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var y=(u,t)=>{for(var e in t)b(u,e,{get:t[e],enumerable:!0})},S=(u,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of E(t))!T.call(u,i)&&i!==e&&b(u,i,{get:()=>t[i],enumerable:!(o=L(t,i))||o.enumerable});return u};var x=u=>S(b({},"__esModule",{value:!0}),u);var I={};y(I,{JotterJS:()=>h,default:()=>R});var g={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(g).forEach(Object.freeze);Object.freeze(g);var s=g,_={minimal:[s.source,s.sep,s.bold,s.italic,s.underline,s.sep,s.link,s.unlink],writing:[s.source,s.sep,s.undo,s.redo,s.sep,s.blockformat,s.sep,s.bold,s.italic,s.underline,s.strike,s.sep,s.bullets,s.numbered,s.sep,s.link,s.unlink,s.sep,s.image],full:[s.source,s.sep,s.undo,s.redo,s.sep,s.copy,s.cut,s.paste,s.sep,s.clearFormat,s.sep,s.blockformat,s.fontfamily,s.fontsize,s.sep,s.bold,s.italic,s.underline,s.strike,s.subscript,s.superscript,s.code,s.sep,s.foreColor,s.hiliteColor,s.sep,s.alignLeft,s.alignCenter,s.alignRight,s.sep,s.bullets,s.numbered,s.sep,s.link,s.unlink,s.sep,s.image,s.video,s.table,s.embed,s.symbol,s.specialChar,s.lorem,s.sep,s.theme]};Object.values(_).forEach(Object.freeze);Object.freeze(_);var M=_.full,C=[{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"],k=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],j=["\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"],H=["\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"],v=[{id:"default",label:"Default"},{id:"warm",label:"Warm"},{id:"ink",label:"Ink / Navy"},{id:"forest",label:"Forest"}],N=[{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>"}],h=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||M).forEach(e=>{let o=this._buildAction(e);o&&t.appendChild(o)}),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 o=document.createElement("span");o.className="material-icons",o.textContent=t.icon,e.appendChild(o)}return e.addEventListener("mousedown",o=>{if(o.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 i=window.prompt(t.prompt);i&&document.execCommand(t.cmd,!1,i)}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",C.forEach(({label:e,tag:o})=>{let i=document.createElement("option");i.value=o,i.textContent=e,t.appendChild(i)}),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),w.forEach(o=>{let i=document.createElement("option");i.value=o,i.textContent=o,i.style.fontFamily=o,t.appendChild(i)}),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),k.forEach(o=>{let i=document.createElement("option");i.value=o,i.textContent=`${o}px`,t.appendChild(i)}),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",v.forEach(({id:e,label:o})=>{let i=document.createElement("option");i.value=e,i.textContent=o,t.appendChild(i)}),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 o=document.createElement("button");o.type="button",o.className="jotter-btn jotter-color-btn",o.dataset.cmd=t.cmd,o.title=t.title,o.setAttribute("aria-label",t.title);let i=document.createElement("span");i.className="material-icons",i.textContent=t.icon,o.appendChild(i);let n=document.createElement("span");n.className="jotter-color-swatch";let a=t.cmd==="foreColor"?this._lastForeColor:this._lastHiliteColor;n.style.background=a,o.appendChild(n);let l=document.createElement("input");return l.type="color",l.className="jotter-color-input",l.value=a,l.tabIndex=-1,l.addEventListener("change",()=>{let r=l.value;n.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())}),o.addEventListener("mousedown",r=>{r.preventDefault(),this._savedRange=this._saveRange(),l.click()}),e.appendChild(o),e.appendChild(l),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 o=document.createElement("span");return o.className="material-icons",o.textContent=t.icon,e.appendChild(o),e.addEventListener("mousedown",i=>{if(i.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,o){this._popup.innerHTML="",this._popup.appendChild(e),this._popup.dataset.popupId=o,this._popup.classList.add("jotter-popup--visible");let i=t.getBoundingClientRect();this._popup.style.top=i.bottom+6+"px",this._popup.style.left=i.left+"px",this._popup.style.right="auto",requestAnimationFrame(()=>{let n=this._popup.getBoundingClientRect();n.right>window.innerWidth-8&&(this._popup.style.left=Math.max(8,i.left-(n.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 o=10,i=8,n=document.createElement("div");n.className="jotter-table-grid",n.style.gridTemplateColumns=`repeat(${o}, 1fr)`;let a=document.createElement("div");a.className="jotter-popup-hint",a.textContent="Hover to select size";let l=[];for(let r=0;r<i;r++)for(let d=0;d<o;d++){let c=document.createElement("span");c.className="jotter-table-cell",c.dataset.r=r,c.dataset.c=d,c.addEventListener("mouseenter",()=>{a.textContent=`${r+1} \xD7 ${d+1} table`,l.forEach(p=>{p.classList.toggle("jotter-table-cell--active",+p.dataset.r<=r&&+p.dataset.c<=d)})}),c.addEventListener("click",()=>{this._insertTable(r+1,d+1),this._hidePopup()}),l.push(c),n.appendChild(c)}return t.appendChild(n),t.appendChild(a),t}_insertTable(t,e){this._restoreRange(this._savedRange),this._editor.focus();let o="<table><tbody>";for(let i=0;i<t;i++){o+="<tr>";for(let n=0;n<e;n++)o+=i===0?"<th><br></th>":"<td><br></td>";o+="</tr>"}o+="</tbody></table><p><br></p>",document.execCommand("insertHTML",!1,o),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,o="";if(this._savedRange){let d=window.getSelection();if(d&&d.rangeCount){o=d.toString();let c=d.anchorNode;for(;c&&c!==this._editor;){if(c.nodeName==="A"){e=c;break}c=c.parentNode}}}let i=this._makeField(t,"URL","url","https://"),n=this._makeField(t,"Link text (leave blank to keep selection)","text",""),a=this._makeField(t,"Title / tooltip","text",""),l=document.createElement("label");l.className="jotter-popup-label",l.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(([d,c])=>{let p=document.createElement("option");p.value=c,p.textContent=d,r.appendChild(p)}),t.appendChild(l),t.appendChild(r),e?(i.value=e.getAttribute("href")||"",n.value=e.textContent||"",a.value=e.getAttribute("title")||"",r.value=e.getAttribute("target")||""):o&&(n.value=o),t.appendChild(this._makeSubmitBtn(e?"Update Link":"Insert Link",()=>{let d=i.value.trim();if(!d)return;let c=n.value.trim()||o||d,p=a.value.trim(),m=r.value,f=`href="${this._esc(d)}"`;m&&(f+=` target="${this._esc(m)}"`),p&&(f+=` title="${this._esc(p)}"`),this._restoreRange(this._savedRange),this._editor.focus(),e?(e.href=d,m?e.target=m:e.removeAttribute("target"),p?e.title=p:e.removeAttribute("title"),e.textContent=c):document.execCommand("insertHTML",!1,`<a ${f}>${this._esc(c)}</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"),o=this._makeField(t,"Alt text","text","Descriptive text"),i=this._makeField(t,"Width (e.g. 400px or 50%)","text","");return t.appendChild(this._makeSubmitBtn("Insert Image",()=>{let n=e.value.trim();if(!n)return;let a=o.value.trim(),l=i.value.trim(),r=l?`max-width:${l}`:"max-width:100%";this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertHTML",!1,`<img src="${this._esc(n)}" alt="${this._esc(a)}" 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 o=this._ytId(e.value.trim());if(!o){e.classList.add("jotter-input--error");return}e.classList.remove("jotter-input--error");let i=`<div class="jotter-video-wrap"><iframe src="https://www.youtube.com/embed/${o}" frameborder="0" allowfullscreen loading="lazy" title="YouTube video"></iframe></div><p><br></p>`;this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertHTML",!1,i),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 o=t.match(e);if(o)return o[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 o=document.createElement("textarea");return o.className="jotter-popup-textarea",o.placeholder='<iframe src="..." ...></iframe>',o.rows=4,t.appendChild(e),t.appendChild(o),t.appendChild(this._makeSubmitBtn("Insert",()=>{let i=o.value.trim();i&&(this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertHTML",!1,i+"<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(j)),t}_popupSpecialChar(){let t=document.createElement("div");return t.className="jotter-popup-inner",t.appendChild(this._popupTitle("Special Characters")),t.appendChild(this._charGrid(H)),t}_charGrid(t){let e=document.createElement("div");return e.className="jotter-char-grid",t.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",n=>{n.preventDefault(),this._restoreRange(this._savedRange),this._editor.focus(),document.execCommand("insertText",!1,o),this._hidePopup(),this._emit("change",this.getHTML()),this._options.onChange&&this._options.onChange(this.getHTML())}),e.appendChild(i)}),e}_popupLorem(){let t=document.createElement("div");return t.className="jotter-popup-inner",t.appendChild(this._popupTitle("Insert Lorem Ipsum")),N.forEach(e=>{let o=document.createElement("button");o.type="button",o.className="jotter-lorem-btn",o.textContent=e.label,o.addEventListener("mousedown",i=>{i.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(o)}),t}_popupTitle(t){let e=document.createElement("div");return e.className="jotter-popup-title",e.textContent=t,e}_makeField(t,e,o,i){let n=document.createElement("label");n.className="jotter-popup-label",n.textContent=e;let a=document.createElement("input");return a.type=o,a.className="jotter-popup-input",a.placeholder=i,t.appendChild(n),t.appendChild(a),a}_makeSubmitBtn(t,e){let o=document.createElement("button");return o.type="button",o.className="jotter-popup-submit",o.textContent=t,o.addEventListener("click",e),o}_esc(t){return t.replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}_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(),o=null;if(e&&e.rangeCount){let n=e.getRangeAt(0);n.startContainer===t&&(o=n.startOffset)}let i=document.createElement("p");if(t.replaceWith(i),i.appendChild(t),o!==null){let n=document.createRange();n.setStart(t,o),n.collapse(!0),e.removeAllRanges(),e.addRange(n)}}}),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," "))}),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,o=" ",i=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),n=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,""").replace(/</g,"<").replace(/>/g,">")}_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," "))}),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(
|
|
5
|
-
`)}_sanitize(
|
|
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="​",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);})();
|