jotterjs 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ A lightweight, vanilla JS rich-text editor component built on `contenteditable`.
14
14
  - Replaceable insert dialogs — swap the built-in image/link/video/embed popups for your own UI
15
15
  - Selection bookmarks that survive async host UI and DOM mutation
16
16
  - Event system (`change`, `focus`, `blur`)
17
+ - Keyboard-operable toolbar — every control activates with Enter and Space
17
18
  - Simple chainable API
18
19
 
19
20
  ## Installation
@@ -238,6 +239,28 @@ open on top of it. Between `beginExternalUI()` and `endExternalUI()`, `blur` and
238
239
  `focus` are not emitted, so that chain never starts. The `onRequest*` hooks wrap
239
240
  this for you.
240
241
 
242
+ ## Keyboard
243
+
244
+ Every toolbar control is operable without a mouse. Buttons activate on **Enter**
245
+ and **Space**; the selects work as native selects do; the colour buttons open the
246
+ native colour picker. Popups take focus when they are opened from the keyboard,
247
+ and the table picker is a single tab stop — **arrows** size the table, **Enter**
248
+ inserts it. **Escape** closes any popup and hands the caret back to the editor.
249
+
250
+ Activation runs the same code for the pointer and the keyboard, and fires
251
+ exactly once either way. Focus behaves the way each input method expects: a
252
+ mouse click leaves the caret in the editor, while a key press keeps focus on the
253
+ control so you can keep working along the toolbar.
254
+
255
+ > **Upgrading from ≤ 0.4.0:** if your app bridged this gap by listening for
256
+ > Enter/Space on `.jotter-toolbar` and re-dispatching a synthetic `mousedown` at
257
+ > `.jotter-btn`, remove that bridge. The editor now handles the key itself, and
258
+ > a synthetic `mousedown` on top of it would apply the command twice.
259
+
260
+ Two known gaps: `Tab` inside the editing area inserts indentation rather than
261
+ moving focus, so reach the toolbar by tabbing in from before the editor; and the
262
+ symbol and special-character grids are one tab stop per character.
263
+
241
264
  ## Development
242
265
 
243
266
  ```bash
@@ -1,5 +1,5 @@
1
- var JotterJS=(()=>{var b=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var S=(p,e)=>{for(var t in e)b(p,t,{get:e[t],enumerable:!0})},y=(p,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of v(e))!E.call(p,i)&&i!==t&&b(p,i,{get:()=>e[i],enumerable:!(o=k(e,i))||o.enumerable});return p};var x=p=>y(b({},"__esModule",{value:!0}),p);var H={};S(H,{JotterJS:()=>m,default:()=>B});var f={source:{custom:"toggleSource",label:"Source",title:"Edit HTML Source"},sep:{type:"sep"},blockformat:{type:"blockformat"},fontfamily:{type:"fontfamily"},fontsize:{type:"fontsize"},theme:{type:"theme"},undo:{cmd:"undo",icon:"undo",title:"Undo (Ctrl+Z)"},redo:{cmd:"redo",icon:"redo",title:"Redo (Ctrl+Y)"},bold:{cmd:"bold",icon:"format_bold",title:"Bold (Ctrl+B)"},italic:{cmd:"italic",icon:"format_italic",title:"Italic (Ctrl+I)"},underline:{cmd:"underline",icon:"format_underlined",title:"Underline (Ctrl+U)"},strike:{cmd:"strikeThrough",icon:"strikethrough_s",title:"Strikethrough"},subscript:{cmd:"subscript",icon:"subscript",title:"Subscript"},superscript:{cmd:"superscript",icon:"superscript",title:"Superscript"},code:{custom:"code",icon:"code",title:"Inline Code"},copy:{cmd:"copy",icon:"content_copy",title:"Copy"},cut:{cmd:"cut",icon:"content_cut",title:"Cut"},paste:{cmd:"paste",icon:"content_paste",title:"Paste"},clearFormat:{cmd:"removeFormat",icon:"format_clear",title:"Clear Formatting"},alignLeft:{cmd:"justifyLeft",icon:"format_align_left",title:"Align Left"},alignCenter:{cmd:"justifyCenter",icon:"format_align_center",title:"Align Center"},alignRight:{cmd:"justifyRight",icon:"format_align_right",title:"Align Right"},bullets:{cmd:"insertUnorderedList",icon:"format_list_bulleted",title:"Bullet List"},numbered:{cmd:"insertOrderedList",icon:"format_list_numbered",title:"Numbered List"},link:{type:"popup",id:"link",icon:"insert_link",title:"Insert Link"},unlink:{cmd:"unlink",icon:"link_off",title:"Remove Link"},foreColor:{type:"color",cmd:"foreColor",icon:"format_color_text",title:"Text Color"},hiliteColor:{type:"color",cmd:"hiliteColor",icon:"format_color_fill",title:"Background Color"},image:{type:"popup",id:"image",icon:"image",title:"Insert Image"},video:{type:"popup",id:"video",icon:"smart_display",title:"Insert YouTube Video"},table:{type:"popup",id:"table",icon:"table_chart",title:"Insert Table"},embed:{type:"popup",id:"embed",icon:"html",title:"Insert Embed"},symbol:{type:"popup",id:"symbol",icon:"emoji_symbols",title:"Insert Symbol"},specialChar:{type:"popup",id:"specialchar",icon:"format_shapes",title:"Special Characters"},lorem:{type:"popup",id:"lorem",icon:"history_edu",title:"Insert Lorem Ipsum"}};Object.values(f).forEach(Object.freeze);Object.freeze(f);var n=f,_={minimal:[n.source,n.sep,n.bold,n.italic,n.underline,n.sep,n.link,n.unlink],writing:[n.source,n.sep,n.undo,n.redo,n.sep,n.blockformat,n.sep,n.bold,n.italic,n.underline,n.strike,n.sep,n.bullets,n.numbered,n.sep,n.link,n.unlink,n.sep,n.image],full:[n.source,n.sep,n.undo,n.redo,n.sep,n.copy,n.cut,n.paste,n.sep,n.clearFormat,n.sep,n.blockformat,n.fontfamily,n.fontsize,n.sep,n.bold,n.italic,n.underline,n.strike,n.subscript,n.superscript,n.code,n.sep,n.foreColor,n.hiliteColor,n.sep,n.alignLeft,n.alignCenter,n.alignRight,n.sep,n.bullets,n.numbered,n.sep,n.link,n.unlink,n.sep,n.image,n.video,n.table,n.embed,n.symbol,n.specialChar,n.lorem,n.sep,n.theme]};Object.values(_).forEach(Object.freeze);Object.freeze(_);var L=_.full,T={image:"onRequestImage",link:"onRequestLink",video:"onRequestVideo",embed:"onRequestEmbed"},g=[{label:"Paragraph",tag:"p"},{label:"Heading 1",tag:"h1"},{label:"Heading 2",tag:"h2"},{label:"Heading 3",tag:"h3"},{label:"Heading 4",tag:"h4"},{label:"Pre / Code",tag:"pre"},{label:"Blockquote",tag:"blockquote"}],w=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Lucida Console","Palatino Linotype","Tahoma","Times New Roman","Trebuchet MS","Verdana"],j=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],M=["\u2190","\u2192","\u2191","\u2193","\u2194","\u2195","\u21D0","\u21D2","\u21D1","\u21D3","\u21D4","\u2022","\xB7","\u25E6","\u25CB","\u25CF","\u25A1","\u25A0","\u25C6","\u25C7","\u25B2","\u25BC","\u2605","\u2606","\u2660","\u2663","\u2665","\u2666","\u2713","\u2717","\u2715","\u2718","\u2248","\u2260","\u2261","\u2264","\u2265","\xF7","\xD7","\xB1","\u221E","\u221A","\u2211","\u220F","\u222B","\u2202","\u2206","\u2207","\u03C0","\u03A9","\u03BC","\u03B1","\u03B2","\u03B3","\xA9","\xAE","\u2122","\xA7","\xB6","\u2020","\u2021","\xB0","\u2032","\u2033","\u2030","\u201C","\u201D","\u2018","\u2019","\xAB","\xBB","\u2039","\u203A","\u2014","\u2013","\u2026","\xBF","\xA1","\u20AC","\xA3","\xA5","\xA2","\u20B9","\u20BD","\u20BF"],N=["\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6","\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE","\xCF","\xD0","\xD1","\xD2","\xD3","\xD4","\xD5","\xD6","\xD8","\xD9","\xDA","\xDB","\xDC","\xDD","\xDE","\xDF","\xE0","\xE1","\xE2","\xE3","\xE4","\xE5","\xE6","\xE7","\xE8","\xE9","\xEA","\xEB","\xEC","\xED","\xEE","\xEF","\xF0","\xF1","\xF2","\xF3","\xF4","\xF5","\xF6","\xF8","\xF9","\xFA","\xFB","\xFC","\xFD","\xFE","\xFF","\u0152","\u0153","\u0160","\u0161","\u0178","\u017D","\u017E"],C=[{id:"default",label:"Default"},{id:"warm",label:"Warm"},{id:"ink",label:"Ink / Navy"},{id:"forest",label:"Forest"}],I=[{label:"Short \u2014 1 sentence",text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit."},{label:"Medium \u2014 1 paragraph",text:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."},{label:"Long \u2014 3 paragraphs",isHTML:!0,text:"<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p><p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p><p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>"}],m=class{constructor(e,t={}){if(this._target=typeof e=="string"?document.querySelector(e):e,!this._target)throw new Error("[JotterJS] Target element not found.");this._options=Object.assign({placeholder:"Start typing\u2026",height:"320px",theme:"default",onChange:null,onFocus:null,onBlur:null,onRequestImage:null,onRequestLink:null,onRequestVideo:null,onRequestEmbed:null},t),this._listeners={},this._bookmarks=new Map,this._bmSeq=0,this._savedBookmark=null,this._externalBookmark=null,this._externalDepth=0,this._changeCount=0,this._destroyed=!1,this._lastForeColor="#e8e4d8",this._lastHiliteColor="#c8a96e",this._init()}_init(){let e=this._target.innerHTML||"";this._target.innerHTML="",this._target.classList.add("jotter-host"),this._root=document.createElement("div"),this._root.className="htmled",this._toolbar=this._buildToolbar(),this._editorWrap=document.createElement("div"),this._editorWrap.className="jotter-editor-wrap",this._editor=document.createElement("div"),this._editor.className="jotter-editor",this._editor.contentEditable="true",this._editor.setAttribute("data-placeholder",this._options.placeholder),this._editor.style.minHeight=this._options.height,this._editor.innerHTML=this._sanitize(e),this._editor.spellcheck=!0,document.execCommand("defaultParagraphSeparator",!1,"p"),this._source=document.createElement("textarea"),this._source.className="jotter-source",this._source.setAttribute("aria-label","HTML source"),this._source.setAttribute("spellcheck","false"),this._source.style.minHeight=this._options.height,this._sourceMode=!1,this._statusBar=this._buildStatusBar(),this._editorWrap.appendChild(this._editor),this._editorWrap.appendChild(this._source),this._root.appendChild(this._toolbar),this._root.appendChild(this._editorWrap),this._root.appendChild(this._statusBar),this._target.appendChild(this._root),this._popup=this._buildPopupContainer(),document.body.appendChild(this._popup),this._bindEvents(),this._updateToolbarState(),this._updateStatus(),this.setTheme(this._options.theme)}_buildToolbar(){let e=document.createElement("div");return e.className="jotter-toolbar",this._toolbarEl=e,(this._options.toolbar||L).forEach(t=>{let o=this._buildAction(t);o&&e.appendChild(o)}),e}_buildAction(e){if(typeof e.onClick=="function")return this._buildBtn(e);switch(e.type){case"sep":return this._makeSep();case"blockformat":return this._buildBlockFormatSelect();case"fontfamily":return this._buildFontFamilySelect();case"fontsize":return this._buildFontSizeSelect();case"color":return this._buildColorBtn(e);case"popup":return this._buildPopupBtn(e);case"theme":return this._buildThemeSelect();default:return this._buildBtn(e)}}_makeSep(){let e=document.createElement("span");return e.className="jotter-sep",e}_buildBtn(e){let t=document.createElement("button");if(t.type="button",t.className="jotter-btn",e.cmd&&(t.dataset.cmd=e.cmd),e.custom&&(t.dataset.custom=e.custom),t.title=e.title,t.setAttribute("aria-label",e.title),e.label)t.classList.add("jotter-btn--text"),t.appendChild(document.createTextNode(e.label));else{let o=document.createElement("span");o.className="material-icons",o.textContent=e.icon,t.appendChild(o)}return t.addEventListener("mousedown",o=>{o.preventDefault(),this._editor.focus(),this._applyEdit(()=>{if(e.onClick)e.onClick(this);else if(e.custom==="toggleSource")this._toggleSourceMode();else if(e.custom==="code")this._toggleInlineCode();else if(e.cmd==="copy")document.execCommand("copy");else if(e.cmd==="cut")document.execCommand("cut");else if(e.cmd==="paste")this._pasteFromClipboard();else if(e.prompt){let i=window.prompt(e.prompt);i&&document.execCommand(e.cmd,!1,i)}else document.execCommand(e.cmd,!1,null)}),this._updateToolbarState()}),t}_buildBlockFormatSelect(){let e=document.createElement("select");return e.className="jotter-select",e.title="Block format",e.dataset.id="blockformat",g.forEach(({label:t,tag:o})=>{let i=document.createElement("option");i.value=o,i.textContent=t,e.appendChild(i)}),e.addEventListener("mousedown",()=>this._saveBookmark()),e.addEventListener("change",()=>{this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("formatBlock",!1,e.value))}),e}_buildFontFamilySelect(){let e=document.createElement("select");e.className="jotter-select jotter-select--font",e.title="Font family",e.dataset.id="fontfamily";let t=document.createElement("option");return t.value="",t.textContent="Font",e.appendChild(t),w.forEach(o=>{let i=document.createElement("option");i.value=o,i.textContent=o,i.style.fontFamily=o,e.appendChild(i)}),e.addEventListener("mousedown",()=>this._saveBookmark()),e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("fontName",!1,e.value)))}),e}_buildFontSizeSelect(){let e=document.createElement("select");e.className="jotter-select jotter-select--size",e.title="Font size",e.dataset.id="fontsize";let t=document.createElement("option");return t.value="",t.textContent="Size",e.appendChild(t),j.forEach(o=>{let i=document.createElement("option");i.value=o,i.textContent=`${o}px`,e.appendChild(i)}),e.addEventListener("mousedown",()=>this._saveBookmark()),e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>this._applyFontSize(e.value)))}),e}_buildThemeSelect(){let e=document.createElement("select");return e.className="jotter-select jotter-select--theme",e.title="Editor theme",e.dataset.id="theme",C.forEach(({id:t,label:o})=>{let i=document.createElement("option");i.value=t,i.textContent=o,e.appendChild(i)}),e.value=this._options.theme,e.addEventListener("change",()=>this.setTheme(e.value)),this._themeSelect=e,e}_buildColorBtn(e){let t=document.createElement("span");t.className="jotter-color-wrap";let o=document.createElement("button");o.type="button",o.className="jotter-btn jotter-color-btn",o.dataset.cmd=e.cmd,o.title=e.title,o.setAttribute("aria-label",e.title);let i=document.createElement("span");i.className="material-icons",i.textContent=e.icon,o.appendChild(i);let s=document.createElement("span");s.className="jotter-color-swatch";let r=e.cmd==="foreColor"?this._lastForeColor:this._lastHiliteColor;s.style.background=r,o.appendChild(s);let a=document.createElement("input");return a.type="color",a.className="jotter-color-input",a.value=r,a.tabIndex=-1,a.addEventListener("change",()=>{let l=a.value;s.style.background=l,e.cmd==="foreColor"?this._lastForeColor=l:this._lastHiliteColor=l,this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand(e.cmd,!1,l))}),o.addEventListener("mousedown",l=>{l.preventDefault(),this._saveBookmark(),a.click()}),t.appendChild(o),t.appendChild(a),t}_buildPopupBtn(e){let t=document.createElement("button");t.type="button",t.className="jotter-btn",t.title=e.title,t.setAttribute("aria-label",e.title);let o=document.createElement("span");return o.className="material-icons",o.textContent=e.icon,t.appendChild(o),t.addEventListener("mousedown",i=>{i.preventDefault();let s=this._resolverFor(e.id);if(s){this._hidePopup(),this._runResolver(e.id,s);return}if(this._saveBookmark(),this._popupVisible()&&this._popup.dataset.popupId===e.id){this._hidePopup();return}this._showPopup(t,this._buildPopupContent(e.id),e.id)}),t}_buildPopupContainer(){let e=document.createElement("div");return e.className="jotter-popup",e.setAttribute("role","dialog"),e}_showPopup(e,t,o){this._popup.innerHTML="",this._popup.appendChild(t),this._popup.dataset.popupId=o,this._popup.classList.add("jotter-popup--visible");let i=e.getBoundingClientRect();this._popup.style.top=i.bottom+6+"px",this._popup.style.left=i.left+"px",this._popup.style.right="auto",requestAnimationFrame(()=>{let s=this._popup.getBoundingClientRect();s.right>window.innerWidth-8&&(this._popup.style.left=Math.max(8,i.left-(s.right-window.innerWidth+8))+"px")})}_popupVisible(){return this._popup.classList.contains("jotter-popup--visible")}_hidePopup(){this._popup.classList.remove("jotter-popup--visible"),this._popup.dataset.popupId="",this._releaseSavedBookmark()}_buildPopupContent(e){switch(e){case"link":return this._popupLink();case"table":return this._popupTable();case"image":return this._popupImage();case"video":return this._popupVideo();case"embed":return this._popupEmbed();case"symbol":return this._popupSymbol();case"specialchar":return this._popupSpecialChar();case"lorem":return this._popupLorem();default:{let t=document.createElement("div");return t.className="jotter-popup-inner",t.textContent="Unknown: "+e,t}}}_popupTable(){let e=document.createElement("div");e.className="jotter-popup-inner";let t=this._popupTitle("Insert Table");e.appendChild(t);let o=10,i=8,s=document.createElement("div");s.className="jotter-table-grid",s.style.gridTemplateColumns=`repeat(${o}, 1fr)`;let r=document.createElement("div");r.className="jotter-popup-hint",r.textContent="Hover to select size";let a=[];for(let l=0;l<i;l++)for(let d=0;d<o;d++){let c=document.createElement("span");c.className="jotter-table-cell",c.dataset.r=l,c.dataset.c=d,c.addEventListener("mouseenter",()=>{r.textContent=`${l+1} \xD7 ${d+1} table`,a.forEach(u=>{u.classList.toggle("jotter-table-cell--active",+u.dataset.r<=l&&+u.dataset.c<=d)})}),c.addEventListener("click",()=>this._insertTable(l+1,d+1)),a.push(c),s.appendChild(c)}return e.appendChild(s),e.appendChild(r),e}_commitPopup(e){this._restoreSavedBookmark(),this._hidePopup(),this._applyEdit(e)}_insertTable(e,t){let o="<table><tbody>";for(let i=0;i<e;i++){o+="<tr>";for(let s=0;s<t;s++)o+=i===0?"<th><br></th>":"<td><br></td>";o+="</tr>"}o+="</tbody></table><p><br></p>",this._commitPopup(()=>document.execCommand("insertHTML",!1,o))}_popupLink(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Link"));let t=this._anchorInSelection(),o=this._selectedText(),i=this._makeField(e,"URL","url","https://"),s=this._makeField(e,"Link text (leave blank to keep selection)","text",""),r=this._makeField(e,"Title / tooltip","text",""),a=document.createElement("label");a.className="jotter-popup-label",a.textContent="Open in";let l=document.createElement("select");return l.className="jotter-popup-select",[["(same window)",""],["New tab (_blank)","_blank"],["Parent frame (_parent)","_parent"],["Top frame (_top)","_top"]].forEach(([d,c])=>{let u=document.createElement("option");u.value=c,u.textContent=d,l.appendChild(u)}),e.appendChild(a),e.appendChild(l),t?(i.value=t.getAttribute("href")||"",s.value=t.textContent||"",r.value=t.getAttribute("title")||"",l.value=t.getAttribute("target")||""):o&&(s.value=o),e.appendChild(this._makeSubmitBtn(t?"Update Link":"Insert Link",()=>{let d=i.value.trim();if(!d)return;let c=s.value.trim()||o||d,u=r.value.trim(),h=l.value;this._commitPopup(()=>{t?(t.href=d,h?t.target=h:t.removeAttribute("target"),u?t.title=u:t.removeAttribute("title"),t.textContent=c):document.execCommand("insertHTML",!1,this._linkHTML({href:d,text:c,title:u,target:h}))})})),e}_popupImage(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Image"));let t=this._makeField(e,"Image URL","text","https://example.com/image.jpg"),o=this._makeField(e,"Alt text","text","Descriptive text"),i=this._makeField(e,"Width (e.g. 400px or 50%)","text","");return e.appendChild(this._makeSubmitBtn("Insert Image",()=>{let s=t.value.trim();if(!s)return;let r=this._imageHTML({src:s,alt:o.value.trim(),width:i.value.trim()});this._commitPopup(()=>document.execCommand("insertHTML",!1,r))})),e}_popupVideo(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert YouTube Video"));let t=this._makeField(e,"YouTube URL","text","https://www.youtube.com/watch?v=...");return e.appendChild(this._makeSubmitBtn("Embed Video",()=>{let o=this._ytId(t.value.trim());if(!o){t.classList.add("jotter-input--error");return}t.classList.remove("jotter-input--error"),this._commitPopup(()=>document.execCommand("insertHTML",!1,this._videoHTML(o)))})),e}_ytId(e){for(let t of[/[?&]v=([A-Za-z0-9_-]{11})/,/youtu\.be\/([A-Za-z0-9_-]{11})/,/embed\/([A-Za-z0-9_-]{11})/]){let o=e.match(t);if(o)return o[1]}return null}_popupEmbed(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Embed"));let t=document.createElement("label");t.className="jotter-popup-label",t.textContent="Paste HTML / embed code";let o=document.createElement("textarea");return o.className="jotter-popup-textarea",o.placeholder='<iframe src="..." ...></iframe>',o.rows=4,e.appendChild(t),e.appendChild(o),e.appendChild(this._makeSubmitBtn("Insert",()=>{let i=o.value.trim();i&&this._commitPopup(()=>document.execCommand("insertHTML",!1,this._embedHTML(i)))})),e}_popupSymbol(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Symbol")),e.appendChild(this._charGrid(M)),e}_popupSpecialChar(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Special Characters")),e.appendChild(this._charGrid(N)),e}_charGrid(e){let t=document.createElement("div");return t.className="jotter-char-grid",e.forEach(o=>{let i=document.createElement("button");i.type="button",i.className="jotter-char-btn",i.textContent=o,i.title=`U+${o.codePointAt(0).toString(16).toUpperCase().padStart(4,"0")}`,i.addEventListener("mousedown",s=>{s.preventDefault(),this._commitPopup(()=>document.execCommand("insertText",!1,o))}),t.appendChild(i)}),t}_popupLorem(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Lorem Ipsum")),I.forEach(t=>{let o=document.createElement("button");o.type="button",o.className="jotter-lorem-btn",o.textContent=t.label,o.addEventListener("mousedown",i=>{i.preventDefault(),this._commitPopup(()=>document.execCommand(t.isHTML?"insertHTML":"insertText",!1,t.text))}),e.appendChild(o)}),e}_popupTitle(e){let t=document.createElement("div");return t.className="jotter-popup-title",t.textContent=e,t}_makeField(e,t,o,i){let s=document.createElement("label");s.className="jotter-popup-label",s.textContent=t;let r=document.createElement("input");return r.type=o,r.className="jotter-popup-input",r.placeholder=i,e.appendChild(s),e.appendChild(r),r}_makeSubmitBtn(e,t){let o=document.createElement("button");return o.type="button",o.className="jotter-popup-submit",o.textContent=e,o.addEventListener("click",t),o}_esc(e){return String(e).replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_imageHTML({src:e,alt:t,width:o}){let i=o?`max-width:${o}`:"max-width:100%";return`<img src="${this._esc(e)}" alt="${this._esc(t||"")}" style="${this._esc(i)}">`}_linkHTML({href:e,text:t,title:o,target:i}){let s=`href="${this._esc(e)}"`;return i&&(s+=` target="${this._esc(i)}"`),o&&(s+=` title="${this._esc(o)}"`),`<a ${s}>${this._esc(t||e)}</a>`}_videoHTML(e){return/^[A-Za-z0-9_-]{11}$/.test(e)?`<div class="jotter-video-wrap"><iframe src="https://www.youtube.com/embed/${e}" frameborder="0" allowfullscreen loading="lazy" title="YouTube video"></iframe></div><p><br></p>`:null}_embedHTML(e){return e+"<p><br></p>"}_resolverFor(e){let t=T[e],o=t?this._options[t]:null;return typeof o=="function"?o:null}async _runResolver(e,t){let o=this._resolverContext(e);this.beginExternalUI();let i=null;try{i=await t(o)}catch{i=null}if(this._destroyed)return;let s=i==null||i===!1?null:this._resolvedHTML(e,i,o);this.endExternalUI(),s&&(this._applyEdit(()=>document.execCommand("insertHTML",!1,s)),this._updateToolbarState())}_resolverContext(e){switch(e){case"image":return{src:"",alt:"",width:"",selection:this._selectedText()};case"video":return{url:"",selection:this._selectedText()};case"embed":return{html:"",selection:this._selectedText()};case"link":return this._linkContext();default:return{selection:this._selectedText()}}}_linkContext(){let e=this._anchorInSelection();if(!e){let o=this._selectedText();return{href:"",text:o,title:"",target:"",selection:o,isEdit:!1}}let t=window.getSelection();if(t){let o=document.createRange();o.selectNode(e),t.removeAllRanges(),t.addRange(o)}return{href:e.getAttribute("href")||"",text:e.textContent||"",title:e.getAttribute("title")||"",target:e.getAttribute("target")||"",selection:e.textContent||"",isEdit:!0}}_resolvedHTML(e,t,o){switch(e){case"image":{let i=typeof t=="string"?{src:t}:t,s=String(i.src||i.url||"").trim();return s?this._imageHTML({...i,src:s}):null}case"link":{let i=typeof t=="string"?{href:t}:t,s=String(i.href||i.url||"").trim();if(!s)return null;let r=String(i.text!=null?i.text:o.text||"").trim();return this._linkHTML({...i,href:s,text:r})}case"video":{let i=typeof t=="string"?{url:t}:t,s=i.id?String(i.id).trim():this._ytId(String(i.url||"").trim());return s?this._videoHTML(s):null}case"embed":{let i=String(typeof t=="string"?t:t.html||"").trim();return i?this._embedHTML(i):null}default:return null}}_selectedText(){let e=window.getSelection();return!e||!e.rangeCount?"":this._editor.contains(e.getRangeAt(0).commonAncestorContainer)?e.toString():""}_anchorInSelection(){let e=window.getSelection();if(!e||!e.rangeCount)return null;let t=e.anchorNode;if(!t||!this._editor.contains(t))return null;for(;t&&t!==this._editor;){if(t.nodeName==="A")return t;t=t.parentNode}return null}_buildStatusBar(){let e=document.createElement("div");e.className="jotter-status",this._wordCountEl=document.createElement("span"),this._wordCountEl.className="jotter-status-words",this._charCountEl=document.createElement("span"),this._charCountEl.className="jotter-status-chars";let t=document.createElement("span");return t.className="jotter-status-mode",t.textContent="HTML",e.appendChild(this._wordCountEl),e.appendChild(this._charCountEl),e.appendChild(t),e}_bindEvents(){this._editor.addEventListener("input",()=>{this._editor.querySelectorAll(":scope > div").forEach(e=>{if(e.attributes.length>0)return;let t=document.createElement("p");t.innerHTML=e.innerHTML,e.replaceWith(t)}),Array.from(this._editor.childNodes).forEach(e=>{if(e.nodeType===Node.TEXT_NODE&&e.textContent.trim()!==""){let t=window.getSelection(),o=null;if(t&&t.rangeCount){let s=t.getRangeAt(0);s.startContainer===e&&(o=s.startOffset)}let i=document.createElement("p");if(e.replaceWith(i),i.appendChild(e),o!==null){let s=document.createRange();s.setStart(e,o),s.collapse(!0),t.removeAllRanges(),t.addRange(s)}}}),this._updateStatus(),this._emitChange()}),this._editor.addEventListener("keyup",()=>this._updateToolbarState()),this._editor.addEventListener("mouseup",()=>this._updateToolbarState()),this._editor.addEventListener("focusin",e=>{this._root.classList.add("jotter--focused"),!(this._externalDepth>0)&&(this._isInternalTarget(e.relatedTarget)||(this._emit("focus"),this._options.onFocus&&this._options.onFocus()))}),this._editor.addEventListener("focusout",e=>{this._externalDepth>0||this._isInternalTarget(e.relatedTarget)||setTimeout(()=>{this._destroyed||this._externalDepth>0||this._isInternalTarget(document.activeElement)||this._popupVisible()||(this._root.classList.remove("jotter--focused"),this._emit("blur"),this._options.onBlur&&this._options.onBlur())},0)}),this._editor.addEventListener("keydown",e=>{e.key==="Tab"&&(e.preventDefault(),document.execCommand("insertHTML",!1,"&nbsp;&nbsp;&nbsp;&nbsp;"))}),this._onDocMouseDown=e=>{this._popupVisible()&&!this._popup.contains(e.target)&&!this._toolbarEl.contains(e.target)&&this._hidePopup()},document.addEventListener("mousedown",this._onDocMouseDown),this._onDocKeyDown=e=>{e.key==="Escape"&&this._popupVisible()&&(this._restoreSavedBookmark(),this._hidePopup())},document.addEventListener("keydown",this._onDocKeyDown)}_isInternalTarget(e){return!!e&&(this._root.contains(e)||this._popup.contains(e))}_toggleSourceMode(){this._sourceMode=!this._sourceMode,this._sourceMode?(this._dropBookmarks(),this._source.value=this._prettyHTML(this._richHTML()),this._editor.style.display="none",this._source.style.display="block"):(this._editor.innerHTML=this._sanitize(this._source.value),this._source.style.display="none",this._editor.style.display="",this._updateStatus(),this._emitChange()),this._root.classList.toggle("jotter--source-mode",this._sourceMode);let e=this._toolbarEl.querySelector('[data-custom="toggleSource"]');e&&e.classList.toggle("jotter-btn--active",this._sourceMode)}_prettyHTML(e){let t=0,o=" ",i=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),s=new Set(["a","abbr","acronym","b","bdo","big","br","button","cite","code","dfn","em","i","img","input","kbd","label","map","object","output","q","samp","select","small","span","strong","sub","sup","textarea","time","tt","u","var"]);return e.replace(/>\s+</g,"><").replace(/(<[^>]+>)/g,`
1
+ var JotterJS=(()=>{var v=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var M=(h,e)=>{for(var t in e)v(h,t,{get:e[t],enumerable:!0})},w=(h,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of L(e))!T.call(h,o)&&o!==t&&v(h,o,{get:()=>e[o],enumerable:!(i=x(e,o))||i.enumerable});return h};var j=h=>w(v({},"__esModule",{value:!0}),h);var D={};M(D,{JotterJS:()=>m,default:()=>z});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 N=_.full,R={image:"onRequestImage",link:"onRequestLink",video:"onRequestVideo",embed:"onRequestEmbed"},k=[{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"}],A=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Lucida Console","Palatino Linotype","Tahoma","Times New Roman","Trebuchet MS","Verdana"],I=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],B=["\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"],E=[{id:"default",label:"Default"},{id:"warm",label:"Warm"},{id:"ink",label:"Ink / Navy"},{id:"forest",label:"Forest"}],F=[{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._selectedMedia=null,this._resizeState=null,this._onResizeMove=this._onResizeMove.bind(this),this._onResizeEnd=this._onResizeEnd.bind(this),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");try{document.execCommand("enableObjectResizing",!1,!1)}catch{}try{document.execCommand("enableInlineTableEditing",!1,!1)}catch{}this._ensureMediaShields(),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._mediaFrame=this._buildMediaFrame(),this._editorWrap.appendChild(this._editor),this._editorWrap.appendChild(this._source),this._editorWrap.appendChild(this._mediaFrame),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||N).forEach(t=>{let i=this._buildAction(t);i&&e.appendChild(i)}),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}_bindActivation(e,t){e.addEventListener("mousedown",i=>{i.preventDefault(),t(!1)}),e.addEventListener("keydown",i=>{i.key!=="Enter"&&i.key!==" "&&i.key!=="Spacebar"||i.repeat||(i.preventDefault(),t(!0))})}_returnFocus(e,t){t&&this._isInternalTarget(document.activeElement)&&e.focus()}_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 i=document.createElement("span");i.className="material-icons",i.textContent=e.icon,t.appendChild(i)}return this._bindActivation(t,i=>{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 o=window.prompt(e.prompt);o&&document.execCommand(e.cmd,!1,o)}else document.execCommand(e.cmd,!1,null)}),this._updateToolbarState(),this._returnFocus(t,i)}),t}_bindSelectCaret(e){let t={viaKeyboard:!1};return e.addEventListener("mousedown",()=>{t.viaKeyboard=!1,this._saveBookmark()}),e.addEventListener("keydown",i=>{i.key==="Tab"||i.key==="Escape"||(t.viaKeyboard=!0,this._savedBookmark==null&&this._saveBookmark())}),t}_buildBlockFormatSelect(){let e=document.createElement("select");e.className="jotter-select",e.title="Block format",e.dataset.id="blockformat",k.forEach(({label:i,tag:o})=>{let s=document.createElement("option");s.value=o,s.textContent=i,e.appendChild(s)});let t=this._bindSelectCaret(e);return e.addEventListener("change",()=>{this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("formatBlock",!1,e.value)),this._returnFocus(e,t.viaKeyboard)}),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");t.value="",t.textContent="Font",e.appendChild(t),A.forEach(o=>{let s=document.createElement("option");s.value=o,s.textContent=o,s.style.fontFamily=o,e.appendChild(s)});let i=this._bindSelectCaret(e);return e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("fontName",!1,e.value)),this._returnFocus(e,i.viaKeyboard))}),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");t.value="",t.textContent="Size",e.appendChild(t),I.forEach(o=>{let s=document.createElement("option");s.value=o,s.textContent=`${o}px`,e.appendChild(s)});let i=this._bindSelectCaret(e);return e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>this._applyFontSize(e.value)),this._returnFocus(e,i.viaKeyboard))}),e}_buildThemeSelect(){let e=document.createElement("select");return e.className="jotter-select jotter-select--theme",e.title="Editor theme",e.dataset.id="theme",E.forEach(({id:t,label:i})=>{let o=document.createElement("option");o.value=t,o.textContent=i,e.appendChild(o)}),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 i=document.createElement("button");i.type="button",i.className="jotter-btn jotter-color-btn",i.dataset.cmd=e.cmd,i.title=e.title,i.setAttribute("aria-label",e.title);let o=document.createElement("span");o.className="material-icons",o.textContent=e.icon,i.appendChild(o);let s=document.createElement("span");s.className="jotter-color-swatch";let r=e.cmd==="foreColor"?this._lastForeColor:this._lastHiliteColor;s.style.background=r,i.appendChild(s);let a=document.createElement("input");a.type="color",a.className="jotter-color-input",a.value=r,a.tabIndex=-1;let u=!1;return a.addEventListener("change",()=>{let p=a.value;s.style.background=p,e.cmd==="foreColor"?this._lastForeColor=p:this._lastHiliteColor=p,this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand(e.cmd,!1,p)),this._returnFocus(i,u)}),this._bindActivation(i,p=>{u=p,this._saveBookmark(),a.click()}),t.appendChild(i),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 i=document.createElement("span");return i.className="material-icons",i.textContent=e.icon,t.appendChild(i),this._bindActivation(t,o=>{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),o&&this._focusPopup()}),t}_focusPopup(){let e=this._popup.querySelector('input, textarea, select, button, [tabindex]:not([tabindex="-1"])');e&&e.focus()}_buildPopupContainer(){let e=document.createElement("div");return e.className="jotter-popup",e.setAttribute("role","dialog"),e}_showPopup(e,t,i){this._popup.innerHTML="",this._popup.appendChild(t),this._popup.dataset.popupId=i,this._popup.classList.add("jotter-popup--visible");let o=e.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")})}_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 i=10,o=8,s=document.createElement("div");s.className="jotter-table-grid",s.style.gridTemplateColumns=`repeat(${i}, 1fr)`,s.setAttribute("role","grid"),s.setAttribute("aria-label","Table size");let r=document.createElement("div");r.className="jotter-popup-hint",r.textContent="Hover or arrow to select size";let a=[],u=(c,l)=>{r.textContent=`${c+1} \xD7 ${l+1} table`,a.forEach(d=>{d.classList.toggle("jotter-table-cell--active",+d.dataset.r<=c&&+d.dataset.c<=l)})};for(let c=0;c<o;c++)for(let l=0;l<i;l++){let d=document.createElement("span");d.className="jotter-table-cell",d.dataset.r=c,d.dataset.c=l,d.setAttribute("role","gridcell"),d.setAttribute("aria-label",`${c+1} by ${l+1} table`),d.tabIndex=c===0&&l===0?0:-1,d.addEventListener("mouseenter",()=>u(c,l)),d.addEventListener("focus",()=>u(c,l)),d.addEventListener("click",()=>this._insertTable(c+1,l+1)),a.push(d),s.appendChild(d)}let p={ArrowRight:[0,1],ArrowLeft:[0,-1],ArrowDown:[1,0],ArrowUp:[-1,0]};return s.addEventListener("keydown",c=>{let l=a.indexOf(document.activeElement)===-1?null:document.activeElement;if(!l)return;let d=+l.dataset.r,C=+l.dataset.c;if(c.key==="Enter"||c.key===" "||c.key==="Spacebar"){c.preventDefault(),this._insertTable(d+1,C+1);return}let b=p[c.key];if(!b)return;c.preventDefault();let S=Math.min(o-1,Math.max(0,d+b[0])),y=Math.min(i-1,Math.max(0,C+b[1])),g=a[S*i+y];g!==l&&(l.tabIndex=-1,g.tabIndex=0,g.focus())}),e.appendChild(s),e.appendChild(r),e}_commitPopup(e){this._restoreSavedBookmark(),this._hidePopup(),this._applyEdit(e)}_insertTable(e,t){let i="<table><tbody>";for(let o=0;o<e;o++){i+="<tr>";for(let s=0;s<t;s++)i+=o===0?"<th><br></th>":"<td><br></td>";i+="</tr>"}i+="</tbody></table><p><br></p>",this._commitPopup(()=>document.execCommand("insertHTML",!1,i))}_popupLink(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Link"));let t=this._anchorInSelection(),i=this._selectedText(),o=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 u=document.createElement("select");return u.className="jotter-popup-select",[["(same window)",""],["New tab (_blank)","_blank"],["Parent frame (_parent)","_parent"],["Top frame (_top)","_top"]].forEach(([p,c])=>{let l=document.createElement("option");l.value=c,l.textContent=p,u.appendChild(l)}),e.appendChild(a),e.appendChild(u),t?(o.value=t.getAttribute("href")||"",s.value=t.textContent||"",r.value=t.getAttribute("title")||"",u.value=t.getAttribute("target")||""):i&&(s.value=i),e.appendChild(this._makeSubmitBtn(t?"Update Link":"Insert Link",()=>{let p=o.value.trim();if(!p)return;let c=s.value.trim()||i||p,l=r.value.trim(),d=u.value;this._commitPopup(()=>{t?(t.href=p,d?t.target=d:t.removeAttribute("target"),l?t.title=l:t.removeAttribute("title"),t.textContent=c):document.execCommand("insertHTML",!1,this._linkHTML({href:p,text:c,title:l,target:d}))})})),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"),i=this._makeField(e,"Alt text","text","Descriptive text"),o=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:i.value.trim(),width:o.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 i=this._ytId(t.value.trim());if(!i){t.classList.add("jotter-input--error");return}t.classList.remove("jotter-input--error"),this._commitPopup(()=>document.execCommand("insertHTML",!1,this._videoHTML(i)))})),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 i=e.match(t);if(i)return i[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 i=document.createElement("textarea");return i.className="jotter-popup-textarea",i.placeholder='<iframe src="..." ...></iframe>',i.rows=4,e.appendChild(t),e.appendChild(i),e.appendChild(this._makeSubmitBtn("Insert",()=>{let o=i.value.trim();o&&this._commitPopup(()=>document.execCommand("insertHTML",!1,this._embedHTML(o)))})),e}_popupSymbol(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Symbol")),e.appendChild(this._charGrid(B)),e}_popupSpecialChar(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Special Characters")),e.appendChild(this._charGrid(H)),e}_charGrid(e){let t=document.createElement("div");return t.className="jotter-char-grid",e.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")}`,this._bindActivation(o,()=>{this._commitPopup(()=>document.execCommand("insertText",!1,i))}),t.appendChild(o)}),t}_popupLorem(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Lorem Ipsum")),F.forEach(t=>{let i=document.createElement("button");i.type="button",i.className="jotter-lorem-btn",i.textContent=t.label,this._bindActivation(i,()=>{this._commitPopup(()=>document.execCommand(t.isHTML?"insertHTML":"insertText",!1,t.text))}),e.appendChild(i)}),e}_popupTitle(e){let t=document.createElement("div");return t.className="jotter-popup-title",t.textContent=e,t}_makeField(e,t,i,o){let s=document.createElement("label");s.className="jotter-popup-label",s.textContent=t;let r=document.createElement("input");return r.type=i,r.className="jotter-popup-input",r.placeholder=o,e.appendChild(s),e.appendChild(r),r}_makeSubmitBtn(e,t){let i=document.createElement("button");return i.type="button",i.className="jotter-popup-submit",i.textContent=e,i.addEventListener("click",t),i}_esc(e){return String(e).replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_imageHTML({src:e,alt:t,width:i}){let o=i?`max-width:${i}`:"max-width:100%";return`<img src="${this._esc(e)}" alt="${this._esc(t||"")}" style="${this._esc(o)}">`}_linkHTML({href:e,text:t,title:i,target:o}){let s=`href="${this._esc(e)}"`;return o&&(s+=` target="${this._esc(o)}"`),i&&(s+=` title="${this._esc(i)}"`),`<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`<div class="jotter-embed-wrap">${e}</div><p><br></p>`}_resolverFor(e){let t=R[e],i=t?this._options[t]:null;return typeof i=="function"?i:null}async _runResolver(e,t){let i=this._resolverContext(e);this.beginExternalUI();let o=null;try{o=await t(i)}catch{o=null}if(this._destroyed)return;let s=o==null||o===!1?null:this._resolvedHTML(e,o,i);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 i=this._selectedText();return{href:"",text:i,title:"",target:"",selection:i,isEdit:!1}}let t=window.getSelection();if(t){let i=document.createRange();i.selectNode(e),t.removeAllRanges(),t.addRange(i)}return{href:e.getAttribute("href")||"",text:e.textContent||"",title:e.getAttribute("title")||"",target:e.getAttribute("target")||"",selection:e.textContent||"",isEdit:!0}}_resolvedHTML(e,t,i){switch(e){case"image":{let o=typeof t=="string"?{src:t}:t,s=String(o.src||o.url||"").trim();return s?this._imageHTML({...o,src:s}):null}case"link":{let o=typeof t=="string"?{href:t}:t,s=String(o.href||o.url||"").trim();if(!s)return null;let r=String(o.text!=null?o.text:i.text||"").trim();return this._linkHTML({...o,href:s,text:r})}case"video":{let o=typeof t=="string"?{url:t}:t,s=o.id?String(o.id).trim():this._ytId(String(o.url||"").trim());return s?this._videoHTML(s):null}case"embed":{let o=String(typeof t=="string"?t:t.html||"").trim();return o?this._embedHTML(o):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}_buildMediaFrame(){let e=document.createElement("div");return e.className="jotter-media-frame",["nw","ne","sw","se","w","e"].forEach(t=>{let i=document.createElement("div");i.className="jotter-resize-handle jotter-resize-handle--"+t,i.dataset.handle=t,i.tabIndex=-1,e.appendChild(i)}),e.addEventListener("mousedown",t=>{let i=t.target.closest(".jotter-resize-handle");!i||!this._selectedMedia||(t.preventDefault(),this._beginResize(i.dataset.handle,t))}),e}_ensureMediaShields(){this._editor.querySelectorAll(".jotter-video-wrap, .jotter-embed-wrap").forEach(e=>{if(e.querySelector(":scope > .jotter-media-shield"))return;let t=document.createElement("div");t.className="jotter-media-shield",e.appendChild(t)})}_mediaElementFromTarget(e){if(!this._editor.contains(e))return null;let t=e.closest("img");if(t&&this._editor.contains(t))return t;let i=e.closest(".jotter-video-wrap, .jotter-embed-wrap");return i&&this._editor.contains(i)?i:null}_selectMedia(e){if(this._selectedMedia===e)return;this._deselectMedia(),this._selectedMedia=e,this._positionMediaFrame(),this._mediaFrame.classList.add("jotter-media-frame--visible"),this._editor.focus({preventScroll:!0});let t=document.createRange();t.setStartBefore(e),t.collapse(!0);let i=window.getSelection();i.removeAllRanges(),i.addRange(t),this._updateToolbarState()}_deselectMedia(){this._resizeState&&this._abortResize(),this._selectedMedia&&(this._selectedMedia=null,this._mediaFrame.classList.remove("jotter-media-frame--visible"))}_positionMediaFrame(){let e=this._selectedMedia;if(!e)return;let t=this._editorWrap.getBoundingClientRect(),i=e.getBoundingClientRect();this._mediaFrame.style.top=i.top-t.top+this._editorWrap.scrollTop+"px",this._mediaFrame.style.left=i.left-t.left+this._editorWrap.scrollLeft+"px",this._mediaFrame.style.width=i.width+"px",this._mediaFrame.style.height=i.height+"px"}_deleteSelectedMedia(){let e=this._selectedMedia;if(!e)return;this._deselectMedia();let t=document.createRange();t.selectNode(e);let i=window.getSelection();i.removeAllRanges(),i.addRange(t),this._applyEdit(()=>document.execCommand("delete",!1,null)),this._updateToolbarState()}_beginResize(e,t){let i=this._selectedMedia,o=i.getBoundingClientRect(),s=e.includes("w")?-1:1;this._resizeState={el:i,sign:s,startX:t.clientX,startWidth:o.width,startInlineWidth:i.style.width,before:this._richHTML()},document.addEventListener("mousemove",this._onResizeMove),document.addEventListener("mouseup",this._onResizeEnd,{once:!0})}_onResizeMove(e){let t=this._resizeState;if(!t)return;let i=(e.clientX-t.startX)*t.sign,o=40,s=Math.max(o,this._editor.clientWidth),r=Math.max(o,Math.min(s,Math.round(t.startWidth+i)));t.el.style.width=r+"px",t.el.style.removeProperty("max-width"),this._positionMediaFrame()}_onResizeEnd(){document.removeEventListener("mousemove",this._onResizeMove);let e=this._resizeState;this._resizeState=null,e&&(this._updateStatus(),this._richHTML()!==e.before&&this._emitChange())}_abortResize(){let e=this._resizeState;e&&(document.removeEventListener("mousemove",this._onResizeMove),document.removeEventListener("mouseup",this._onResizeEnd),e.el.style.width=e.startInlineWidth,this._resizeState=null,this._positionMediaFrame())}_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(),i=null;if(t&&t.rangeCount){let s=t.getRangeAt(0);s.startContainer===e&&(i=s.startOffset)}let o=document.createElement("p");if(e.replaceWith(o),o.appendChild(e),i!==null){let s=document.createRange();s.setStart(e,i),s.collapse(!0),t.removeAllRanges(),t.addRange(s)}}}),this._ensureMediaShields(),this._selectedMedia&&this._positionMediaFrame(),this._updateStatus(),this._emitChange()}),this._editor.addEventListener("keyup",()=>this._updateToolbarState()),this._editor.addEventListener("mouseup",()=>this._updateToolbarState()),this._editor.addEventListener("mousedown",e=>{let t=this._mediaElementFromTarget(e.target);t&&(e.preventDefault(),this._selectMedia(t))}),this._onWindowResize=()=>{this._selectedMedia&&this._positionMediaFrame()},window.addEventListener("resize",this._onWindowResize),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._deselectMedia(),!(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=>{if((e.key==="Backspace"||e.key==="Delete")&&this._selectedMedia){e.preventDefault(),this._deleteSelectedMedia();return}e.key==="Tab"&&(e.preventDefault(),document.execCommand("insertHTML",!1,"&nbsp;&nbsp;&nbsp;&nbsp;"))}),this._onDocMouseDown=e=>{this._popupVisible()&&!this._popup.contains(e.target)&&!this._toolbarEl.contains(e.target)&&this._hidePopup(),this._selectedMedia&&!this._mediaFrame.contains(e.target)&&e.target!==this._selectedMedia&&!this._selectedMedia.contains(e.target)&&this._deselectMedia()},document.addEventListener("mousedown",this._onDocMouseDown),this._onDocKeyDown=e=>{if(e.key==="Escape"){if(this._resizeState){this._abortResize();return}if(this._selectedMedia){this._deselectMedia();return}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._deselectMedia(),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._ensureMediaShields(),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,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 e.replace(/>\s+</g,"><").replace(/(<[^>]+>)/g,`
2
2
  $1
3
3
  `).split(`
4
- `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let a=r.match(/^<\/(\w+)/),l=r.match(/^<(\w+)/),d=r.endsWith("/>"),c=l?l[1].toLowerCase():null,u=a?a[1].toLowerCase():null;u&&!s.has(u)&&(t=Math.max(0,t-1));let h=o.repeat(t)+r;return c&&!d&&!i.has(c)&&!u&&!s.has(c)&&t++,h}).join(`
5
- `)}_sanitize(e){let t=new DOMParser().parseFromString(e,"text/html");return t.querySelectorAll("script").forEach(o=>o.remove()),t.querySelectorAll("[data-jotter-bookmark]").forEach(o=>o.remove()),t.querySelectorAll("*").forEach(o=>{Array.from(o.attributes).forEach(i=>{(i.name.startsWith("on")||["href","src","action","formaction","data"].includes(i.name)&&/^\s*javascript:/i.test(i.value))&&o.removeAttribute(i.name)})}),t.body.innerHTML}_toggleInlineCode(){let e=window.getSelection();if(!e||e.rangeCount===0)return;let t=e.getRangeAt(0),o=t.commonAncestorContainer;o.nodeType===3&&(o=o.parentNode);let i=o.closest?o.closest("code"):null;if(i){let s=i.parentNode;for(;i.firstChild;)s.insertBefore(i.firstChild,i);s.removeChild(i)}else if(t.collapsed){let s=document.createElement("code");s.innerHTML="&#8203;",t.insertNode(s);let r=document.createRange();r.setStart(s,0),r.setEnd(s,s.childNodes.length),e.removeAllRanges(),e.addRange(r)}else{let s=document.createElement("code");try{t.surroundContents(s)}catch{let a=t.extractContents();s.appendChild(a),t.insertNode(s)}}}_applyFontSize(e){let t=window.getSelection();if(!t||t.rangeCount===0)return;let o=t.getRangeAt(0);if(o.collapsed)return;let i=document.createElement("span");i.style.fontSize=e+"px";try{o.surroundContents(i)}catch{let r=o.extractContents();i.appendChild(r),o.insertNode(i)}}async _pasteFromClipboard(){try{if(navigator.clipboard&&navigator.clipboard.readText){let e=await navigator.clipboard.readText();document.execCommand("insertText",!1,e)}else document.execCommand("paste")}catch{}}_updateToolbarState(){this._toolbarEl.querySelectorAll(".jotter-btn[data-cmd]").forEach(t=>{try{t.classList.toggle("jotter-btn--active",document.queryCommandState(t.dataset.cmd))}catch{}});let e=this._toolbarEl.querySelector('[data-id="blockformat"]');if(e){let t=document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g,""),o=g.find(i=>i.tag===t);o&&(e.value=o.tag)}}_updateStatus(){let e=this._editor.innerText||"",t=e.trim()===""?0:e.trim().split(/\s+/).length,o=e.replace(/\n/g,"").length;this._wordCountEl.textContent=`${t} word${t!==1?"s":""}`,this._charCountEl.textContent=`${o} char${o!==1?"s":""}`}_richHTML(){if(this._bookmarks.size===0)return this._editor.innerHTML;let e=this._editor.cloneNode(!0);return e.querySelectorAll("[data-jotter-bookmark]").forEach(t=>t.remove()),e.innerHTML}_makeMarker(e){let t=document.createElement("span");return t.className="jotter-bookmark",t.dataset.jotterBookmark=e,t}_takeRange(e){let t=e!=null?this._bookmarks.get(e):null;if(!t)return null;if(this._bookmarks.delete(e),t.atStart){let a=document.createRange();return a.setStart(this._editor,0),a.collapse(!0),a}let{start:o,end:i}=t,s=this._editor.contains(o)&&(!i||this._editor.contains(i)),r=null;return s&&(r=document.createRange(),r.setStartAfter(o),i?r.setEndBefore(i):r.collapse(!0)),o.parentNode&&o.remove(),i&&i.parentNode&&i.remove(),r}_saveBookmark(){this._releaseSavedBookmark(),this._savedBookmark=this.saveSelection()}_restoreSavedBookmark(){this.restoreSelection(this._savedBookmark),this._savedBookmark=null,this._editor.focus()}_releaseSavedBookmark(){this.releaseSelection(this._savedBookmark),this._savedBookmark=null}_dropBookmarks(){Array.from(this._bookmarks.keys()).forEach(e=>this.releaseSelection(e)),this._savedBookmark=null}_emit(e,t){(this._listeners[e]||[]).forEach(o=>o(t))}_emitChange(){let e=this.getHTML();this._changeCount++,this._emit("change",e),this._options.onChange&&this._options.onChange(e)}_applyEdit(e){let t=this._changeCount,o=this._richHTML();e(),this._updateStatus(),this._changeCount===t&&this._richHTML()!==o&&this._emitChange()}getHTML(){return this._sourceMode?this._source.value:this._richHTML()}getText(){return this._editor.innerText}isSourceMode(){return this._sourceMode}toggleSource(){return this._toggleSourceMode(),this}clear(){return this._dropBookmarks(),this._editor.innerHTML="",this._updateStatus(),this}focus(){return this._editor.focus(),this}insertHTML(e,t={}){return t.at!=null&&this.restoreSelection(t.at),this._editor.focus(),this._applyEdit(()=>document.execCommand("insertHTML",!1,this._sanitize(e))),this}insertText(e,t={}){return t.at!=null&&this.restoreSelection(t.at),this._editor.focus(),this._applyEdit(()=>document.execCommand("insertText",!1,e)),this}saveSelection(){let e=window.getSelection();if(!e||e.rangeCount===0)return null;let t=e.getRangeAt(0);if(!this._editor.contains(t.startContainer)||!this._editor.contains(t.endContainer))return null;let o="jbm"+ ++this._bmSeq;if(this._editor.childNodes.length===0)return this._bookmarks.set(o,{atStart:!0}),o;let i=this._makeMarker(o),s=t.collapsed?null:this._makeMarker(o);if(s){let l=t.cloneRange();l.collapse(!1),l.insertNode(s)}let r=t.cloneRange();r.collapse(!0),r.insertNode(i),this._bookmarks.set(o,{start:i,end:s});let a=document.createRange();return a.setStartAfter(i),s?a.setEndBefore(s):a.collapse(!0),e.removeAllRanges(),e.addRange(a),o}restoreSelection(e){let t=this._takeRange(e);if(!t)return this;this._sourceMode||this._editor.focus();let o=window.getSelection();return o.removeAllRanges(),o.addRange(t),this}releaseSelection(e){return this._takeRange(e),this}beginExternalUI(){return this._externalDepth===0&&(this._externalBookmark=this.saveSelection()),this._externalDepth++,this}endExternalUI(e={}){if(this._externalDepth===0)return this;if(this._externalDepth===1){let t=this._externalBookmark;this._externalBookmark=null,e.restore===!1?this.releaseSelection(t):this.restoreSelection(t)}return this._externalDepth--,this}setHTML(e){return this._dropBookmarks(),this._editor.innerHTML=this._sanitize(e),this._updateStatus(),this}setTheme(e){let o=C.find(i=>i.id===e)?e:"default";return this._editor.dataset.theme=o,this._options.theme=o,this._themeSelect&&(this._themeSelect.value=o),this}setEnabled(e){return this._editor.contentEditable=String(e),this._root.classList.toggle("jotter--disabled",!e),this}on(e,t){return this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push(t),this}off(e,t){return this._listeners[e]&&(this._listeners[e]=this._listeners[e].filter(o=>o!==t)),this}destroy(){let e=this.getHTML();return this._destroyed=!0,this._hidePopup(),this._dropBookmarks(),this._externalBookmark=null,this._externalDepth=0,document.removeEventListener("mousedown",this._onDocMouseDown),document.removeEventListener("keydown",this._onDocKeyDown),this._popup.parentNode&&this._popup.parentNode.removeChild(this._popup),this._target.classList.remove("jotter-host"),this._target.innerHTML=e,this._listeners={},e}};m.toolbar=_.full;m.actions=f;m.presets=_;var B=m;return x(H);})();
4
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let a=r.match(/^<\/(\w+)/),u=r.match(/^<(\w+)/),p=r.endsWith("/>"),c=u?u[1].toLowerCase():null,l=a?a[1].toLowerCase():null;l&&!s.has(l)&&(t=Math.max(0,t-1));let d=i.repeat(t)+r;return c&&!p&&!o.has(c)&&!l&&!s.has(c)&&t++,d}).join(`
5
+ `)}_sanitize(e){let t=new DOMParser().parseFromString(e,"text/html");return t.querySelectorAll("script").forEach(i=>i.remove()),t.querySelectorAll("[data-jotter-bookmark], .jotter-media-shield").forEach(i=>i.remove()),t.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)})}),t.body.innerHTML}_toggleInlineCode(){let e=window.getSelection();if(!e||e.rangeCount===0)return;let t=e.getRangeAt(0),i=t.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(t.collapsed){let s=document.createElement("code");s.innerHTML="&#8203;",t.insertNode(s);let r=document.createRange();r.setStart(s,0),r.setEnd(s,s.childNodes.length),e.removeAllRanges(),e.addRange(r)}else{let s=document.createElement("code");try{t.surroundContents(s)}catch{let a=t.extractContents();s.appendChild(a),t.insertNode(s)}}}_applyFontSize(e){let t=window.getSelection();if(!t||t.rangeCount===0)return;let i=t.getRangeAt(0);if(i.collapsed)return;let o=document.createElement("span");o.style.fontSize=e+"px";try{i.surroundContents(o)}catch{let r=i.extractContents();o.appendChild(r),i.insertNode(o)}}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,""),i=k.find(o=>o.tag===t);i&&(e.value=i.tag)}}_updateStatus(){let e=this._editor.innerText||"",t=e.trim()===""?0:e.trim().split(/\s+/).length,i=e.replace(/\n/g,"").length;this._wordCountEl.textContent=`${t} word${t!==1?"s":""}`,this._charCountEl.textContent=`${i} char${i!==1?"s":""}`}_richHTML(){let e=this._editor.cloneNode(!0);return e.querySelectorAll("[data-jotter-bookmark], .jotter-media-shield").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:i,end:o}=t,s=this._editor.contains(i)&&(!o||this._editor.contains(o)),r=null;return s&&(r=document.createRange(),r.setStartAfter(i),o?r.setEndBefore(o):r.collapse(!0)),i.parentNode&&i.remove(),o&&o.parentNode&&o.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(i=>i(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,i=this._richHTML();e(),this._updateStatus(),this._changeCount===t&&this._richHTML()!==i&&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._deselectMedia(),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 i="jbm"+ ++this._bmSeq;if(this._editor.childNodes.length===0)return this._bookmarks.set(i,{atStart:!0}),i;let o=this._makeMarker(i),s=t.collapsed?null:this._makeMarker(i);if(s){let u=t.cloneRange();u.collapse(!1),u.insertNode(s)}let r=t.cloneRange();r.collapse(!0),r.insertNode(o),this._bookmarks.set(i,{start:o,end:s});let a=document.createRange();return a.setStartAfter(o),s?a.setEndBefore(s):a.collapse(!0),e.removeAllRanges(),e.addRange(a),i}restoreSelection(e){let t=this._takeRange(e);if(!t)return this;this._sourceMode||this._editor.focus();let i=window.getSelection();return i.removeAllRanges(),i.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._deselectMedia(),this._dropBookmarks(),this._editor.innerHTML=this._sanitize(e),this._ensureMediaShields(),this._updateStatus(),this}setTheme(e){let i=E.find(o=>o.id===e)?e:"default";return this._editor.dataset.theme=i,this._options.theme=i,this._themeSelect&&(this._themeSelect.value=i),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(i=>i!==t)),this}destroy(){let e=this.getHTML();return this._destroyed=!0,this._deselectMedia(),this._hidePopup(),this._dropBookmarks(),this._externalBookmark=null,this._externalDepth=0,document.removeEventListener("mousedown",this._onDocMouseDown),document.removeEventListener("keydown",this._onDocKeyDown),window.removeEventListener("resize",this._onWindowResize),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 z=m;return j(D);})();
package/dist/jotter.js CHANGED
@@ -358,6 +358,10 @@ var JotterJS = class {
358
358
  this._destroyed = false;
359
359
  this._lastForeColor = "#e8e4d8";
360
360
  this._lastHiliteColor = "#c8a96e";
361
+ this._selectedMedia = null;
362
+ this._resizeState = null;
363
+ this._onResizeMove = this._onResizeMove.bind(this);
364
+ this._onResizeEnd = this._onResizeEnd.bind(this);
361
365
  this._init();
362
366
  }
363
367
  // ─── Init ─────────────────────────────────────────────────────────────────
@@ -381,6 +385,15 @@ var JotterJS = class {
381
385
  this._editor.innerHTML = this._sanitize(initialHTML);
382
386
  this._editor.spellcheck = true;
383
387
  document.execCommand("defaultParagraphSeparator", false, "p");
388
+ try {
389
+ document.execCommand("enableObjectResizing", false, false);
390
+ } catch (_) {
391
+ }
392
+ try {
393
+ document.execCommand("enableInlineTableEditing", false, false);
394
+ } catch (_) {
395
+ }
396
+ this._ensureMediaShields();
384
397
  this._source = document.createElement("textarea");
385
398
  this._source.className = "jotter-source";
386
399
  this._source.setAttribute("aria-label", "HTML source");
@@ -388,8 +401,10 @@ var JotterJS = class {
388
401
  this._source.style.minHeight = this._options.height;
389
402
  this._sourceMode = false;
390
403
  this._statusBar = this._buildStatusBar();
404
+ this._mediaFrame = this._buildMediaFrame();
391
405
  this._editorWrap.appendChild(this._editor);
392
406
  this._editorWrap.appendChild(this._source);
407
+ this._editorWrap.appendChild(this._mediaFrame);
393
408
  this._root.appendChild(this._toolbar);
394
409
  this._root.appendChild(this._editorWrap);
395
410
  this._root.appendChild(this._statusBar);
@@ -446,6 +461,55 @@ var JotterJS = class {
446
461
  s.className = "jotter-sep";
447
462
  return s;
448
463
  }
464
+ // ─── Activation ───────────────────────────────────────────────────────────
465
+ // Every toolbar control activates through _bindActivation, so the mouse and
466
+ // the keyboard always run the same body. Binding them separately is what let
467
+ // the keyboard rot: the pointer path grew a selection save/restore dance the
468
+ // key path never had, and a focused button did nothing at all.
469
+ /**
470
+ * Binds a control's activation to the pointer *and* the keyboard.
471
+ *
472
+ * The pointer path has to run on `mousedown` with the default prevented —
473
+ * that is what keeps the caret inside the contenteditable instead of letting
474
+ * focus jump to the button before the command runs. A focused button has no
475
+ * such problem, so the key path just calls the same `run()`.
476
+ *
477
+ * preventDefault() on the keydown does double duty: Space no longer scrolls
478
+ * the page, and the browser no longer synthesises the click it derives from a
479
+ * key press — so one activation stays one activation, here and for anything
480
+ * else listening further down. Auto-repeat is dropped for the same reason.
481
+ *
482
+ * @param {Element} el
483
+ * @param {Function} run Receives true when the activation came from the keyboard.
484
+ */
485
+ _bindActivation(el, run) {
486
+ el.addEventListener("mousedown", (e) => {
487
+ e.preventDefault();
488
+ run(false);
489
+ });
490
+ el.addEventListener("keydown", (e) => {
491
+ if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar")
492
+ return;
493
+ if (e.repeat)
494
+ return;
495
+ e.preventDefault();
496
+ run(true);
497
+ });
498
+ }
499
+ /**
500
+ * Hands focus back to a control after a keyboard activation, so the user
501
+ * keeps their place on the toolbar instead of being dropped into the editor
502
+ * on every keystroke. The pointer path never moved focus off the editor in
503
+ * the first place, so it is left alone.
504
+ *
505
+ * Skipped when something outside the editor's own chrome has taken focus —
506
+ * a host modal opened from an `onClick`, say — since stealing it back would
507
+ * break that UI.
508
+ */
509
+ _returnFocus(el, viaKeyboard) {
510
+ if (viaKeyboard && this._isInternalTarget(document.activeElement))
511
+ el.focus();
512
+ }
449
513
  /**
450
514
  * General button builder. Priority order for click handling:
451
515
  * 1. action.onClick(editor) — external callback
@@ -472,8 +536,7 @@ var JotterJS = class {
472
536
  icon.textContent = action.icon;
473
537
  btn.appendChild(icon);
474
538
  }
475
- btn.addEventListener("mousedown", (e) => {
476
- e.preventDefault();
539
+ this._bindActivation(btn, (viaKeyboard) => {
477
540
  this._editor.focus();
478
541
  this._applyEdit(() => {
479
542
  if (action.onClick) {
@@ -497,9 +560,35 @@ var JotterJS = class {
497
560
  }
498
561
  });
499
562
  this._updateToolbarState();
563
+ this._returnFocus(btn, viaKeyboard);
500
564
  });
501
565
  return btn;
502
566
  }
567
+ /**
568
+ * Keeps the caret bookmarked for a select, whichever way the user drives it.
569
+ * By mouse the bookmark has to be taken on `mousedown`, before focus leaves
570
+ * the editable; by keyboard the value can change on the very first arrow
571
+ * press, so the keydown has to take one too.
572
+ *
573
+ * Returns a state object the `change` handler reads to decide where focus
574
+ * belongs afterwards: back on the select for a keyboard user still walking
575
+ * the options, in the editor for a mouse user who has finished picking.
576
+ */
577
+ _bindSelectCaret(sel) {
578
+ const state = { viaKeyboard: false };
579
+ sel.addEventListener("mousedown", () => {
580
+ state.viaKeyboard = false;
581
+ this._saveBookmark();
582
+ });
583
+ sel.addEventListener("keydown", (e) => {
584
+ if (e.key === "Tab" || e.key === "Escape")
585
+ return;
586
+ state.viaKeyboard = true;
587
+ if (this._savedBookmark == null)
588
+ this._saveBookmark();
589
+ });
590
+ return state;
591
+ }
503
592
  _buildBlockFormatSelect() {
504
593
  const sel = document.createElement("select");
505
594
  sel.className = "jotter-select";
@@ -511,10 +600,11 @@ var JotterJS = class {
511
600
  opt.textContent = label;
512
601
  sel.appendChild(opt);
513
602
  });
514
- sel.addEventListener("mousedown", () => this._saveBookmark());
603
+ const caret = this._bindSelectCaret(sel);
515
604
  sel.addEventListener("change", () => {
516
605
  this._restoreSavedBookmark();
517
606
  this._applyEdit(() => document.execCommand("formatBlock", false, sel.value));
607
+ this._returnFocus(sel, caret.viaKeyboard);
518
608
  });
519
609
  return sel;
520
610
  }
@@ -534,12 +624,13 @@ var JotterJS = class {
534
624
  opt.style.fontFamily = f;
535
625
  sel.appendChild(opt);
536
626
  });
537
- sel.addEventListener("mousedown", () => this._saveBookmark());
627
+ const caret = this._bindSelectCaret(sel);
538
628
  sel.addEventListener("change", () => {
539
629
  if (!sel.value)
540
630
  return;
541
631
  this._restoreSavedBookmark();
542
632
  this._applyEdit(() => document.execCommand("fontName", false, sel.value));
633
+ this._returnFocus(sel, caret.viaKeyboard);
543
634
  });
544
635
  return sel;
545
636
  }
@@ -558,12 +649,13 @@ var JotterJS = class {
558
649
  opt.textContent = `${s}px`;
559
650
  sel.appendChild(opt);
560
651
  });
561
- sel.addEventListener("mousedown", () => this._saveBookmark());
652
+ const caret = this._bindSelectCaret(sel);
562
653
  sel.addEventListener("change", () => {
563
654
  if (!sel.value)
564
655
  return;
565
656
  this._restoreSavedBookmark();
566
657
  this._applyEdit(() => this._applyFontSize(sel.value));
658
+ this._returnFocus(sel, caret.viaKeyboard);
567
659
  });
568
660
  return sel;
569
661
  }
@@ -606,6 +698,7 @@ var JotterJS = class {
606
698
  input.className = "jotter-color-input";
607
699
  input.value = defaultColor;
608
700
  input.tabIndex = -1;
701
+ let viaKeyboard = false;
609
702
  input.addEventListener("change", () => {
610
703
  const color = input.value;
611
704
  swatch.style.background = color;
@@ -615,9 +708,10 @@ var JotterJS = class {
615
708
  this._lastHiliteColor = color;
616
709
  this._restoreSavedBookmark();
617
710
  this._applyEdit(() => document.execCommand(action.cmd, false, color));
711
+ this._returnFocus(btn, viaKeyboard);
618
712
  });
619
- btn.addEventListener("mousedown", (e) => {
620
- e.preventDefault();
713
+ this._bindActivation(btn, (fromKeyboard) => {
714
+ viaKeyboard = fromKeyboard;
621
715
  this._saveBookmark();
622
716
  input.click();
623
717
  });
@@ -641,8 +735,7 @@ var JotterJS = class {
641
735
  icon.className = "material-icons";
642
736
  icon.textContent = action.icon;
643
737
  btn.appendChild(icon);
644
- btn.addEventListener("mousedown", (e) => {
645
- e.preventDefault();
738
+ this._bindActivation(btn, (viaKeyboard) => {
646
739
  const resolver = this._resolverFor(action.id);
647
740
  if (resolver) {
648
741
  this._hidePopup();
@@ -655,9 +748,24 @@ var JotterJS = class {
655
748
  return;
656
749
  }
657
750
  this._showPopup(btn, this._buildPopupContent(action.id), action.id);
751
+ if (viaKeyboard)
752
+ this._focusPopup();
658
753
  });
659
754
  return btn;
660
755
  }
756
+ /**
757
+ * Moves focus into a popup that was opened from the keyboard. The popup lives
758
+ * on document.body, a whole document away from the button that opened it, so
759
+ * without this the fields the user just asked for would be unreachable
760
+ * without tabbing through the rest of the page.
761
+ */
762
+ _focusPopup() {
763
+ const el = this._popup.querySelector(
764
+ 'input, textarea, select, button, [tabindex]:not([tabindex="-1"])'
765
+ );
766
+ if (el)
767
+ el.focus();
768
+ }
661
769
  _buildPopupContainer() {
662
770
  const el = document.createElement("div");
663
771
  el.className = "jotter-popup";
@@ -716,7 +824,12 @@ var JotterJS = class {
716
824
  }
717
825
  }
718
826
  }
719
- /** Hover-to-select grid (up to 8×10). Click inserts <table> with <th> header row. */
827
+ /**
828
+ * Size picker (up to 8×10) inserting a <table> with a <th> header row.
829
+ * Pointer: hover sizes it, click inserts. Keyboard: the grid is a single tab
830
+ * stop (roving tabindex), arrows size it and Enter/Space inserts — 80 tab
831
+ * stops would be worse than no keyboard support at all.
832
+ */
720
833
  _popupTable() {
721
834
  const wrap = document.createElement("div");
722
835
  wrap.className = "jotter-popup-inner";
@@ -726,30 +839,61 @@ var JotterJS = class {
726
839
  const grid = document.createElement("div");
727
840
  grid.className = "jotter-table-grid";
728
841
  grid.style.gridTemplateColumns = `repeat(${COLS}, 1fr)`;
842
+ grid.setAttribute("role", "grid");
843
+ grid.setAttribute("aria-label", "Table size");
729
844
  const hint = document.createElement("div");
730
845
  hint.className = "jotter-popup-hint";
731
- hint.textContent = "Hover to select size";
846
+ hint.textContent = "Hover or arrow to select size";
732
847
  const cells = [];
848
+ const preview = (r, c) => {
849
+ hint.textContent = `${r + 1} \xD7 ${c + 1} table`;
850
+ cells.forEach((cl) => {
851
+ cl.classList.toggle(
852
+ "jotter-table-cell--active",
853
+ +cl.dataset.r <= r && +cl.dataset.c <= c
854
+ );
855
+ });
856
+ };
733
857
  for (let r = 0; r < ROWS; r++) {
734
858
  for (let c = 0; c < COLS; c++) {
735
859
  const cell = document.createElement("span");
736
860
  cell.className = "jotter-table-cell";
737
861
  cell.dataset.r = r;
738
862
  cell.dataset.c = c;
739
- cell.addEventListener("mouseenter", () => {
740
- hint.textContent = `${r + 1} \xD7 ${c + 1} table`;
741
- cells.forEach((cl) => {
742
- cl.classList.toggle(
743
- "jotter-table-cell--active",
744
- +cl.dataset.r <= r && +cl.dataset.c <= c
745
- );
746
- });
747
- });
863
+ cell.setAttribute("role", "gridcell");
864
+ cell.setAttribute("aria-label", `${r + 1} by ${c + 1} table`);
865
+ cell.tabIndex = r === 0 && c === 0 ? 0 : -1;
866
+ cell.addEventListener("mouseenter", () => preview(r, c));
867
+ cell.addEventListener("focus", () => preview(r, c));
748
868
  cell.addEventListener("click", () => this._insertTable(r + 1, c + 1));
749
869
  cells.push(cell);
750
870
  grid.appendChild(cell);
751
871
  }
752
872
  }
873
+ const STEP = { ArrowRight: [0, 1], ArrowLeft: [0, -1], ArrowDown: [1, 0], ArrowUp: [-1, 0] };
874
+ grid.addEventListener("keydown", (e) => {
875
+ const cell = cells.indexOf(document.activeElement) === -1 ? null : document.activeElement;
876
+ if (!cell)
877
+ return;
878
+ const r = +cell.dataset.r, c = +cell.dataset.c;
879
+ if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
880
+ e.preventDefault();
881
+ this._insertTable(r + 1, c + 1);
882
+ return;
883
+ }
884
+ const step = STEP[e.key];
885
+ if (!step)
886
+ return;
887
+ e.preventDefault();
888
+ const nr = Math.min(ROWS - 1, Math.max(0, r + step[0]));
889
+ const nc = Math.min(COLS - 1, Math.max(0, c + step[1]));
890
+ const next = cells[nr * COLS + nc];
891
+ if (next === cell)
892
+ return;
893
+ cell.tabIndex = -1;
894
+ next.tabIndex = 0;
895
+ next.focus();
896
+ });
753
897
  wrap.appendChild(grid);
754
898
  wrap.appendChild(hint);
755
899
  return wrap;
@@ -926,8 +1070,7 @@ var JotterJS = class {
926
1070
  btn.className = "jotter-char-btn";
927
1071
  btn.textContent = ch;
928
1072
  btn.title = `U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
929
- btn.addEventListener("mousedown", (e) => {
930
- e.preventDefault();
1073
+ this._bindActivation(btn, () => {
931
1074
  this._commitPopup(() => document.execCommand("insertText", false, ch));
932
1075
  });
933
1076
  grid.appendChild(btn);
@@ -944,8 +1087,7 @@ var JotterJS = class {
944
1087
  btn.type = "button";
945
1088
  btn.className = "jotter-lorem-btn";
946
1089
  btn.textContent = v.label;
947
- btn.addEventListener("mousedown", (e) => {
948
- e.preventDefault();
1090
+ this._bindActivation(btn, () => {
949
1091
  this._commitPopup(() => document.execCommand(
950
1092
  v.isHTML ? "insertHTML" : "insertText",
951
1093
  false,
@@ -1011,9 +1153,14 @@ var JotterJS = class {
1011
1153
  return null;
1012
1154
  return `<div class="jotter-video-wrap"><iframe src="https://www.youtube.com/embed/${id}" frameborder="0" allowfullscreen loading="lazy" title="YouTube video"></iframe></div><p><br></p>`;
1013
1155
  }
1014
- /** Embeds are inserted verbatim — the host, not the end user, supplies them. */
1156
+ /**
1157
+ * The embed markup itself is inserted verbatim (the host, not the end user,
1158
+ * supplies it, so it is never sanitized) inside a .jotter-embed-wrap, added
1159
+ * purely so the block is selectable and can carry a click shield — same
1160
+ * treatment as the video wrap.
1161
+ */
1015
1162
  _embedHTML(html) {
1016
- return html + "<p><br></p>";
1163
+ return `<div class="jotter-embed-wrap">${html}</div><p><br></p>`;
1017
1164
  }
1018
1165
  // ─── Resolver hooks ───────────────────────────────────────────────────────
1019
1166
  // An onRequest* option replaces the built-in popup for that insertable. The
@@ -1155,6 +1302,183 @@ var JotterJS = class {
1155
1302
  bar.appendChild(modeEl);
1156
1303
  return bar;
1157
1304
  }
1305
+ // ─── Media selection ──────────────────────────────────────────────────────
1306
+ // Images, YouTube embeds and generic embeds are atomic blocks: clicking one
1307
+ // shows a floating frame (positioned over it, not part of the saved HTML)
1308
+ // with resize handles, and Backspace/Delete removes the whole element.
1309
+ //
1310
+ // The frame lives in _editorWrap, a sibling of _editor/_source, rather than
1311
+ // inside the editable content or on document.body like the popup — being a
1312
+ // normal descendant of the scrolling wrap means it scrolls with the content
1313
+ // for free, with no scroll listener needed.
1314
+ //
1315
+ // Video/embed content sits behind a permanent "shield" div so it can never
1316
+ // be interacted with while editing (no accidental playback, no swallowed
1317
+ // clicks) — every click on the block always just selects it. The shield is
1318
+ // injected live-DOM only and stripped out of getHTML()/_sanitize(), exactly
1319
+ // like the selection bookmarks above are, so published HTML keeps a normal,
1320
+ // interactive iframe.
1321
+ /** Builds the reusable floating selection frame with its 6 resize handles. */
1322
+ _buildMediaFrame() {
1323
+ const frame = document.createElement("div");
1324
+ frame.className = "jotter-media-frame";
1325
+ ["nw", "ne", "sw", "se", "w", "e"].forEach((pos) => {
1326
+ const h = document.createElement("div");
1327
+ h.className = "jotter-resize-handle jotter-resize-handle--" + pos;
1328
+ h.dataset.handle = pos;
1329
+ h.tabIndex = -1;
1330
+ frame.appendChild(h);
1331
+ });
1332
+ frame.addEventListener("mousedown", (e) => {
1333
+ const handle = e.target.closest(".jotter-resize-handle");
1334
+ if (!handle || !this._selectedMedia)
1335
+ return;
1336
+ e.preventDefault();
1337
+ this._beginResize(handle.dataset.handle, e);
1338
+ });
1339
+ return frame;
1340
+ }
1341
+ /** Adds a click shield to every video/embed block that doesn't already have one. */
1342
+ _ensureMediaShields() {
1343
+ this._editor.querySelectorAll(".jotter-video-wrap, .jotter-embed-wrap").forEach((wrap) => {
1344
+ if (wrap.querySelector(":scope > .jotter-media-shield"))
1345
+ return;
1346
+ const shield = document.createElement("div");
1347
+ shield.className = "jotter-media-shield";
1348
+ wrap.appendChild(shield);
1349
+ });
1350
+ }
1351
+ /** Nearest selectable media element (img / video-wrap / embed-wrap) under target, or null. */
1352
+ _mediaElementFromTarget(target) {
1353
+ if (!this._editor.contains(target))
1354
+ return null;
1355
+ const img = target.closest("img");
1356
+ if (img && this._editor.contains(img))
1357
+ return img;
1358
+ const wrap = target.closest(".jotter-video-wrap, .jotter-embed-wrap");
1359
+ if (wrap && this._editor.contains(wrap))
1360
+ return wrap;
1361
+ return null;
1362
+ }
1363
+ /** Selects a media element: shows the frame over it and parks a real caret before it. */
1364
+ _selectMedia(el) {
1365
+ if (this._selectedMedia === el)
1366
+ return;
1367
+ this._deselectMedia();
1368
+ this._selectedMedia = el;
1369
+ this._positionMediaFrame();
1370
+ this._mediaFrame.classList.add("jotter-media-frame--visible");
1371
+ this._editor.focus({ preventScroll: true });
1372
+ const range = document.createRange();
1373
+ range.setStartBefore(el);
1374
+ range.collapse(true);
1375
+ const sel = window.getSelection();
1376
+ sel.removeAllRanges();
1377
+ sel.addRange(range);
1378
+ this._updateToolbarState();
1379
+ }
1380
+ /** Clears the current media selection (and aborts any in-flight resize). */
1381
+ _deselectMedia() {
1382
+ if (this._resizeState)
1383
+ this._abortResize();
1384
+ if (!this._selectedMedia)
1385
+ return;
1386
+ this._selectedMedia = null;
1387
+ this._mediaFrame.classList.remove("jotter-media-frame--visible");
1388
+ }
1389
+ /** Repositions the floating frame to match the selected element's current box. */
1390
+ _positionMediaFrame() {
1391
+ const el = this._selectedMedia;
1392
+ if (!el)
1393
+ return;
1394
+ const wrapRect = this._editorWrap.getBoundingClientRect();
1395
+ const elRect = el.getBoundingClientRect();
1396
+ this._mediaFrame.style.top = elRect.top - wrapRect.top + this._editorWrap.scrollTop + "px";
1397
+ this._mediaFrame.style.left = elRect.left - wrapRect.left + this._editorWrap.scrollLeft + "px";
1398
+ this._mediaFrame.style.width = elRect.width + "px";
1399
+ this._mediaFrame.style.height = elRect.height + "px";
1400
+ }
1401
+ /**
1402
+ * Removes the selected element via execCommand('delete') rather than a plain
1403
+ * el.remove() — the browser's native undo stack (what Ctrl+Z / the Undo
1404
+ * button run via execCommand('undo')) only tracks edits made through
1405
+ * execCommand, so a raw DOM removal would be invisible to it. Selecting the
1406
+ * whole node first (rather than a collapsed caret before it) means 'delete'
1407
+ * removes exactly that node regardless of direction, and the browser
1408
+ * collapses the caret to where it used to be on its own.
1409
+ */
1410
+ _deleteSelectedMedia() {
1411
+ const el = this._selectedMedia;
1412
+ if (!el)
1413
+ return;
1414
+ this._deselectMedia();
1415
+ const range = document.createRange();
1416
+ range.selectNode(el);
1417
+ const sel = window.getSelection();
1418
+ sel.removeAllRanges();
1419
+ sel.addRange(range);
1420
+ this._applyEdit(() => document.execCommand("delete", false, null));
1421
+ this._updateToolbarState();
1422
+ }
1423
+ /**
1424
+ * Starts a width-only resize drag. Height is never set directly: an image's
1425
+ * natural aspect ratio, the video wrap's padding-bottom-percent-of-width box,
1426
+ * and an embed's normal reflow all keep it correct on their own once width
1427
+ * changes, so one code path covers all three media types.
1428
+ */
1429
+ _beginResize(handleType, e) {
1430
+ const el = this._selectedMedia;
1431
+ const rect = el.getBoundingClientRect();
1432
+ const sign = handleType.includes("w") ? -1 : 1;
1433
+ this._resizeState = {
1434
+ el,
1435
+ sign,
1436
+ startX: e.clientX,
1437
+ startWidth: rect.width,
1438
+ startInlineWidth: el.style.width,
1439
+ before: this._richHTML()
1440
+ };
1441
+ document.addEventListener("mousemove", this._onResizeMove);
1442
+ document.addEventListener("mouseup", this._onResizeEnd, { once: true });
1443
+ }
1444
+ _onResizeMove(e) {
1445
+ const s = this._resizeState;
1446
+ if (!s)
1447
+ return;
1448
+ const dx = (e.clientX - s.startX) * s.sign;
1449
+ const min = 40;
1450
+ const max = Math.max(min, this._editor.clientWidth);
1451
+ const width = Math.max(min, Math.min(max, Math.round(s.startWidth + dx)));
1452
+ s.el.style.width = width + "px";
1453
+ s.el.style.removeProperty("max-width");
1454
+ this._positionMediaFrame();
1455
+ }
1456
+ /**
1457
+ * A drag spans async mousemove/mouseup events, so it can't go through the
1458
+ * synchronous _applyEdit() helper — this manually replicates its contract
1459
+ * instead: emit 'change' exactly once, only if the width actually changed.
1460
+ */
1461
+ _onResizeEnd() {
1462
+ document.removeEventListener("mousemove", this._onResizeMove);
1463
+ const s = this._resizeState;
1464
+ this._resizeState = null;
1465
+ if (!s)
1466
+ return;
1467
+ this._updateStatus();
1468
+ if (this._richHTML() !== s.before)
1469
+ this._emitChange();
1470
+ }
1471
+ /** Escape mid-drag: revert to the pre-drag width and emit nothing. */
1472
+ _abortResize() {
1473
+ const s = this._resizeState;
1474
+ if (!s)
1475
+ return;
1476
+ document.removeEventListener("mousemove", this._onResizeMove);
1477
+ document.removeEventListener("mouseup", this._onResizeEnd);
1478
+ s.el.style.width = s.startInlineWidth;
1479
+ this._resizeState = null;
1480
+ this._positionMediaFrame();
1481
+ }
1158
1482
  // ─── Events ───────────────────────────────────────────────────────────────
1159
1483
  _bindEvents() {
1160
1484
  this._editor.addEventListener("input", () => {
@@ -1186,11 +1510,26 @@ var JotterJS = class {
1186
1510
  }
1187
1511
  }
1188
1512
  });
1513
+ this._ensureMediaShields();
1514
+ if (this._selectedMedia)
1515
+ this._positionMediaFrame();
1189
1516
  this._updateStatus();
1190
1517
  this._emitChange();
1191
1518
  });
1192
1519
  this._editor.addEventListener("keyup", () => this._updateToolbarState());
1193
1520
  this._editor.addEventListener("mouseup", () => this._updateToolbarState());
1521
+ this._editor.addEventListener("mousedown", (e) => {
1522
+ const media = this._mediaElementFromTarget(e.target);
1523
+ if (media) {
1524
+ e.preventDefault();
1525
+ this._selectMedia(media);
1526
+ }
1527
+ });
1528
+ this._onWindowResize = () => {
1529
+ if (this._selectedMedia)
1530
+ this._positionMediaFrame();
1531
+ };
1532
+ window.addEventListener("resize", this._onWindowResize);
1194
1533
  this._editor.addEventListener("focusin", (e) => {
1195
1534
  this._root.classList.add("jotter--focused");
1196
1535
  if (this._externalDepth > 0)
@@ -1202,6 +1541,7 @@ var JotterJS = class {
1202
1541
  this._options.onFocus();
1203
1542
  });
1204
1543
  this._editor.addEventListener("focusout", (e) => {
1544
+ this._deselectMedia();
1205
1545
  if (this._externalDepth > 0)
1206
1546
  return;
1207
1547
  if (this._isInternalTarget(e.relatedTarget))
@@ -1220,6 +1560,11 @@ var JotterJS = class {
1220
1560
  }, 0);
1221
1561
  });
1222
1562
  this._editor.addEventListener("keydown", (e) => {
1563
+ if ((e.key === "Backspace" || e.key === "Delete") && this._selectedMedia) {
1564
+ e.preventDefault();
1565
+ this._deleteSelectedMedia();
1566
+ return;
1567
+ }
1223
1568
  if (e.key === "Tab") {
1224
1569
  e.preventDefault();
1225
1570
  document.execCommand("insertHTML", false, "&nbsp;&nbsp;&nbsp;&nbsp;");
@@ -1229,10 +1574,23 @@ var JotterJS = class {
1229
1574
  if (this._popupVisible() && !this._popup.contains(e.target) && !this._toolbarEl.contains(e.target)) {
1230
1575
  this._hidePopup();
1231
1576
  }
1577
+ if (this._selectedMedia && !this._mediaFrame.contains(e.target) && e.target !== this._selectedMedia && !this._selectedMedia.contains(e.target)) {
1578
+ this._deselectMedia();
1579
+ }
1232
1580
  };
1233
1581
  document.addEventListener("mousedown", this._onDocMouseDown);
1234
1582
  this._onDocKeyDown = (e) => {
1235
- if (e.key === "Escape" && this._popupVisible()) {
1583
+ if (e.key !== "Escape")
1584
+ return;
1585
+ if (this._resizeState) {
1586
+ this._abortResize();
1587
+ return;
1588
+ }
1589
+ if (this._selectedMedia) {
1590
+ this._deselectMedia();
1591
+ return;
1592
+ }
1593
+ if (this._popupVisible()) {
1236
1594
  this._restoreSavedBookmark();
1237
1595
  this._hidePopup();
1238
1596
  }
@@ -1250,6 +1608,7 @@ var JotterJS = class {
1250
1608
  * Leaving source mode: sanitizes textarea value back into innerHTML.
1251
1609
  */
1252
1610
  _toggleSourceMode() {
1611
+ this._deselectMedia();
1253
1612
  this._sourceMode = !this._sourceMode;
1254
1613
  if (this._sourceMode) {
1255
1614
  this._dropBookmarks();
@@ -1258,6 +1617,7 @@ var JotterJS = class {
1258
1617
  this._source.style.display = "block";
1259
1618
  } else {
1260
1619
  this._editor.innerHTML = this._sanitize(this._source.value);
1620
+ this._ensureMediaShields();
1261
1621
  this._source.style.display = "none";
1262
1622
  this._editor.style.display = "";
1263
1623
  this._updateStatus();
@@ -1300,7 +1660,7 @@ var JotterJS = class {
1300
1660
  _sanitize(html) {
1301
1661
  const doc = new DOMParser().parseFromString(html, "text/html");
1302
1662
  doc.querySelectorAll("script").forEach((el) => el.remove());
1303
- doc.querySelectorAll("[data-jotter-bookmark]").forEach((el) => el.remove());
1663
+ doc.querySelectorAll("[data-jotter-bookmark], .jotter-media-shield").forEach((el) => el.remove());
1304
1664
  doc.querySelectorAll("*").forEach((el) => {
1305
1665
  Array.from(el.attributes).forEach((attr) => {
1306
1666
  if (attr.name.startsWith("on")) {
@@ -1424,12 +1784,14 @@ var JotterJS = class {
1424
1784
  // indicator or a host re-render. Empty marker <span>s move with the DOM
1425
1785
  // instead, so they still point at the right spot afterwards. They are stripped
1426
1786
  // from getHTML() and from _sanitize(), so hosts never see them.
1427
- /** Editable area's HTML, minus any live selection markers. */
1787
+ /**
1788
+ * Editable area's HTML, minus any live selection markers and media click-
1789
+ * shields. Always clones (no bookmark-free fast path): shields can be
1790
+ * present independently of bookmarks, so both must be stripped every time.
1791
+ */
1428
1792
  _richHTML() {
1429
- if (this._bookmarks.size === 0)
1430
- return this._editor.innerHTML;
1431
1793
  const clone = this._editor.cloneNode(true);
1432
- clone.querySelectorAll("[data-jotter-bookmark]").forEach((el) => el.remove());
1794
+ clone.querySelectorAll("[data-jotter-bookmark], .jotter-media-shield").forEach((el) => el.remove());
1433
1795
  return clone.innerHTML;
1434
1796
  }
1435
1797
  _makeMarker(id) {
@@ -1536,6 +1898,7 @@ var JotterJS = class {
1536
1898
  return this;
1537
1899
  }
1538
1900
  clear() {
1901
+ this._deselectMedia();
1539
1902
  this._dropBookmarks();
1540
1903
  this._editor.innerHTML = "";
1541
1904
  this._updateStatus();
@@ -1660,8 +2023,10 @@ var JotterJS = class {
1660
2023
  return this;
1661
2024
  }
1662
2025
  setHTML(html) {
2026
+ this._deselectMedia();
1663
2027
  this._dropBookmarks();
1664
2028
  this._editor.innerHTML = this._sanitize(html);
2029
+ this._ensureMediaShields();
1665
2030
  this._updateStatus();
1666
2031
  return this;
1667
2032
  }
@@ -1694,12 +2059,14 @@ var JotterJS = class {
1694
2059
  destroy() {
1695
2060
  const html = this.getHTML();
1696
2061
  this._destroyed = true;
2062
+ this._deselectMedia();
1697
2063
  this._hidePopup();
1698
2064
  this._dropBookmarks();
1699
2065
  this._externalBookmark = null;
1700
2066
  this._externalDepth = 0;
1701
2067
  document.removeEventListener("mousedown", this._onDocMouseDown);
1702
2068
  document.removeEventListener("keydown", this._onDocKeyDown);
2069
+ window.removeEventListener("resize", this._onWindowResize);
1703
2070
  if (this._popup.parentNode)
1704
2071
  this._popup.parentNode.removeChild(this._popup);
1705
2072
  this._target.classList.remove("jotter-host");
@@ -1 +1 @@
1
- @font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url("./fonts/material-symbols-rounded.woff2") format("woff2")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-feature-settings:"liga";font-feature-settings:"liga";-webkit-font-smoothing:antialiased}:root{--jotter-bg: #ffffff;--jotter-surface: #f4f3f0;--jotter-surface2: #eceae5;--jotter-border: #d8d4cc;--jotter-border-focus: #7a6a4a;--jotter-text: #1e1c18;--jotter-text-muted: #9a9488;--jotter-accent: #7a6a4a;--jotter-btn-hover: rgba(122, 106, 74, .07);--jotter-btn-active-bg: rgba(122, 106, 74, .14);--jotter-radius: 4px;--jotter-font-ui: "DM Mono", "Fira Mono", "Consolas", monospace;--jotter-font-editor: Georgia, "Times New Roman", serif;--jotter-status-h: 32px;--jotter-transition: .14s ease}.jotter-host{display:block}.htmled{display:flex;flex-direction:column;border:1px solid var(--jotter-border);border-radius:var(--jotter-radius);background:var(--jotter-bg);color:var(--jotter-text);font-family:var(--jotter-font-ui);overflow:hidden;transition:border-color var(--jotter-transition),box-shadow var(--jotter-transition)}.jotter--focused{border-color:var(--jotter-border-focus);box-shadow:0 0 0 3px #7a6a4a1a}.jotter--disabled{opacity:.55;pointer-events:none}.jotter-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:2px;padding:6px 8px;background:var(--jotter-surface);border-bottom:1px solid var(--jotter-border)}.jotter-select{appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--jotter-border);border-radius:var(--jotter-radius);color:var(--jotter-text);font-family:var(--jotter-font-ui);font-size:11px;letter-spacing:.04em;padding:4px 8px;height:28px;cursor:pointer;flex-shrink:0;transition:border-color var(--jotter-transition),background var(--jotter-transition)}.jotter-select{min-width:108px;text-transform:uppercase}.jotter-select--font{min-width:130px;text-transform:none}.jotter-select--size{min-width:68px;text-transform:none}.jotter-select--theme{min-width:90px;text-transform:none}.jotter-select:hover{border-color:var(--jotter-accent);background:var(--jotter-btn-hover)}.jotter-select:focus{outline:none;border-color:var(--jotter-accent)}.jotter-select option{background:var(--jotter-surface2);color:var(--jotter-text)}.jotter-btn{display:flex;align-items:center;justify-content:center;flex-direction:column;width:32px;height:30px;padding:0;border:none;border-radius:var(--jotter-radius);background:transparent;color:var(--jotter-text-muted);cursor:pointer;flex-shrink:0;transition:color var(--jotter-transition),background var(--jotter-transition);position:relative}.jotter-btn .material-icons{pointer-events:none;font-size:18px;line-height:1;user-select:none}.jotter-btn:hover{color:var(--jotter-accent);background:var(--jotter-btn-hover)}.jotter-btn--active{color:var(--jotter-accent)!important;background:var(--jotter-btn-active-bg)!important}.jotter-btn--text{width:auto;padding:0 10px;font-family:var(--jotter-font-ui);font-size:11px;letter-spacing:.08em;text-transform:uppercase;border:1px solid var(--jotter-border)}.jotter-sep{width:1px;height:18px;background:var(--jotter-border);margin:0 4px;flex-shrink:0}.jotter-color-wrap{display:inline-flex;align-items:center;flex-shrink:0}.jotter-color-input{position:absolute;width:0;height:0;opacity:0;pointer-events:none}.jotter-color-btn{width:32px;height:34px;gap:0;padding-bottom:3px}.jotter-color-swatch{display:block;width:18px;height:3px;border-radius:1px;pointer-events:none;flex-shrink:0}.jotter-editor-wrap{flex:1;overflow-y:auto;background:var(--jotter-bg);scrollbar-width:thin;scrollbar-color:var(--jotter-border) transparent}.jotter-editor-wrap::-webkit-scrollbar{width:6px}.jotter-editor-wrap::-webkit-scrollbar-track{background:transparent}.jotter-editor-wrap::-webkit-scrollbar-thumb{background:var(--jotter-border);border-radius:3px}.jotter-editor{outline:none;padding:24px 28px;min-height:200px;font-family:var(--jotter-font-editor);font-size:16px;line-height:1.75;color:var(--jotter-text);caret-color:auto;word-break:break-word}.jotter-source{display:none;width:100%;min-height:200px;padding:24px 28px;background:var(--jotter-bg);color:#3a6030;font-family:var(--jotter-font-ui);font-size:13px;line-height:1.7;border:none;outline:none;resize:vertical;caret-color:var(--jotter-accent);tab-size:2;white-space:pre;overflow-wrap:normal;overflow-x:auto}.jotter--source-mode .jotter-btn:not([data-custom=toggleSource]),.jotter--source-mode .jotter-select,.jotter--source-mode .jotter-color-btn{opacity:.35;pointer-events:none}.jotter-editor:empty:before{content:attr(data-placeholder);color:var(--jotter-text-muted);pointer-events:none;font-style:italic}.jotter-editor .jotter-bookmark{display:none}.jotter-editor h1,.jotter-editor h2,.jotter-editor h3,.jotter-editor h4,.jotter-editor h5,.jotter-editor h6{text-transform:none;font-family:var(--ed-heading-font, inherit);letter-spacing:var(--ed-heading-tracking, normal)}.jotter-editor h1{font-size:2em;font-weight:700;margin:.6em 0 .3em;color:var(--ed-h1, inherit);border-bottom:var(--ed-h1-rule, none);padding-bottom:.25em}.jotter-editor h2{font-size:1.5em;font-weight:600;margin:.75em 0 .25em;color:var(--ed-h2, inherit)}.jotter-editor h3{font-size:1.2em;font-weight:600;margin:.75em 0 .25em;color:var(--ed-h3, inherit)}.jotter-editor h4{font-size:1em;font-weight:600;margin:.75em 0 .25em;color:var(--ed-h4, inherit)}.jotter-editor p{margin:0 0 .85em}.jotter-editor a{color:var(--ed-link, -webkit-link);text-decoration-color:var(--ed-link-deco, auto);text-underline-offset:3px}.jotter-editor a:hover{text-decoration-color:var(--ed-link, -webkit-link)}.jotter-editor strong,.jotter-editor b{color:var(--ed-strong, inherit);font-weight:700}.jotter-editor em,.jotter-editor i{color:var(--ed-em, inherit)}.jotter-editor blockquote{margin:1em 0;padding:8px 0 8px 20px;font-style:italic;border-left:3px solid var(--ed-quote-bar, #ccc);color:var(--ed-quote, inherit)}.jotter-editor pre{font-family:var(--ed-code-font, monospace);font-size:.875em;margin:.75em 0;padding:12px 16px;overflow-x:auto;white-space:pre-wrap;background:var(--ed-code-bg, transparent);color:var(--ed-code-text, inherit);border:1px solid var(--ed-code-border, transparent);border-radius:var(--jotter-radius)}.jotter-editor code{font-family:var(--ed-code-font, monospace);font-size:.875em;padding:1px 4px;background:var(--ed-code-bg, transparent);color:var(--ed-code-text, inherit);border:1px solid var(--ed-code-border, transparent);border-radius:2px}.jotter-editor ul,.jotter-editor ol{padding-left:24px;margin:.5em 0 .85em}.jotter-editor li{margin-bottom:.25em}.jotter-editor ul li::marker{color:var(--ed-marker, inherit)}.jotter-editor ol li::marker{color:var(--ed-marker, inherit)}.jotter-editor hr{border:none;border-top:1px solid var(--ed-hr, currentColor);opacity:.2;margin:1.5em 0}.jotter-editor sub,.jotter-editor sup{font-size:.75em}.jotter-editor ::selection{background:var(--ed-selection, highlight)}.jotter-editor table{border-collapse:collapse;width:100%;margin:.75em 0;font-size:.95em}.jotter-editor th,.jotter-editor td{border:1px solid var(--ed-table-border, #ccc);padding:7px 12px;text-align:left;vertical-align:top;min-width:40px}.jotter-editor th{background:var(--ed-th-bg, transparent);color:var(--ed-th-text, inherit);font-family:var(--ed-heading-font, inherit);font-size:.85em;font-weight:600;text-transform:none}.jotter-editor[data-theme=warm]{--ed-heading-font: "DM Mono", "Fira Mono", monospace;--ed-heading-tracking: -.01em;--ed-caret: #7a6a4a;--ed-selection: rgba(122, 106, 74, .18);--ed-h1: #7a6a4a;--ed-h2: #4a3e28;--ed-h3: #5a4e38;--ed-h4: #9a9488;--ed-h1-rule: 1px solid #d8d4cc;--ed-link: #7a6a4a;--ed-link-deco: rgba(122, 106, 74, .35);--ed-strong: #1a1612;--ed-em: #3a3028;--ed-quote: #7a7468;--ed-quote-bar: #7a6a4a;--ed-code-font: "DM Mono", "Fira Mono", monospace;--ed-code-bg: #eceae5;--ed-code-border: #d8d4cc;--ed-code-text: #3a6030;--ed-marker: #7a6a4a;--ed-hr: #d8d4cc;--ed-th-bg: #f4f3f0;--ed-th-text: #7a6a4a;--ed-table-border: #d8d4cc}.jotter-editor[data-theme=ink]{--ed-heading-font: "DM Mono", "Fira Mono", monospace;--ed-heading-tracking: -.01em;--ed-caret: #1a2a4a;--ed-selection: rgba(26, 42, 74, .14);--ed-h1: #0d1f3c;--ed-h2: #1a2a4a;--ed-h3: #243560;--ed-h4: #5a6888;--ed-h1-rule: 1px solid #b8c4d8;--ed-link: #1a4a8a;--ed-link-deco: rgba(26, 74, 138, .3);--ed-strong: #0a1428;--ed-em: #2a3a5a;--ed-quote: #4a5a78;--ed-quote-bar: #1a4a8a;--ed-code-font: "DM Mono", "Fira Mono", monospace;--ed-code-bg: #eef0f6;--ed-code-border: #c8cedd;--ed-code-text: #1a4a3a;--ed-marker: #1a4a8a;--ed-hr: #b8c4d8;--ed-th-bg: #eef0f6;--ed-th-text: #1a2a4a;--ed-table-border: #b8c4d8}.jotter-editor[data-theme=forest]{--ed-heading-font: "DM Mono", "Fira Mono", monospace;--ed-heading-tracking: -.01em;--ed-caret: #1a3a28;--ed-selection: rgba(26, 58, 40, .14);--ed-h1: #1a3a28;--ed-h2: #1e4430;--ed-h3: #2a5a40;--ed-h4: #5a7a68;--ed-h1-rule: 1px solid #b0ccc0;--ed-link: #1a6040;--ed-link-deco: rgba(26, 96, 64, .3);--ed-strong: #0e2018;--ed-em: #2a4038;--ed-quote: #3a5a48;--ed-quote-bar: #1a6040;--ed-code-font: "DM Mono", "Fira Mono", monospace;--ed-code-bg: #eef5f0;--ed-code-border: #c0d8c8;--ed-code-text: #1a5828;--ed-marker: #1a6040;--ed-hr: #b0ccc0;--ed-th-bg: #eef5f0;--ed-th-text: #1a3a28;--ed-table-border: #b0ccc0}.jotter-editor .jotter-video-wrap{position:relative;padding-bottom:56.25%;height:0;overflow:hidden;margin:.75em 0;border-radius:var(--jotter-radius);border:1px solid var(--jotter-border)}.jotter-editor .jotter-video-wrap iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none}.jotter-status{display:flex;align-items:center;gap:16px;padding:0 14px;height:var(--jotter-status-h);min-height:var(--jotter-status-h);background:var(--jotter-surface);border-top:1px solid var(--jotter-border);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--jotter-text-muted)}.jotter-status-mode{margin-left:auto;color:var(--jotter-accent);font-weight:600}.jotter-popup{display:none;position:fixed;z-index:9999;background:#fff;border:1px solid #d8d4cc;border-radius:5px;box-shadow:0 8px 24px #0000001f,0 2px 6px #00000012;min-width:220px;max-width:360px}.jotter-popup--visible{display:block}.jotter-popup-inner{padding:14px}.jotter-popup-title{font-family:var(--jotter-font-ui);font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:#9a9488;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #d8d4cc}.jotter-popup-hint{font-size:11px;color:#9a9488;margin-top:8px;text-align:center;letter-spacing:.04em}.jotter-popup-form{display:flex;flex-direction:column;gap:6px;min-width:280px}.jotter-popup-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:#9a9488;margin-top:4px}.jotter-popup-input,.jotter-popup-select,.jotter-popup-textarea{width:100%;background:#f8f7f4;border:1px solid #d8d4cc;border-radius:3px;color:#1e1c18;font-family:DM Mono,Fira Mono,monospace;font-size:12px;padding:7px 9px;outline:none;transition:border-color .12s;resize:vertical}.jotter-popup-input:focus,.jotter-popup-select:focus,.jotter-popup-textarea:focus{border-color:#7a6a4a}.jotter-popup-select{appearance:auto;cursor:pointer;resize:none}.jotter-input--error{border-color:#b85050!important}.jotter-popup-submit{margin-top:6px;padding:7px 14px;background:#7a6a4a14;border:1px solid #7a6a4a;border-radius:3px;color:#7a6a4a;font-family:DM Mono,Fira Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;cursor:pointer;transition:background .12s}.jotter-popup-submit:hover{background:#7a6a4a29}.jotter-table-grid{display:grid;gap:3px}.jotter-table-cell{width:18px;height:18px;border:1px solid #d8d4cc;border-radius:2px;cursor:pointer;transition:border-color 80ms,background 80ms}.jotter-table-cell--active{background:#7a6a4a24;border-color:#7a6a4a}.jotter-char-grid{display:flex;flex-wrap:wrap;gap:2px;max-width:300px;max-height:220px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#d8d4cc transparent}.jotter-char-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid #d8d4cc;border-radius:3px;background:transparent;color:#1e1c18;font-size:14px;cursor:pointer;transition:background 80ms,border-color 80ms,color 80ms;flex-shrink:0}.jotter-char-btn:hover{background:#7a6a4a1a;border-color:#7a6a4a;color:#7a6a4a}.jotter-lorem-btn{display:block;width:100%;padding:9px 12px;background:transparent;border:1px solid #d8d4cc;border-radius:3px;color:#1e1c18;font-family:DM Mono,Fira Mono,monospace;font-size:11px;letter-spacing:.04em;text-align:left;cursor:pointer;margin-bottom:4px;transition:background .1s,border-color .1s,color .1s}.jotter-lorem-btn:hover{background:#7a6a4a12;border-color:#7a6a4a;color:#7a6a4a}
1
+ @font-face{font-family:Material Icons;font-style:normal;font-weight:400;src:url("./fonts/material-symbols-rounded.woff2") format("woff2")}.material-icons{font-family:Material Icons;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-feature-settings:"liga";font-feature-settings:"liga";-webkit-font-smoothing:antialiased}:root{--jotter-bg: #ffffff;--jotter-surface: #f4f3f0;--jotter-surface2: #eceae5;--jotter-border: #d8d4cc;--jotter-border-focus: #7a6a4a;--jotter-text: #1e1c18;--jotter-text-muted: #9a9488;--jotter-accent: #7a6a4a;--jotter-btn-hover: rgba(122, 106, 74, .07);--jotter-btn-active-bg: rgba(122, 106, 74, .14);--jotter-radius: 4px;--jotter-font-ui: "DM Mono", "Fira Mono", "Consolas", monospace;--jotter-font-editor: Georgia, "Times New Roman", serif;--jotter-status-h: 32px;--jotter-transition: .14s ease}.jotter-host{display:block}.htmled{display:flex;flex-direction:column;border:1px solid var(--jotter-border);border-radius:var(--jotter-radius);background:var(--jotter-bg);color:var(--jotter-text);font-family:var(--jotter-font-ui);overflow:hidden;transition:border-color var(--jotter-transition),box-shadow var(--jotter-transition)}.jotter--focused{border-color:var(--jotter-border-focus);box-shadow:0 0 0 3px #7a6a4a1a}.jotter--disabled{opacity:.55;pointer-events:none}.jotter-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:2px;padding:6px 8px;background:var(--jotter-surface);border-bottom:1px solid var(--jotter-border)}.jotter-select{appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--jotter-border);border-radius:var(--jotter-radius);color:var(--jotter-text);font-family:var(--jotter-font-ui);font-size:11px;letter-spacing:.04em;padding:4px 8px;height:28px;cursor:pointer;flex-shrink:0;transition:border-color var(--jotter-transition),background var(--jotter-transition)}.jotter-select{min-width:108px;text-transform:uppercase}.jotter-select--font{min-width:130px;text-transform:none}.jotter-select--size{min-width:68px;text-transform:none}.jotter-select--theme{min-width:90px;text-transform:none}.jotter-select:hover{border-color:var(--jotter-accent);background:var(--jotter-btn-hover)}.jotter-select:focus{outline:none;border-color:var(--jotter-accent)}.jotter-select option{background:var(--jotter-surface2);color:var(--jotter-text)}.jotter-btn{display:flex;align-items:center;justify-content:center;flex-direction:column;width:32px;height:30px;padding:0;border:none;border-radius:var(--jotter-radius);background:transparent;color:var(--jotter-text-muted);cursor:pointer;flex-shrink:0;transition:color var(--jotter-transition),background var(--jotter-transition);position:relative}.jotter-btn .material-icons{pointer-events:none;font-size:18px;line-height:1;user-select:none}.jotter-btn:hover{color:var(--jotter-accent);background:var(--jotter-btn-hover)}.jotter-btn--active{color:var(--jotter-accent)!important;background:var(--jotter-btn-active-bg)!important}.jotter-btn--text{width:auto;padding:0 10px;font-family:var(--jotter-font-ui);font-size:11px;letter-spacing:.08em;text-transform:uppercase;border:1px solid var(--jotter-border)}.jotter-sep{width:1px;height:18px;background:var(--jotter-border);margin:0 4px;flex-shrink:0}.jotter-color-wrap{display:inline-flex;align-items:center;flex-shrink:0}.jotter-color-input{position:absolute;width:0;height:0;opacity:0;pointer-events:none}.jotter-color-btn{width:32px;height:34px;gap:0;padding-bottom:3px}.jotter-color-swatch{display:block;width:18px;height:3px;border-radius:1px;pointer-events:none;flex-shrink:0}.jotter-editor-wrap{position:relative;flex:1;overflow-y:auto;background:var(--jotter-bg);scrollbar-width:thin;scrollbar-color:var(--jotter-border) transparent}.jotter-editor-wrap::-webkit-scrollbar{width:6px}.jotter-editor-wrap::-webkit-scrollbar-track{background:transparent}.jotter-editor-wrap::-webkit-scrollbar-thumb{background:var(--jotter-border);border-radius:3px}.jotter-editor{outline:none;padding:24px 28px;min-height:200px;font-family:var(--jotter-font-editor);font-size:16px;line-height:1.75;color:var(--jotter-text);caret-color:auto;word-break:break-word}.jotter-source{display:none;width:100%;min-height:200px;padding:24px 28px;background:var(--jotter-bg);color:#3a6030;font-family:var(--jotter-font-ui);font-size:13px;line-height:1.7;border:none;outline:none;resize:vertical;caret-color:var(--jotter-accent);tab-size:2;white-space:pre;overflow-wrap:normal;overflow-x:auto}.jotter--source-mode .jotter-btn:not([data-custom=toggleSource]),.jotter--source-mode .jotter-select,.jotter--source-mode .jotter-color-btn{opacity:.35;pointer-events:none}.jotter-editor:empty:before{content:attr(data-placeholder);color:var(--jotter-text-muted);pointer-events:none;font-style:italic}.jotter-editor .jotter-bookmark{display:none}.jotter-editor h1,.jotter-editor h2,.jotter-editor h3,.jotter-editor h4,.jotter-editor h5,.jotter-editor h6{text-transform:none;font-family:var(--ed-heading-font, inherit);letter-spacing:var(--ed-heading-tracking, normal)}.jotter-editor h1{font-size:2em;font-weight:700;margin:.6em 0 .3em;color:var(--ed-h1, inherit);border-bottom:var(--ed-h1-rule, none);padding-bottom:.25em}.jotter-editor h2{font-size:1.5em;font-weight:600;margin:.75em 0 .25em;color:var(--ed-h2, inherit)}.jotter-editor h3{font-size:1.2em;font-weight:600;margin:.75em 0 .25em;color:var(--ed-h3, inherit)}.jotter-editor h4{font-size:1em;font-weight:600;margin:.75em 0 .25em;color:var(--ed-h4, inherit)}.jotter-editor p{margin:0 0 .85em}.jotter-editor a{color:var(--ed-link, -webkit-link);text-decoration-color:var(--ed-link-deco, auto);text-underline-offset:3px}.jotter-editor a:hover{text-decoration-color:var(--ed-link, -webkit-link)}.jotter-editor strong,.jotter-editor b{color:var(--ed-strong, inherit);font-weight:700}.jotter-editor em,.jotter-editor i{color:var(--ed-em, inherit)}.jotter-editor blockquote{margin:1em 0;padding:8px 0 8px 20px;font-style:italic;border-left:3px solid var(--ed-quote-bar, #ccc);color:var(--ed-quote, inherit)}.jotter-editor pre{font-family:var(--ed-code-font, monospace);font-size:.875em;margin:.75em 0;padding:12px 16px;overflow-x:auto;white-space:pre-wrap;background:var(--ed-code-bg, transparent);color:var(--ed-code-text, inherit);border:1px solid var(--ed-code-border, transparent);border-radius:var(--jotter-radius)}.jotter-editor code{font-family:var(--ed-code-font, monospace);font-size:.875em;padding:1px 4px;background:var(--ed-code-bg, transparent);color:var(--ed-code-text, inherit);border:1px solid var(--ed-code-border, transparent);border-radius:2px}.jotter-editor ul,.jotter-editor ol{padding-left:24px;margin:.5em 0 .85em}.jotter-editor li{margin-bottom:.25em}.jotter-editor ul li::marker{color:var(--ed-marker, inherit)}.jotter-editor ol li::marker{color:var(--ed-marker, inherit)}.jotter-editor hr{border:none;border-top:1px solid var(--ed-hr, currentColor);opacity:.2;margin:1.5em 0}.jotter-editor sub,.jotter-editor sup{font-size:.75em}.jotter-editor ::selection{background:var(--ed-selection, highlight)}.jotter-editor table{border-collapse:collapse;width:100%;margin:.75em 0;font-size:.95em}.jotter-editor th,.jotter-editor td{border:1px solid var(--ed-table-border, #ccc);padding:7px 12px;text-align:left;vertical-align:top;min-width:40px}.jotter-editor th{background:var(--ed-th-bg, transparent);color:var(--ed-th-text, inherit);font-family:var(--ed-heading-font, inherit);font-size:.85em;font-weight:600;text-transform:none}.jotter-editor[data-theme=warm]{--ed-heading-font: "DM Mono", "Fira Mono", monospace;--ed-heading-tracking: -.01em;--ed-caret: #7a6a4a;--ed-selection: rgba(122, 106, 74, .18);--ed-h1: #7a6a4a;--ed-h2: #4a3e28;--ed-h3: #5a4e38;--ed-h4: #9a9488;--ed-h1-rule: 1px solid #d8d4cc;--ed-link: #7a6a4a;--ed-link-deco: rgba(122, 106, 74, .35);--ed-strong: #1a1612;--ed-em: #3a3028;--ed-quote: #7a7468;--ed-quote-bar: #7a6a4a;--ed-code-font: "DM Mono", "Fira Mono", monospace;--ed-code-bg: #eceae5;--ed-code-border: #d8d4cc;--ed-code-text: #3a6030;--ed-marker: #7a6a4a;--ed-hr: #d8d4cc;--ed-th-bg: #f4f3f0;--ed-th-text: #7a6a4a;--ed-table-border: #d8d4cc}.jotter-editor[data-theme=ink]{--ed-heading-font: "DM Mono", "Fira Mono", monospace;--ed-heading-tracking: -.01em;--ed-caret: #1a2a4a;--ed-selection: rgba(26, 42, 74, .14);--ed-h1: #0d1f3c;--ed-h2: #1a2a4a;--ed-h3: #243560;--ed-h4: #5a6888;--ed-h1-rule: 1px solid #b8c4d8;--ed-link: #1a4a8a;--ed-link-deco: rgba(26, 74, 138, .3);--ed-strong: #0a1428;--ed-em: #2a3a5a;--ed-quote: #4a5a78;--ed-quote-bar: #1a4a8a;--ed-code-font: "DM Mono", "Fira Mono", monospace;--ed-code-bg: #eef0f6;--ed-code-border: #c8cedd;--ed-code-text: #1a4a3a;--ed-marker: #1a4a8a;--ed-hr: #b8c4d8;--ed-th-bg: #eef0f6;--ed-th-text: #1a2a4a;--ed-table-border: #b8c4d8}.jotter-editor[data-theme=forest]{--ed-heading-font: "DM Mono", "Fira Mono", monospace;--ed-heading-tracking: -.01em;--ed-caret: #1a3a28;--ed-selection: rgba(26, 58, 40, .14);--ed-h1: #1a3a28;--ed-h2: #1e4430;--ed-h3: #2a5a40;--ed-h4: #5a7a68;--ed-h1-rule: 1px solid #b0ccc0;--ed-link: #1a6040;--ed-link-deco: rgba(26, 96, 64, .3);--ed-strong: #0e2018;--ed-em: #2a4038;--ed-quote: #3a5a48;--ed-quote-bar: #1a6040;--ed-code-font: "DM Mono", "Fira Mono", monospace;--ed-code-bg: #eef5f0;--ed-code-border: #c0d8c8;--ed-code-text: #1a5828;--ed-marker: #1a6040;--ed-hr: #b0ccc0;--ed-th-bg: #eef5f0;--ed-th-text: #1a3a28;--ed-table-border: #b0ccc0}.jotter-editor .jotter-video-wrap{position:relative;padding-bottom:56.25%;height:0;overflow:hidden;margin:.75em 0;border-radius:var(--jotter-radius);border:1px solid var(--jotter-border)}.jotter-editor .jotter-video-wrap iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none;z-index:1}.jotter-editor .jotter-embed-wrap{position:relative;display:block;width:100%;margin:.75em 0}.jotter-media-frame{position:absolute;display:none;box-sizing:border-box;border:2px solid var(--jotter-accent);border-radius:2px;pointer-events:none;z-index:5}.jotter-media-frame--visible{display:block}.jotter-resize-handle{position:absolute;width:10px;height:10px;background:var(--jotter-bg);border:2px solid var(--jotter-accent);border-radius:50%;pointer-events:auto;transform:translate(-50%,-50%)}.jotter-resize-handle--nw{top:0;left:0;cursor:nwse-resize}.jotter-resize-handle--ne{top:0;left:100%;cursor:nesw-resize}.jotter-resize-handle--sw{top:100%;left:0;cursor:nesw-resize}.jotter-resize-handle--se{top:100%;left:100%;cursor:nwse-resize}.jotter-resize-handle--w{top:50%;left:0;cursor:ew-resize}.jotter-resize-handle--e{top:50%;left:100%;cursor:ew-resize}.jotter--source-mode .jotter-media-frame{display:none!important}.jotter-media-shield{position:absolute;inset:0;z-index:2;background:transparent;cursor:pointer}.jotter-status{display:flex;align-items:center;gap:16px;padding:0 14px;height:var(--jotter-status-h);min-height:var(--jotter-status-h);background:var(--jotter-surface);border-top:1px solid var(--jotter-border);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--jotter-text-muted)}.jotter-status-mode{margin-left:auto;color:var(--jotter-accent);font-weight:600}.jotter-popup{display:none;position:fixed;z-index:9999;background:#fff;border:1px solid #d8d4cc;border-radius:5px;box-shadow:0 8px 24px #0000001f,0 2px 6px #00000012;min-width:220px;max-width:360px}.jotter-popup--visible{display:block}.jotter-popup-inner{padding:14px}.jotter-popup-title{font-family:var(--jotter-font-ui);font-size:10px;text-transform:uppercase;letter-spacing:.12em;color:#9a9488;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid #d8d4cc}.jotter-popup-hint{font-size:11px;color:#9a9488;margin-top:8px;text-align:center;letter-spacing:.04em}.jotter-popup-form{display:flex;flex-direction:column;gap:6px;min-width:280px}.jotter-popup-label{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:#9a9488;margin-top:4px}.jotter-popup-input,.jotter-popup-select,.jotter-popup-textarea{width:100%;background:#f8f7f4;border:1px solid #d8d4cc;border-radius:3px;color:#1e1c18;font-family:DM Mono,Fira Mono,monospace;font-size:12px;padding:7px 9px;outline:none;transition:border-color .12s;resize:vertical}.jotter-popup-input:focus,.jotter-popup-select:focus,.jotter-popup-textarea:focus{border-color:#7a6a4a}.jotter-popup-select{appearance:auto;cursor:pointer;resize:none}.jotter-input--error{border-color:#b85050!important}.jotter-popup-submit{margin-top:6px;padding:7px 14px;background:#7a6a4a14;border:1px solid #7a6a4a;border-radius:3px;color:#7a6a4a;font-family:DM Mono,Fira Mono,monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;cursor:pointer;transition:background .12s}.jotter-popup-submit:hover{background:#7a6a4a29}.jotter-table-grid{display:grid;gap:3px}.jotter-table-cell{width:18px;height:18px;border:1px solid #d8d4cc;border-radius:2px;cursor:pointer;transition:border-color 80ms,background 80ms}.jotter-table-cell--active{background:#7a6a4a24;border-color:#7a6a4a}.jotter-char-grid{display:flex;flex-wrap:wrap;gap:2px;max-width:300px;max-height:220px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#d8d4cc transparent}.jotter-char-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid #d8d4cc;border-radius:3px;background:transparent;color:#1e1c18;font-size:14px;cursor:pointer;transition:background 80ms,border-color 80ms,color 80ms;flex-shrink:0}.jotter-char-btn:hover{background:#7a6a4a1a;border-color:#7a6a4a;color:#7a6a4a}.jotter-lorem-btn{display:block;width:100%;padding:9px 12px;background:transparent;border:1px solid #d8d4cc;border-radius:3px;color:#1e1c18;font-family:DM Mono,Fira Mono,monospace;font-size:11px;letter-spacing:.04em;text-align:left;cursor:pointer;margin-bottom:4px;transition:background .1s,border-color .1s,color .1s}.jotter-lorem-btn:hover{background:#7a6a4a12;border-color:#7a6a4a;color:#7a6a4a}.jotter-btn:focus-visible,.jotter-select:focus-visible,.jotter-char-btn:focus-visible,.jotter-lorem-btn:focus-visible,.jotter-popup-submit:focus-visible,.jotter-table-cell:focus-visible{outline:2px solid var(--jotter-accent);outline-offset:1px}
@@ -1,5 +1,5 @@
1
- var _={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(_).forEach(Object.freeze);Object.freeze(_);var n=_,h={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(h).forEach(Object.freeze);Object.freeze(h);var g=h.full,C={image:"onRequestImage",link:"onRequestLink",video:"onRequestVideo",embed:"onRequestEmbed"},f=[{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"}],k=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Lucida Console","Palatino Linotype","Tahoma","Times New Roman","Trebuchet MS","Verdana"],v=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],E=["\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"],S=["\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"],b=[{id:"default",label:"Default"},{id:"warm",label:"Warm"},{id:"ink",label:"Ink / Navy"},{id:"forest",label:"Forest"}],y=[{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>"}],p=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||g).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",f.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),k.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),v.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",b.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(),m=l.value;this._commitPopup(()=>{t?(t.href=d,m?t.target=m: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:m}))})})),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(E)),e}_popupSpecialChar(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Special Characters")),e.appendChild(this._charGrid(S)),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")),y.forEach(t=>{let o=document.createElement("button");o.type="button",o.className="jotter-lorem-btn",o.textContent=t.label,o.addEventListener("mousedown",i=>{i.preventDefault(),this._commitPopup(()=>document.execCommand(t.isHTML?"insertHTML":"insertText",!1,t.text))}),e.appendChild(o)}),e}_popupTitle(e){let t=document.createElement("div");return t.className="jotter-popup-title",t.textContent=e,t}_makeField(e,t,o,i){let s=document.createElement("label");s.className="jotter-popup-label",s.textContent=t;let r=document.createElement("input");return r.type=o,r.className="jotter-popup-input",r.placeholder=i,e.appendChild(s),e.appendChild(r),r}_makeSubmitBtn(e,t){let o=document.createElement("button");return o.type="button",o.className="jotter-popup-submit",o.textContent=e,o.addEventListener("click",t),o}_esc(e){return String(e).replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_imageHTML({src:e,alt:t,width:o}){let i=o?`max-width:${o}`:"max-width:100%";return`<img src="${this._esc(e)}" alt="${this._esc(t||"")}" style="${this._esc(i)}">`}_linkHTML({href:e,text:t,title:o,target:i}){let s=`href="${this._esc(e)}"`;return i&&(s+=` target="${this._esc(i)}"`),o&&(s+=` title="${this._esc(o)}"`),`<a ${s}>${this._esc(t||e)}</a>`}_videoHTML(e){return/^[A-Za-z0-9_-]{11}$/.test(e)?`<div class="jotter-video-wrap"><iframe src="https://www.youtube.com/embed/${e}" frameborder="0" allowfullscreen loading="lazy" title="YouTube video"></iframe></div><p><br></p>`:null}_embedHTML(e){return e+"<p><br></p>"}_resolverFor(e){let t=C[e],o=t?this._options[t]:null;return typeof o=="function"?o:null}async _runResolver(e,t){let o=this._resolverContext(e);this.beginExternalUI();let i=null;try{i=await t(o)}catch{i=null}if(this._destroyed)return;let s=i==null||i===!1?null:this._resolvedHTML(e,i,o);this.endExternalUI(),s&&(this._applyEdit(()=>document.execCommand("insertHTML",!1,s)),this._updateToolbarState())}_resolverContext(e){switch(e){case"image":return{src:"",alt:"",width:"",selection:this._selectedText()};case"video":return{url:"",selection:this._selectedText()};case"embed":return{html:"",selection:this._selectedText()};case"link":return this._linkContext();default:return{selection:this._selectedText()}}}_linkContext(){let e=this._anchorInSelection();if(!e){let o=this._selectedText();return{href:"",text:o,title:"",target:"",selection:o,isEdit:!1}}let t=window.getSelection();if(t){let o=document.createRange();o.selectNode(e),t.removeAllRanges(),t.addRange(o)}return{href:e.getAttribute("href")||"",text:e.textContent||"",title:e.getAttribute("title")||"",target:e.getAttribute("target")||"",selection:e.textContent||"",isEdit:!0}}_resolvedHTML(e,t,o){switch(e){case"image":{let i=typeof t=="string"?{src:t}:t,s=String(i.src||i.url||"").trim();return s?this._imageHTML({...i,src:s}):null}case"link":{let i=typeof t=="string"?{href:t}:t,s=String(i.href||i.url||"").trim();if(!s)return null;let r=String(i.text!=null?i.text:o.text||"").trim();return this._linkHTML({...i,href:s,text:r})}case"video":{let i=typeof t=="string"?{url:t}:t,s=i.id?String(i.id).trim():this._ytId(String(i.url||"").trim());return s?this._videoHTML(s):null}case"embed":{let i=String(typeof t=="string"?t:t.html||"").trim();return i?this._embedHTML(i):null}default:return null}}_selectedText(){let e=window.getSelection();return!e||!e.rangeCount?"":this._editor.contains(e.getRangeAt(0).commonAncestorContainer)?e.toString():""}_anchorInSelection(){let e=window.getSelection();if(!e||!e.rangeCount)return null;let t=e.anchorNode;if(!t||!this._editor.contains(t))return null;for(;t&&t!==this._editor;){if(t.nodeName==="A")return t;t=t.parentNode}return null}_buildStatusBar(){let e=document.createElement("div");e.className="jotter-status",this._wordCountEl=document.createElement("span"),this._wordCountEl.className="jotter-status-words",this._charCountEl=document.createElement("span"),this._charCountEl.className="jotter-status-chars";let t=document.createElement("span");return t.className="jotter-status-mode",t.textContent="HTML",e.appendChild(this._wordCountEl),e.appendChild(this._charCountEl),e.appendChild(t),e}_bindEvents(){this._editor.addEventListener("input",()=>{this._editor.querySelectorAll(":scope > div").forEach(e=>{if(e.attributes.length>0)return;let t=document.createElement("p");t.innerHTML=e.innerHTML,e.replaceWith(t)}),Array.from(this._editor.childNodes).forEach(e=>{if(e.nodeType===Node.TEXT_NODE&&e.textContent.trim()!==""){let t=window.getSelection(),o=null;if(t&&t.rangeCount){let s=t.getRangeAt(0);s.startContainer===e&&(o=s.startOffset)}let i=document.createElement("p");if(e.replaceWith(i),i.appendChild(e),o!==null){let s=document.createRange();s.setStart(e,o),s.collapse(!0),t.removeAllRanges(),t.addRange(s)}}}),this._updateStatus(),this._emitChange()}),this._editor.addEventListener("keyup",()=>this._updateToolbarState()),this._editor.addEventListener("mouseup",()=>this._updateToolbarState()),this._editor.addEventListener("focusin",e=>{this._root.classList.add("jotter--focused"),!(this._externalDepth>0)&&(this._isInternalTarget(e.relatedTarget)||(this._emit("focus"),this._options.onFocus&&this._options.onFocus()))}),this._editor.addEventListener("focusout",e=>{this._externalDepth>0||this._isInternalTarget(e.relatedTarget)||setTimeout(()=>{this._destroyed||this._externalDepth>0||this._isInternalTarget(document.activeElement)||this._popupVisible()||(this._root.classList.remove("jotter--focused"),this._emit("blur"),this._options.onBlur&&this._options.onBlur())},0)}),this._editor.addEventListener("keydown",e=>{e.key==="Tab"&&(e.preventDefault(),document.execCommand("insertHTML",!1,"&nbsp;&nbsp;&nbsp;&nbsp;"))}),this._onDocMouseDown=e=>{this._popupVisible()&&!this._popup.contains(e.target)&&!this._toolbarEl.contains(e.target)&&this._hidePopup()},document.addEventListener("mousedown",this._onDocMouseDown),this._onDocKeyDown=e=>{e.key==="Escape"&&this._popupVisible()&&(this._restoreSavedBookmark(),this._hidePopup())},document.addEventListener("keydown",this._onDocKeyDown)}_isInternalTarget(e){return!!e&&(this._root.contains(e)||this._popup.contains(e))}_toggleSourceMode(){this._sourceMode=!this._sourceMode,this._sourceMode?(this._dropBookmarks(),this._source.value=this._prettyHTML(this._richHTML()),this._editor.style.display="none",this._source.style.display="block"):(this._editor.innerHTML=this._sanitize(this._source.value),this._source.style.display="none",this._editor.style.display="",this._updateStatus(),this._emitChange()),this._root.classList.toggle("jotter--source-mode",this._sourceMode);let e=this._toolbarEl.querySelector('[data-custom="toggleSource"]');e&&e.classList.toggle("jotter-btn--active",this._sourceMode)}_prettyHTML(e){let t=0,o=" ",i=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),s=new Set(["a","abbr","acronym","b","bdo","big","br","button","cite","code","dfn","em","i","img","input","kbd","label","map","object","output","q","samp","select","small","span","strong","sub","sup","textarea","time","tt","u","var"]);return e.replace(/>\s+</g,"><").replace(/(<[^>]+>)/g,`
1
+ var _={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(_).forEach(Object.freeze);Object.freeze(_);var n=_,m={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(m).forEach(Object.freeze);Object.freeze(m);var S=m.full,y={image:"onRequestImage",link:"onRequestLink",video:"onRequestVideo",embed:"onRequestEmbed"},v=[{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"}],x=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Lucida Console","Palatino Linotype","Tahoma","Times New Roman","Trebuchet MS","Verdana"],L=[8,9,10,11,12,14,16,18,20,24,28,32,36,48,72],T=["\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"],C=[{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>"}],h=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._selectedMedia=null,this._resizeState=null,this._onResizeMove=this._onResizeMove.bind(this),this._onResizeEnd=this._onResizeEnd.bind(this),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");try{document.execCommand("enableObjectResizing",!1,!1)}catch{}try{document.execCommand("enableInlineTableEditing",!1,!1)}catch{}this._ensureMediaShields(),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._mediaFrame=this._buildMediaFrame(),this._editorWrap.appendChild(this._editor),this._editorWrap.appendChild(this._source),this._editorWrap.appendChild(this._mediaFrame),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||S).forEach(t=>{let i=this._buildAction(t);i&&e.appendChild(i)}),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}_bindActivation(e,t){e.addEventListener("mousedown",i=>{i.preventDefault(),t(!1)}),e.addEventListener("keydown",i=>{i.key!=="Enter"&&i.key!==" "&&i.key!=="Spacebar"||i.repeat||(i.preventDefault(),t(!0))})}_returnFocus(e,t){t&&this._isInternalTarget(document.activeElement)&&e.focus()}_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 i=document.createElement("span");i.className="material-icons",i.textContent=e.icon,t.appendChild(i)}return this._bindActivation(t,i=>{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 o=window.prompt(e.prompt);o&&document.execCommand(e.cmd,!1,o)}else document.execCommand(e.cmd,!1,null)}),this._updateToolbarState(),this._returnFocus(t,i)}),t}_bindSelectCaret(e){let t={viaKeyboard:!1};return e.addEventListener("mousedown",()=>{t.viaKeyboard=!1,this._saveBookmark()}),e.addEventListener("keydown",i=>{i.key==="Tab"||i.key==="Escape"||(t.viaKeyboard=!0,this._savedBookmark==null&&this._saveBookmark())}),t}_buildBlockFormatSelect(){let e=document.createElement("select");e.className="jotter-select",e.title="Block format",e.dataset.id="blockformat",v.forEach(({label:i,tag:o})=>{let s=document.createElement("option");s.value=o,s.textContent=i,e.appendChild(s)});let t=this._bindSelectCaret(e);return e.addEventListener("change",()=>{this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("formatBlock",!1,e.value)),this._returnFocus(e,t.viaKeyboard)}),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");t.value="",t.textContent="Font",e.appendChild(t),x.forEach(o=>{let s=document.createElement("option");s.value=o,s.textContent=o,s.style.fontFamily=o,e.appendChild(s)});let i=this._bindSelectCaret(e);return e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand("fontName",!1,e.value)),this._returnFocus(e,i.viaKeyboard))}),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");t.value="",t.textContent="Size",e.appendChild(t),L.forEach(o=>{let s=document.createElement("option");s.value=o,s.textContent=`${o}px`,e.appendChild(s)});let i=this._bindSelectCaret(e);return e.addEventListener("change",()=>{e.value&&(this._restoreSavedBookmark(),this._applyEdit(()=>this._applyFontSize(e.value)),this._returnFocus(e,i.viaKeyboard))}),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:i})=>{let o=document.createElement("option");o.value=t,o.textContent=i,e.appendChild(o)}),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 i=document.createElement("button");i.type="button",i.className="jotter-btn jotter-color-btn",i.dataset.cmd=e.cmd,i.title=e.title,i.setAttribute("aria-label",e.title);let o=document.createElement("span");o.className="material-icons",o.textContent=e.icon,i.appendChild(o);let s=document.createElement("span");s.className="jotter-color-swatch";let r=e.cmd==="foreColor"?this._lastForeColor:this._lastHiliteColor;s.style.background=r,i.appendChild(s);let a=document.createElement("input");a.type="color",a.className="jotter-color-input",a.value=r,a.tabIndex=-1;let u=!1;return a.addEventListener("change",()=>{let p=a.value;s.style.background=p,e.cmd==="foreColor"?this._lastForeColor=p:this._lastHiliteColor=p,this._restoreSavedBookmark(),this._applyEdit(()=>document.execCommand(e.cmd,!1,p)),this._returnFocus(i,u)}),this._bindActivation(i,p=>{u=p,this._saveBookmark(),a.click()}),t.appendChild(i),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 i=document.createElement("span");return i.className="material-icons",i.textContent=e.icon,t.appendChild(i),this._bindActivation(t,o=>{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),o&&this._focusPopup()}),t}_focusPopup(){let e=this._popup.querySelector('input, textarea, select, button, [tabindex]:not([tabindex="-1"])');e&&e.focus()}_buildPopupContainer(){let e=document.createElement("div");return e.className="jotter-popup",e.setAttribute("role","dialog"),e}_showPopup(e,t,i){this._popup.innerHTML="",this._popup.appendChild(t),this._popup.dataset.popupId=i,this._popup.classList.add("jotter-popup--visible");let o=e.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")})}_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 i=10,o=8,s=document.createElement("div");s.className="jotter-table-grid",s.style.gridTemplateColumns=`repeat(${i}, 1fr)`,s.setAttribute("role","grid"),s.setAttribute("aria-label","Table size");let r=document.createElement("div");r.className="jotter-popup-hint",r.textContent="Hover or arrow to select size";let a=[],u=(c,l)=>{r.textContent=`${c+1} \xD7 ${l+1} table`,a.forEach(d=>{d.classList.toggle("jotter-table-cell--active",+d.dataset.r<=c&&+d.dataset.c<=l)})};for(let c=0;c<o;c++)for(let l=0;l<i;l++){let d=document.createElement("span");d.className="jotter-table-cell",d.dataset.r=c,d.dataset.c=l,d.setAttribute("role","gridcell"),d.setAttribute("aria-label",`${c+1} by ${l+1} table`),d.tabIndex=c===0&&l===0?0:-1,d.addEventListener("mouseenter",()=>u(c,l)),d.addEventListener("focus",()=>u(c,l)),d.addEventListener("click",()=>this._insertTable(c+1,l+1)),a.push(d),s.appendChild(d)}let p={ArrowRight:[0,1],ArrowLeft:[0,-1],ArrowDown:[1,0],ArrowUp:[-1,0]};return s.addEventListener("keydown",c=>{let l=a.indexOf(document.activeElement)===-1?null:document.activeElement;if(!l)return;let d=+l.dataset.r,g=+l.dataset.c;if(c.key==="Enter"||c.key===" "||c.key==="Spacebar"){c.preventDefault(),this._insertTable(d+1,g+1);return}let f=p[c.key];if(!f)return;c.preventDefault();let k=Math.min(o-1,Math.max(0,d+f[0])),E=Math.min(i-1,Math.max(0,g+f[1])),b=a[k*i+E];b!==l&&(l.tabIndex=-1,b.tabIndex=0,b.focus())}),e.appendChild(s),e.appendChild(r),e}_commitPopup(e){this._restoreSavedBookmark(),this._hidePopup(),this._applyEdit(e)}_insertTable(e,t){let i="<table><tbody>";for(let o=0;o<e;o++){i+="<tr>";for(let s=0;s<t;s++)i+=o===0?"<th><br></th>":"<td><br></td>";i+="</tr>"}i+="</tbody></table><p><br></p>",this._commitPopup(()=>document.execCommand("insertHTML",!1,i))}_popupLink(){let e=document.createElement("div");e.className="jotter-popup-inner jotter-popup-form",e.appendChild(this._popupTitle("Insert Link"));let t=this._anchorInSelection(),i=this._selectedText(),o=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 u=document.createElement("select");return u.className="jotter-popup-select",[["(same window)",""],["New tab (_blank)","_blank"],["Parent frame (_parent)","_parent"],["Top frame (_top)","_top"]].forEach(([p,c])=>{let l=document.createElement("option");l.value=c,l.textContent=p,u.appendChild(l)}),e.appendChild(a),e.appendChild(u),t?(o.value=t.getAttribute("href")||"",s.value=t.textContent||"",r.value=t.getAttribute("title")||"",u.value=t.getAttribute("target")||""):i&&(s.value=i),e.appendChild(this._makeSubmitBtn(t?"Update Link":"Insert Link",()=>{let p=o.value.trim();if(!p)return;let c=s.value.trim()||i||p,l=r.value.trim(),d=u.value;this._commitPopup(()=>{t?(t.href=p,d?t.target=d:t.removeAttribute("target"),l?t.title=l:t.removeAttribute("title"),t.textContent=c):document.execCommand("insertHTML",!1,this._linkHTML({href:p,text:c,title:l,target:d}))})})),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"),i=this._makeField(e,"Alt text","text","Descriptive text"),o=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:i.value.trim(),width:o.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 i=this._ytId(t.value.trim());if(!i){t.classList.add("jotter-input--error");return}t.classList.remove("jotter-input--error"),this._commitPopup(()=>document.execCommand("insertHTML",!1,this._videoHTML(i)))})),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 i=e.match(t);if(i)return i[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 i=document.createElement("textarea");return i.className="jotter-popup-textarea",i.placeholder='<iframe src="..." ...></iframe>',i.rows=4,e.appendChild(t),e.appendChild(i),e.appendChild(this._makeSubmitBtn("Insert",()=>{let o=i.value.trim();o&&this._commitPopup(()=>document.execCommand("insertHTML",!1,this._embedHTML(o)))})),e}_popupSymbol(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Symbol")),e.appendChild(this._charGrid(T)),e}_popupSpecialChar(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Special Characters")),e.appendChild(this._charGrid(M)),e}_charGrid(e){let t=document.createElement("div");return t.className="jotter-char-grid",e.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")}`,this._bindActivation(o,()=>{this._commitPopup(()=>document.execCommand("insertText",!1,i))}),t.appendChild(o)}),t}_popupLorem(){let e=document.createElement("div");return e.className="jotter-popup-inner",e.appendChild(this._popupTitle("Insert Lorem Ipsum")),w.forEach(t=>{let i=document.createElement("button");i.type="button",i.className="jotter-lorem-btn",i.textContent=t.label,this._bindActivation(i,()=>{this._commitPopup(()=>document.execCommand(t.isHTML?"insertHTML":"insertText",!1,t.text))}),e.appendChild(i)}),e}_popupTitle(e){let t=document.createElement("div");return t.className="jotter-popup-title",t.textContent=e,t}_makeField(e,t,i,o){let s=document.createElement("label");s.className="jotter-popup-label",s.textContent=t;let r=document.createElement("input");return r.type=i,r.className="jotter-popup-input",r.placeholder=o,e.appendChild(s),e.appendChild(r),r}_makeSubmitBtn(e,t){let i=document.createElement("button");return i.type="button",i.className="jotter-popup-submit",i.textContent=e,i.addEventListener("click",t),i}_esc(e){return String(e).replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}_imageHTML({src:e,alt:t,width:i}){let o=i?`max-width:${i}`:"max-width:100%";return`<img src="${this._esc(e)}" alt="${this._esc(t||"")}" style="${this._esc(o)}">`}_linkHTML({href:e,text:t,title:i,target:o}){let s=`href="${this._esc(e)}"`;return o&&(s+=` target="${this._esc(o)}"`),i&&(s+=` title="${this._esc(i)}"`),`<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`<div class="jotter-embed-wrap">${e}</div><p><br></p>`}_resolverFor(e){let t=y[e],i=t?this._options[t]:null;return typeof i=="function"?i:null}async _runResolver(e,t){let i=this._resolverContext(e);this.beginExternalUI();let o=null;try{o=await t(i)}catch{o=null}if(this._destroyed)return;let s=o==null||o===!1?null:this._resolvedHTML(e,o,i);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 i=this._selectedText();return{href:"",text:i,title:"",target:"",selection:i,isEdit:!1}}let t=window.getSelection();if(t){let i=document.createRange();i.selectNode(e),t.removeAllRanges(),t.addRange(i)}return{href:e.getAttribute("href")||"",text:e.textContent||"",title:e.getAttribute("title")||"",target:e.getAttribute("target")||"",selection:e.textContent||"",isEdit:!0}}_resolvedHTML(e,t,i){switch(e){case"image":{let o=typeof t=="string"?{src:t}:t,s=String(o.src||o.url||"").trim();return s?this._imageHTML({...o,src:s}):null}case"link":{let o=typeof t=="string"?{href:t}:t,s=String(o.href||o.url||"").trim();if(!s)return null;let r=String(o.text!=null?o.text:i.text||"").trim();return this._linkHTML({...o,href:s,text:r})}case"video":{let o=typeof t=="string"?{url:t}:t,s=o.id?String(o.id).trim():this._ytId(String(o.url||"").trim());return s?this._videoHTML(s):null}case"embed":{let o=String(typeof t=="string"?t:t.html||"").trim();return o?this._embedHTML(o):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}_buildMediaFrame(){let e=document.createElement("div");return e.className="jotter-media-frame",["nw","ne","sw","se","w","e"].forEach(t=>{let i=document.createElement("div");i.className="jotter-resize-handle jotter-resize-handle--"+t,i.dataset.handle=t,i.tabIndex=-1,e.appendChild(i)}),e.addEventListener("mousedown",t=>{let i=t.target.closest(".jotter-resize-handle");!i||!this._selectedMedia||(t.preventDefault(),this._beginResize(i.dataset.handle,t))}),e}_ensureMediaShields(){this._editor.querySelectorAll(".jotter-video-wrap, .jotter-embed-wrap").forEach(e=>{if(e.querySelector(":scope > .jotter-media-shield"))return;let t=document.createElement("div");t.className="jotter-media-shield",e.appendChild(t)})}_mediaElementFromTarget(e){if(!this._editor.contains(e))return null;let t=e.closest("img");if(t&&this._editor.contains(t))return t;let i=e.closest(".jotter-video-wrap, .jotter-embed-wrap");return i&&this._editor.contains(i)?i:null}_selectMedia(e){if(this._selectedMedia===e)return;this._deselectMedia(),this._selectedMedia=e,this._positionMediaFrame(),this._mediaFrame.classList.add("jotter-media-frame--visible"),this._editor.focus({preventScroll:!0});let t=document.createRange();t.setStartBefore(e),t.collapse(!0);let i=window.getSelection();i.removeAllRanges(),i.addRange(t),this._updateToolbarState()}_deselectMedia(){this._resizeState&&this._abortResize(),this._selectedMedia&&(this._selectedMedia=null,this._mediaFrame.classList.remove("jotter-media-frame--visible"))}_positionMediaFrame(){let e=this._selectedMedia;if(!e)return;let t=this._editorWrap.getBoundingClientRect(),i=e.getBoundingClientRect();this._mediaFrame.style.top=i.top-t.top+this._editorWrap.scrollTop+"px",this._mediaFrame.style.left=i.left-t.left+this._editorWrap.scrollLeft+"px",this._mediaFrame.style.width=i.width+"px",this._mediaFrame.style.height=i.height+"px"}_deleteSelectedMedia(){let e=this._selectedMedia;if(!e)return;this._deselectMedia();let t=document.createRange();t.selectNode(e);let i=window.getSelection();i.removeAllRanges(),i.addRange(t),this._applyEdit(()=>document.execCommand("delete",!1,null)),this._updateToolbarState()}_beginResize(e,t){let i=this._selectedMedia,o=i.getBoundingClientRect(),s=e.includes("w")?-1:1;this._resizeState={el:i,sign:s,startX:t.clientX,startWidth:o.width,startInlineWidth:i.style.width,before:this._richHTML()},document.addEventListener("mousemove",this._onResizeMove),document.addEventListener("mouseup",this._onResizeEnd,{once:!0})}_onResizeMove(e){let t=this._resizeState;if(!t)return;let i=(e.clientX-t.startX)*t.sign,o=40,s=Math.max(o,this._editor.clientWidth),r=Math.max(o,Math.min(s,Math.round(t.startWidth+i)));t.el.style.width=r+"px",t.el.style.removeProperty("max-width"),this._positionMediaFrame()}_onResizeEnd(){document.removeEventListener("mousemove",this._onResizeMove);let e=this._resizeState;this._resizeState=null,e&&(this._updateStatus(),this._richHTML()!==e.before&&this._emitChange())}_abortResize(){let e=this._resizeState;e&&(document.removeEventListener("mousemove",this._onResizeMove),document.removeEventListener("mouseup",this._onResizeEnd),e.el.style.width=e.startInlineWidth,this._resizeState=null,this._positionMediaFrame())}_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(),i=null;if(t&&t.rangeCount){let s=t.getRangeAt(0);s.startContainer===e&&(i=s.startOffset)}let o=document.createElement("p");if(e.replaceWith(o),o.appendChild(e),i!==null){let s=document.createRange();s.setStart(e,i),s.collapse(!0),t.removeAllRanges(),t.addRange(s)}}}),this._ensureMediaShields(),this._selectedMedia&&this._positionMediaFrame(),this._updateStatus(),this._emitChange()}),this._editor.addEventListener("keyup",()=>this._updateToolbarState()),this._editor.addEventListener("mouseup",()=>this._updateToolbarState()),this._editor.addEventListener("mousedown",e=>{let t=this._mediaElementFromTarget(e.target);t&&(e.preventDefault(),this._selectMedia(t))}),this._onWindowResize=()=>{this._selectedMedia&&this._positionMediaFrame()},window.addEventListener("resize",this._onWindowResize),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._deselectMedia(),!(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=>{if((e.key==="Backspace"||e.key==="Delete")&&this._selectedMedia){e.preventDefault(),this._deleteSelectedMedia();return}e.key==="Tab"&&(e.preventDefault(),document.execCommand("insertHTML",!1,"&nbsp;&nbsp;&nbsp;&nbsp;"))}),this._onDocMouseDown=e=>{this._popupVisible()&&!this._popup.contains(e.target)&&!this._toolbarEl.contains(e.target)&&this._hidePopup(),this._selectedMedia&&!this._mediaFrame.contains(e.target)&&e.target!==this._selectedMedia&&!this._selectedMedia.contains(e.target)&&this._deselectMedia()},document.addEventListener("mousedown",this._onDocMouseDown),this._onDocKeyDown=e=>{if(e.key==="Escape"){if(this._resizeState){this._abortResize();return}if(this._selectedMedia){this._deselectMedia();return}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._deselectMedia(),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._ensureMediaShields(),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,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 e.replace(/>\s+</g,"><").replace(/(<[^>]+>)/g,`
2
2
  $1
3
3
  `).split(`
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 m=o.repeat(t)+r;return c&&!d&&!i.has(c)&&!u&&!s.has(c)&&t++,m}).join(`
5
- `)}_sanitize(e){let t=new DOMParser().parseFromString(e,"text/html");return t.querySelectorAll("script").forEach(o=>o.remove()),t.querySelectorAll("[data-jotter-bookmark]").forEach(o=>o.remove()),t.querySelectorAll("*").forEach(o=>{Array.from(o.attributes).forEach(i=>{(i.name.startsWith("on")||["href","src","action","formaction","data"].includes(i.name)&&/^\s*javascript:/i.test(i.value))&&o.removeAttribute(i.name)})}),t.body.innerHTML}_toggleInlineCode(){let e=window.getSelection();if(!e||e.rangeCount===0)return;let t=e.getRangeAt(0),o=t.commonAncestorContainer;o.nodeType===3&&(o=o.parentNode);let i=o.closest?o.closest("code"):null;if(i){let s=i.parentNode;for(;i.firstChild;)s.insertBefore(i.firstChild,i);s.removeChild(i)}else if(t.collapsed){let s=document.createElement("code");s.innerHTML="&#8203;",t.insertNode(s);let r=document.createRange();r.setStart(s,0),r.setEnd(s,s.childNodes.length),e.removeAllRanges(),e.addRange(r)}else{let s=document.createElement("code");try{t.surroundContents(s)}catch{let a=t.extractContents();s.appendChild(a),t.insertNode(s)}}}_applyFontSize(e){let t=window.getSelection();if(!t||t.rangeCount===0)return;let o=t.getRangeAt(0);if(o.collapsed)return;let i=document.createElement("span");i.style.fontSize=e+"px";try{o.surroundContents(i)}catch{let r=o.extractContents();i.appendChild(r),o.insertNode(i)}}async _pasteFromClipboard(){try{if(navigator.clipboard&&navigator.clipboard.readText){let e=await navigator.clipboard.readText();document.execCommand("insertText",!1,e)}else document.execCommand("paste")}catch{}}_updateToolbarState(){this._toolbarEl.querySelectorAll(".jotter-btn[data-cmd]").forEach(t=>{try{t.classList.toggle("jotter-btn--active",document.queryCommandState(t.dataset.cmd))}catch{}});let e=this._toolbarEl.querySelector('[data-id="blockformat"]');if(e){let t=document.queryCommandValue("formatBlock").toLowerCase().replace(/[<>]/g,""),o=f.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=b.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}};p.toolbar=h.full;p.actions=_;p.presets=h;var L=p;export{p as JotterJS,L as default};
4
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let a=r.match(/^<\/(\w+)/),u=r.match(/^<(\w+)/),p=r.endsWith("/>"),c=u?u[1].toLowerCase():null,l=a?a[1].toLowerCase():null;l&&!s.has(l)&&(t=Math.max(0,t-1));let d=i.repeat(t)+r;return c&&!p&&!o.has(c)&&!l&&!s.has(c)&&t++,d}).join(`
5
+ `)}_sanitize(e){let t=new DOMParser().parseFromString(e,"text/html");return t.querySelectorAll("script").forEach(i=>i.remove()),t.querySelectorAll("[data-jotter-bookmark], .jotter-media-shield").forEach(i=>i.remove()),t.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)})}),t.body.innerHTML}_toggleInlineCode(){let e=window.getSelection();if(!e||e.rangeCount===0)return;let t=e.getRangeAt(0),i=t.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(t.collapsed){let s=document.createElement("code");s.innerHTML="&#8203;",t.insertNode(s);let r=document.createRange();r.setStart(s,0),r.setEnd(s,s.childNodes.length),e.removeAllRanges(),e.addRange(r)}else{let s=document.createElement("code");try{t.surroundContents(s)}catch{let a=t.extractContents();s.appendChild(a),t.insertNode(s)}}}_applyFontSize(e){let t=window.getSelection();if(!t||t.rangeCount===0)return;let i=t.getRangeAt(0);if(i.collapsed)return;let o=document.createElement("span");o.style.fontSize=e+"px";try{i.surroundContents(o)}catch{let r=i.extractContents();o.appendChild(r),i.insertNode(o)}}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,""),i=v.find(o=>o.tag===t);i&&(e.value=i.tag)}}_updateStatus(){let e=this._editor.innerText||"",t=e.trim()===""?0:e.trim().split(/\s+/).length,i=e.replace(/\n/g,"").length;this._wordCountEl.textContent=`${t} word${t!==1?"s":""}`,this._charCountEl.textContent=`${i} char${i!==1?"s":""}`}_richHTML(){let e=this._editor.cloneNode(!0);return e.querySelectorAll("[data-jotter-bookmark], .jotter-media-shield").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:i,end:o}=t,s=this._editor.contains(i)&&(!o||this._editor.contains(o)),r=null;return s&&(r=document.createRange(),r.setStartAfter(i),o?r.setEndBefore(o):r.collapse(!0)),i.parentNode&&i.remove(),o&&o.parentNode&&o.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(i=>i(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,i=this._richHTML();e(),this._updateStatus(),this._changeCount===t&&this._richHTML()!==i&&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._deselectMedia(),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 i="jbm"+ ++this._bmSeq;if(this._editor.childNodes.length===0)return this._bookmarks.set(i,{atStart:!0}),i;let o=this._makeMarker(i),s=t.collapsed?null:this._makeMarker(i);if(s){let u=t.cloneRange();u.collapse(!1),u.insertNode(s)}let r=t.cloneRange();r.collapse(!0),r.insertNode(o),this._bookmarks.set(i,{start:o,end:s});let a=document.createRange();return a.setStartAfter(o),s?a.setEndBefore(s):a.collapse(!0),e.removeAllRanges(),e.addRange(a),i}restoreSelection(e){let t=this._takeRange(e);if(!t)return this;this._sourceMode||this._editor.focus();let i=window.getSelection();return i.removeAllRanges(),i.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._deselectMedia(),this._dropBookmarks(),this._editor.innerHTML=this._sanitize(e),this._ensureMediaShields(),this._updateStatus(),this}setTheme(e){let i=C.find(o=>o.id===e)?e:"default";return this._editor.dataset.theme=i,this._options.theme=i,this._themeSelect&&(this._themeSelect.value=i),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(i=>i!==t)),this}destroy(){let e=this.getHTML();return this._destroyed=!0,this._deselectMedia(),this._hidePopup(),this._dropBookmarks(),this._externalBookmark=null,this._externalDepth=0,document.removeEventListener("mousedown",this._onDocMouseDown),document.removeEventListener("keydown",this._onDocKeyDown),window.removeEventListener("resize",this._onWindowResize),this._popup.parentNode&&this._popup.parentNode.removeChild(this._popup),this._target.classList.remove("jotter-host"),this._target.innerHTML=e,this._listeners={},e}};h.toolbar=m.full;h.actions=_;h.presets=m;var N=h;export{h as JotterJS,N as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jotterjs",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "A lightweight, vanilla JS rich-text editor component. No dependencies.",
5
5
  "main": "dist/jotter.min.js",
6
6
  "module": "dist/jotter.js",