rhino-editor-slash-commands
Version:
Notion-like slash commands for rhino-editor with modern UI and comprehensive formatting options
15 lines (14 loc) • 14.4 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).RhinoSlashCommands={})}(this,(function(e){"use strict";
/**
* ========================================================================
* RHINO EDITOR SLASH COMMANDS
* ========================================================================
*
* Notion-like slash commands for rhino-editor instances.
* Replaces the default toolbar with a dropdown that appears when typing "/"
* and provides comprehensive formatting options.
*
* @version 1.0.0
* @author Your Name
* @license MIT
*/const t={DROPDOWN:{OFFSET_Y:5,SCROLL_BEHAVIOR:"nearest"},TIMING:{RETRY_INTERVAL:200,MAX_RETRIES:10,INITIAL_DELAY:100,TURBO_DELAY:500},SELECTORS:{RHINO_EDITOR:"rhino-editor",FILE_INPUT:"#file-input",PARAGRAPH:"p"},CLASSES:{DROPDOWN:"slash-commands-dropdown",ITEM:"slash-command-item",SELECTED:"selected",EMPTY:"is-empty"},KEYS:{ESCAPE:"Escape",ARROW_DOWN:"ArrowDown",ARROW_UP:"ArrowUp",ENTER:"Enter",TAB:"Tab"}};let o=null,i=!1,n=-1;const a={safeExecute(e,t="Unknown"){try{return e()}catch(e){return console.warn(`[SlashCommands] Error in ${t}:`,e),null}},filterCommands(e,t){if(!t.trim())return e;const o=t.toLowerCase();return e.filter((e=>{const t=e.title.toLowerCase();if(t.includes(o))return!0;return t.split(" ").some((e=>e.startsWith(o)))}))},findFileInput(e){const o=e.view.dom.closest(t.SELECTORS.RHINO_EDITOR);return o?o.shadowRoot?.querySelector(t.SELECTORS.FILE_INPUT)||o.querySelector('input[type="file"]'):null}},s=[{title:"Heading 1",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" d="M6.25 4a.75.75 0 01.75.75V11h10V4.75a.75.75 0 011.5 0v14.5a.75.75 0 01-1.5 0V12.5H7v6.75a.75.75 0 01-1.5 0V4.75A.75.75 0 016.25 4z"></path></svg>',command:e=>e.chain().focus().toggleHeading({level:1}).run()},{title:"Heading 2",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" d="M6.25 4a.75.75 0 01.75.75V11h10V4.75a.75.75 0 011.5 0v14.5a.75.75 0 01-1.5 0V12.5H7v6.75a.75.75 0 01-1.5 0V4.75A.75.75 0 016.25 4z"></path></svg>',command:e=>e.chain().focus().toggleHeading({level:2}).run()},{title:"Bold",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" d="M6 4.75c0-.69.56-1.25 1.25-1.25h5a4.75 4.75 0 013.888 7.479A5 5 0 0114 20.5H7.25c-.69 0-1.25-.56-1.25-1.25V4.75zM8.5 13v5H14a2.5 2.5 0 000-5H8.5zm0-2.5h3.751A2.25 2.25 0 0012.25 6H8.5v4.5z"></path></svg>',command:e=>e.chain().focus().toggleBold().run()},{title:"Italic",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" d="M10 4.75a.75.75 0 01.75-.75h8.5a.75.75 0 010 1.5h-3.514l-5.828 13h3.342a.75.75 0 010 1.5h-8.5a.75.75 0 010-1.5h3.514l5.828-13H10.75a.75.75 0 01-.75-.75z"></path></svg>',command:e=>e.chain().focus().toggleItalic().run()},{title:"Strikethrough",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" d="M12.36 5C9.37 5 8.105 6.613 8.105 7.848c0 .411.072.744.193 1.02a.75.75 0 01-1.373.603 3.993 3.993 0 01-.32-1.623c0-2.363 2.271-4.348 5.755-4.348 1.931 0 3.722.794 4.814 1.5a.75.75 0 11-.814 1.26c-.94-.607-2.448-1.26-4-1.26zm4.173 7.5h3.717a.75.75 0 000-1.5H3.75a.75.75 0 000 1.5h9.136c1.162.28 2.111.688 2.76 1.211.642.518.979 1.134.979 1.898a2.63 2.63 0 01-.954 2.036c-.703.601-1.934 1.105-3.999 1.105-2.018 0-3.529-.723-4.276-1.445a.75.75 0 10-1.042 1.08c1.066 1.028 2.968 1.865 5.318 1.865 2.295 0 3.916-.56 4.974-1.464a4.131 4.131 0 001.479-3.177c0-1.296-.608-2.316-1.538-3.066a5.77 5.77 0 00-.054-.043z"></path></svg>',command:e=>e.chain().focus().toggleStrike().run()},{title:"Inline Code",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 16 16" width="16" height="16"><path d="M10.478 1.647a.5.5 0 1 0-.956-.294l-4 13a.5.5 0 0 0 .956.294zM4.854 4.146a.5.5 0 0 1 0 .708L1.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0m6.292 0a.5.5 0 0 0 0 .708L14.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 0-.708 0"/></svg>',command:e=>e.chain().focus().toggleCode().run()},{title:"Bullet List",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" d="M4 7a1 1 0 100-2 1 1 0 000 2zm4.75-1.5a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zm0 6a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zm0 6a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zM5 12a1 1 0 11-2 0 1 1 0 012 0zm-1 7a1 1 0 100-2 1 1 0 000 2z"></path></svg>',command:e=>e.chain().focus().toggleBulletList().run()},{title:"Numbered List",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path d="M3.604 3.089A.75.75 0 014 3.75V8.5h.75a.75.75 0 010 1.5h-3a.75.75 0 110-1.5h.75V5.151l-.334.223a.75.75 0 01-.832-1.248l1.5-1a.75.75 0 01.77-.037zM8.75 5.5a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zm0 6a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zm0 6a.75.75 0 000 1.5h11.5a.75.75 0 000-1.5H8.75zM5.5 15.75c0-.704-.271-1.286-.72-1.686a2.302 2.302 0 00-1.53-.564c-.535 0-1.094.178-1.53.565-.449.399-.72.982-.72 1.685a.75.75 0 001.5 0c0-.296.104-.464.217-.564A.805.805 0 013.25 15c.215 0 .406.072.533.185.113.101.217.268.217.565 0 .332-.069.48-.21.657-.092.113-.216.24-.403.419l-.147.14c-.152.144-.33.313-.52.504l-1.5 1.5a.75.75 0 00-.22.53v.25c0 .414.336.75.75.75H5A.75.75 0 005 19H3.31l.47-.47c.176-.176.333-.324.48-.465l.165-.156a5.98 5.98 0 00.536-.566c.358-.447.539-.925.539-1.593z"></path></svg>',command:e=>e.chain().focus().toggleOrderedList().run()},{title:"Blockquote",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 16 16" width="16" height="16"><path d="M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z" /><path d="M7.066 6.76A1.665 1.665 0 0 0 4 7.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 0 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 7.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 0 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z" /></svg>',command:e=>e.chain().focus().toggleBlockquote().run()},{title:"Code Block",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 16 16" width="16" height="16"><path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/><path d="M6.854 4.646a.5.5 0 0 1 0 .708L4.207 8l2.647 2.646a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0m2.292 0a.5.5 0 0 0 0 .708L11.793 8l-2.647 2.646a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708 0"/></svg>',command:e=>e.chain().focus().toggleCodeBlock().run()},{title:"Horizontal Rule",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24" width="16" height="16"><path d="M3 12h18v2H3z"/></svg>',command:e=>e.chain().focus().setHorizontalRule().run()},{title:"Attach Files",icon:'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 16 16" width="16" height="16"><path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0V3z" /></svg>',command:e=>{const t=a.findFileInput(e);t&&t.click()}}];e.CONFIG=t,e.default=class{constructor(e={}){this.options={commands:s,placeholder:"Write something or type / for options",...e},this.commands=this.options.commands,this.isInitialized=!1}init(){this.isInitialized||(this.setupEventListeners(),this.initializeExistingEditors(),this.isInitialized=!0)}addCommand(e){this.commands.push(e)}removeCommand(e){this.commands=this.commands.filter((t=>t.title!==e))}setupEventListeners(){document.addEventListener("rhino-before-initialize",this.configureRhinoEditor.bind(this)),document.addEventListener("rhino-initialize",this.onRhinoInitialized.bind(this)),document.addEventListener("DOMContentLoaded",this.initializeExistingEditors.bind(this)),document.addEventListener("turbo:load",(()=>{setTimeout(this.initializeExistingEditors.bind(this),t.TIMING.TURBO_DELAY)})),document.addEventListener("click",(e=>{if(i&&o&&!o.contains(e.target)){e.target.closest(t.SELECTORS.RHINO_EDITOR)||this.hideDropdown()}}))}configureRhinoEditor(e){const t=e.target;t.starterKitOptions={...t.starterKitOptions,rhinoPlaceholder:{placeholder:this.options.placeholder}}}onRhinoInitialized(e){const o=e.target;o.hasSlashCommands||setTimeout((()=>{o.editor?(this.setupSlashCommands(o.editor),o.hasSlashCommands=!0):this.retryEditorSetup(o)}),t.TIMING.INITIAL_DELAY)}initializeExistingEditors(){document.querySelectorAll(t.SELECTORS.RHINO_EDITOR).forEach((e=>{e.editor&&!e.hasSlashCommands&&(this.setupSlashCommands(e.editor),e.hasSlashCommands=!0)}))}retryEditorSetup(e){let o=0;const i=setInterval((()=>{o++,e.editor?(this.setupSlashCommands(e.editor),e.hasSlashCommands=!0,clearInterval(i)):o>=t.TIMING.MAX_RETRIES&&(clearInterval(i),console.warn("[SlashCommands] Max retries reached for editor setup"))}),t.TIMING.RETRY_INTERVAL)}setupSlashCommands(e){if(e.slashCommandsKeydownHandler)return;e.on("update",(({editor:e})=>{a.safeExecute((()=>{this.updateEmptyParagraphs(e),this.checkForSlashCommand(e)}),"editor update")}));const o=this.handleKeydown.bind(this);e.slashCommandsKeydownHandler=o,e.view.dom.addEventListener("keydown",o,!0),setTimeout((()=>{this.updateEmptyParagraphs(e)}),t.TIMING.INITIAL_DELAY)}updateEmptyParagraphs(e){e.state.selection.empty&&a.safeExecute((()=>{e.view.dom.querySelectorAll(t.SELECTORS.PARAGRAPH).forEach((e=>{const o=""===e.textContent.trim()&&0===e.children.length;e.classList.toggle(t.CLASSES.EMPTY,o)}))}),"updateEmptyParagraphs")}checkForSlashCommand(e){const{state:o}=e,{selection:n}=o,{$from:a}=n,s=a.parent.textContent,r=a.parentOffset,d=s.lastIndexOf("/",r);if(-1!==d&&d===r-1){const o=e.view.coordsAtPos(a.pos);this.showDropdown(o.left,o.bottom+t.DROPDOWN.OFFSET_Y,"",e)}else if(-1!==d&&d<r){const o=s.slice(d+1,r),i=e.view.coordsAtPos(a.pos);this.showDropdown(i.left,i.bottom+t.DROPDOWN.OFFSET_Y,o,e)}else i&&this.hideDropdown()}handleKeydown(e){if(!i)return;const a=o?.filteredCommands,s=o?.currentEditor;if(!a||!s)return;let r=!1;switch(e.key){case t.KEYS.ESCAPE:this.hideDropdown(),r=!0;break;case t.KEYS.ARROW_DOWN:n=n<a.length-1?n+1:0,this.updateSelectedItem(),r=!0;break;case t.KEYS.ARROW_UP:n=n>0?n-1:a.length-1,this.updateSelectedItem(),r=!0;break;case t.KEYS.ENTER:this.executeSelectedCommand(s,a),r=!0;break;case t.KEYS.TAB:n=n<a.length-1?n+1:0,this.updateSelectedItem(),r=!0}return r?(e.preventDefault(),e.stopPropagation(),!1):void 0}createGlobalDropdown(){if(o)return o;const e=document.createElement("div");return e.className=t.CLASSES.DROPDOWN,e.style.cssText="\n position: absolute;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n z-index: 1000;\n max-height: 200px;\n overflow-y: auto;\n min-width: 200px;\n display: none;\n ",o=e,e}updateSelectedItem(){const e=o?.querySelectorAll(`.${t.CLASSES.ITEM}`);e&&e.forEach(((e,o)=>{o===n?(e.classList.add(t.CLASSES.SELECTED),e.scrollIntoView({block:t.DROPDOWN.SCROLL_BEHAVIOR})):e.classList.remove(t.CLASSES.SELECTED)}))}executeSelectedCommand(e,t){if(n<0||n>=t.length)return;const o=t[n];this.hideDropdown(),a.safeExecute((()=>{this.removeSlashText(e),o.command(e)}),"executeSelectedCommand")}removeSlashText(e){const{state:t}=e,{selection:o}=t,{$from:i}=o,n=i.parent.textContent,a=i.parentOffset,s=n.lastIndexOf("/",a);if(-1!==s){const t=i.pos-(a-s),o=i.pos;e.chain().focus().deleteRange({from:t,to:o}).run()}}renderDropdown(e,o){const i=this.createGlobalDropdown();i.innerHTML="",n=0,e.forEach(((a,s)=>{const r=document.createElement("div");r.className=t.CLASSES.ITEM,r.style.cssText="\n padding: 8px 12px;\n cursor: pointer;\n border-bottom: 1px solid #f3f4f6;\n display: flex;\n align-items: center;\n gap: 8px;\n ",r.innerHTML=`\n <span style="font-size: 16px;">${a.icon}</span>\n <div style="font-weight: 500; font-size: 14px;">${a.title}</div>\n `,r.addEventListener("mouseenter",(()=>{n=s,this.updateSelectedItem()})),r.addEventListener("click",(()=>{n=s,this.executeSelectedCommand(o,e)})),i.appendChild(r)})),this.updateSelectedItem()}showDropdown(e,o,n="",s){const r=a.filterCommands(this.commands,n);if(0===r.length)return void this.hideDropdown();this.renderDropdown(r,s);const d=this.createGlobalDropdown(),l=s.view.dom.closest(t.SELECTORS.RHINO_EDITOR),h=l?.closest("dialog");if(h){d.parentNode===document.body&&document.body.removeChild(d),h.appendChild(d);const i=h.getBoundingClientRect(),n=e-i.left,a=o-i.top;d.style.position="absolute",d.style.left=n+"px",d.style.top=a+t.DROPDOWN.OFFSET_Y+"px",d.style.zIndex="1000"}else d.parentNode!==document.body&&(d.parentNode&&d.parentNode.removeChild(d),document.body.appendChild(d)),d.style.position="absolute",d.style.left=e+"px",d.style.top=o+t.DROPDOWN.OFFSET_Y+"px",d.style.zIndex="1000";d.style.display="block",i=!0,d.filteredCommands=r,d.currentEditor=s}hideDropdown(){o&&(o.style.display="none",o.filteredCommands=null,o.currentEditor=null,o.parentNode&&o.parentNode.removeChild(o)),i=!1,n=-1}},e.defaultCommands=s,Object.defineProperty(e,"__esModule",{value:!0})}));