How to use relative links in Filament RichEditor fields

Introduction

FilamentPHP's RichEditor field is a WYSIWYG editor that allows users to author rich text. It uses the Trix editor under the hood, which supports inserting links. However, the input element that allows users to enter a URL to be linked to is implemented as an <input type="url" />, which means relative links like /about-us can't be used. In this post, we'll look at how to work around this problem by changing the input type from url to text.

The Problem

The following code shows what the actual HTML looks like when the "insert link" dialog opens:

<div data-trix-dialogs="" class="trix-dialogs">
  <div
    data-trix-dialog="href"
    data-trix-dialog-attribute="href"
    class="trix-dialog trix-dialog--link trix-active"
    data-trix-active=""
  >
    <div class="trix-dialog__link-fields">
      <input
        name="href"
        placeholder="Enter a URL"
        aria-label="URL"
        required=""
        data-trix-input=""
        type="url"
        class="trix-input trix-input--dialog"
      />

      <div class="trix-button-group">
        <input
          value="Link"
          data-trix-method="setAttribute"
          type="button"
          class="trix-button trix-button--dialog"
        />

        <input
          value="Unlink"
          data-trix-method="removeAttribute"
          type="button"
          class="trix-button trix-button--dialog"
        />
      </div>
    </div>
  </div>
</div>

The type="url" attribute of the input element breaks our ability to use relative links.

The Solution

To work around this problem, we need to change the input type from url to text. We can do this by writing some Javascript that finds the input element and changes its type. However, since we're working within a Livewire based system, we need to be mindful that the DOM is going to be mutated and our "changes" to the DOM will be overwritten when anything changes. To work around these potential problems, we use a MutationObserver to constantly "re-apply" our code whenever the input field's type is changed back to url.

Here's the code:

<script>
  document.addEventListener('DOMContentLoaded', function () {
    const observer = new MutationObserver(() => {
      document
        .querySelectorAll('[data-trix-input][name=href]')
        .forEach((el) => (el.type = 'text'))
    })

    document
      .querySelectorAll('.trix-dialog--link')
      .forEach((el) => observer.observe(el, { attributes: true }))
  })
</script>

To inject this script into the Filament admin panel, we can use FilamentPHP's render hooks, which allow us to hook into the "body.start" event and inject a Blade view into the page. Here's an example of how to do this:

<?php

namespace App\Providers;

use Filament\Facades\Filament;
use Illuminate\Contracts\View\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register() {}

    public function boot()
    {
        Filament::registerRenderHook(
            'body.start',
            fn(): View => view('admin.trix-editor-url-workaround'),
        );
    }
}

With this code in place, You can now use relative links in the RichEditor field.

Enjoy.

Links