# `PhiaUi.Components.Editor.TipTapHelpers`
[🔗](https://github.com/charlenopires/PhiaUI/blob/v0.1.17/lib/phia_ui/components/editor/tiptap_helpers.ex#L1)

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)

# `content_to_html`

```elixir
@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`

```elixir
@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`

```elixir
@spec empty_document() :: map()
```

Returns the canonical empty TipTap document.

## Example

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

# `sanitize_content`

```elixir
@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`

```elixir
@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`

```elixir
@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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
