jotterjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xavier Follet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
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
@@ -0,0 +1,5 @@
1
+ var JotterJS=(()=>{var _=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var y=(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))!v.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={};y(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:"article",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,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._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,`
2
+ $1
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);})();