PhiaUi.Components.Editor.TipTapHelpers (phia_ui v0.1.17)

Copy Markdown View Source

Pure Elixir utilities for working with TipTap JSON content.

TipTap stores documents as a JSON tree of typed nodes with marks. This module provides server-side operations — HTML rendering, plain-text extraction, validation, and sanitization — so you can process editor content without JavaScript.

TipTap JSON Format

%{
  "type" => "doc",
  "content" => [
    %{
      "type" => "paragraph",
      "content" => [
        %{"type" => "text", "text" => "Hello ", "marks" => [%{"type" => "bold"}]},
        %{"type" => "text", "text" => "world"}
      ]
    }
  ]
}

Usage

alias PhiaUi.Components.Editor.TipTapHelpers

# Render JSON to safe HTML
html = TipTapHelpers.content_to_html(json)

# Extract plain text for search indexing
text = TipTapHelpers.content_to_text(json)

# Validate against allowed schema
{:ok, json} = TipTapHelpers.validate_content(json, allowed_nodes: ~w(paragraph heading))

# Count words
count = TipTapHelpers.word_count(json)

Summary

Functions

Converts a TipTap JSON document to an HTML string.

Extracts plain text from a TipTap JSON document.

Returns the canonical empty TipTap document.

Strips disallowed nodes and marks from a TipTap JSON document.

Validates a TipTap JSON document against allowed node and mark types.

Counts words in a TipTap JSON document.

Functions

content_to_html(arg1)

@spec content_to_html(map() | nil) :: String.t()

Converts a TipTap JSON document to an HTML string.

The output is not marked as safe — use Phoenix.HTML.raw/1 when rendering in templates, after sanitizing user content.

Examples

iex> doc = %{"type" => "doc", "content" => [
...>   %{"type" => "paragraph", "content" => [
...>     %{"type" => "text", "text" => "Hello"}
...>   ]}
...> ]}
iex> TipTapHelpers.content_to_html(doc)
"<p>Hello</p>"

content_to_text(arg1)

@spec content_to_text(map() | nil) :: String.t()

Extracts plain text from a TipTap JSON document.

Block-level nodes are separated by newlines. Useful for search indexing, word counting, and preview generation.

Examples

iex> doc = %{"type" => "doc", "content" => [
...>   %{"type" => "paragraph", "content" => [
...>     %{"type" => "text", "text" => "Hello world"}
...>   ]},
...>   %{"type" => "paragraph", "content" => [
...>     %{"type" => "text", "text" => "Second paragraph"}
...>   ]}
...> ]}
iex> TipTapHelpers.content_to_text(doc)
"Hello world\nSecond paragraph"

empty_document()

@spec empty_document() :: map()

Returns the canonical empty TipTap document.

Example

iex> TipTapHelpers.empty_document()
%{"type" => "doc", "content" => [%{"type" => "paragraph"}]}

sanitize_content(doc, opts \\ [])

@spec sanitize_content(
  map(),
  keyword()
) :: map()

Strips disallowed nodes and marks from a TipTap JSON document.

Nodes with disallowed types are removed entirely. Marks with disallowed types are stripped from text nodes (text is kept). Returns the cleaned document.

Options

  • :allowed_nodes — list of allowed node type strings
  • :allowed_marks — list of allowed mark type strings

Examples

iex> doc = %{"type" => "doc", "content" => [
...>   %{"type" => "paragraph", "content" => [%{"type" => "text", "text" => "ok"}]},
...>   %{"type" => "script", "content" => []}
...> ]}
iex> TipTapHelpers.sanitize_content(doc)
%{"type" => "doc", "content" => [
  %{"type" => "paragraph", "content" => [%{"type" => "text", "text" => "ok"}]}
]}

validate_content(doc, opts \\ [])

@spec validate_content(
  map(),
  keyword()
) :: {:ok, map()} | {:error, [String.t()]}

Validates a TipTap JSON document against allowed node and mark types.

Returns {:ok, doc} if valid, or {:error, reasons} with a list of issues.

Options

  • :allowed_nodes — list of allowed node type strings (default: standard set)
  • :allowed_marks — list of allowed mark type strings (default: standard set)

Examples

iex> doc = %{"type" => "doc", "content" => [%{"type" => "paragraph"}]}
iex> TipTapHelpers.validate_content(doc)
{:ok, doc}

iex> doc = %{"type" => "doc", "content" => [%{"type" => "script"}]}
iex> TipTapHelpers.validate_content(doc)
{:error, ["disallowed node type: script"]}

word_count(doc)

@spec word_count(map() | nil) :: non_neg_integer()

Counts words in a TipTap JSON document.

Examples

iex> doc = %{"type" => "doc", "content" => [
...>   %{"type" => "paragraph", "content" => [
...>     %{"type" => "text", "text" => "Hello beautiful world"}
...>   ]}
...> ]}
iex> TipTapHelpers.word_count(doc)
3