Rhino Editor

repository·main·Indexed 43 days ago

https://github.com/konnorrogers/rhino-editor

A 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.

Tokens
29.7K
Snippets
99
Records
116
Agent score
55%

What's inside Rhino Editor

  1. Why use Rhino Editor?

    main

    Rhino 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.
  2. Why use Rhino Editor instead of Trix?

    main

    Rhino 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 ~100kb gzipped vs Trix's ~40kb gzipped).
    • 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.
  3. How fake selections and cursors work in Rhino Editor

    main

    Rhino 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.

  4. Ways to modify the RhinoEditor instance

    main

    To 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:

    1. rhino-before-initialize event: Fires when the <rhino-editor> element connects to the DOM, but before the TipTap instance is created. Use this for early configuration like setting extensions or starterKitOptions.
    2. rhino-initialize event: Fires after the TipTap instance has been created. Use this if you need to interact with the initialized editor state.
    3. 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 extensions array, calling addExtensions(), or updating starterKitOptions.

    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 }
    })
  5. Handle attachment uploads via the rhino-attachment-add event

    main

    The rhino-attachment-add event is triggered when a user attempts to add an attachment. To implement custom upload logic (like using Shrine), you must:

    1. Call event.preventDefault() to stop the default attachment process.
    2. Access the file via event.attachment.file.
    3. Perform your upload (e.g., via fetch).
    4. Once the upload is successful, call event.attachment.setAttributes({ url: '...' }) to finalize the attachment with the new remote URL.
    5. 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 the XMLHttpRequest API instead.

  6. Change toolbar button icons

    main

    If you only need to change the visual icon of a button without replacing its entire functionality, use the *-icon slot pattern. For example, to change the attachment button icon, use the attach-files-icon slot.

    <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>
  7. Delay initialization using the `defer-initialize` attribute

    main

    If JavaScript load order makes it difficult to catch the rhino-before-initialize or rhino-initialize events, you can use the defer-initialize attribute 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:

    1. Add defer-initialize to your <rhino-editor> tag.
    2. Set up your rhino-before-initialize or rhino-initialize event listeners.
    3. Remove the defer-initialize attribute 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>
  8. Customize the Bubble Menu via Slots

    main

    Rhino 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" or slot="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, use slot="bubble-menu__strike-icon" instead of slot="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>
  9. Install Rhino Editor for local development

    main

    To 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