Rhino Editor
repository·main·Indexed 43 days ago
https://github.com/konnorrogers/rhino-editorA custom element wrapped WYSIWYG rich text editor designed to integrate with Ruby on Rails ActionText. Version 0.18.3 utilizes TipTap for editing, with planned support for ProseMirror to enable Markdown capabilities.
What's inside Rhino Editor
- Rhino Editor is a 'grab and go' WYSIWYG editing experience designed to hook into a Ruby on Rails ActionText backend. It currently utilizes TipTap for editing, with plans to include a ProseMirror integration to support both Markdown and WYSIWYG editing workflows.
Why use Rhino Editor?
mainRhino Editor is designed for developers who need a highly customizable and extensible rich-text editor. Key benefits include:
- ActionText Compatibility: Works seamlessly with existing ActionText views.
- Extensibility: Built on top of ProseMirror and TipTap, allowing you to leverage the entire ecosystem of ProseMirror/TipTap plugins.
- Advanced Features: Supports table editing and real-time collaboration through plugin integration.
- Improved Toolbar: Features a focusable toolbar with an API designed for easy extension.
Why use Rhino Editor instead of Trix?
mainRhino Editor is designed as a drop-in replacement for Trix, the standard WYSIWYG editor for Rails. While Trix is lightweight, Rhino Editor is intended for developers who need advanced features and extensibility that Trix cannot easily provide.
Key advantages include:
- ActionText Compatibility: You can continue using Rails ActionText while providing a richer editing experience.
- Advanced Features: Supports Real Time Collaboration, Code Syntax Highlighting, and Table Editing out of the box.
- Extensibility: Built on top of TipTap and ProseMirror, allowing you to use any existing TipTap or ProseMirror community extensions.
Tradeoffs to consider:
- Bundle Size: Rhino Editor is larger than Trix (approximately
~100kbgzipped vs Trix's~40kbgzipped). - Shadow DOM: Rhino Editor uses Shadow DOM for components like the toolbar and link insertion dialogs, which may require specific approaches for styling and extension.
How fake selections and cursors work in Rhino Editor
mainRhino Editor uses 'fake' insertions and selections to provide visual feedback in specific UI contexts. For example, when moving focus to link dialog inputs, the editor displays a fake insertion cursor or a fake selection 'box' around currently selected text. Additionally, the Codemark Plugin uses a fake cursor for inline code blocks.
These visual elements are controlled via CSS.
Ways to modify the RhinoEditor instance
mainTo modify the configuration or extensions of a
<rhino-editor>element, you must wait for the element to connect to the DOM. There are three primary patterns for intercepting the editor instance:rhino-before-initializeevent: Fires when the<rhino-editor>element connects to the DOM, but before the TipTap instance is created. Use this for early configuration like settingextensionsorstarterKitOptions.rhino-initializeevent: Fires after the TipTap instance has been created. Use this if you need to interact with the initialized editor state.customElements.whenDefined("rhino-editor"): A standard Web Components approach. This ensures the element is defined, but the TipTap instance may or may not have been created yet when the callback executes.
Common modification tasks include setting the
extensionsarray, callingaddExtensions(), or updatingstarterKitOptions.import "rhino-editor/exports/styles/trix.css" import "rhino-editor" // Example using rhino-before-initialize document.addEventListener("rhino-before-initialize", (e) => { const rhinoEditor = e.target rhinoEditor.extensions = [MyExtension] rhinoEditor.addExtensions(MyOtherExtension) rhinoEditor.starterKitOptions = { ...rhinoEditor.starterKitOptions, rhinoGallery: false } })Handle attachment uploads via the rhino-attachment-add event
mainThe
rhino-attachment-addevent is triggered when a user attempts to add an attachment. To implement custom upload logic (like using Shrine), you must:- Call
event.preventDefault()to stop the default attachment process. - Access the file via
event.attachment.file. - Perform your upload (e.g., via
fetch). - Once the upload is successful, call
event.attachment.setAttributes({ url: '...' })to finalize the attachment with the new remote URL. - Optionally call
event.attachment.setUploadProgress(percentage)to update the UI.
Note: Using the
fetch()API does not provide native upload progress tracking. To show a progress bar, use theXMLHttpRequestAPI instead.- Call
Getting Started with Rhino Editor
mainTo begin using Rhino Editor, follow the official Getting Started tutorial. This guide will walk you through the initial setup and basic usage of the editor.
Visit /tutorials/getting-started for the full guide.Install dependencies for Syntax Highlighting
mainTo enable syntax highlighting in Rhino, you must install the TipTap Lowlight extension and its required utilities. This setup uses
lowlightfor highlighting andhast-util-to-htmlandhefor HTML processing.yarn add lowlight @tiptap/extension-code-block-lowlight hast-util-to-html heChange toolbar button icons
mainIf you only need to change the visual icon of a button without replacing its entire functionality, use the
*-iconslot pattern. For example, to change the attachment button icon, use theattach-files-iconslot.<rhino-editor> <svg slot="attach-files-icon" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-paperclip" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"></path> </svg> </rhino-editor>Delay initialization using the `defer-initialize` attribute
mainIf JavaScript load order makes it difficult to catch the
rhino-before-initializeorrhino-initializeevents, you can use thedefer-initializeattribute on the<rhino-editor>element.When this attribute is present, the editor will not start or create the TipTap instance until the attribute is manually removed. This allows you to reliably set up all event listeners and configurations before the editor begins its lifecycle.
Workflow:
- Add
defer-initializeto your<rhino-editor>tag. - Set up your
rhino-before-initializeorrhino-initializeevent listeners. - Remove the
defer-initializeattribute via JavaScript to trigger initialization.
<rhino-editor defer-initialize></rhino-editor> <script type="module"> // 1. Setup your event listeners to modify the editor document.addEventListener("rhino-before-initialize", (e) => { // configuration logic here }) // 2. The editor will initialize and start the TipTap instance once the attribute is removed document.querySelectorAll("rhino-editor").forEach((el) => el.removeAttribute("defer-initialize")) </script>- Add
Customize the Bubble Menu via Slots
mainRhino Editor uses an event and slot-based system for its built-in bubble menu, making it easier to customize than extension-based alternatives. You can override different parts of the bubble menu using specific slots within the
<rhino-editor>component:- Entire Toolbar: Use
slot="bubble-menu-toolbar"with a<role-toolbar>to replace the whole menu. - Inner Items: Use
slot="bubble-menu-toolbar-items"to replace only the buttons inside the toolbar. - Prepend/Append Items: Use
slot="before-bubble-menu-toolbar-items"orslot="after-bubble-menu-toolbar-items"to add items to the start or end of the existing list. - Additional Toolbars: Use
slot="additional-bubble-menu-toolbar"with one or more<role-toolbar>elements to add entirely new toolbar groups.
Note on Slot Naming: The bubble menu supports all slots available to the base Rhino Editor toolbar, but they must be prefixed with
bubble-menu__. For example, to customize the strike icon, useslot="bubble-menu__strike-icon"instead ofslot="strike-icon".<rhino-editor> <!-- Replace the entire toolbar --> <role-toolbar slot="bubble-menu-toolbar"> <!-- Custom content --> </role-toolbar> <!-- Add items before the existing ones --> <div slot="before-bubble-menu-toolbar-items"> <button tabindex="-1" data-role="toolbar-item">New Item</button> </div> <!-- Add a completely new toolbar group --> <role-toolbar id="custom-toolbar" slot="additional-bubble-menu-toolbar"> <button data-role="toolbar-item" tabindex="-1">Custom Action</button> </role-toolbar> </rhino-editor>- Entire Toolbar: Use
Install Rhino Editor for local development
mainTo set up the repository and its dependencies for local development, clone the repository and run the setup command using
pnpm.Prerequisites
- Ruby 3.1.2
- Rails 7.0.4
- PNPM (
npm install -g pnpm) - Playwright (
npm install -g playwright) - Node >= 16
- Docker (for a simulated S3 server)
- A Docker virtualizer (e.g., Orbstack or Docker Desktop)
- Overmind (recommended:
gem install overmind)
git clone https://github.com/konnorrogers/rhino-editor cd rhino-editor pnpm run setup