TipTap Editor

Usage

We can instantiate a Tiptap editor with the following code. There's a hidden field which holds the output value in markdown, and the Tiptap div references that hidden input. When the page is loaded, we'll scan for any div with data-tiptap-input and replace it with the Tiptap component.

<div data-tiptap-input 
     data-tiptap-input-id="task_task_content" 
     class="ui-styled-text">
</div>
<%= form.hidden_field :task_content %>

Full Code


<div class="ui-container max-w-lg mx-auto py-8">
  <h1 class="ui-title --xl mb-6">New Task</h1>

  <%= form_with(model: @task, local: true, class: "ui-form space-y-6") do |form| %>
    <div  data-tiptap-input 
          data-tiptap-input-id="task_task_content" 
          class="ui-styled-text">
    </div>

    <%= form.hidden_field :task_content %>
    <%= form.submit "Create Task", class: "ui-button --solid --motion-forward w-full" %>
  <% end %>
</div> 

<script type="module">
  import { html, render, useState, useEffect } from 'https://esm.sh/htm/preact/standalone';
  import { Editor } from 'https://esm.sh/@tiptap/core';
  import StarterKit from 'https://esm.sh/@tiptap/starter-kit';
  import TurndownService from 'https://esm.sh/turndown';

  const turndownService = new TurndownService();

  const Tiptap = ({ element }) => {
    const [editor, setEditor] = useState(null);
    const [editorElement, setEditorElement] = useState(null);

    const targetInputId = element.getAttribute('data-tiptap-input-id');

    useEffect(() => {
      if (!editorElement) return;

      const editorInstance = new Editor({
        element: editorElement,
        extensions: [StarterKit],
        content: '',
        autofocus: 'end',
        onUpdate: ({ editor }) => {
          const hiddenField = document.getElementById(targetInputId);
          if (hiddenField) {
            const markdown = turndownService.turndown(editor.getHTML());
            hiddenField.value = markdown;
          }
        },
      });

      setEditor(editorInstance);

      return () => {
        editorInstance.destroy();
      };
    }, [editorElement]);

    return html`
      <div ref=${setEditorElement}></div>
    `;
  };

  document.querySelectorAll('[data-tiptap-input]').forEach(input => {
    render(html`<${Tiptap} element=${input} />`, input);
  });

</script>

Last updated