Full Code of ueberdosis/tiptap-php for AI

main 6ea321fa6650 cached
150 files
287.1 KB
64.1k tokens
244 symbols
1 requests
Download .txt
Showing preview only (321K chars total). Download the full file or copy to clipboard to get everything.
Repository: ueberdosis/tiptap-php
Branch: main
Commit: 6ea321fa6650
Files: 150
Total size: 287.1 KB

Directory structure:
gitextract_xdo5lijc/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── SECURITY.md
│   └── workflows/
│       ├── php-cs-fixer.yml
│       ├── psalm.yml
│       └── run-tests.yml
├── .gitignore
├── .php_cs.dist.php
├── .vscode/
│   └── launch.json
├── LICENSE.md
├── README.md
├── composer.json
├── package.json
├── phpunit.xml.dist
├── psalm.xml.dist
├── src/
│   ├── Core/
│   │   ├── DOMParser.php
│   │   ├── DOMSerializer.php
│   │   ├── Extension.php
│   │   ├── JSONSerializer.php
│   │   ├── Mark.php
│   │   ├── Node.php
│   │   ├── Schema.php
│   │   └── TextSerializer.php
│   ├── Editor.php
│   ├── Extensions/
│   │   ├── Color.php
│   │   ├── FontFamily.php
│   │   ├── StarterKit.php
│   │   └── TextAlign.php
│   ├── Marks/
│   │   ├── Bold.php
│   │   ├── Code.php
│   │   ├── Highlight.php
│   │   ├── Italic.php
│   │   ├── Link.php
│   │   ├── Strike.php
│   │   ├── Subscript.php
│   │   ├── Superscript.php
│   │   ├── TextStyle.php
│   │   └── Underline.php
│   ├── Nodes/
│   │   ├── Blockquote.php
│   │   ├── BulletList.php
│   │   ├── CodeBlock.php
│   │   ├── CodeBlockHighlight.php
│   │   ├── CodeBlockShiki.php
│   │   ├── Details.php
│   │   ├── DetailsContent.php
│   │   ├── DetailsSummary.php
│   │   ├── Document.php
│   │   ├── HardBreak.php
│   │   ├── Heading.php
│   │   ├── HorizontalRule.php
│   │   ├── Image.php
│   │   ├── ListItem.php
│   │   ├── Mention.php
│   │   ├── OrderedList.php
│   │   ├── Paragraph.php
│   │   ├── Table.php
│   │   ├── TableCell.php
│   │   ├── TableHeader.php
│   │   ├── TableRow.php
│   │   ├── TaskItem.php
│   │   ├── TaskList.php
│   │   └── Text.php
│   └── Utils/
│       ├── HTML.php
│       ├── InlineStyle.php
│       └── Minify.php
└── tests/
    ├── DOMParser/
    │   ├── EmojiTest.php
    │   ├── EmptyNodesTest.php
    │   ├── EmptyTextNodesTest.php
    │   ├── Extensions/
    │   │   ├── ColorTest.php
    │   │   ├── FontFamilyTest.php
    │   │   └── TextAlignTest.php
    │   ├── KeepContentOfUnknownTagsTest.php
    │   ├── Marks/
    │   │   ├── BoldTest.php
    │   │   ├── CodeTest.php
    │   │   ├── CustomMarkTest.php
    │   │   ├── HighlightTest.php
    │   │   ├── ItalicTest.php
    │   │   ├── LinkTest.php
    │   │   ├── NestedMarksTest.php
    │   │   ├── StrikeTest.php
    │   │   ├── SubscriptTest.php
    │   │   ├── SuperscriptTest.php
    │   │   ├── TextStyleTest.php
    │   │   └── UnderlineTest.php
    │   ├── MarksInNodesTest.php
    │   ├── MultipleMarksTest.php
    │   ├── Nodes/
    │   │   ├── BlockquoteTest.php
    │   │   ├── BulletListTest.php
    │   │   ├── CodeBlockTest.php
    │   │   ├── DetailsTest.php
    │   │   ├── HardBreakTest.php
    │   │   ├── HeadingTest.php
    │   │   ├── HighPriorityParagraph.php
    │   │   ├── HorizontalRuleTest.php
    │   │   ├── ImageTest.php
    │   │   ├── MentionTest.php
    │   │   ├── OrderedListTest.php
    │   │   ├── ParagraphTest.php
    │   │   └── TableTest.php
    │   ├── ParseHTMLPriorityTest.php
    │   ├── SpecialCharacterTest.php
    │   ├── TaskListTest.php
    │   └── WhitespaceTest.php
    ├── DOMSerializer/
    │   ├── ExampleJsonTest.php
    │   ├── Extensions/
    │   │   ├── ColorTest.php
    │   │   ├── FontFamilyTest.php
    │   │   └── TextAlignTest.php
    │   ├── InputTest.php
    │   ├── Marks/
    │   │   ├── BoldTest.php
    │   │   ├── CodeTest.php
    │   │   ├── HighlightTest.php
    │   │   ├── ItalicTest.php
    │   │   ├── LinkTest.php
    │   │   ├── StrikeTest.php
    │   │   ├── SubscriptTest.php
    │   │   ├── SuperscriptTest.php
    │   │   └── UnderlineTest.php
    │   ├── MultipleMarksTest.php
    │   ├── Nodes/
    │   │   ├── BlockquoteTest.php
    │   │   ├── BulletListTest.php
    │   │   ├── CodeBlockHighlightTest.php
    │   │   ├── CodeBlockShikiTest.php
    │   │   ├── CodeBlockTest.php
    │   │   ├── DetailsTest.php
    │   │   ├── HardBreakNodeTest.php
    │   │   ├── HeadingTest.php
    │   │   ├── HorizontalRuleNodeTest.php
    │   │   ├── ImageTest.php
    │   │   ├── MentionTest.php
    │   │   ├── OrderedListTest.php
    │   │   ├── ParagraphTest.php
    │   │   ├── TableTest.php
    │   │   ├── TaskListTest.php
    │   │   └── XSSTest.php
    │   └── WrongFormatTest.php
    ├── Editor/
    │   ├── DescendantsTest.php
    │   ├── GetDocumentTest.php
    │   ├── GetHTMLTest.php
    │   ├── GetJSONTest.php
    │   ├── GetTextTest.php
    │   ├── SanitizeTest.php
    │   └── SetContentTest.php
    ├── Pest.php
    ├── Schema/
    │   ├── GetTopNodeTest.php
    │   └── PriorityTest.php
    └── Utils/
        └── HTMLTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2


================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.gitattributes     export-ignore
/.gitignore         export-ignore
/phpunit.xml.dist   export-ignore
/tests              export-ignore
/.editorconfig      export-ignore
/.php_cs            export-ignore
/.github            export-ignore
/psalm.xml          export-ignore



================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Contributions are **welcome** and will be fully **credited**.

Please read and understand the contribution guide before creating an issue or pull request.

## Etiquette

This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.

Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.

It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.

## Viability

When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.

## Procedure

Before filing an issue:

- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.

Before submitting a pull request:

- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

## Requirements

If the project maintainer has any additional requirements, you will find them listed here.

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.

**Happy coding**!


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
title: "[Bug]: "
description: Found a bug? Report it here to help us improve.
labels:
  - "bug"
body:
  - type: markdown
    attributes:
      value: "### Please provide details to help us diagnose the bug."
  - type: input
    id: php_version
    attributes:
      label: PHP Version
      description: The version of PHP you are using.
      placeholder: e.g. 8.0
    validations:
      required: true
  - type: input
    id: version
    attributes:
      label: Version
      description: Specify the version of Tiptap PHP you are using.
      placeholder: 1.0.0
    validations:
      required: true
  - type: textarea
    id: problem
    attributes:
      label: Bug Description
      description: Provide a clear and concise description of what the bug is.
      placeholder: "The issue occurs when..."
    validations:
      required: true
  - type: markdown
    attributes:
      value: |
        ### Additional Information
        Please provide any additional information that may help us understand the issue.
  - type: textarea
    id: expectation
    attributes:
      label: Expected Behavior
      description: Describe what you expected to happen.
    validations:
      required: true
  - type: textarea
    id: context
    attributes:
      label: Additional Context (Optional)
      description: "Add any other context about the problem here, such as screenshots or videos."
  - type: checkboxes
    attributes:
      label: Dependency Updates
      description: "Have you updated your dependencies? This can often resolve issues."
      options:
        - label: Yes, I've updated all my dependencies.
          required: true
  - type: markdown
    attributes:
      value: "Thank you for helping us improve our open-source projects by reporting this issue!"


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Request a feature
description: Share ideas for new features
title: "[Feature Request]: "
labels:
  - "enhancement"
body:
  - type: markdown
    attributes:
      value: |
        ## Feature Request

        Thank you for considering contributing to Tiptap PHP! We welcome your ideas and suggestions for new features.
  - type: textarea
    id: feature_description
    attributes:
      label: "Feature Description"
      description: "Please provide a clear and concise description of the feature you would like to see."
      placeholder: "e.g. I would like to have a new command to..."
    validations:
      required: true
  - type: textarea
    id: additional_context
    attributes:
      label: "Additional Context"
      description: "Please provide any additional context or information that may be helpful in understanding your request."
      placeholder: "e.g. use case, related features, etc."


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

If you discover any security related issues, please email humans@tiptap.dev instead of using the issue tracker.


================================================
FILE: .github/workflows/php-cs-fixer.yml
================================================
name: Check & fix styling

on:
  push:
    branches:
      - main
      - develop
      - next
      - release/*
  pull_request:
    branches:
      - main
      - develop
      - next
  workflow_dispatch:

jobs:
  php-cs-fixer:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}

      - name: Run PHP CS Fixer
        uses: docker://oskarstark/php-cs-fixer-ga
        with:
          args: --config=.php_cs.dist.php --allow-risky=yes

      - name: Commit changes
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: Fix styling


================================================
FILE: .github/workflows/psalm.yml
================================================
name: Psalm

on:
  push:
    branches:
      - main
      - develop
      - next
      - release/*
  pull_request:
    branches:
      - main
      - develop
      - next
  workflow_dispatch:

jobs:
  psalm:
    name: psalm
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: "8.3"
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
          coverage: none

      - name: Cache composer dependencies
        uses: actions/cache@v4
        with:
          path: vendor
          key: composer-${{ hashFiles('composer.lock') }}

      - name: Run composer install
        run: composer install -n --prefer-dist

      - name: Run psalm
        run: ./vendor/bin/psalm --output-format=github


================================================
FILE: .github/workflows/run-tests.yml
================================================
name: Tests

on:
  push:
    branches:
      - main
      - develop
      - next
      - release/*
  pull_request:
    branches:
      - main
      - develop
      - next
  workflow_dispatch:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: true
      matrix:
        os: [ubuntu-latest, windows-latest]
        php: [8.1, 8.2, 8.3]
        stability: [prefer-lowest, prefer-stable]
        node-version: [22]

    name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
          coverage: none

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v2.5.1
        with:
          node-version: ${{ matrix.node-version }}

      - name: Load cached dependencies
        uses: actions/cache@v4
        id: cache
        with:
          path: |
            **/node_modules
          key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}

      - name: Install dependencies
        id: install-dependencies
        if: steps.cache.outputs.cache-hit != 'true'
        run: npm install

      - name: Setup problem matchers
        run: |
          echo "::add-matcher::${{ runner.tool_cache }}/php.json"
          echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

      - name: Install dependencies
        run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction

      - name: Execute tests
        run: vendor/bin/pest


================================================
FILE: .gitignore
================================================
.idea
.php_cs
.php_cs.cache
.phpunit.result.cache
build
composer.lock
coverage
docs
phpunit.xml
psalm.xml
vendor
.php-cs-fixer.cache
node_modules

================================================
FILE: .php_cs.dist.php
================================================
<?php

$finder = Symfony\Component\Finder\Finder::create()
    ->in([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->name('*.php')
    ->notName('*.blade.php')
    ->ignoreDotFiles(true)
    ->ignoreVCS(true);

return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR2' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'no_unused_imports' => true,
        'not_operator_with_successor_space' => true,
        'trailing_comma_in_multiline' => true,
        'phpdoc_scalar' => true,
        'unary_operator_spaces' => true,
        'binary_operator_spaces' => true,
        'blank_line_before_statement' => [
            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
        ],
        'phpdoc_single_line_var_spacing' => true,
        'phpdoc_var_without_name' => true,
        'method_argument_space' => [
            'on_multiline' => 'ensure_fully_multiline',
            'keep_multiple_spaces_after_comma' => true,
        ],
        'single_trait_insert_per_statement' => true,
    ])
    ->setFinder($finder);


================================================
FILE: .vscode/launch.json
================================================
{
    "configurations": [
      {
        "type": "php",
        "request": "launch",
        "name": "Run Test",
        "program": "${workspaceFolder}/vendor/bin/pest",
        "args": [
            "--filter",
            "${input:testFilter}"
        ],
        "runtimeArgs": [
            "-dxdebug.mode=debug",
            "-dxdebug.start_with_request=trigger"
        ],
        "cwd": "${workspaceFolder}",
        "port": 9003
      }
    ],
    "inputs": [
      {
        "type": "promptString",
        "id": "testFilter",
        "description": "Filter by test",
        "default": ""
      }
    ]
  }

================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) überdosis <humans@tiptap.dev>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Tiptap for PHP
[![Latest Version on Packagist](https://img.shields.io/packagist/v/ueberdosis/tiptap-php.svg?style=flat-square)](https://packagist.org/packages/ueberdosis/tiptap-php)
[![GitHub Tests Action Status](https://github.com/ueberdosis/tiptap-php/actions/workflows/run-tests.yml/badge.svg)](https://github.com/ueberdosis/tiptap-php/actions/workflows/run-tests.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/ueberdosis/tiptap-php.svg?style=flat-square)](https://packagist.org/packages/ueberdosis/tiptap-php)
[![License](https://img.shields.io/packagist/l/ueberdosis/tiptap-php?style=flat-square)](https://packagist.org/packages/ueberdosis/tiptap-php)
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true)](https://discord.gg/WtJ49jGshW)
[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)

A PHP package to work with [Tiptap](https://tiptap.dev/) content. You can transform Tiptap-compatible JSON to HTML, and the other way around, sanitize your content, or just modify it.

## Installation
You can install the package via composer:

```bash
composer require ueberdosis/tiptap-php
```

## Usage
The PHP package mimics large parts of the JavaScript package. If you know your way around Tiptap, the PHP syntax will feel familiar to you.

### Convert Tiptap HTML to JSON
Let’s start by converting a HTML snippet to a PHP array with a Tiptap-compatible structure:

```php
(new \Tiptap\Editor)
    ->setContent('<p>Example Text</p>')
    ->getDocument();

// Returns:
// ['type' => 'doc', 'content' => …]
```

You can get a JSON string in PHP, too.

```php
(new \Tiptap\Editor)
    ->setContent('<p>Example Text</p>')
    ->getJSON();

// Returns:
// {"type": "doc", "content": …}
```

### Convert Tiptap JSON to HTML
The other way works aswell. Just pass a JSON string or an PHP array to generate the HTML.

```php
(new \Tiptap\Editor)
    ->setContent([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ]
            ]
        ],
    ])
    ->getHTML();

// Returns:
// <h1>Example Text</h1>
```

This doesn’t fully adhere to the ProseMirror schema. Some things are supported too, for example aren’t marks allowed in a `CodeBlock`.

If you need better schema support, create an issue with the feature you’re missing.

### Syntax highlighting for code blocks with [highlight.php](https://github.com/scrivo/highlight.php)
The default `CodeBlock` extension doesn’t add syntax highlighting to your code blocks. However, if you want to add syntax highlighting to your code blocks, there’s a special `CodeBlockHighlight` extension.

Swapping our the default one works like that:

```php
(new \Tiptap\Editor([
    'extensions' => [
        new \Tiptap\Extensions\StarterKit([
            'codeBlock' => false,
        ]),
        new \Tiptap\Nodes\CodeBlockHighlight(),
    ],
]))
->setContent('<pre><code>&lt;?php phpinfo()</code></pre>')
->getHTML();

// Returns:
// <pre><code class="hljs php"><span class="hljs-meta">&lt;?php</span> phpinfo()</code></pre>
```

This is still unstyled. You need to [load a CSS file](https://highlightjs.org/download/) to add colors to the output, for example like that:

```html
<link rel="stylesheet" href="//unpkg.com/@highlightjs/cdn-assets@11.4.0/styles/default.min.css">
```

Boom, syntax highlighting! By the way, this is powered by the amazing [scrivo/highlight.php](https://github.com/scrivo/highlight.php).

### Syntax highlighting for code blocks with [Shiki](https://github.com/shikijs/shiki) (Requires Node.js)
There is an alternate syntax highlighter that utilizes [Shiki](https://github.com/shikijs/shiki). Shiki is a beautiful syntax highlighter powered by the same language engine that many code editors use. The major differences from the `CodeBlockHighlight` extensions are:

1. you must install the `shiki` npm package.
2. Shiki code highlighting works by injecting inline styles so pulling in a external css file is not required.
3. you can use most VS Code themes to highlight your code.

To use the Shiki extension, first install the npm package

```bash
npm install shiki
```

Then follow the example below:

```php
(new \Tiptap\Editor([
    'extensions' => [
        new \Tiptap\Extensions\StarterKit([
            'codeBlock' => false,
        ]),
        new \Tiptap\Nodes\CodeBlockShiki(),
    ],
]))
->setContent('<pre><code>&lt;?php phpinfo()</code></pre>')
->getHTML();
```

To configure the theme or default language for code blocks pass additonal configuration into the constructor as show below:

```php
(new \Tiptap\Editor([
    'extensions' => [
        new \Tiptap\Extensions\StarterKit([
            'codeBlock' => false,
        ]),
        new \Tiptap\Nodes\CodeBlockShiki([
            'theme' => 'github-dark', // default: nord, see https://github.com/shikijs/shiki/blob/main/docs/themes.md
            'defaultLanguage' => 'php', // default: html, see https://github.com/shikijs/shiki/blob/main/docs/languages.md
            'guessLanguage' => true, // default: true, if the language isn’t passed, it tries to guess the language with highlight.php
        ]),
    ],
]))
->setContent('<pre><code>&lt;?php phpinfo()</code></pre>')
->getHTML();
```

Under the hood the Shiki extension utilizes [Shiki PHP by Spatie](https://github.com/spatie/shiki-php), so please see the documentation for additional details and considerations.

### Convert content to plain text
Content can also be transformed to plain text, for example to put it into a search index.

```php
(new \Tiptap\Editor)
    ->setContent('<h1>Heading</h1><p>Paragraph</p>')
    ->getText();

// Returns:
// "Heading
//
// Paragraph"
```

What’s coming between blocks can be configured, too.

```php
(new \Tiptap\Editor)
    ->setContent('<h1>Heading</h1><p>Paragraph</p>')
    ->getText([
        'blockSeparator' => "\n",
    ]);

// Returns:
// "Heading
// Paragraph"
```

### Sanitize content
A great use case for the PHP package is to clean (or “sanitize”) the content. You can do that with the `sanitize()` method. Works with JSON strings, PHP arrays and HTML.

It’ll return the same format you’re using as the input format.

```php
(new \Tiptap\Editor)
    ->sanitize('<p>Example Text<script>alert("HACKED!")</script></p>');

// Returns:
// '<p>Example Text</p>'
```

### Modifying the content
With the `descendants()` method you can loop through all nodes recursively as you are used to from the JavaScript package. But in PHP, you can even modify the node to update attributes and all that.

> Warning: You need to add `&` to the parameter. Thats keeping a reference to the original item and allows to modify the original one, instead of just a copy.

```php
$editor->descendants(function (&$node) {
    if ($node->type !== 'heading') {
        return;
    }

    $node->attrs->level = 1;
});
```

### Configuration
Pass the configuration to the constructor of the editor. There’s not much to configure, but at least you can pass the initial content and load specific extensions.

```php
new \Tiptap\Editor([
    'content' => '<p>Example Text</p>',
    'extensions' => [
        new \Tiptap\Extensions\StarterKit,
    ],
])
```

The `StarterKit` is loaded by default. If you just want to use that, there’s no need to set it.

### Extensions
By default, the [`StarterKit`](https://tiptap.dev/api/extensions/starter-kit) is loaded, but you can pass a custom array of extensions aswell.

```php
new \Tiptap\Editor([
    'extensions' => [
        new \Tiptap\Extensions\StarterKit,
        new \Tiptap\Marks\Link,
    ],
])
```

### Configure extensions
Some extensions can be configured. Just pass an array to the constructor, that’s it. We’re aiming to support the same configuration as the JavaScript package.

```php
new \Tiptap\Editor([
    'extensions' => [
        // …
        new \Tiptap\Nodes\Heading([
            'levels' => [1, 2, 3],
        ]),
    ],
])
```

You can pass custom HTML attributes through the configuration, too.

```php
new \Tiptap\Editor([
    'extensions' => [
        // …
        new \Tiptap\Nodes\Heading([
            'HTMLAttributes' => [
                'class' => 'my-custom-class',
            ],
        ]),
    ],
])
```

For the `StarterKit`, it’s slightly different, but works as you are used to from the JavaScript package.

```php
new \Tiptap\Editor([
    'extensions' => [
        new Tiptap\Extensions\StarterKit([
            'codeBlock' => false,
            'heading' => [
                'HTMLAttributes' => [
                    'class' => 'my-custom-class',
                ],
            ]
        ]),
    ],
])
```

### Extend existing extensions
If you need to change minor details of the supported extensions, you can just extend an extension.

```php
<?php

class CustomBold extends \Tiptap\Marks\Bold
{
    public function renderHTML($mark)
    {
        // Renders <b> instead of <strong>
        return ['b', 0]
    }
}

new \Tiptap\Editor([
    'extensions' => [
        new Paragraph,
        new Text,
        new CustomBold,
    ],
])
```

#### Custom extensions
You can even build custom extensions. If you are used to the JavaScript API, you will be surprised how much of that works in PHP, too. 🤯 Find a simple example below.

Make sure to dig through the extensions in this repository to learn more about the PHP extension API.

```php
<?php

use Tiptap\Core\Node;

class CustomNode extends Node
{
    public static $name = 'customNode';
    
    public static $priority = 100;

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'my-custom-tag[data-id]',
            ],
            [
                'tag' => 'my-custom-tag',
                'getAttrs' => function ($DOMNode) {
                    return ! \Tiptap\Utils\InlineStyle::hasAttribute($DOMNode, [
                        'background-color' => '#000000',
                    ]) ? null : false;
                },
            ],
            [
                'style' => 'background-color',
                'getAttrs' => function ($value) {
                    return (bool) preg_match('/^(black)$/', $value) ? null : false;
                },
            ],
        ];
    }

    public function renderHTML($node)
    {
        return ['my-custom-tag', ['class' => 'foobar'], 0]
    }
}
```

#### Extension priority

Extensions are evaluated in the order of descending priority. By default, all Nodes, Marks, and Extensions, have a priority value of `100`.

Priority should be defined when creating a Node extension to match markup that could be matched be other Nodes - an example of this is the [TaskItem Node](src/Nodes/TaskItem.php) which has evaluation priority over the [ListItem Node](src/Nodes/ListItem.php).

## Testing
```bash
composer test
```

You can install nodemon (`npm install -g nodemon`) to keep the test suite running and watch for file changes:

```bash
composer test-watch
```

## Contributing
Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.

## Security Vulnerabilities
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

## Credits
- [Hans Pagel](https://github.com/hanspagel)
- [All Contributors](../../contributors)

## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.


================================================
FILE: composer.json
================================================
{
    "name": "ueberdosis/tiptap-php",
    "description": "A PHP package to work with Tiptap output",
    "keywords": [
        "ueberdosis",
        "tiptap",
        "prosemirror"
    ],
    "homepage": "https://github.com/ueberdosis/tiptap-php",
    "license": "MIT",
    "authors": [
        {
            "name": "Hans Pagel",
            "email": "humans@tiptap.dev",
            "role": "Developer"
        }
    ],
    "require": {
        "php": "^8.0",
        "scrivo/highlight.php": "^9.18",
        "spatie/shiki-php": "^2.0"
    },
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^3.5",
        "pestphp/pest": "^1.21",
        "phpunit/phpunit": "^9.5",
        "vimeo/psalm": "^4.3"
    },
    "autoload": {
        "psr-4": {
            "Tiptap\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tiptap\\Tests\\": "tests"
        }
    },
    "scripts": {
        "psalm": "vendor/bin/psalm",
        "psalm-watch": "nodemon --exec './vendor/bin/psalm || exit 1' --ext php",
        "test": "./vendor/bin/pest",
        "test-watch": "nodemon --exec './vendor/bin/pest || exit 1' --ext php",
        "test-coverage": "./vendor/bin/pest --coverage-html coverage",
        "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes --config=.php_cs.dist.php"
    },
    "config": {
        "sort-packages": true,
        "allow-plugins": {
            "pestphp/pest-plugin": true
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}


================================================
FILE: package.json
================================================
{
  "name": "tiptap-php",
  "private": true,
  "description": "This package.json has all Node dependencies for the local development of the package.",
  "homepage": "https://github.com/ueberdosis/tiptap-php",
  "devDependencies": {
    "shiki": "^2.0.0"
  }
}


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
    backupGlobals="false"
    backupStaticAttributes="false"
    bootstrap="vendor/autoload.php"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false"
    executionOrder="random"
    failOnWarning="true"
    failOnRisky="true"
    failOnEmptyTestSuite="true"
    beStrictAboutOutputDuringTests="true"
    verbose="false"
>
    <testsuites>
        <testsuite name="VendorName Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <coverage>
        <include>
            <directory suffix=".php">./src</directory>
        </include>
        <report>
            <html outputDirectory="build/coverage"/>
            <text outputFile="build/coverage.txt"/>
            <clover outputFile="build/logs/clover.xml"/>
        </report>
    </coverage>
    <logging>
        <junit outputFile="build/report.junit.xml"/>
    </logging>
</phpunit>


================================================
FILE: psalm.xml.dist
================================================
<?xml version="1.0"?>
<psalm
    errorLevel="3"
    findUnusedVariablesAndParams="true"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src"/>
        <ignoreFiles>
            <directory name="vendor"/>
        </ignoreFiles>
    </projectFiles>
</psalm>


================================================
FILE: src/Core/DOMParser.php
================================================
<?php

namespace Tiptap\Core;

use DOMDocument;
use DOMElement;
use Tiptap\Utils\InlineStyle;
use Tiptap\Utils\Minify;

class DOMParser
{
    protected $DOM;

    protected $schema;

    protected $storedMarks = [];

    public function __construct($schema)
    {
        $this->schema = $schema;
    }

    public function process(string $value): array
    {
        $this->setDocument($value);

        $content = $this->processChildren(
            $this->getDocumentBody()
        );

        return [
            'type' => $this->schema->topNode::$name,
            'content' => $content,
        ];
    }

    private function setDocument(string $value): DOMParser
    {
        libxml_use_internal_errors(true);

        $this->DOM = new DOMDocument;
        /**
         * @psalm-suppress ArgumentTypeCoercion
         */
        $this->DOM->loadHTML(
            $this->makeValidXMLDocument(
                $this->minify($value)
            )
        );

        return $this;
    }

    private function minify(string $value): string
    {
        return (new Minify)->process($value);
    }

    private function makeValidXMLDocument($value): string
    {
        return '<?xml encoding="utf-8" ?>' . $value;
    }

    private function getDocumentBody(): DOMElement
    {
        return $this->DOM->getElementsByTagName('body')->item(0);
    }

    private function processChildren($node): array
    {
        $nodes = [];

        foreach ($node->childNodes as $child) {
            if ($class = $this->getNodeFor($child)) {
                $item = $this->parseAttributes($class, $child);

                if ($item === null) {
                    if ($child->hasChildNodes()) {
                        $nodes = array_merge($nodes, $this->processChildren($child));
                    }

                    continue;
                }

                if ($child->hasChildNodes()) {
                    $item = array_merge($item, [
                        'content' => $this->processChildren($child),
                    ]);
                }

                if (count($this->storedMarks)) {
                    $item = array_merge($item, [
                        'marks' => $this->storedMarks,
                    ]);
                }

                array_push($nodes, $item);
            } elseif ($class = $this->getMarkFor($child)) {
                array_push($this->storedMarks, $this->parseAttributes($class, $child));

                if ($child->hasChildNodes()) {
                    $nodes = array_merge($nodes, $this->processChildren($child));
                }

                array_pop($this->storedMarks);
            } elseif ($child->hasChildNodes()) {
                $nodes = array_merge($nodes, $this->processChildren($child));
            }
        }


        // If similar nodes with different text follow each other,
        // we can merge them into a single node.
        return $this->mergeSimilarNodes($nodes);
    }

    private function isMultidimensionalArray($array)
    {
        foreach ($array as $value) {
            if (is_array($value)) {
                return true;
            }
        }

        return false;
    }

    private function mergeSimilarNodes($nodes)
    {
        $result = [];

        /**
         * @psalm-suppress UnusedFunctionCall
         */
        array_reduce($nodes, function ($carry, $node) use (&$result) {
            // Ignore multidimensional arrays
            if ($this->isMultidimensionalArray($node) || $this->isMultidimensionalArray($carry)) {
                $result[] = $node;

                return $node;
            }

            // Check if text is the only difference
            $differentKeys = array_keys(array_diff($carry, $node));
            if ($differentKeys != ['text']) {
                $result[] = $node;

                return $node;
            }

            // Merge it!
            $result[count($result) - 1]['text'] .= $node['text'];

            return $result[count($result) - 1];
        }, []);

        return $result;
    }


    private function getNodeFor($item)
    {
        return $this->getExtensionFor($item, $this->schema->nodes);
    }

    private function getMarkFor($item)
    {
        return $this->getExtensionFor($item, $this->schema->marks);
    }

    private function getExtensionFor($node, $classes)
    {
        $parseRules = [];

        foreach ($classes as $class) {
            $classParseRules = $this->getClassParseRules($class, $node);
            $parseRules = array_merge($parseRules, $classParseRules);
        }

        usort($parseRules, fn ($parseRuleA, $parseRuleB) => $parseRuleB['priority'] - $parseRuleA['priority']);

        foreach ($parseRules as $parseRule) {
            if ($this->checkParseRule($parseRule, $node)) {
                return $parseRule['class'];
            }
        }

        return false;
    }

    private function getClassParseRules($class, $node): array
    {
        $parseRules = $class->parseHTML($node);

        if (! is_array($parseRules)) {
            return [];
        }
        $classParseRules = [];
        foreach ($parseRules as $parseRule) {
            $parseRule['class'] = $class;
            $parseRule['priority'] = $parseRule['priority'] ?? 50;
            $classParseRules[] = $parseRule;
        }

        return $classParseRules;
    }

    private function checkParseRule($parseRule, $DOMNode): bool
    {
        // ['tag' => 'span[type="mention"]']
        if (isset($parseRule['tag'])) {
            if (preg_match('/([a-zA-Z-]*)\[([a-z-]+)(="?([a-zA-Z]*)"?)?\]$/', $parseRule['tag'], $matches)) {
                $tag = $matches[1];
                $attribute = $matches[2];
                if (isset($matches[4])) {
                    $value = $matches[4];
                }
            } else {
                $tag = $parseRule['tag'];
            }

            if ($tag !== $DOMNode->nodeName) {
                return false;
            }

            if (isset($attribute) && ! $DOMNode->hasAttribute($attribute)) {
                return false;
            }

            if (isset($attribute) && isset($value) && $DOMNode->getAttribute($attribute) !== $value) {
                return false;
            }
        }

        // ['style' => 'font-weight=italic']
        if (isset($parseRule['style'])) {
            if (preg_match('/([a-zA-Z-]*)(="?([a-zA-Z-]*)"?)?$/', $parseRule['style'], $matches)) {
                $style = $matches[1];

                if (isset($matches[3])) {
                    $value = $matches[3];
                }
            } else {
                $style = $parseRule['style'];
            }

            if (! InlineStyle::hasAttribute($DOMNode, $style)) {
                return false;
            }

            if (isset($value) && InlineStyle::getAttribute($DOMNode, $style) !== $value) {
                return false;
            }
        }

        // ['getAttrs' => function($DOMNode) { … }]
        if (isset($parseRule['getAttrs'])) {
            if (isset($parseRule['style']) && InlineStyle::hasAttribute($DOMNode, $parseRule['style'])) {
                $parameter = InlineStyle::getAttribute($DOMNode, $parseRule['style']);
            } else {
                $parameter = $DOMNode;
            }

            if ($parseRule['getAttrs']($parameter) === false) {
                return false;
            }
        }

        if (
            ! is_array($parseRule)
            || ! count($parseRule)
            || (
                ! isset($parseRule['tag'])
                && ! isset($parseRule['style'])
                && ! isset($parseRule['getAttrs'])
            )) {
            return false;
        }

        return true;
    }

    /**
     * @return (array|mixed|string)[]|null
     *
     * @psalm-return array{type: mixed, text?: string, attrs?: array}|null
     */
    private function parseAttributes($class, $DOMNode): ?array
    {
        $item = [
            'type' => $class::$name,
        ];

        if ($class::$name === 'text') {
            $text = ltrim($DOMNode->nodeValue, "\n");

            if ($text === '') {
                return null;
            }

            $item = array_merge($item, [
                'text' => $text,
            ]);
        }

        $parseRules = $class->parseHTML();

        if (! is_array($parseRules)) {
            return $item;
        }

        foreach ($parseRules as $parseRule) {
            if (! $this->checkParseRule($parseRule, $DOMNode)) {
                continue;
            }

            $attributes = $parseRule['attrs'] ?? [];
            if (count($attributes)) {
                if (! isset($item['attrs'])) {
                    $item['attrs'] = [];
                }

                $item['attrs'] = array_merge($item['attrs'], $attributes);
            }

            if (isset($parseRule['getAttrs'])) {
                if (isset($parseRule['style']) && InlineStyle::hasAttribute($DOMNode, $parseRule['style'])) {
                    $parameter = InlineStyle::getAttribute($DOMNode, $parseRule['style']);
                } else {
                    $parameter = $DOMNode;
                }

                $attributes = $parseRule['getAttrs']($parameter);

                if (! is_array($attributes)) {
                    continue;
                }

                if (! isset($item['attrs'])) {
                    $item['attrs'] = [];
                }

                $item['attrs'] = array_merge($item['attrs'], $attributes);
            }
        }

        /**
         * public function addAttributes()
         * {
         *     return [
         *         'href' => [
         *             'parseHTML' => function ($DOMNode) {
         *                 $attrs['href'] = $DOMNode->getAttribute('href');
         *             }
         *         ],
         *     ];
         * }
         */
        foreach ($this->schema->getAttributeConfigurations($class) as $attribute => $configuration) {
            if (isset($configuration['parseHTML'])) {
                $value = $configuration['parseHTML']($DOMNode);
            } else {
                $value = $DOMNode->getAttribute($attribute) ?: null;
            }

            if ($value !== null) {
                $item['attrs'][$attribute] = $value;
            }
        }

        return $item;
    }
}


================================================
FILE: src/Core/DOMSerializer.php
================================================
<?php

namespace Tiptap\Core;

use DOMDocument;
use stdClass;
use Tiptap\Utils\HTML;

class DOMSerializer
{
    protected $document;

    protected $schema;

    public function __construct($schema)
    {
        $this->schema = $schema;
    }

    private function renderNode($node, $previousNode = null, $nextNode = null, &$markStack = []): string
    {
        $html = [];
        $markTagsToClose = [];

        if (isset($node->marks)) {
            foreach ($node->marks as $mark) {
                foreach ($this->schema->marks as $class) {
                    $renderClass = $class;

                    if (! $this->isMarkOrNode($mark, $renderClass)) {
                        continue;
                    }

                    if (! $this->markShouldOpen($mark, $previousNode)) {
                        continue;
                    }

                    $html[] = $this->renderOpeningTag($renderClass, $mark);
                    # push recently created mark tag to the stack
                    $markStack[] = [$renderClass, $mark];
                }
            }
        }

        foreach ($this->schema->nodes as $extension) {
            if (! $this->isMarkOrNode($node, $extension)) {
                continue;
            }

            $html[] = $this->renderOpeningTag($extension, $node);

            break;
        }

        // ["content" => …]
        $lastKey = array_key_last($html);
        $lastElement = $html[$lastKey] ?? null;
        if (! is_null($lastKey) && isset($lastElement['content'])) {
            $html[$lastKey] = $lastElement['content'];
        }
        // child nodes
        elseif (isset($node->content)) {
            $nestedNodeMarkStack = [];
            foreach ($node->content as $index => $nestedNode) {
                $previousNestedNode = $node->content[$index - 1] ?? null;
                $nextNestedNode = $node->content[$index + 1] ?? null;

                $html[] = $this->renderNode($nestedNode, $previousNestedNode, $nextNestedNode, $nestedNodeMarkStack);
            }
        }
        // renderText($node)
        elseif (isset($extension) && method_exists($extension, 'renderText')) {
            $html[] = $extension->renderText($node);
        }
        // text
        elseif (isset($node->text)) {
            $html[] = htmlspecialchars($node->text, ENT_QUOTES, 'UTF-8');
        }

        foreach ($this->schema->nodes as $extension) {
            if (! $this->isMarkOrNode($node, $extension)) {
                continue;
            }

            $html[] = $this->renderClosingTag($extension->renderHTML($node));
        }

        if (isset($node->marks)) {
            foreach (array_reverse($node->marks) as $mark) {
                foreach ($this->schema->marks as $extension) {
                    if (! $this->isMarkOrNode($mark, $extension)) {
                        continue;
                    }

                    if (! $this->markShouldClose($mark, $nextNode)) {
                        continue;
                    }

                    # remember which mark tags to close
                    $markTagsToClose[] = [$extension, $mark];
                }
            }
            # close mark tags and reopen when necessary
            $html = array_merge($html, $this->closeAndReopenTags($markTagsToClose, $markStack));
        }

        return join($html);
    }

    private function closeAndReopenTags(array $markTagsToClose, array &$markStack): array
    {
        $markTagsToReopen = [];
        $closingTags = $this->closeMarkTags($markTagsToClose, $markStack, $markTagsToReopen);
        $reopeningTags = $this->reopenMarkTags($markTagsToReopen, $markStack);

        return array_merge($closingTags, $reopeningTags);
    }

    private function closeMarkTags($markTagsToClose, &$markStack, &$markTagsToReopen): array
    {
        $html = [];
        while (! empty($markTagsToClose)) {
            # close mark tag from the top of the stack
            $markTag = array_pop($markStack);
            $markExtension = $markTag[0];
            $mark = $markTag[1];
            $html[] = $this->renderClosingTag($markExtension->renderHTML($mark));

            # check if the last closed tag is overlapping and has to be reopened
            # find the first matching mark to close
            $foundIndex = null;
            foreach ($markTagsToClose as $index => $markToClose) {
                if ($markExtension == $markToClose[0] && $mark == $markToClose[1]) {
                    $foundIndex = $index;

                    break;
                }
            }

            if ($foundIndex === null) {
                $markTagsToReopen[] = $markTag;
            } else {
                # specific mark tag does not have to be reopened, but deleted from the 'to close' list
                unset($markTagsToClose[$foundIndex]);
                $markTagsToClose = array_values($markTagsToClose); // Re-index array
            }
        }

        return $html;
    }

    private function reopenMarkTags($markTagsToReopen, &$markStack): array
    {
        $html = [];
        # reopen the overlapping mark tags and push them to the stack
        foreach (array_reverse($markTagsToReopen) as $markTagToOpen) {
            $renderClass = $markTagToOpen[0];
            $mark = $markTagToOpen[1];
            $html[] = $this->renderOpeningTag($renderClass, $mark);
            $markStack[] = [$renderClass, $mark];
        }

        return $html;
    }

    private function isMarkOrNode($markOrNode, $renderClass): bool
    {
        return isset($markOrNode->type) && $markOrNode->type === $renderClass::$name;
    }

    private function markShouldOpen($mark, $previousNode): bool
    {
        return $this->nodeHasMark($previousNode, $mark);
    }

    private function markShouldClose($mark, $nextNode): bool
    {
        return $this->nodeHasMark($nextNode, $mark);
    }

    private function nodeHasMark($node, $mark): bool
    {
        if (! $node) {
            return true;
        }

        if (! property_exists($node, 'marks')) {
            return true;
        }

        // The other node has same mark
        foreach ($node->marks as $otherMark) {
            if ($mark == $otherMark) {
                return false;
            }
        }

        return true;
    }

    private function renderOpeningTag($extension, $nodeOrMark, $renderHTML = false)
    {
        /**
         * public function addAttributes()
         * {
         *     return [
         *        'color' => [
         *            'renderHTML' => function ($attributes) {
         *                return [
         *                    'style' => "color: {$attributes['color']}",
         *                ];
         *            }
         *        ],
         *    ];
         * }
         */
        $HTMLAttributes = [];

        foreach ($this->schema->getAttributeConfigurations($extension) as $attribute => $configuration) {
            // 'rendered' => false
            if (isset($configuration['rendered']) && $configuration['rendered'] === false) {
                continue;
            }

            // 'default' => 'foobar'
            if (! isset($nodeOrMark->attrs->{$attribute}) && isset($configuration['default'])) {
                if (! isset($nodeOrMark->attrs)) {
                    $nodeOrMark->attrs = new stdClass;
                }

                $nodeOrMark->attrs->{$attribute} = $configuration['default'];
            }

            // 'renderHTML' => fn($attributes) …
            if (isset($configuration['renderHTML'])) {
                $value = $configuration['renderHTML']($nodeOrMark->attrs ?? new stdClass);
            } else {
                $value = [
                    $attribute => $nodeOrMark->attrs->{$attribute} ?? null,
                ];
            }

            if ($value !== null) {
                $HTMLAttributes = HTML::mergeAttributes($HTMLAttributes, $value);
            }
        }

        // Remove empty attributes
        $HTMLAttributes = array_filter($HTMLAttributes, fn ($HTMLAttribute) => $HTMLAttribute !== null);

        if ($renderHTML === false) {
            $renderHTML = $extension->renderHTML($nodeOrMark, $HTMLAttributes);
        }

        // ["content" => …]
        if (isset($renderHTML['content'])) {
            return $renderHTML;
        }

        // null
        if (is_null($renderHTML)) {
            return '';
        }

        // ['table', ['tbody', 0]]
        // ['table', ['class' => 'foobar'], ['tbody', 0]]
        if (is_array($renderHTML)) {
            $html = [];

            foreach ($renderHTML as $index => $renderInstruction) {
                // ['div', …]
                if (is_string($renderInstruction)) {
                    if (is_integer($index) && $nextTag = $renderHTML[$index + 1] ?? null) {
                        // ['table', ['class' => 'custom-class']]
                        if (! in_array(0, $nextTag, true)) {
                            if (is_array($nextTag) && $this->isAnAttributeArray($nextTag)) {
                                $attributes = HTML::renderAttributes($nextTag);
                            } else {
                                $attributes = '';
                            }

                            // <a href="#">
                            $html[] = "<{$renderInstruction}{$attributes}>";
                        } else {
                            $html[] = "<{$renderInstruction}>";
                        }
                    } else {
                        $html[] = "<{$renderInstruction}>";
                    }

                    // ['div', 'span']
                    if (isset($nextTag) && is_array($nextTag) && ! in_array(0, $nextTag, true)) {
                        if (! $this->isAnAttributeArray($nextTag)) {
                            $html[] = $this->renderOpeningTag($extension, $nodeOrMark, $nextTag);
                            $html[] = $this->renderClosingTag($nextTag);
                        }
                    }

                    // ['div', ?, 'span']
                    if (is_integer($index) && $nextTag = $renderHTML[$index + 2] ?? null) {
                        if (! in_array(0, $nextTag, true)) {
                            if (! $this->isAnAttributeArray($nextTag)) {
                                $html[] = $this->renderOpeningTag($extension, $nodeOrMark, $nextTag);
                                $html[] = $this->renderClosingTag($nextTag);
                            }
                        }
                    }

                    continue;
                }
                // ['tbody', 0]
                elseif (is_array($renderInstruction) && in_array(0, $renderInstruction, true)) {
                    $html[] = $this->renderOpeningTag($extension, $nodeOrMark, $renderInstruction);
                }
                // ['class' => 'foobar']
                elseif (is_array($renderInstruction)) {
                    continue;
                }
            }

            return join($html);
        }

        throw new \Exception('[renderOpeningTag] Failed to use renderHTML: ' . json_encode($renderHTML));
    }

    private function isAnAttributeArray($items): bool
    {
        if (! is_array($items)) {
            return false;
        }

        $keys = array_keys($items);

        return $keys !== array_keys($keys);
    }

    private function isSelfClosing($tag): bool
    {
        $dom = new DOMDocument('1.0', 'utf-8');
        $element = $dom->createElement($tag, 'test');
        $dom->appendChild($element);
        $rendered = $dom->saveHTML();

        return substr_count($rendered, $tag) === 1;
    }

    /**
     * @return null|string
     */
    private function renderClosingTag($renderHTML)
    {
        // null
        if (is_null($renderHTML)) {
            return '';
        }

        // ["content" => …]
        if (isset($renderHTML['content'])) {
            return;
        }

        // ['table', ['tbody']]
        if (is_array($renderHTML)) {
            $html = [];

            foreach (array_reverse($renderHTML) as $renderInstruction) {
                // 'div'
                if (is_string($renderInstruction)) {
                    if ($this->isSelfClosing($renderInstruction)) {
                        return null;
                    }

                    $html[] = "</{$renderInstruction}>";
                }
                // ['div', 0]
                elseif (is_array($renderInstruction) && in_array(0, $renderInstruction, true)) {
                    $html[] = $this->renderClosingTag($renderInstruction);
                }
            }

            return join($html);
        }

        throw new \Exception('[renderClosingTag] Failed to use renderHTML: ' . json_encode($renderHTML));
    }

    public function process(array $value): string
    {
        $html = [];

        // transform document to object
        $this->document = json_decode(json_encode($value));

        $content = is_array($this->document->content) ? $this->document->content : [];

        $markStack = [];

        foreach ($content as $index => $node) {
            $previousNode = $content[$index - 1] ?? null;
            $nextNode = $content[$index + 1] ?? null;

            $html[] = $this->renderNode($node, $previousNode, $nextNode, $markStack);
        }

        return join($html);
    }
}


================================================
FILE: src/Core/Extension.php
================================================
<?php

namespace Tiptap\Core;

class Extension
{
    public static $name;

    public static $priority = 100;

    public $options = [];

    public function __construct(array $options = [])
    {
        $this->options = array_merge($this->addOptions(), $options);
    }

    public function addOptions()
    {
        return [];
    }

    public function addGlobalAttributes()
    {
        return [];
    }

    public function addExtensions()
    {
        return [];
    }
}


================================================
FILE: src/Core/JSONSerializer.php
================================================
<?php

namespace Tiptap\Core;

class JSONSerializer
{
    protected $document;

    public function process(array $value): string
    {
        $this->document = json_decode(json_encode($value));

        return json_encode($this->document);
    }
}


================================================
FILE: src/Core/Mark.php
================================================
<?php

namespace Tiptap\Core;

class Mark extends Extension
{
    public static $priority = 100;

    public function addAttributes()
    {
        return [];
    }

    public function renderHTML($mark)
    {
        return null;
    }

    public function parseHTML()
    {
        return [];
    }
}


================================================
FILE: src/Core/Node.php
================================================
<?php

namespace Tiptap\Core;

class Node extends Extension
{
    public static $priority = 100;

    public static $topNode = false;

    public static $marks = '_';

    public function addAttributes()
    {
        return [];
    }

    public function parseHTML()
    {
        return [];
    }

    public function renderHTML($node)
    {
        return null;
    }
}


================================================
FILE: src/Core/Schema.php
================================================
<?php

namespace Tiptap\Core;

class Schema
{
    public array $allExtensions = [];

    public array $nodes = [];
    public array $marks = [];
    public array $extensions = [];

    public $defaultNode;
    public $topNode;

    public array $globalAttributes = [];

    public function __construct(array $extensions = [])
    {
        $this->allExtensions = $this->loadExtensions($extensions);
        usort($this->allExtensions, fn ($a, $b) => $b::$priority - $a::$priority);

        $this->nodes = array_filter($this->allExtensions, function ($extension) {
            return is_subclass_of($extension, \Tiptap\Core\Node::class);
        });

        $this->marks = array_filter($this->allExtensions, function ($extension) {
            return is_subclass_of($extension, \Tiptap\Core\Mark::class);
        });

        $this->extensions = array_filter($this->allExtensions, function ($extension) {
            return is_subclass_of($extension, \Tiptap\Core\Extension::class);
        });

        $this->defaultNode = reset($this->nodes);
        $this->topNode = current(array_filter($this->nodes, fn ($node) => $node::$topNode));

        return $this;
    }

    private function loadExtensions($extensions = [])
    {
        foreach ($extensions as $extension) {
            if (method_exists($extension, 'addExtensions') && count($extension->addExtensions())) {
                $extensions = array_merge(
                    $extensions,
                    $this->loadExtensions($extension->addExtensions()),
                );
            }

            if (method_exists($extension, 'addGlobalAttributes')) {
                $globalAttributes = $extension->addGlobalAttributes();

                foreach ($globalAttributes as $globalAttributeConfiguration) {
                    foreach ($globalAttributeConfiguration['types'] ?? [] as $type) {
                        $this->globalAttributes[$type] = array_merge(
                            $this->globalAttributes[$type] ?? [],
                            $globalAttributeConfiguration['attributes']
                        );
                    }
                }
            }
        }

        return $extensions;
    }

    public function apply($document)
    {
        if (! is_array($document['content'])) {
            return $document;
        }

        $document['content'] = array_map(function ($node) {
            foreach ($this->allExtensions as $extension) {
                if (! isset($node['type']) || $node['type'] !== $extension::$name) {
                    continue;
                }

                if (property_exists($extension, 'marks')) {
                    if ($extension::$marks === '') {
                        $node = $this->filterMarks($node);

                        unset($node['marks']);
                    }

                    // TODO: Support for multiple marks is missing
                }

                break;
            }

            return $node;
        }, $document['content']);

        return $document;
    }

    public function filterMarks(&$node)
    {
        unset($node['marks']);

        if (isset($node['content'])) {
            $node['content'] = array_map(function ($child) {
                return $this->filterMarks($child);
            }, $node['content']);
        }

        return $node;
    }

    public function getAttributeConfigurations($class): array
    {
        return array_merge(
            $this->globalAttributes[$class::$name] ?? [],
            $class->addAttributes(),
        );
    }
}


================================================
FILE: src/Core/TextSerializer.php
================================================
<?php

namespace Tiptap\Core;

class TextSerializer
{
    protected $document;

    protected $schema;

    protected $configuration = [
        'blockSeparator' => "\n\n",
    ];

    public function __construct($schema, $configuration = [])
    {
        $this->schema = $schema;
        $this->configuration = array_merge($this->configuration, $configuration);
    }

    public function process(array $value): string
    {
        $html = [];

        // transform document to object
        $this->document = json_decode(json_encode($value));

        $content = is_array($this->document->content) ? $this->document->content : [];

        foreach ($content as $node) {
            $html[] = $this->renderNode($node);
        }

        return join($this->configuration['blockSeparator'], $html);
    }

    private function renderNode($node): string
    {
        $text = [];

        if (isset($node->content)) {
            foreach ($node->content as $nestedNode) {
                $text[] = $this->renderNode($nestedNode);
            }
        } elseif (isset($node->text)) {
            $text[] = htmlspecialchars($node->text, ENT_QUOTES, 'UTF-8');
        }

        return join($this->configuration['blockSeparator'], $text);
    }
}


================================================
FILE: src/Editor.php
================================================
<?php

namespace Tiptap;

use Exception;
use Tiptap\Core\DOMParser;
use Tiptap\Core\DOMSerializer;
use Tiptap\Core\JSONSerializer;
use Tiptap\Core\Schema;
use Tiptap\Core\TextSerializer;
use Tiptap\Extensions\StarterKit;

class Editor
{
    protected $document;

    public $schema;

    public $configuration = [
        'content' => null,
        'extensions' => [],
    ];

    public function __construct(array $configuration = [])
    {
        if (! isset($configuration['extensions'])) {
            $configuration['extensions'] = [
                new StarterKit,
            ];
        }

        $this->configuration = array_merge_recursive($this->configuration, $configuration);
        $this->schema = new Schema($this->configuration['extensions']);

        if (isset($configuration['content'])) {
            $this->setContent($configuration['content']);
        }
    }

    /**
     * @return static
     */
    public function setContent($value): self
    {
        if ($this->getContentType($value) === 'HTML') {
            $this->document = (new DOMParser($this->schema))->process($value);
        } elseif ($this->getContentType($value) === 'Array') {
            $this->document = json_decode(json_encode($value), true);
        } elseif ($this->getContentType($value) === 'JSON') {
            $this->document = json_decode($value, true);
        }

        $this->document = $this->schema->apply($this->document);

        return $this;
    }

    public function getDocument()
    {
        return $this->document;
    }

    public function getJSON(): string
    {
        return (new JSONSerializer)->process($this->document);
    }

    public function getHTML(): string
    {
        return (new DOMSerializer($this->schema))->process($this->document);
    }

    public function getText($configuration = []): string
    {
        return (new TextSerializer($this->schema, $configuration))->process($this->document);
    }

    public function sanitize($value)
    {
        if ($this->getContentType($value) === 'HTML') {
            return $this->setContent($value)->getHTML();
        } elseif ($this->getContentType($value) === 'Array') {
            return $this->setContent($value)->getDocument();
        } elseif ($this->getContentType($value) === 'JSON') {
            return $this->setContent($value)->getJSON();
        }
    }

    public function getContentType($value): string
    {
        if (is_string($value)) {
            try {
                /**
                 * @psalm-suppress UnusedFunctionCall
                 */
                json_decode($value, true, 512, JSON_THROW_ON_ERROR);

                return 'JSON';
            } catch (Exception $exception) {
                return 'HTML';
            }
        }

        if (is_array($value)) {
            return 'Array';
        }

        throw new Exception('Unknown format passed to setContent(). Try passing HTML, JSON or an Array.');
    }

    public function descendants($closure): Editor
    {
        // Transform the document to an object
        $node = json_decode(json_encode($this->document));

        $this->walkThroughNodes($node, $closure);

        // Store the updated document.
        $this->setContent(json_decode(json_encode($node), true));

        return $this;
    }

    /**
     * @return void
     */
    private function walkThroughNodes(&$node, $closure)
    {
        // Skip, if it’s just text.
        if ($node->type === 'text') {
            return;
        }

        // Call the closure.
        $closure($node);

        // Skip, if there are no children.
        if (! isset($node->content)) {
            return;
        }

        // Make sure content is an Array.
        $content = is_array($node->content) ? $node->content : [];

        // Loop through all children.
        foreach ($content as $child) {
            $this->walkThroughNodes($child, $closure);
        }
    }
}


================================================
FILE: src/Extensions/Color.php
================================================
<?php

namespace Tiptap\Extensions;

use Tiptap\Core\Extension;
use Tiptap\Utils\InlineStyle;

class Color extends Extension
{
    public static $name = 'color';

    public function addOptions()
    {
        return [
            'types' => ['textStyle'],
        ];
    }

    public function addGlobalAttributes()
    {
        return [
            [
                'types' => $this->options['types'],
                'attributes' => [
                    'color' => [
                        'default' => null,
                        'parseHTML' => function ($DOMNode) {
                            $attribute = InlineStyle::getAttribute($DOMNode, 'color');

                            if ($attribute === null) {
                                return null;
                            }

                            return preg_replace('/[\'"]+/', '', $attribute);
                        },
                        'renderHTML' => function ($attributes) {
                            $color = $attributes?->color ?? null;

                            if ($color === null) {
                                return null;
                            }

                            return ['style' => "color: {$color}"];
                        },
                    ],
                ],
            ],
        ];
    }
}


================================================
FILE: src/Extensions/FontFamily.php
================================================
<?php

namespace Tiptap\Extensions;

use Tiptap\Core\Extension;
use Tiptap\Utils\InlineStyle;

class FontFamily extends Extension
{
    public static $name = 'fontFamily';

    public function addOptions()
    {
        return [
            'types' => ['textStyle'],
        ];
    }

    public function addGlobalAttributes()
    {
        return [
            [
                'types' => $this->options['types'],
                'attributes' => [
                    'fontFamily' => [
                        'default' => null,
                        'parseHTML' => function ($DOMNode) {
                            $attribute = InlineStyle::getAttribute($DOMNode, 'font-family');

                            if ($attribute === null) {
                                return null;
                            }

                            return $attribute;
                        },
                        'renderHTML' => function ($attributes) {
                            $fontFamily = $attributes?->fontFamily ?? null;

                            if ($fontFamily === null) {
                                return null;
                            }

                            return ['style' => "font-family: {$fontFamily}"];
                        },
                    ],
                ],
            ],
        ];
    }
}


================================================
FILE: src/Extensions/StarterKit.php
================================================
<?php

namespace Tiptap\Extensions;

use Tiptap\Core\Extension;

class StarterKit extends Extension
{
    public static $name = 'starterKit';

    public function addOptions()
    {
        return [
            'document' => [],
            'blockquote' => [],
            'bulletList' => [],
            'codeBlock' => [],
            'hardBreak' => [],
            'heading' => [],
            'horizontalRule' => [],
            'listItem' => [],
            'orderedList' => [],
            'paragraph' => [],
            'text' => [],
            'bold' => [],
            'code' => [],
            'italic' => [],
            'strike' => [],
        ];
    }

    public function addExtensions()
    {
        return array_filter([
            $this->options['document'] !== false
                ? new \Tiptap\Nodes\Document($this->options['document'])
                : null,
            $this->options['blockquote'] !== false
                ? new \Tiptap\Nodes\Blockquote($this->options['blockquote'])
                : null,
            $this->options['bulletList'] !== false
                ? new \Tiptap\Nodes\BulletList($this->options['bulletList'])
                : null,
            $this->options['codeBlock'] !== false
                ? new \Tiptap\Nodes\CodeBlock($this->options['codeBlock'])
                : null,
            $this->options['hardBreak'] !== false
                ? new \Tiptap\Nodes\HardBreak($this->options['hardBreak'])
                : null,
            $this->options['heading'] !== false
                ? new \Tiptap\Nodes\Heading($this->options['heading'])
                : null,
            $this->options['horizontalRule'] !== false
                ? new \Tiptap\Nodes\HorizontalRule($this->options['horizontalRule'])
                : null,
            $this->options['listItem'] !== false
                ? new \Tiptap\Nodes\ListItem($this->options['listItem'])
                : null,
            $this->options['orderedList'] !== false
                ? new \Tiptap\Nodes\OrderedList($this->options['orderedList'])
                : null,
            $this->options['paragraph'] !== false
                ? new \Tiptap\Nodes\Paragraph($this->options['paragraph'])
                : null,
            $this->options['text'] !== false
                ? new \Tiptap\Nodes\Text($this->options['text'])
                : null,
            $this->options['bold'] !== false
                ? new \Tiptap\Marks\Bold($this->options['bold'])
                : null,
            $this->options['code'] !== false
                ? new \Tiptap\Marks\Code($this->options['code'])
                : null,
            $this->options['italic'] !== false
                ? new \Tiptap\Marks\Italic($this->options['italic'])
                : null,
            $this->options['strike'] !== false
                ? new \Tiptap\Marks\Strike($this->options['strike'])
                : null,
        ]);
    }
}


================================================
FILE: src/Extensions/TextAlign.php
================================================
<?php

namespace Tiptap\Extensions;

use Tiptap\Core\Extension;
use Tiptap\Utils\InlineStyle;

class TextAlign extends Extension
{
    public static $name = 'textAlign';

    public function addOptions()
    {
        return [
            'types' => [],
            'alignments' => ['left', 'center', 'right', 'justify'],
            'defaultAlignment' => 'left',
        ];
    }

    public function addGlobalAttributes()
    {
        return [
            [
              'types' => $this->options['types'],
              'attributes' => [
                'textAlign' => [
                    'default' => $this->options['defaultAlignment'],
                    'parseHTML' => fn ($DOMNode) =>
                        InlineStyle::getAttribute($DOMNode, 'text-align') ?? $this->options['defaultAlignment'],
                    'renderHTML' => function ($attributes) {
                        if ($attributes->textAlign === $this->options['defaultAlignment']) {
                            return null;
                        }

                        return ['style' => "text-align: {$attributes->textAlign}"];
                    },
                ],
              ],
            ],
        ];
    }
}


================================================
FILE: src/Marks/Bold.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;
use Tiptap\Utils\InlineStyle;

class Bold extends Mark
{
    public static $name = 'bold';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'strong',
            ],
            [
                'tag' => 'b',
                'getAttrs' => function ($DOMNode) {
                    return ! InlineStyle::hasAttribute($DOMNode, [
                        'font-weight' => 'normal',
                    ]) ? null : false;
                },
            ],
            [
                'style' => 'font-weight',
                'getAttrs' => function ($value) {
                    return (bool) preg_match('/^(bold(er)?|[5-9]\d{2,})$/', $value) ? null : false;
                },
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return [
            'strong',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            0,
        ];
    }
}


================================================
FILE: src/Marks/Code.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class Code extends Mark
{
    public static $name = 'code';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'code',
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['code', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Marks/Highlight.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;
use Tiptap\Utils\InlineStyle;

class Highlight extends Mark
{
    public static $name = 'highlight';

    public function addOptions()
    {
        return [
            'multicolor' => false,
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'mark',
            ],
        ];
    }

    public function addAttributes()
    {
        if (! $this->options['multicolor']) {
            return [];
        }

        return [
            'color' => [
                'parseHTML' => function ($DOMNode) {
                    if ($color = $DOMNode->getAttribute('data-color')) {
                        return $color;
                    }

                    return InlineStyle::getAttribute($DOMNode, 'background-color') ?: null;
                },
                'renderHTML' => function ($attributes) {
                    if (! $attributes->color) {
                        return null;
                    }

                    return [
                        'data-color' => $attributes->color,
                        'style' => "background-color: {$attributes->color}",
                    ];
                },
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return [
            'mark',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            0,
        ];
    }
}


================================================
FILE: src/Marks/Italic.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;
use Tiptap\Utils\InlineStyle;

class Italic extends Mark
{
    public static $name = 'italic';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'em',
            ],
            [
                'tag' => 'i',
                'getAttrs' => function ($DOMNode) {
                    return ! InlineStyle::hasAttribute($DOMNode, [
                        'font-style' => 'normal',
                    ]) ? null : false;
                },
            ],
            [
                'style' => 'font-style=italic',
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['em', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Marks/Link.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class Link extends Mark
{
    public static $name = 'link';

    // Port of the DOMPurify helper used by Tiptap’s Link extension
    // https://github.com/ueberdosis/tiptap/blob/next/packages/extension-link/src/link.ts#L161
    const ATTR_WHITESPACE = '/[\x00-\x20\x{00A0}\x{1680}\x{180E}\x{2000}-\x{2029}\x{205F}\x{3000}]/u';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [
                'target' => '_blank',
                'rel' => 'noopener noreferrer nofollow',
            ],
            'allowedProtocols' => [
                'http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp',
            ],
            'isAllowedUri' => fn ($uri) => $this->isAllowedUri($uri),
        ];
    }

    public function isAllowedUri($uri)
    {
        if ($uri === null || $uri === '') {
            return true;
        }

        $sanitised = preg_replace(self::ATTR_WHITESPACE, '', $uri);

        $pattern = '/^(?:(?:' . implode('|', array_map('preg_quote', $this->options['allowedProtocols']))
        . '):|[^a-z]|[a-z0-9+.\-]+(?:[^a-z+.\-:]|$))/i';

        return (bool) preg_match($pattern, $sanitised);
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'a[href]',
                'getAttrs' => function ($DOMNode) {
                    $href = $DOMNode->getAttribute('href');

                    if (
                        $href === '' ||
                        ! $this->options['isAllowedUri']($href)
                    ) {
                        return false;
                    }

                    return null;
                },
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'href' => [],
            'target' => [],
            'rel' => [],
            'class' => [],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        $isAllowed = $this->options['isAllowedUri']($HTMLAttributes['href'] ?? '');

        if (! $isAllowed) {
            $HTMLAttributes['href'] = '';
        }

        $attributes = HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes);

        if (isset($mark->attrs)) {
            foreach ((array) $mark->attrs as $key => $value) {
                if ($value === null) {
                    unset($attributes[$key]);
                }
            }
        }

        return [
            'a',
            $attributes,
            0,
        ];
    }
}


================================================
FILE: src/Marks/Strike.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class Strike extends Mark
{
    public static $name = 'strike';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 's',
            ],
            [
                'tag' => 'del',
            ],
            [
                'tag' => 'strike',
            ],
            [
                'style' => 'text-decoration=line-through',
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['s', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Marks/Subscript.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class Subscript extends Mark
{
    public static $name = 'subscript';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'sub',
            ],
            [
                'style' => 'vertical-align=sub',
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['sub', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Marks/Superscript.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class Superscript extends Mark
{
    public static $name = 'superscript';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'sup',
            ],
            [
                'style' => 'vertical-align=super',
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['sup', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Marks/TextStyle.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class TextStyle extends Mark
{
    public static $name = 'textStyle';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'span',
                'getAttrs' => function ($DOMNode) {
                    return $DOMNode->hasAttribute('style') ? null : false;
                },
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['span', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Marks/Underline.php
================================================
<?php

namespace Tiptap\Marks;

use Tiptap\Core\Mark;
use Tiptap\Utils\HTML;

class Underline extends Mark
{
    public static $name = 'underline';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'u',
            ],
            [
                'style' => 'text-decoration=underline',
            ],
        ];
    }

    public function renderHTML($mark, $HTMLAttributes = [])
    {
        return ['u', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/Blockquote.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Blockquote extends Node
{
    public static $name = 'blockquote';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'blockquote',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['blockquote', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/BulletList.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class BulletList extends Node
{
    public static $name = 'bulletList';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'ul',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['ul', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/CodeBlock.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class CodeBlock extends Node
{
    public static $name = 'codeBlock';

    public static $marks = '';

    public function addOptions()
    {
        return [
            'languageClassPrefix' => 'language-',
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'pre',
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'language' => [
                'parseHTML' => function ($DOMNode) {
                    if (! ($DOMNode->childNodes[0] instanceof \DOMElement)) {
                        return null;
                    }

                    return preg_replace(
                        "/^" . $this->options['languageClassPrefix']. "/",
                        "",
                        $DOMNode->childNodes[0]->getAttribute('class')
                    ) ?: null;
                },
                'rendered' => false,
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'pre',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            [
                'code',
                [
                    'class' => $node->attrs->language ?? null
                        ? $this->options['languageClassPrefix'] . $node->attrs->language
                        : null,
                ],
                0,
            ],
        ];
    }
}


================================================
FILE: src/Nodes/CodeBlockHighlight.php
================================================
<?php

namespace Tiptap\Nodes;

use DomainException;
use Highlight\Highlighter;
use Tiptap\Utils\HTML;

class CodeBlockHighlight extends CodeBlock
{
    public function addOptions()
    {
        return [
            'languageClassPrefix' => 'hljs ',
            'HTMLAttributes' => [],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        $code = $node->content[0]->text ?? '';

        try {
            $highlighter = new Highlighter();

            if ($node->attrs->language ?? null) {
                $result = $highlighter->highlight($node->attrs->language, $code);
            } else {
                $result = $highlighter->highlightAuto($code);
            }

            $mergedAttributes = HTML::mergeAttributes(
                [
                    'class' => $this->options['languageClassPrefix'] . $result->language,
                ],
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
            );

            $renderedAttributes = HTML::renderAttributes($mergedAttributes);

            $content = "<pre><code" . $renderedAttributes . ">";
            $content .= $result->value;
            $content .= "</code></pre>";
        } catch (DomainException $exception) {
            $mergedAttributes = HTML::mergeAttributes(
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
            );

            $renderedAttributes = HTML::renderAttributes($mergedAttributes);

            $content = "<pre><code" . $renderedAttributes . ">";
            $content .= htmlentities($code);
            $content .= "</code></pre>";
        }

        return [
            'content' => $content,
        ];
    }
}


================================================
FILE: src/Nodes/CodeBlockShiki.php
================================================
<?php

namespace Tiptap\Nodes;

use DomainException;
use Exception;
use Highlight\Highlighter;
use Spatie\ShikiPhp\Shiki;
use Tiptap\Utils\HTML;

class CodeBlockShiki extends CodeBlock
{
    public function addOptions()
    {
        return [
            'languageClassPrefix' => 'language-',
            'HTMLAttributes' => [],
            'defaultLanguage' => 'html',
            'theme' => 'nord',
            'guessLanguage' => true,
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        $code = $node->content[0]->text ?? '';

        // Language is set
        if ($node->attrs->language === null) {
            $language = $node->attrs->language;
        }
        // Auto-detect the language
        elseif ($this->options['guessLanguage']) {
            try {
                $highlighter = new Highlighter();
                $result = $highlighter->highlightAuto($code);
                $language = $result->language;
            } catch (Exception $exception) {
                //
            }
        }

        // Use the default language
        if (! isset($language)) {
            $language = $this->options['defaultLanguage'];
        }

        try {
            $content = Shiki::highlight($code, $language, 'nord');
        } catch (DomainException $exception) {
            $mergedAttributes = HTML::mergeAttributes(
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
            );

            $renderedAttributes = HTML::renderAttributes($mergedAttributes);

            $content = "<pre><code" . $renderedAttributes . ">";
            $content .= htmlentities($code);
            $content .= "</code></pre>";
        }

        return [
            'content' => $content,
        ];
    }
}


================================================
FILE: src/Nodes/Details.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Details extends Node
{
    public static $name = 'details';

    public function addOptions()
    {
        return [
            'persist' => false,
            'openClassName' => 'is-open',
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'details',
            ],
        ];
    }

    public function addAttributes()
    {
        if (! $this->options['persist']) {
            return [];
        }

        return [
            'open' => [
                'default' => false,
                'parseHTML' => fn ($DOMNode) => $DOMNode->hasAttribute('open'),
                'renderHTML' => fn ($attributes) => $attributes->open ? ['open' => 'open'] : [],
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'details',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/DetailsContent.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class DetailsContent extends Node
{
    public static $name = 'detailsContent';

    public function addOptions(): array
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML(): array
    {
        return [
            [
                'tag' => 'div[data-type]',
                'getAttrs' => fn ($value): bool => (bool) $value == 'detailsContent',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = []): array
    {
        return [
            'div',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes, ['data-type' => 'detailsContent']),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/DetailsSummary.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class DetailsSummary extends Node
{
    public static $name = 'detailsSummary';

    public function addOptions(): array
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML(): array
    {
        return [
            [
                'tag' => 'summary',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = []): array
    {
        return [
            'summary',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/Document.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;

class Document extends Node
{
    public static $name = 'doc';

    public static $topNode = true;
}


================================================
FILE: src/Nodes/HardBreak.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class HardBreak extends Node
{
    public static $name = 'hardBreak';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'br',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['br', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];
    }
}


================================================
FILE: src/Nodes/Heading.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Heading extends Node
{
    public static $name = 'heading';

    public function addOptions()
    {
        return [
            'levels' => [1, 2, 3, 4, 5, 6],
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return array_map(function ($level) {
            return [
                'tag' => "h{$level}",
                'attrs' => [
                    'level' => $level,
                ],
            ];
        }, $this->options['levels']);
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        $hasLevel = in_array($node->attrs->level, $this->options['levels']);

        $level = $hasLevel ?
            $node->attrs->level :
            $this->options['levels'][0];

        return [
            "h{$level}",
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/HorizontalRule.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class HorizontalRule extends Node
{
    public static $name = 'horizontalRule';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'hr',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['hr', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];
    }
}


================================================
FILE: src/Nodes/Image.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Image extends Node
{
    public static $name = 'image';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'img[src]',
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'src' => [],
            'alt' => [],
            'title' => [],
            'width' => [],
            'height' => [],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['img', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/ListItem.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class ListItem extends Node
{
    public static $name = 'listItem';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'li',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['li', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/Mention.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Mention extends Node
{
    public static $name = 'mention';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
            'renderLabel' => fn () => null,
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'span[data-type="' . self::$name . '"]',
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'id' => [
                'parseHTML' => fn ($DOMNode) => $DOMNode->getAttribute('data-id') ?: null,
                'renderHTML' => fn ($attributes) => ['data-id' => $attributes->id ?? null],
            ],
        ];
    }

    public function renderText($node)
    {
        return $this->options['renderLabel']($node);
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'span',
            HTML::mergeAttributes(
                ['data-type' => self::$name],
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
            ),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/OrderedList.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class OrderedList extends Node
{
    public static $name = 'orderedList';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'ol',
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'start' => [
                'parseHTML' => fn ($DOMNode) => (int) $DOMNode->getAttribute('start') ?: null,
                'renderHTML' => fn ($attributes) => ($attributes->start ?? null) ? ['start' => $attributes->start] : null,
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['ol', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/Paragraph.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Paragraph extends Node
{
    public static $name = 'paragraph';

    public static $priority = 1000;

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'p',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['p', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/Table.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class Table extends Node
{
    public static $name = 'table';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'table',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'table',
            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),
            ['tbody', 0],
        ];
    }
}


================================================
FILE: src/Nodes/TableCell.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class TableCell extends Node
{
    public static $name = 'tableCell';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'td',
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'rowspan' => [
                'parseHTML' => fn ($DOMNode) => intval($DOMNode->getAttribute('rowspan')) ?: null,
            ],
            'colspan' => [
                'parseHTML' => fn ($DOMNode) => intval($DOMNode->getAttribute('colspan')) ?: null,
            ],
            'colwidth' => [
                'parseHTML' => function ($DOMNode) {
                    $colwidth = $DOMNode->getAttribute('data-colwidth');

                    if (! $colwidth) {
                        return null;
                    }

                    $widths = array_map(function ($w) {
                        return intval($w);
                    }, explode(',', $colwidth));

                    return $widths;
                },
                'renderHTML' => function ($attributes) {
                    if (! isset($attributes->colwidth)) {
                        return null;
                    }

                    return [
                        'data-colwidth' => join(',', $attributes->colwidth),
                    ];
                },
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'td',
            HTML::mergeAttributes(
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
            ),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/TableHeader.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Utils\HTML;

class TableHeader extends TableCell
{
    public static $name = 'tableHeader';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'th',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'th',
            HTML::mergeAttributes(
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
            ),
            0,
        ];
    }
}


================================================
FILE: src/Nodes/TableRow.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class TableRow extends Node
{
    public static $name = 'tableRow';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'tr',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['tr', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];
    }
}


================================================
FILE: src/Nodes/TaskItem.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class TaskItem extends Node
{
    public static $name = 'taskItem';

    public static $priority = 1000;

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function addAttributes()
    {
        return [
            'checked' => [
                'default' => false,
                'renderHTML' => fn ($attributes) => [
                    'data-checked' => $attributes->checked ?? null,
                ],
            ],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'li[data-type="' . self::$name . '"]',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return [
            'li',
            HTML::mergeAttributes(
                $this->options['HTMLAttributes'],
                $HTMLAttributes,
                ['data-type' => self::$name],
            ),
            [
                'label',
                [
                    'input',
                    [
                        'type' => 'checkbox',
                        'checked' => $node->attrs->checked ?? null
                        ? 'checked'
                        : null,
                    ],
                ],
                ['span'],
            ],
            [
                'div',
                0,
            ],
        ];
    }
}


================================================
FILE: src/Nodes/TaskList.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;

class TaskList extends Node
{
    public static $name = 'taskList';

    public static $priority = 1000;

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'ul[data-type="' . self::$name . '"]',
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['ul', HTML::mergeAttributes(
            $this->options['HTMLAttributes'],
            $HTMLAttributes,
            ['data-type' => self::$name],
        ), 0];
    }
}


================================================
FILE: src/Nodes/Text.php
================================================
<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;

class Text extends Node
{
    public static $name = 'text';

    public function parseHTML()
    {
        return [
            [
                'tag' => '#text',
            ],
        ];
    }
}


================================================
FILE: src/Utils/HTML.php
================================================
<?php

namespace Tiptap\Utils;

class HTML
{
    /**
     * Merge an associative array of attributes,
     * and make sure to merge classes and inline styles.
     */
    public static function mergeAttributes()
    {
        $args = func_get_args();

        $attributes = array_shift($args);

        foreach ($args as $moreAttributes) {
            foreach ($moreAttributes as $key => $value) {
                // class="foo bar"
                if ($key === 'class') {
                    $attributes['class'] = trim(($attributes['class'] ?? '') . ' ' . $value);

                    continue;
                }

                // style="color: red;"
                if ($key === 'style') {
                    $style = rtrim($attributes['style'] ?? '', '; ') . '; ' . rtrim($value ?? '', ';') . '; ';
                    $attributes['style'] = ltrim(trim($style), '; ');

                    continue;
                }

                $attributes[$key] = $value;
            }
        }

        return $attributes;
    }

    /**
     * Render an associative array of attributes
     * as a HTML string.
     */
    public static function renderAttributes(array $attrs): string
    {
        // Make boolean values a string, so they can be rendered in HTML
        $attrs = array_map(function ($attribute) {
            if ($attribute === true) {
                return 'true';
            }

            if ($attribute === false) {
                return 'false';
            }

            return $attribute;
        }, $attrs);

        $attributes = [];

        // class="custom"
        foreach (array_filter($attrs) as $name => $value) {
            $escapedValue = htmlentities($value);

            $attributes[] = " {$name}=\"{$escapedValue}\"";
        }

        return join($attributes);
    }
}


================================================
FILE: src/Utils/InlineStyle.php
================================================
<?php

namespace Tiptap\Utils;

use Exception;

class InlineStyle
{
    /**
     * @return string[]
     *
     * @psalm-return array<string, string>
     */
    public static function get($DOMNode): array
    {
        $results = [];

        if (! method_exists($DOMNode, 'getAttribute')) {
            return [];
        }

        $style = $DOMNode->getAttribute('style');

        preg_match_all(
            "/([\w-]+)\s*:\s*([^;]+)\s*;?/",
            $style,
            $matches,
            PREG_SET_ORDER
        );

        foreach ($matches as $match) {
            $results[$match[1]] = $match[2];
        }

        return $results;
    }

    public static function hasAttribute($DOMNode, $value): bool
    {
        $styles = self::get($DOMNode);

        if (is_string($value)) {
            return in_array($value, array_keys($styles));
        }

        if (is_array($value)) {
            return array_diff($value, $styles) == [];
        }

        throw new Exception('Can’t compare inline styles to ' . json_encode($value));
    }

    public static function getAttribute($DOMNode, $attribute): ?string
    {
        return self::get($DOMNode)[$attribute] ?? null;
    }
}


================================================
FILE: src/Utils/Minify.php
================================================
<?php

namespace Tiptap\Utils;

class Minify
{
    protected $_replacementHash;
    protected $_placeholders = [];
    protected $_html;

    public function process($html): string
    {
        $this->_html = str_replace("\r\n", "\n", trim($html));

        $hash = isset($_SERVER['REQUEST_TIME']) ? (string) $_SERVER['REQUEST_TIME'] : (string) time();
        $this->_replacementHash = 'MINIFYHTML' . md5($hash);

        // replace PREs with placeholders
        $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/iu', [$this, '_removePreCB'], $this->_html);

        // trim each line.
        $this->_html = preg_replace('/^\\s+|\\s+$/mu', '', $this->_html);

        // remove ws around block/undisplayed elements
        $this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
            . '|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
            . '|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
            . '|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
            . '|ul|video)\\b[^>]*>)/iu', '$1', $this->_html);

        // fill placeholders
        $this->_html = str_replace(
            array_keys($this->_placeholders),
            array_values($this->_placeholders),
            $this->_html
        );

        return $this->_html;
    }

    protected function _removePreCB($m): string
    {
        return $this->_reservePlace("<pre{$m[1]}");
    }

    protected function _reservePlace($content): string
    {
        $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
        $this->_placeholders[$placeholder] = $content;

        return $placeholder;
    }
}


================================================
FILE: tests/DOMParser/EmojiTest.php
================================================
<?php

use Tiptap\Editor;

test('emojis are transformed correctly', function () {
    $html = "<p>🔥</p>";

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => "🔥",
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/EmptyNodesTest.php
================================================
<?php

use Tiptap\Editor;

test('parsing must not fail on empty nodes', function () {
    $html = '<p><img /></p><p><img /></p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [],
            ],
            [
                'type' => 'paragraph',
                'content' => [],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/EmptyTextNodesTest.php
================================================
<?php

use Tiptap\Editor;

test('output_must_not_have_empty_text_nodes()', function () {
    $html = "<em><br />\n</em>";

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'hardBreak',
                'marks' => [
                    [
                        'type' => 'italic',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Extensions/ColorTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\Color;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\TextStyle;

test('color is parsed correctly', function () {
    $html = '<p><span style="color: red;">red text</span></p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new TextStyle(),
                new Color(),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'marks' => [
                            [
                                'type' => 'textStyle',
                                'attrs' => [
                                    'color' => 'red',
                                ],
                            ],
                        ],
                        'text' => 'red text',
                    ],
                ],
            ],
        ],
    ]);
});

test('color extension respects the types option', function () {
    $html = '<h1 style="color: red;">red heading</h1>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit(),
            new Color([
                'types' => ['heading'],
            ]),
        ],
    ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'heading',
                'attrs' => [
                    'level' => 1,
                    'color' => 'red',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'red heading',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Extensions/FontFamilyTest.php
================================================
<?php

namespace Tiptap\Tests\DOMParser\Extensions;

use Tiptap\Editor;
use Tiptap\Extensions\FontFamily;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\TextStyle;

test('font family is parsed correctly', function () {
    $html = '<p><span style="font-family: Arial;">Arial text</span></p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new TextStyle(),
                new FontFamily(),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'marks' => [
                            [
                                'type' => 'textStyle',
                                'attrs' => [
                                    'fontFamily' => 'Arial',
                                ],
                            ],
                        ],
                        'text' => 'Arial text',
                    ],
                ],
            ],
        ],
    ]);
});

test('multiple font family values are parsed correctly', function () {
    $html = '<p><span style="font-family: Helvetica Neue, Arial, \'Times New Roman\', &quot;Open Sans&quot;, sans-serif;">Multiple fonts</span></p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new TextStyle(),
                new FontFamily(),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'marks' => [
                            [
                                'type' => 'textStyle',
                                'attrs' => [
                                    'fontFamily' => 'Helvetica Neue, Arial, \'Times New Roman\', "Open Sans", sans-serif',
                                ],
                            ],
                        ],
                        'text' => 'Multiple fonts',
                    ],
                ],
            ],
        ],
    ]);
});

test('font family extension respects the types option', function () {
    $html = '<h1 style="font-family: Times New Roman;">Times New Roman heading</h1>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit(),
            new FontFamily([
                'types' => ['heading'],
            ]),
        ],
    ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'heading',
                'attrs' => [
                    'level' => 1,
                    'fontFamily' => 'Times New Roman',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Times New Roman heading',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Extensions/TextAlignTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Extensions\TextAlign;

test('text align is parsed correctly', function () {
    $html = '<p style="text-align: center;">Example Text</p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new TextAlign([
                    'types' => ['paragraph'],
                ]),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'attrs' => [
                    'textAlign' => 'center',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('text align uses default value', function () {
    $html = '<p>Example Text</p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new TextAlign([
                    'types' => ['paragraph'],
                ]),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'attrs' => [
                    'textAlign' => 'left',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('default text align is configureable', function () {
    $html = '<p>Example Text</p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new TextAlign([
                    'types' => ['paragraph'],
                    'defaultAlignment' => 'center',
                ]),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'attrs' => [
                    'textAlign' => 'center',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/KeepContentOfUnknownTagsTest.php
================================================
<?php

use Tiptap\Editor;

test('keeps content of unknown tags', function () {
    $html = "<p>Example <x-unknown-tag>Text</x-unknown-tag></p>";

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => "Example Text",
                    ],
                ],
            ],
        ],
    ]);
});

test('keeps content of unknown tags even if it has known tags', function () {
    $html = '<p>Example <x-unknown-tag><b>Text</b></x-unknown-tag></p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => "Example ",
                    ],
                    [
                        'type' => 'text',
                        'text' => "Text",
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/BoldTest.php
================================================
<?php

use Tiptap\Editor;

test('b gets rendered correctly', function () {
    $html = '<p><b>Example</b> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('strong gets rendered correctly', function () {
    $html = '<p><strong>Example</strong> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('b with font weight normal is ignored', function () {
    $html = '<p><b style="font-weight: normal;">Example</b> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('span with font weight bold is parsed', function () {
    $html = '<p><span style="font-weight: bold;">Example</span> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('span with font weight 500 is parsed', function () {
    $html = '<p><span style="font-weight: 500;">Example</span> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/CodeTest.php
================================================
<?php

use Tiptap\Editor;

test('code gets rendered correctly', function () {
    $html = '<p><code>Example Text</code></p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'code',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/CustomMarkTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;

class CustomMark extends \Tiptap\Core\Mark
{
    public static $name = 'custom';

    public function parseHTML()
    {
        return [
            [
                'tag' => 'span',
            ],
        ];
    }

    public function addAttributes()
    {
        return [
            'foo' => [
                'parseHTML' => fn ($DOMNode) => $DOMNode->getAttribute('data-foo') ?: null,
            ],
            'fruit' => [],
        ];
    }
}

test('b and strong get rendered correctly', function () {
    $html = '<p><span data-foo="bar" fruit="banana">Example</span> text</p>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit,
                new CustomMark,
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'custom',
                                'attrs' => [
                                    'foo' => 'bar',
                                    'fruit' => 'banana',
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/HighlightTest.php
================================================
<?php

use Tiptap\Editor;

test('mark gets rendered correctly', function () {
    $html = '<p><mark>Example</mark> Text</p>';

    $result = (new Editor([
        'extensions' => [
            new \Tiptap\Extensions\StarterKit,
            new \Tiptap\Marks\Highlight,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'highlight',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('color is ignored by default', function () {
    $html = '<p><mark>Example</mark> Text</p>';

    $result = (new Editor([
        'extensions' => [
            new \Tiptap\Extensions\StarterKit,
            new \Tiptap\Marks\Highlight,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'highlight',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('color is parsed from data attribute', function () {
    $html = '<p><mark data-color="red">Example</mark> Text</p>';

    $result = (new Editor([
        'extensions' => [
            new \Tiptap\Extensions\StarterKit,
            new \Tiptap\Marks\Highlight([
                'multicolor' => true,
            ]),
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'highlight',
                                'attrs' => [
                                    'color' => 'red',
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('color is parsed from the background color inline style', function () {
    $html = '<p><mark style="background-color: #ffcc00">Example</mark> Text</p>';

    $result = (new Editor([
        'extensions' => [
            new \Tiptap\Extensions\StarterKit,
            new \Tiptap\Marks\Highlight([
                'multicolor' => true,
            ]),
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'highlight',
                                'attrs' => [
                                    'color' => '#ffcc00',
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/ItalicTest.php
================================================
<?php


use Tiptap\Editor;

test('i gets rendered correctly', function () {
    $html = '<p><i>Example</i> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('em gets rendered correctly', function () {
    $html = '<p><em>Example</em> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('i with font style normal is ignored', function () {
    $html = '<p><i style="font-style: normal;">Example</i> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('span with font style italic is parsed', function () {
    $html = '<p><span style="font-style: italic;">Example</span> Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/LinkTest.php
================================================
<?php


use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\Link;

test('link gets rendered correctly', function () {
    $html = '<a href="https://tiptap.dev">Example Link</a>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'text',
                'text' => 'Example Link',
                'marks' => [
                    [
                        'type' => 'link',
                        'attrs' => [
                            'href' => 'https://tiptap.dev',
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('link_mark_has_support_for_rel', function () {
    $html = '<a href="https://tiptap.dev" rel="noopener">Example Link</a>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'text',
                'text' => 'Example Link',
                'marks' => [
                    [
                        'type' => 'link',
                        'attrs' => [
                            'href' => 'https://tiptap.dev',
                            'rel' => 'noopener',
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('link_mark_has_support_for_class', function () {
    $html = '<a class="tiptap" href="https://tiptap.dev">Example Link</a>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'text',
                'text' => 'Example Link',
                'marks' => [
                    [
                        'type' => 'link',
                        'attrs' => [
                            'href' => 'https://tiptap.dev',
                            'class' => 'tiptap',
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('link_mark_has_support_for_target', function () {
    $html = '<a href="https://tiptap.dev" target="_blank">Example Link</a>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'text',
                'text' => 'Example Link',
                'marks' => [
                    [
                        'type' => 'link',
                        'attrs' => [
                            'href' => 'https://tiptap.dev',
                            'target' => '_blank',
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

function getValidUrls()
{
    return [
        'https://example.com',
        'http://example.com',
        '/same-site/index.html',
        '../relative.html',
        'mailto:info@example.com',
        'ftp://info@example.com',
    ];
}

function getInvalidUrls()
{
    // Copied from https://github.com/ueberdosis/tiptap/blob/next/tests/cypress/integration/extensions/link.spec.ts

    return [
        // A standard JavaScript protocol
        "javascript:alert(window.origin)",
    
        // The protocol is not case sensitive
        "jAvAsCrIpT:alert(window.origin)",
    
        // Characters \x01-\x20 are allowed before the protocol
        "\x00javascript:alert(window.origin)",
        "\x01javascript:alert(window.origin)",
        "\x02javascript:alert(window.origin)",
        "\x03javascript:alert(window.origin)",
        "\x04javascript:alert(window.origin)",
        "\x05javascript:alert(window.origin)",
        "\x06javascript:alert(window.origin)",
        "\x07javascript:alert(window.origin)",
        "\x08javascript:alert(window.origin)",
        "\x09javascript:alert(window.origin)",
        "\x0ajavascript:alert(window.origin)",
        "\x0bjavascript:alert(window.origin)",
        "\x0cjavascript:alert(window.origin)",
        "\x0djavascript:alert(window.origin)",
        "\x0ejavascript:alert(window.origin)",
        "\x0fjavascript:alert(window.origin)",
        "\x10javascript:alert(window.origin)",
        "\x11javascript:alert(window.origin)",
        "\x12javascript:alert(window.origin)",
        "\x13javascript:alert(window.origin)",
        "\x14javascript:alert(window.origin)",
        "\x15javascript:alert(window.origin)",
        "\x16javascript:alert(window.origin)",
        "\x17javascript:alert(window.origin)",
        "\x18javascript:alert(window.origin)",
        "\x19javascript:alert(window.origin)",
        "\x1ajavascript:alert(window.origin)",
        "\x1bjavascript:alert(window.origin)",
        "\x1cjavascript:alert(window.origin)",
        "\x1djavascript:alert(window.origin)",
        "\x1ejavascript:alert(window.origin)",
        "\x1fjavascript:alert(window.origin)",
    
        // Characters \x09,\x0a,\x0d are allowed inside the protocol
        "java\x09script:alert(window.origin)",
        "java\x0ascript:alert(window.origin)",
        "java\x0dscript:alert(window.origin)",
    
        // Characters \x09,\x0a,\x0d are allowed after protocol name before the colon
        "javascript\x09:alert(window.origin)",
        "javascript\x0a:alert(window.origin)",
        "javascript\x0d:alert(window.origin)",
    ];
}

function getJsonContent($url)
{
    return [
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Click me',
                        'marks' => [
                            [
                                'type' => 'link',
                                'attrs' => [
                                    'href' => $url,
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];
}

function getHtmlContent($url)
{
    return '<p><a href="' . $url . '">Click me</a></p>';
}

test('link_mark_does_output_href_tag_for_valid_JSON_schemas', function () {
    foreach (getValidUrls() as $url) {
        $content = getJsonContent($url);

        $editor = (new Editor([
            'content' => $content,
            'extensions' => [
                new StarterKit,
                new Link,
            ],
        ]));

        $result = $editor->getHTML();
        expect($result)->toContain($url);
    }
});

test('link_mark_does_not_output_href_tag_for_valid_JSON_schemas', function () {
    foreach (getInvalidUrls() as $url) {
        $content = getJsonContent($url);

        $editor = (new Editor([
            'content' => $content,
            'extensions' => [
                new StarterKit,
                new Link,
            ],
        ]));

        $result = $editor->getHTML();
        expect($result)->not->toContain($url);
    }
});

test('link_mark_does_output_href_tag_for_valid_HTML_schemas', function () {
    foreach (getValidUrls() as $url) {
        $content = getHtmlContent($url);

        $editor = (new Editor([
            'content' => $content,
            'extensions' => [
                new StarterKit,
                new Link,
            ],
        ]));

        $result = $editor->getHTML();
        expect($result)->toContain($url);
    }
});

test('link_mark_does_not_output_href_tag_for_valid_HTML_schemas', function () {
    foreach (getInvalidUrls() as $url) {
        $content = getHtmlContent($url);

        $editor = (new Editor([
            'content' => $content,
            'extensions' => [
                new StarterKit,
                new Link,
            ],
        ]));

        $result = $editor->getJson();
        expect($result)->not->toContain($url);
    }
});


================================================
FILE: tests/DOMParser/Marks/NestedMarksTest.php
================================================
<?php

use Tiptap\Editor;

test('nested marks are rendered correctly', function () {
    $html = '<strong>only bold <em>bold and italic</em> only bold</strong>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'text',
                'text' => 'only bold ',
                'marks' => [
                    [
                        'type' => 'bold',
                    ],
                ],
            ],
            [
                'type' => 'text',
                'text' => 'bold and italic',
                'marks' => [
                    [
                        'type' => 'bold',
                    ],
                    [
                        'type' => 'italic',
                    ],
                ],
            ],
            [
                'type' => 'text',
                'text' => ' only bold',
                'marks' => [
                    [
                        'type' => 'bold',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/StrikeTest.php
================================================
<?php

use Tiptap\Editor;

test('strike and s del get rendered correctly', function () {
    $html = '<p><strike>Example text using strike</strike> and <s>example text using s</s> and <del>example text using del</del></p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example text using strike',
                        'marks' => [
                            [
                                'type' => 'strike',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' and ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'example text using s',
                        'marks' => [
                            [
                                'type' => 'strike',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' and ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'example text using del',
                        'marks' => [
                            [
                                'type' => 'strike',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('inline style is parsed correctly', function () {
    $html = '<p><span style="text-decoration: line-through">Example Text</span></p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'strike',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/SubscriptTest.php
================================================
<?php


use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\Subscript;

test('subscript gets rendered correctly', function () {
    $html = '<p><sub>Example Text</sub></p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Subscript,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'subscript',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('inline style is parsed correctly', function () {
    $html = '<p><span style="vertical-align: sub;">Example Text</span></p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Subscript,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'subscript',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/SuperscriptTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\Superscript;

test('superscript gets rendered correctly', function () {
    $html = '<p><sup>Example Text</sup></p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Superscript,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'superscript',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('inline style is parsed correctly', function () {
    $html = '<p><span style="vertical-align: super;">Example Text</span></p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Superscript,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'superscript',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/TextStyleTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\TextStyle;

test('span gets rendered correctly', function () {
    $html = '<p><span style="color: red">Example</span> Text</p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new TextStyle,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                        'marks' => [
                            [
                                'type' => 'textStyle',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ' Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('span without inline style is ignored', function () {
    $html = '<p><span>Example</span> Text</p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new TextStyle,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Marks/UnderlineTest.php
================================================
<?php


use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\Underline;

test('underline gets rendered correctly', function () {
    $html = '<p><u>Example Text</u></p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Underline,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'underline',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('inline style is parsed correctly', function () {
    $html = '<p><span style="text-decoration: underline;">Example Text</span></p>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Underline,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'underline',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/MarksInNodesTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Marks\Link;

test('paragraph with marks gets rendered correctly', function () {
    $html = "<p>Example <strong><em>Text</em></strong>.</p>";

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'Text',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => '.',
                    ],
                ],
            ],
        ],
    ]);
});

test('complex markup gets rendered correctly', function () {
    $html = '
        <h1>Headline 1</h1>
        <p>
            Some text. <strong>Bold Text</strong>. <em>Italic Text</em>. <strong><em>Bold and italic Text</em></strong>. Here is a <a href="https://tiptap.dev">Link</a>.
        </p>
    ';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'heading',
                'attrs' => [
                    'level' => '1',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Headline 1',
                    ],
                ],
            ],
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Some text. ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'Bold Text',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => '. ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'Italic Text',
                        'marks' => [
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => '. ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'Bold and italic Text',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => '. Here is a ',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'Link',
                        'marks' => [
                            [
                                'type' => 'link',
                                'attrs' => [
                                    'href' => 'https://tiptap.dev',
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => '.',
                    ],
                ],
            ],
        ],
    ]);
});

test('multiple lists gets rendered correctly', function () {
    $html = '
        <h2>Headline 2</h2>
        <ol>
            <li><p>ordered list item</p></li>
            <li><p>ordered list item</p></li>
            <li><p>ordered list item</p></li>
        </ol>
        <ul>
            <li><p>unordered list item</p></li>
            <li><p>unordered list item with <a href="https://tiptap.dev"><strong>link</strong></a></p></li>
            <li><p>unordered list item</p></li>
        </ul>
        <p>Some Text.</p>
    ';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Link,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' =>
        [
            [
                'type' => 'heading',
                'attrs' => [
                    'level' => '2',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Headline 2',
                    ],
                ],
            ],
            [
                'type' => 'orderedList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'ordered list item',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'ordered list item',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'ordered list item',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            [
                'type' => 'bulletList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'unordered list item',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'unordered list item with ',
                                    ],
                                    [
                                        'type' => 'text',
                                        'text' => 'link',
                                        'marks' => [
                                            [
                                                'type' => 'link',
                                                'attrs' => [
                                                    'href' => 'https://tiptap.dev',
                                                ],
                                            ],
                                            [
                                                'type' => 'bold',
                                            ],
                                        ],
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'unordered list item',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Some Text.',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/MultipleMarksTest.php
================================================
<?php

use Tiptap\Editor;

test('multiple marks are rendered correctly', function () {
    $html = '<p><strong><em>Example Text</em></strong></p>';

    $result = (new Editor)->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'bold',
                            ],
                            [
                                'type' => 'italic',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/BlockquoteTest.php
================================================
<?php

use Tiptap\Editor;

test('blockquote gets rendered correctly', function () {
    $html = '<blockquote><p>Paragraph</p></blockquote>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'blockquote',
                'content' => [
                    [
                        'type' => 'paragraph',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => 'Paragraph',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/BulletListTest.php
================================================
<?php

use Tiptap\Editor;

test('bulletList gets rendered correctly', function () {
    $html = '<ul><li><p>Example</p></li><li><p>Text</p></li></ul>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'bulletList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Example',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Text',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('bulletlistItem with text only gets wrapped in paragraph', function () {
    $html = '<ul><li><p>Example</p></li><li><p>Text <em>Test</em></p></li></ul>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'bulletList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Example',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Text ',
                                    ],
                                    [
                                        'type' => 'text',
                                        'text' => 'Test',
                                        'marks' => [
                                            [
                                                'type' => 'italic',
                                            ],
                                        ],
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('listItems with space get rendered correctly', function () {
    $html = '<ul><li><p> </p></li></ul>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'bulletList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('listItems content get rendered correctly', function () {
    $html = '<ul><li><p>Tiptap</p></li></ul>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'bulletList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Tiptap',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/CodeBlockTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;

test('codeBlock gets rendered correctly', function () {
    $html = '<pre><code>Example Text</code></pre>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'codeBlock',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('codeBlock with language gets rendered correctly', function () {
    $html = '<pre><code class="language-css">body { display: none }</code></pre>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'codeBlock',
                'attrs' => [
                    'language' => 'css',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'body { display: none }',
                    ],
                ],
            ],
        ],
    ]);
});

test('language class prefix is configureable', function () {
    $html = '<pre><code class="custom-language-prefix-css">body { display: none }</code></pre>';

    $result =
        (new Editor([
            'extensions' => [
                new StarterKit([
                    'codeBlock' => [
                        'languageClassPrefix' => 'custom-language-prefix-',
                    ],
                ]),
            ],
        ]))
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'codeBlock',
                'attrs' => [
                    'language' => 'css',
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'body { display: none }',
                    ],
                ],
            ],
        ],
    ]);
});

test('code block and inline code are rendered correctly', function () {
    $html = '<p><code>Example Text</code></p><pre><code>body { display: none }</code></pre>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                        'marks' => [
                            [
                                'type' => 'code',
                            ],
                        ],
                    ],
                ],
            ],
            [
                'type' => 'codeBlock',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'body { display: none }',
                    ],
                ],
            ],
        ],
    ]);
});

test('it handles code blocks without a code tag', function () {
    $html = '<pre>body { display: none }</pre>';

    $result = (new Editor)->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'codeBlock',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'body { display: none }',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/DetailsTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Nodes\Details;
use Tiptap\Nodes\DetailsContent;
use Tiptap\Nodes\DetailsSummary;

test('details gets rendered correctly', function () {
    $html = '<details><summary>Summary</summary><div data-type="detailsContent"><p>Content</p></div></details>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Details,
            new DetailsSummary,
            new DetailsContent,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'details',
                'content' => [
                    [
                        'type' => 'detailsSummary',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => 'Summary',
                            ],
                        ],
                    ],
                    [
                        'type' => 'detailsContent',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Content',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('details open attribute is ignored by default', function () {
    $html = '<details open><summary>Summary</summary><div data-type="detailsContent"><p>Content</p></div></details>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Details,
            new DetailsSummary,
            new DetailsContent,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'details',
                'content' => [
                    [
                        'type' => 'detailsSummary',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => 'Summary',
                            ],
                        ],
                    ],
                    [
                        'type' => 'detailsContent',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Content',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('details open attribute is parsed when persist is enabled', function () {
    $html = '<details open><summary>Summary</summary><div data-type="detailsContent"><p>Content</p></div></details>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Details(['persist' => true]),
            new DetailsSummary,
            new DetailsContent,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'details',
                'attrs' => [
                    'open' => true,
                ],
                'content' => [
                    [
                        'type' => 'detailsSummary',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => 'Summary',
                            ],
                        ],
                    ],
                    [
                        'type' => 'detailsContent',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Content',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('details without open attribute sets open to false when persist is enabled', function () {
    $html = '<details><summary>Summary</summary><div data-type="detailsContent"><p>Content</p></div></details>';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Details(['persist' => true]),
            new DetailsSummary,
            new DetailsContent,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'details',
                'attrs' => [
                    'open' => false,
                ],
                'content' => [
                    [
                        'type' => 'detailsSummary',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => 'Summary',
                            ],
                        ],
                    ],
                    [
                        'type' => 'detailsContent',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Content',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/HardBreakTest.php
================================================
<?php

use Tiptap\Editor;

test('break gets rendered correctly', function () {
    $html = '<p>Hard <br />Break</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Hard ',
                    ],
                    [
                        'type' => 'hardBreak',
                    ],
                    [
                        'type' => 'text',
                        'text' => 'Break',
                    ],
                ],
            ],
        ],
    ]);
});


test('multiple nodes get rendered correctly', function () {
    $html = '<p>Example</p><p>Text</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example',
                    ],
                ],
            ],
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Text',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/HeadingTest.php
================================================
<?php

use Tiptap\Editor;

test('h1 is rendered correctly', function () {
    $html = '<h1>Example Text</h1>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'heading',
                'attrs' => [
                    'level' => 1,
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('h2 is rendered correctly', function () {
    $html = '<h2>Example Text</h2>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'heading',
                'attrs' => [
                    'level' => 2,
                ],
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Example Text',
                    ],
                ],
            ],
        ],
    ]);
});

test('h7 is ignored', function () {
    $html = '<h7>Example Text</h7>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'text',
                'text' => 'Example Text',
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/HighPriorityParagraph.php
================================================
<?php

namespace Tiptap\Tests\DOMParser\Nodes;

use Tiptap\Core\Node;

class HighPriorityParagraph extends Node
{
    public static $name = 'highPriorityParagraph';

    public function parseHTML()
    {
        return [
            [
                'tag' => 'p',
                'priority' => 60,
            ],
        ];
    }
}


================================================
FILE: tests/DOMParser/Nodes/HorizontalRuleTest.php
================================================
<?php

use Tiptap\Editor;

test('hr gets rendered correctly', function () {
    $html = '<p>Horizontal</p><hr /><p>Rule</p>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Horizontal',
                    ],
                ],
            ],
            [
                'type' => 'horizontalRule',
            ],
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Rule',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/ImageTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Nodes\Image;

test('image gets rendered correctly', function () {
    $html = '<img src="https://example.com/eggs.png" alt="The Finished Dish" title="Eggs in a dish" />';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Image,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'image',
                'attrs' => [
                    'alt' => 'The Finished Dish',
                    'src' => 'https://example.com/eggs.png',
                    'title' => 'Eggs in a dish',
                ],
            ],
        ],
    ]);
});

test('image gets rendered correctly when title is missing', function () {
    $html = '<img src="https://example.com/eggs.png" alt="The Finished Dish" />';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Image,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'image',
                'attrs' => [
                    'alt' => 'The Finished Dish',
                    'src' => 'https://example.com/eggs.png',
                ],
            ],
        ],
    ]);
});

test('image gets rendered correctly when alt is missing', function () {
    $html = '<img src="https://example.com/eggs.png" title="Eggs in a dish" />';

    $result = (new Editor([
        'extensions' => [
            new StarterKit,
            new Image,
        ],
    ]))->setContent($html)->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'image',
                'attrs' => [
                    'src' => 'https://example.com/eggs.png',
                    'title' => 'Eggs in a dish',
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/MentionTest.php
================================================
<?php

use Tiptap\Editor;
use Tiptap\Extensions\StarterKit;
use Tiptap\Nodes\Mention;

test('user mention gets rendered correctly', function () {
    $html = '<p>Hey <span data-type="mention" data-id="123"></span>, was geht?</p>';

    $output = (new Editor([
        'extensions' => [
            new StarterKit,
            new Mention,
        ],
    ]))->setContent($html)->getDocument();

    expect($output)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'paragraph',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Hey ',
                    ],
                    [
                        'type' => 'mention',
                        'attrs' => [
                            'id' => 123,
                        ],
                    ],
                    [
                        'type' => 'text',
                        'text' => ', was geht?',
                    ],
                ],
            ],
        ],
    ]);
});


================================================
FILE: tests/DOMParser/Nodes/OrderedListTest.php
================================================
<?php

use Tiptap\Editor;

test('orderedList gets rendered correctly', function () {
    $html = '<ol><li><p>Example</p></li><li><p>Text</p></li></ol>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'orderedList',
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Example',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Text',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ]);
});

test('orderedList has correct offset', function () {
    $html = '<ol start="3"><li><p>Example</p></li><li><p>Text</p></li></ol>';

    $result = (new Editor)
        ->setContent($html)
        ->getDocument();

    expect($result)->toEqual([
        'type' => 'doc',
        'content' => [
            [
                'type' => 'orderedList',
                'attrs' => [
                    'start' => 3,
                ],
                'content' => [
                    [
                        'type' => 'listItem',
                        'content' => [
                            [
                                'type' => 'paragraph',
                                'content' => [
                                    [
                                        'type' => 'text',
                                        'text' => 'Example',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    [
                        'type' => 'listItem',
                        'content' => [
    
Download .txt
gitextract_xdo5lijc/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── SECURITY.md
│   └── workflows/
│       ├── php-cs-fixer.yml
│       ├── psalm.yml
│       └── run-tests.yml
├── .gitignore
├── .php_cs.dist.php
├── .vscode/
│   └── launch.json
├── LICENSE.md
├── README.md
├── composer.json
├── package.json
├── phpunit.xml.dist
├── psalm.xml.dist
├── src/
│   ├── Core/
│   │   ├── DOMParser.php
│   │   ├── DOMSerializer.php
│   │   ├── Extension.php
│   │   ├── JSONSerializer.php
│   │   ├── Mark.php
│   │   ├── Node.php
│   │   ├── Schema.php
│   │   └── TextSerializer.php
│   ├── Editor.php
│   ├── Extensions/
│   │   ├── Color.php
│   │   ├── FontFamily.php
│   │   ├── StarterKit.php
│   │   └── TextAlign.php
│   ├── Marks/
│   │   ├── Bold.php
│   │   ├── Code.php
│   │   ├── Highlight.php
│   │   ├── Italic.php
│   │   ├── Link.php
│   │   ├── Strike.php
│   │   ├── Subscript.php
│   │   ├── Superscript.php
│   │   ├── TextStyle.php
│   │   └── Underline.php
│   ├── Nodes/
│   │   ├── Blockquote.php
│   │   ├── BulletList.php
│   │   ├── CodeBlock.php
│   │   ├── CodeBlockHighlight.php
│   │   ├── CodeBlockShiki.php
│   │   ├── Details.php
│   │   ├── DetailsContent.php
│   │   ├── DetailsSummary.php
│   │   ├── Document.php
│   │   ├── HardBreak.php
│   │   ├── Heading.php
│   │   ├── HorizontalRule.php
│   │   ├── Image.php
│   │   ├── ListItem.php
│   │   ├── Mention.php
│   │   ├── OrderedList.php
│   │   ├── Paragraph.php
│   │   ├── Table.php
│   │   ├── TableCell.php
│   │   ├── TableHeader.php
│   │   ├── TableRow.php
│   │   ├── TaskItem.php
│   │   ├── TaskList.php
│   │   └── Text.php
│   └── Utils/
│       ├── HTML.php
│       ├── InlineStyle.php
│       └── Minify.php
└── tests/
    ├── DOMParser/
    │   ├── EmojiTest.php
    │   ├── EmptyNodesTest.php
    │   ├── EmptyTextNodesTest.php
    │   ├── Extensions/
    │   │   ├── ColorTest.php
    │   │   ├── FontFamilyTest.php
    │   │   └── TextAlignTest.php
    │   ├── KeepContentOfUnknownTagsTest.php
    │   ├── Marks/
    │   │   ├── BoldTest.php
    │   │   ├── CodeTest.php
    │   │   ├── CustomMarkTest.php
    │   │   ├── HighlightTest.php
    │   │   ├── ItalicTest.php
    │   │   ├── LinkTest.php
    │   │   ├── NestedMarksTest.php
    │   │   ├── StrikeTest.php
    │   │   ├── SubscriptTest.php
    │   │   ├── SuperscriptTest.php
    │   │   ├── TextStyleTest.php
    │   │   └── UnderlineTest.php
    │   ├── MarksInNodesTest.php
    │   ├── MultipleMarksTest.php
    │   ├── Nodes/
    │   │   ├── BlockquoteTest.php
    │   │   ├── BulletListTest.php
    │   │   ├── CodeBlockTest.php
    │   │   ├── DetailsTest.php
    │   │   ├── HardBreakTest.php
    │   │   ├── HeadingTest.php
    │   │   ├── HighPriorityParagraph.php
    │   │   ├── HorizontalRuleTest.php
    │   │   ├── ImageTest.php
    │   │   ├── MentionTest.php
    │   │   ├── OrderedListTest.php
    │   │   ├── ParagraphTest.php
    │   │   └── TableTest.php
    │   ├── ParseHTMLPriorityTest.php
    │   ├── SpecialCharacterTest.php
    │   ├── TaskListTest.php
    │   └── WhitespaceTest.php
    ├── DOMSerializer/
    │   ├── ExampleJsonTest.php
    │   ├── Extensions/
    │   │   ├── ColorTest.php
    │   │   ├── FontFamilyTest.php
    │   │   └── TextAlignTest.php
    │   ├── InputTest.php
    │   ├── Marks/
    │   │   ├── BoldTest.php
    │   │   ├── CodeTest.php
    │   │   ├── HighlightTest.php
    │   │   ├── ItalicTest.php
    │   │   ├── LinkTest.php
    │   │   ├── StrikeTest.php
    │   │   ├── SubscriptTest.php
    │   │   ├── SuperscriptTest.php
    │   │   └── UnderlineTest.php
    │   ├── MultipleMarksTest.php
    │   ├── Nodes/
    │   │   ├── BlockquoteTest.php
    │   │   ├── BulletListTest.php
    │   │   ├── CodeBlockHighlightTest.php
    │   │   ├── CodeBlockShikiTest.php
    │   │   ├── CodeBlockTest.php
    │   │   ├── DetailsTest.php
    │   │   ├── HardBreakNodeTest.php
    │   │   ├── HeadingTest.php
    │   │   ├── HorizontalRuleNodeTest.php
    │   │   ├── ImageTest.php
    │   │   ├── MentionTest.php
    │   │   ├── OrderedListTest.php
    │   │   ├── ParagraphTest.php
    │   │   ├── TableTest.php
    │   │   ├── TaskListTest.php
    │   │   └── XSSTest.php
    │   └── WrongFormatTest.php
    ├── Editor/
    │   ├── DescendantsTest.php
    │   ├── GetDocumentTest.php
    │   ├── GetHTMLTest.php
    │   ├── GetJSONTest.php
    │   ├── GetTextTest.php
    │   ├── SanitizeTest.php
    │   └── SetContentTest.php
    ├── Pest.php
    ├── Schema/
    │   ├── GetTopNodeTest.php
    │   └── PriorityTest.php
    └── Utils/
        └── HTMLTest.php
Download .txt
SYMBOL INDEX (244 symbols across 55 files)

FILE: src/Core/DOMParser.php
  class DOMParser (line 10) | class DOMParser
    method __construct (line 18) | public function __construct($schema)
    method process (line 23) | public function process(string $value): array
    method setDocument (line 37) | private function setDocument(string $value): DOMParser
    method minify (line 54) | private function minify(string $value): string
    method makeValidXMLDocument (line 59) | private function makeValidXMLDocument($value): string
    method getDocumentBody (line 64) | private function getDocumentBody(): DOMElement
    method processChildren (line 69) | private function processChildren($node): array
    method isMultidimensionalArray (line 117) | private function isMultidimensionalArray($array)
    method mergeSimilarNodes (line 128) | private function mergeSimilarNodes($nodes)
    method getNodeFor (line 161) | private function getNodeFor($item)
    method getMarkFor (line 166) | private function getMarkFor($item)
    method getExtensionFor (line 171) | private function getExtensionFor($node, $classes)
    method getClassParseRules (line 191) | private function getClassParseRules($class, $node): array
    method checkParseRule (line 208) | private function checkParseRule($parseRule, $DOMNode): bool
    method parseAttributes (line 288) | private function parseAttributes($class, $DOMNode): ?array

FILE: src/Core/DOMSerializer.php
  class DOMSerializer (line 9) | class DOMSerializer
    method __construct (line 15) | public function __construct($schema)
    method renderNode (line 20) | private function renderNode($node, $previousNode = null, $nextNode = n...
    method closeAndReopenTags (line 110) | private function closeAndReopenTags(array $markTagsToClose, array &$ma...
    method closeMarkTags (line 119) | private function closeMarkTags($markTagsToClose, &$markStack, &$markTa...
    method reopenMarkTags (line 152) | private function reopenMarkTags($markTagsToReopen, &$markStack): array
    method isMarkOrNode (line 166) | private function isMarkOrNode($markOrNode, $renderClass): bool
    method markShouldOpen (line 171) | private function markShouldOpen($mark, $previousNode): bool
    method markShouldClose (line 176) | private function markShouldClose($mark, $nextNode): bool
    method nodeHasMark (line 181) | private function nodeHasMark($node, $mark): bool
    method renderOpeningTag (line 201) | private function renderOpeningTag($extension, $nodeOrMark, $renderHTML...
    method isAnAttributeArray (line 327) | private function isAnAttributeArray($items): bool
    method isSelfClosing (line 338) | private function isSelfClosing($tag): bool
    method renderClosingTag (line 351) | private function renderClosingTag($renderHTML)
    method process (line 388) | public function process(array $value): string

FILE: src/Core/Extension.php
  class Extension (line 5) | class Extension
    method __construct (line 13) | public function __construct(array $options = [])
    method addOptions (line 18) | public function addOptions()
    method addGlobalAttributes (line 23) | public function addGlobalAttributes()
    method addExtensions (line 28) | public function addExtensions()

FILE: src/Core/JSONSerializer.php
  class JSONSerializer (line 5) | class JSONSerializer
    method process (line 9) | public function process(array $value): string

FILE: src/Core/Mark.php
  class Mark (line 5) | class Mark extends Extension
    method addAttributes (line 9) | public function addAttributes()
    method renderHTML (line 14) | public function renderHTML($mark)
    method parseHTML (line 19) | public function parseHTML()

FILE: src/Core/Node.php
  class Node (line 5) | class Node extends Extension
    method addAttributes (line 13) | public function addAttributes()
    method parseHTML (line 18) | public function parseHTML()
    method renderHTML (line 23) | public function renderHTML($node)

FILE: src/Core/Schema.php
  class Schema (line 5) | class Schema
    method __construct (line 18) | public function __construct(array $extensions = [])
    method loadExtensions (line 41) | private function loadExtensions($extensions = [])
    method apply (line 68) | public function apply($document)
    method filterMarks (line 99) | public function filterMarks(&$node)
    method getAttributeConfigurations (line 112) | public function getAttributeConfigurations($class): array

FILE: src/Core/TextSerializer.php
  class TextSerializer (line 5) | class TextSerializer
    method __construct (line 15) | public function __construct($schema, $configuration = [])
    method process (line 21) | public function process(array $value): string
    method renderNode (line 37) | private function renderNode($node): string

FILE: src/Editor.php
  class Editor (line 13) | class Editor
    method __construct (line 24) | public function __construct(array $configuration = [])
    method setContent (line 43) | public function setContent($value): self
    method getDocument (line 58) | public function getDocument()
    method getJSON (line 63) | public function getJSON(): string
    method getHTML (line 68) | public function getHTML(): string
    method getText (line 73) | public function getText($configuration = []): string
    method sanitize (line 78) | public function sanitize($value)
    method getContentType (line 89) | public function getContentType($value): string
    method descendants (line 111) | public function descendants($closure): Editor
    method walkThroughNodes (line 127) | private function walkThroughNodes(&$node, $closure)

FILE: src/Extensions/Color.php
  class Color (line 8) | class Color extends Extension
    method addOptions (line 12) | public function addOptions()
    method addGlobalAttributes (line 19) | public function addGlobalAttributes()

FILE: src/Extensions/FontFamily.php
  class FontFamily (line 8) | class FontFamily extends Extension
    method addOptions (line 12) | public function addOptions()
    method addGlobalAttributes (line 19) | public function addGlobalAttributes()

FILE: src/Extensions/StarterKit.php
  class StarterKit (line 7) | class StarterKit extends Extension
    method addOptions (line 11) | public function addOptions()
    method addExtensions (line 32) | public function addExtensions()

FILE: src/Extensions/TextAlign.php
  class TextAlign (line 8) | class TextAlign extends Extension
    method addOptions (line 12) | public function addOptions()
    method addGlobalAttributes (line 21) | public function addGlobalAttributes()

FILE: src/Marks/Bold.php
  class Bold (line 9) | class Bold extends Mark
    method addOptions (line 13) | public function addOptions()
    method parseHTML (line 20) | public function parseHTML()
    method renderHTML (line 43) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Code.php
  class Code (line 8) | class Code extends Mark
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Highlight.php
  class Highlight (line 9) | class Highlight extends Mark
    method addOptions (line 13) | public function addOptions()
    method parseHTML (line 21) | public function parseHTML()
    method addAttributes (line 30) | public function addAttributes()
    method renderHTML (line 59) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Italic.php
  class Italic (line 9) | class Italic extends Mark
    method addOptions (line 13) | public function addOptions()
    method parseHTML (line 20) | public function parseHTML()
    method renderHTML (line 40) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Link.php
  class Link (line 8) | class Link extends Mark
    method addOptions (line 16) | public function addOptions()
    method isAllowedUri (line 30) | public function isAllowedUri($uri)
    method parseHTML (line 44) | public function parseHTML()
    method addAttributes (line 65) | public function addAttributes()
    method renderHTML (line 75) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Strike.php
  class Strike (line 8) | class Strike extends Mark
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 37) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Subscript.php
  class Subscript (line 8) | class Subscript extends Mark
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 31) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Superscript.php
  class Superscript (line 8) | class Superscript extends Mark
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 31) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/TextStyle.php
  class TextStyle (line 8) | class TextStyle extends Mark
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 31) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Marks/Underline.php
  class Underline (line 8) | class Underline extends Mark
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 31) | public function renderHTML($mark, $HTMLAttributes = [])

FILE: src/Nodes/Blockquote.php
  class Blockquote (line 8) | class Blockquote extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/BulletList.php
  class BulletList (line 8) | class BulletList extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/CodeBlock.php
  class CodeBlock (line 8) | class CodeBlock extends Node
    method addOptions (line 14) | public function addOptions()
    method parseHTML (line 22) | public function parseHTML()
    method addAttributes (line 31) | public function addAttributes()
    method renderHTML (line 51) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/CodeBlockHighlight.php
  class CodeBlockHighlight (line 9) | class CodeBlockHighlight extends CodeBlock
    method addOptions (line 11) | public function addOptions()
    method renderHTML (line 19) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/CodeBlockShiki.php
  class CodeBlockShiki (line 11) | class CodeBlockShiki extends CodeBlock
    method addOptions (line 13) | public function addOptions()
    method renderHTML (line 24) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Details.php
  class Details (line 8) | class Details extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 21) | public function parseHTML()
    method addAttributes (line 30) | public function addAttributes()
    method renderHTML (line 45) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/DetailsContent.php
  class DetailsContent (line 8) | class DetailsContent extends Node
    method addOptions (line 12) | public function addOptions(): array
    method parseHTML (line 19) | public function parseHTML(): array
    method renderHTML (line 29) | public function renderHTML($node, $HTMLAttributes = []): array

FILE: src/Nodes/DetailsSummary.php
  class DetailsSummary (line 8) | class DetailsSummary extends Node
    method addOptions (line 12) | public function addOptions(): array
    method parseHTML (line 19) | public function parseHTML(): array
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = []): array

FILE: src/Nodes/Document.php
  class Document (line 7) | class Document extends Node

FILE: src/Nodes/HardBreak.php
  class HardBreak (line 8) | class HardBreak extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Heading.php
  class Heading (line 8) | class Heading extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 20) | public function parseHTML()
    method renderHTML (line 32) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/HorizontalRule.php
  class HorizontalRule (line 8) | class HorizontalRule extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Image.php
  class Image (line 8) | class Image extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method addAttributes (line 28) | public function addAttributes()
    method renderHTML (line 39) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/ListItem.php
  class ListItem (line 8) | class ListItem extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Mention.php
  class Mention (line 8) | class Mention extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 20) | public function parseHTML()
    method addAttributes (line 29) | public function addAttributes()
    method renderText (line 39) | public function renderText($node)
    method renderHTML (line 44) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/OrderedList.php
  class OrderedList (line 8) | class OrderedList extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method addAttributes (line 28) | public function addAttributes()
    method renderHTML (line 38) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Paragraph.php
  class Paragraph (line 8) | class Paragraph extends Node
    method addOptions (line 14) | public function addOptions()
    method parseHTML (line 21) | public function parseHTML()
    method renderHTML (line 30) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Table.php
  class Table (line 8) | class Table extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/TableCell.php
  class TableCell (line 8) | class TableCell extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method addAttributes (line 28) | public function addAttributes()
    method renderHTML (line 64) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/TableHeader.php
  class TableHeader (line 7) | class TableHeader extends TableCell
    method addOptions (line 11) | public function addOptions()
    method parseHTML (line 18) | public function parseHTML()
    method renderHTML (line 27) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/TableRow.php
  class TableRow (line 8) | class TableRow extends Node
    method addOptions (line 12) | public function addOptions()
    method parseHTML (line 19) | public function parseHTML()
    method renderHTML (line 28) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/TaskItem.php
  class TaskItem (line 8) | class TaskItem extends Node
    method addOptions (line 14) | public function addOptions()
    method addAttributes (line 21) | public function addAttributes()
    method parseHTML (line 33) | public function parseHTML()
    method renderHTML (line 42) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/TaskList.php
  class TaskList (line 8) | class TaskList extends Node
    method addOptions (line 14) | public function addOptions()
    method parseHTML (line 21) | public function parseHTML()
    method renderHTML (line 30) | public function renderHTML($node, $HTMLAttributes = [])

FILE: src/Nodes/Text.php
  class Text (line 7) | class Text extends Node
    method parseHTML (line 11) | public function parseHTML()

FILE: src/Utils/HTML.php
  class HTML (line 5) | class HTML
    method mergeAttributes (line 11) | public static function mergeAttributes()
    method renderAttributes (line 45) | public static function renderAttributes(array $attrs): string

FILE: src/Utils/InlineStyle.php
  class InlineStyle (line 7) | class InlineStyle
    method get (line 14) | public static function get($DOMNode): array
    method hasAttribute (line 38) | public static function hasAttribute($DOMNode, $value): bool
    method getAttribute (line 53) | public static function getAttribute($DOMNode, $attribute): ?string

FILE: src/Utils/Minify.php
  class Minify (line 5) | class Minify
    method process (line 11) | public function process($html): string
    method _removePreCB (line 41) | protected function _removePreCB($m): string
    method _reservePlace (line 46) | protected function _reservePlace($content): string

FILE: tests/DOMParser/Marks/CustomMarkTest.php
  class CustomMark (line 6) | class CustomMark extends \Tiptap\Core\Mark
    method parseHTML (line 10) | public function parseHTML()
    method addAttributes (line 19) | public function addAttributes()

FILE: tests/DOMParser/Marks/LinkTest.php
  function getValidUrls (line 127) | function getValidUrls()
  function getInvalidUrls (line 139) | function getInvalidUrls()
  function getJsonContent (line 196) | function getJsonContent($url)
  function getHtmlContent (line 222) | function getHtmlContent($url)

FILE: tests/DOMParser/Nodes/HighPriorityParagraph.php
  class HighPriorityParagraph (line 7) | class HighPriorityParagraph extends Node
    method parseHTML (line 11) | public function parseHTML()

FILE: tests/DOMSerializer/Nodes/HeadingTest.php
  class CustomHeading (line 161) | class CustomHeading extends \Tiptap\Nodes\Heading
    method addAttributes (line 163) | public function addAttributes()
  class AnotherCustomHeading (line 216) | class AnotherCustomHeading extends \Tiptap\Nodes\Heading
    method addAttributes (line 218) | public function addAttributes()

FILE: tests/Pest.php
  function something (line 42) | function something()
Condensed preview — 150 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (316K chars).
[
  {
    "path": ".editorconfig",
    "chars": 220,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".gitattributes",
    "chars": 430,
    "preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2972,
    "preview": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contributi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1797,
    "preview": "name: Bug Report\ntitle: \"[Bug]: \"\ndescription: Found a bug? Report it here to help us improve.\nlabels:\n  - \"bug\"\nbody:\n "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 28,
    "preview": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 912,
    "preview": "name: Request a feature\ndescription: Share ideas for new features\ntitle: \"[Feature Request]: \"\nlabels:\n  - \"enhancement\""
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 131,
    "preview": "# Security Policy\n\nIf you discover any security related issues, please email humans@tiptap.dev instead of using the issu"
  },
  {
    "path": ".github/workflows/php-cs-fixer.yml",
    "chars": 674,
    "preview": "name: Check & fix styling\n\non:\n  push:\n    branches:\n      - main\n      - develop\n      - next\n      - release/*\n  pull_"
  },
  {
    "path": ".github/workflows/psalm.yml",
    "chars": 894,
    "preview": "name: Psalm\n\non:\n  push:\n    branches:\n      - main\n      - develop\n      - next\n      - release/*\n  pull_request:\n    b"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "chars": 1802,
    "preview": "name: Tests\n\non:\n  push:\n    branches:\n      - main\n      - develop\n      - next\n      - release/*\n  pull_request:\n    b"
  },
  {
    "path": ".gitignore",
    "chars": 145,
    "preview": ".idea\n.php_cs\n.php_cs.cache\n.phpunit.result.cache\nbuild\ncomposer.lock\ncoverage\ndocs\nphpunit.xml\npsalm.xml\nvendor\n.php-cs"
  },
  {
    "path": ".php_cs.dist.php",
    "chars": 1149,
    "preview": "<?php\n\n$finder = Symfony\\Component\\Finder\\Finder::create()\n    ->in([\n        __DIR__ . '/src',\n        __DIR__ . '/test"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 616,
    "preview": "{\n    \"configurations\": [\n      {\n        \"type\": \"php\",\n        \"request\": \"launch\",\n        \"name\": \"Run Test\",\n      "
  },
  {
    "path": "LICENSE.md",
    "chars": 1091,
    "preview": "The MIT License (MIT)\n\nCopyright (c) überdosis <humans@tiptap.dev>\n\nPermission is hereby granted, free of charge, to any"
  },
  {
    "path": "README.md",
    "chars": 11686,
    "preview": "# Tiptap for PHP\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/ueberdosis/tiptap-php.svg?style=flat"
  },
  {
    "path": "composer.json",
    "chars": 1512,
    "preview": "{\n    \"name\": \"ueberdosis/tiptap-php\",\n    \"description\": \"A PHP package to work with Tiptap output\",\n    \"keywords\": [\n"
  },
  {
    "path": "package.json",
    "chars": 260,
    "preview": "{\n  \"name\": \"tiptap-php\",\n  \"private\": true,\n  \"description\": \"This package.json has all Node dependencies for the local"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1192,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noName"
  },
  {
    "path": "psalm.xml.dist",
    "chars": 482,
    "preview": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"3\"\n    findUnusedVariablesAndParams=\"true\"\n    resolveFromConfigFile=\"true\""
  },
  {
    "path": "src/Core/DOMParser.php",
    "chars": 10371,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nuse DOMDocument;\nuse DOMElement;\nuse Tiptap\\Utils\\InlineStyle;\nuse Tiptap\\Utils\\Minify;\n\n"
  },
  {
    "path": "src/Core/DOMSerializer.php",
    "chars": 13389,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nuse DOMDocument;\nuse stdClass;\nuse Tiptap\\Utils\\HTML;\n\nclass DOMSerializer\n{\n    protecte"
  },
  {
    "path": "src/Core/Extension.php",
    "chars": 481,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Extension\n{\n    public static $name;\n\n    public static $priority = 100;\n\n    publi"
  },
  {
    "path": "src/Core/JSONSerializer.php",
    "chars": 250,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nclass JSONSerializer\n{\n    protected $document;\n\n    public function process(array $value"
  },
  {
    "path": "src/Core/Mark.php",
    "chars": 303,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Mark extends Extension\n{\n    public static $priority = 100;\n\n    public function ad"
  },
  {
    "path": "src/Core/Node.php",
    "chars": 373,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Node extends Extension\n{\n    public static $priority = 100;\n\n    public static $top"
  },
  {
    "path": "src/Core/Schema.php",
    "chars": 3557,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Schema\n{\n    public array $allExtensions = [];\n\n    public array $nodes = [];\n    p"
  },
  {
    "path": "src/Core/TextSerializer.php",
    "chars": 1247,
    "preview": "<?php\n\nnamespace Tiptap\\Core;\n\nclass TextSerializer\n{\n    protected $document;\n\n    protected $schema;\n\n    protected $c"
  },
  {
    "path": "src/Editor.php",
    "chars": 3938,
    "preview": "<?php\n\nnamespace Tiptap;\n\nuse Exception;\nuse Tiptap\\Core\\DOMParser;\nuse Tiptap\\Core\\DOMSerializer;\nuse Tiptap\\Core\\JSONS"
  },
  {
    "path": "src/Extensions/Color.php",
    "chars": 1329,
    "preview": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Color extends Exten"
  },
  {
    "path": "src/Extensions/FontFamily.php",
    "chars": 1346,
    "preview": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass FontFamily extends "
  },
  {
    "path": "src/Extensions/StarterKit.php",
    "chars": 2945,
    "preview": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\n\nclass StarterKit extends Extension\n{\n    public static "
  },
  {
    "path": "src/Extensions/TextAlign.php",
    "chars": 1209,
    "preview": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass TextAlign extends E"
  },
  {
    "path": "src/Marks/Bold.php",
    "chars": 1168,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Bold e"
  },
  {
    "path": "src/Marks/Code.php",
    "chars": 560,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Code extends Mark\n{\n    public stati"
  },
  {
    "path": "src/Marks/Highlight.php",
    "chars": 1551,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Highli"
  },
  {
    "path": "src/Marks/Italic.php",
    "chars": 953,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Italic"
  },
  {
    "path": "src/Marks/Link.php",
    "chars": 2611,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Link extends Mark\n{\n    public stati"
  },
  {
    "path": "src/Marks/Strike.php",
    "chars": 771,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Strike extends Mark\n{\n    public sta"
  },
  {
    "path": "src/Marks/Subscript.php",
    "chars": 646,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Subscript extends Mark\n{\n    public "
  },
  {
    "path": "src/Marks/Superscript.php",
    "chars": 652,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Superscript extends Mark\n{\n    publi"
  },
  {
    "path": "src/Marks/TextStyle.php",
    "chars": 716,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass TextStyle extends Mark\n{\n    public "
  },
  {
    "path": "src/Marks/Underline.php",
    "chars": 649,
    "preview": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Underline extends Mark\n{\n    public "
  },
  {
    "path": "src/Nodes/Blockquote.php",
    "chars": 584,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Blockquote extends Node\n{\n    public"
  },
  {
    "path": "src/Nodes/BulletList.php",
    "chars": 568,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass BulletList extends Node\n{\n    public"
  },
  {
    "path": "src/Nodes/CodeBlock.php",
    "chars": 1587,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass CodeBlock extends Node\n{\n    public "
  },
  {
    "path": "src/Nodes/CodeBlockHighlight.php",
    "chars": 1727,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse DomainException;\nuse Highlight\\Highlighter;\nuse Tiptap\\Utils\\HTML;\n\nclass CodeBlockH"
  },
  {
    "path": "src/Nodes/CodeBlockShiki.php",
    "chars": 1788,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse DomainException;\nuse Exception;\nuse Highlight\\Highlighter;\nuse Spatie\\ShikiPhp\\Shiki"
  },
  {
    "path": "src/Nodes/Details.php",
    "chars": 1099,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Details extends Node\n{\n    public st"
  },
  {
    "path": "src/Nodes/DetailsContent.php",
    "chars": 778,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass DetailsContent extends Node\n{\n    pu"
  },
  {
    "path": "src/Nodes/DetailsSummary.php",
    "chars": 654,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass DetailsSummary extends Node\n{\n    pu"
  },
  {
    "path": "src/Nodes/Document.php",
    "chars": 156,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\n\nclass Document extends Node\n{\n    public static $name = 'doc';\n\n "
  },
  {
    "path": "src/Nodes/HardBreak.php",
    "chars": 563,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass HardBreak extends Node\n{\n    public "
  },
  {
    "path": "src/Nodes/Heading.php",
    "chars": 999,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Heading extends Node\n{\n    public st"
  },
  {
    "path": "src/Nodes/HorizontalRule.php",
    "chars": 573,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass HorizontalRule extends Node\n{\n    pu"
  },
  {
    "path": "src/Nodes/Image.php",
    "chars": 774,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Image extends Node\n{\n    public stat"
  },
  {
    "path": "src/Nodes/ListItem.php",
    "chars": 564,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass ListItem extends Node\n{\n    public s"
  },
  {
    "path": "src/Nodes/Mention.php",
    "chars": 1184,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Mention extends Node\n{\n    public st"
  },
  {
    "path": "src/Nodes/OrderedList.php",
    "chars": 905,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass OrderedList extends Node\n{\n    publi"
  },
  {
    "path": "src/Nodes/Paragraph.php",
    "chars": 601,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Paragraph extends Node\n{\n    public "
  },
  {
    "path": "src/Nodes/Table.php",
    "chars": 622,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Table extends Node\n{\n    public stat"
  },
  {
    "path": "src/Nodes/TableCell.php",
    "chars": 1815,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TableCell extends Node\n{\n    public "
  },
  {
    "path": "src/Nodes/TableHeader.php",
    "chars": 647,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Utils\\HTML;\n\nclass TableHeader extends TableCell\n{\n    public static $name = "
  },
  {
    "path": "src/Nodes/TableRow.php",
    "chars": 564,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TableRow extends Node\n{\n    public s"
  },
  {
    "path": "src/Nodes/TaskItem.php",
    "chars": 1498,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TaskItem extends Node\n{\n    public s"
  },
  {
    "path": "src/Nodes/TaskList.php",
    "chars": 711,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TaskList extends Node\n{\n    public s"
  },
  {
    "path": "src/Nodes/Text.php",
    "chars": 253,
    "preview": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\n\nclass Text extends Node\n{\n    public static $name = 'text';\n\n    "
  },
  {
    "path": "src/Utils/HTML.php",
    "chars": 1818,
    "preview": "<?php\n\nnamespace Tiptap\\Utils;\n\nclass HTML\n{\n    /**\n     * Merge an associative array of attributes,\n     * and make su"
  },
  {
    "path": "src/Utils/InlineStyle.php",
    "chars": 1198,
    "preview": "<?php\n\nnamespace Tiptap\\Utils;\n\nuse Exception;\n\nclass InlineStyle\n{\n    /**\n     * @return string[]\n     *\n     * @psalm"
  },
  {
    "path": "src/Utils/Minify.php",
    "chars": 1813,
    "preview": "<?php\n\nnamespace Tiptap\\Utils;\n\nclass Minify\n{\n    protected $_replacementHash;\n    protected $_placeholders = [];\n    p"
  },
  {
    "path": "tests/DOMParser/EmojiTest.php",
    "chars": 534,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('emojis are transformed correctly', function () {\n    $html = \"<p>🔥</p>\";\n\n    $result ="
  },
  {
    "path": "tests/DOMParser/EmptyNodesTest.php",
    "chars": 516,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('parsing must not fail on empty nodes', function () {\n    $html = '<p><img /></p><p><img"
  },
  {
    "path": "tests/DOMParser/EmptyTextNodesTest.php",
    "chars": 511,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('output_must_not_have_empty_text_nodes()', function () {\n    $html = \"<em><br />\\n</em>\""
  },
  {
    "path": "tests/DOMParser/Extensions/ColorTest.php",
    "chars": 1972,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\Color;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\nte"
  },
  {
    "path": "tests/DOMParser/Extensions/FontFamilyTest.php",
    "chars": 3334,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\DOMParser\\Extensions;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\FontFamily;\nuse Tiptap\\Ext"
  },
  {
    "path": "tests/DOMParser/Extensions/TextAlignTest.php",
    "chars": 2626,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Extensions\\TextAlign;\n\ntest('text align is parsed"
  },
  {
    "path": "tests/DOMParser/KeepContentOfUnknownTagsTest.php",
    "chars": 1475,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('keeps content of unknown tags', function () {\n    $html = \"<p>Example <x-unknown-tag>Te"
  },
  {
    "path": "tests/DOMParser/Marks/BoldTest.php",
    "chars": 4017,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('b gets rendered correctly', function () {\n    $html = '<p><b>Example</b> Text</p>';\n\n  "
  },
  {
    "path": "tests/DOMParser/Marks/CodeTest.php",
    "chars": 740,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('code gets rendered correctly', function () {\n    $html = '<p><code>Example Text</code><"
  },
  {
    "path": "tests/DOMParser/Marks/CustomMarkTest.php",
    "chars": 1710,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\n\nclass CustomMark extends \\Tiptap\\Core\\Mark\n{\n    public sta"
  },
  {
    "path": "tests/DOMParser/Marks/HighlightTest.php",
    "chars": 4331,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('mark gets rendered correctly', function () {\n    $html = '<p><mark>Example</mark> Text<"
  },
  {
    "path": "tests/DOMParser/Marks/ItalicTest.php",
    "chars": 3140,
    "preview": "<?php\n\n\nuse Tiptap\\Editor;\n\ntest('i gets rendered correctly', function () {\n    $html = '<p><i>Example</i> Text</p>';\n\n "
  },
  {
    "path": "tests/DOMParser/Marks/LinkTest.php",
    "chars": 8363,
    "preview": "<?php\n\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('link gets rendered correctly'"
  },
  {
    "path": "tests/DOMParser/Marks/NestedMarksTest.php",
    "chars": 1154,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('nested marks are rendered correctly', function () {\n    $html = '<strong>only bold <em>"
  },
  {
    "path": "tests/DOMParser/Marks/StrikeTest.php",
    "chars": 2517,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('strike and s del get rendered correctly', function () {\n    $html = '<p><strike>Example"
  },
  {
    "path": "tests/DOMParser/Marks/SubscriptTest.php",
    "chars": 1729,
    "preview": "<?php\n\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Subscript;\n\ntest('subscript gets rendered "
  },
  {
    "path": "tests/DOMParser/Marks/SuperscriptTest.php",
    "chars": 1742,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Superscript;\n\ntest('superscript gets render"
  },
  {
    "path": "tests/DOMParser/Marks/TextStyleTest.php",
    "chars": 1664,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('span gets rendered correc"
  },
  {
    "path": "tests/DOMParser/Marks/UnderlineTest.php",
    "chars": 1732,
    "preview": "<?php\n\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Underline;\n\ntest('underline gets rendered "
  },
  {
    "path": "tests/DOMParser/MarksInNodesTest.php",
    "chars": 10261,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('paragraph with marks gets rend"
  },
  {
    "path": "tests/DOMParser/MultipleMarksTest.php",
    "chars": 857,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('multiple marks are rendered correctly', function () {\n    $html = '<p><strong><em>Examp"
  },
  {
    "path": "tests/DOMParser/Nodes/BlockquoteTest.php",
    "chars": 768,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('blockquote gets rendered correctly', function () {\n    $html = '<blockquote><p>Paragrap"
  },
  {
    "path": "tests/DOMParser/Nodes/BulletListTest.php",
    "chars": 5217,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('bulletList gets rendered correctly', function () {\n    $html = '<ul><li><p>Example</p><"
  },
  {
    "path": "tests/DOMParser/Nodes/CodeBlockTest.php",
    "chars": 3843,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('codeBlock gets rendered correctly', function () {\n   "
  },
  {
    "path": "tests/DOMParser/Nodes/DetailsTest.php",
    "chars": 6441,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Details;\nuse Tiptap\\Nodes\\DetailsContent;\nu"
  },
  {
    "path": "tests/DOMParser/Nodes/HardBreakTest.php",
    "chars": 1556,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('break gets rendered correctly', function () {\n    $html = '<p>Hard <br />Break</p>';\n\n "
  },
  {
    "path": "tests/DOMParser/Nodes/HeadingTest.php",
    "chars": 1597,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('h1 is rendered correctly', function () {\n    $html = '<h1>Example Text</h1>';\n\n    $res"
  },
  {
    "path": "tests/DOMParser/Nodes/HighPriorityParagraph.php",
    "chars": 333,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\DOMParser\\Nodes;\n\nuse Tiptap\\Core\\Node;\n\nclass HighPriorityParagraph extends Node\n{\n    pu"
  },
  {
    "path": "tests/DOMParser/Nodes/HorizontalRuleTest.php",
    "chars": 883,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('hr gets rendered correctly', function () {\n    $html = '<p>Horizontal</p><hr /><p>Rule<"
  },
  {
    "path": "tests/DOMParser/Nodes/ImageTest.php",
    "chars": 2059,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Image;\n\ntest('image gets rendered correctly"
  },
  {
    "path": "tests/DOMParser/Nodes/MentionTest.php",
    "chars": 1074,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Mention;\n\ntest('user mention gets rendered "
  },
  {
    "path": "tests/DOMParser/Nodes/OrderedListTest.php",
    "chars": 3173,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('orderedList gets rendered correctly', function () {\n    $html = '<ol><li><p>Example</p>"
  },
  {
    "path": "tests/DOMParser/Nodes/ParagraphTest.php",
    "chars": 1343,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('simple text gets rendered correctly', function () {\n    $html = '<p>Example Text</p>';\n"
  },
  {
    "path": "tests/DOMParser/Nodes/TableTest.php",
    "chars": 7473,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\JSONOutput\\Nodes;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\T"
  },
  {
    "path": "tests/DOMParser/ParseHTMLPriorityTest.php",
    "chars": 665,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Tests\\DOMParser\\Nodes\\HighPriorityParagraph;\n\ntes"
  },
  {
    "path": "tests/DOMParser/SpecialCharacterTest.php",
    "chars": 2106,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('emojis are transformed correctly()', function () {\n    $html = \"<p>🔥</p>\";\n\n    $result"
  },
  {
    "path": "tests/DOMParser/TaskListTest.php",
    "chars": 2324,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\TaskItem;\nuse Tiptap\\Nodes\\TaskList;\n\ntest("
  },
  {
    "path": "tests/DOMParser/WhitespaceTest.php",
    "chars": 1574,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('whitespace at the beginning is stripped', function () {\n    $html = \"<p>\\nExample\\n Tex"
  },
  {
    "path": "tests/DOMSerializer/ExampleJsonTest.php",
    "chars": 3269,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('example json gets rendered correctly', function () {\n    $document = '{\n        \"type\":"
  },
  {
    "path": "tests/DOMSerializer/Extensions/ColorTest.php",
    "chars": 1107,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\Color;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\nte"
  },
  {
    "path": "tests/DOMSerializer/Extensions/FontFamilyTest.php",
    "chars": 1250,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\DOMSerializer\\Extensions;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\FontFamily;\nuse Tiptap"
  },
  {
    "path": "tests/DOMSerializer/Extensions/TextAlignTest.php",
    "chars": 2644,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Extensions\\TextAlign;\n\ntest('text align is render"
  },
  {
    "path": "tests/DOMSerializer/InputTest.php",
    "chars": 2695,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Image;\n\ntest('array gets rendered to html',"
  },
  {
    "path": "tests/DOMSerializer/Marks/BoldTest.php",
    "chars": 556,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('bold mark gets rendered correctly', function () {\n    $document = [\n        'type' => '"
  },
  {
    "path": "tests/DOMSerializer/Marks/CodeTest.php",
    "chars": 552,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('code mark gets rendered correctly', function () {\n    $document = [\n        'type' => '"
  },
  {
    "path": "tests/DOMSerializer/Marks/HighlightTest.php",
    "chars": 2357,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('mark doesn’t allow specific colors by default', function () {\n    $document = [\n       "
  },
  {
    "path": "tests/DOMSerializer/Marks/ItalicTest.php",
    "chars": 552,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('italic mark gets rendered correctly', function () {\n    $document = [\n        'type' =>"
  },
  {
    "path": "tests/DOMSerializer/Marks/LinkTest.php",
    "chars": 7944,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('link mark gets rendered correc"
  },
  {
    "path": "tests/DOMSerializer/Marks/StrikeTest.php",
    "chars": 546,
    "preview": "<?php\n\n\nuse Tiptap\\Editor;\n\ntest('strike gets rendered correctly', function () {\n    $document = [\n        'type' => 'do"
  },
  {
    "path": "tests/DOMSerializer/Marks/SubscriptTest.php",
    "chars": 736,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\Marks;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Subscript;\n\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/SuperscriptTest.php",
    "chars": 744,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\Marks;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Superscript;"
  },
  {
    "path": "tests/DOMSerializer/Marks/UnderlineTest.php",
    "chars": 732,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\Marks;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Underline;\n\n"
  },
  {
    "path": "tests/DOMSerializer/MultipleMarksTest.php",
    "chars": 7483,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\HTMLOutput\\Mix;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Tex"
  },
  {
    "path": "tests/DOMSerializer/Nodes/BlockquoteTest.php",
    "chars": 570,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('blockquote node gets rendered correctly', function () {\n    $document = [\n        'type"
  },
  {
    "path": "tests/DOMSerializer/Nodes/BulletListTest.php",
    "chars": 774,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('bulletList node gets rendered correctly', function () {\n    $document = [\n        'type"
  },
  {
    "path": "tests/DOMSerializer/Nodes/CodeBlockHighlightTest.php",
    "chars": 2380,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\CodeBlockHighlight;\n\ntest('codeBlockHighlig"
  },
  {
    "path": "tests/DOMSerializer/Nodes/CodeBlockShikiTest.php",
    "chars": 4700,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\CodeBlockShiki;\n\ntest('editor can be create"
  },
  {
    "path": "tests/DOMSerializer/Nodes/CodeBlockTest.php",
    "chars": 2218,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('codeBlock node gets rendered correctly', function () "
  },
  {
    "path": "tests/DOMSerializer/Nodes/DetailsTest.php",
    "chars": 5038,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Details;\nuse Tiptap\\Nodes\\DetailsContent;\nu"
  },
  {
    "path": "tests/DOMSerializer/Nodes/HardBreakNodeTest.php",
    "chars": 812,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('self closing node gets rendered correctly', function () {\n    $document = [\n        'ty"
  },
  {
    "path": "tests/DOMSerializer/Nodes/HeadingTest.php",
    "chars": 6537,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('heading node gets rendered correctly', function () {\n    $document = [\n        'type' ="
  },
  {
    "path": "tests/DOMSerializer/Nodes/HorizontalRuleNodeTest.php",
    "chars": 918,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('self closing node gets rendered correctly', function () {\n    $document = [\n        'ty"
  },
  {
    "path": "tests/DOMSerializer/Nodes/ImageTest.php",
    "chars": 736,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Image;\n\ntest('image node gets rendered corr"
  },
  {
    "path": "tests/DOMSerializer/Nodes/MentionTest.php",
    "chars": 2141,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Mention;\n\ntest('user mention is serialized "
  },
  {
    "path": "tests/DOMSerializer/Nodes/OrderedListTest.php",
    "chars": 1609,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('orderedList node gets rendered correctly', function () {\n    $document = [\n        'typ"
  },
  {
    "path": "tests/DOMSerializer/Nodes/ParagraphTest.php",
    "chars": 578,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('paragraph node gets rendered correctly()', function () {\n    $document = [\n        'typ"
  },
  {
    "path": "tests/DOMSerializer/Nodes/TableTest.php",
    "chars": 8972,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Nodes\\Paragraph;\nuse Tiptap\\Nodes\\Table;\nuse Tiptap\\Nodes\\TableCell;\nuse Tiptap\\Nod"
  },
  {
    "path": "tests/DOMSerializer/Nodes/TaskListTest.php",
    "chars": 2667,
    "preview": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\TaskItem;\nuse Tiptap\\Nodes\\TaskList;\n\ntest("
  },
  {
    "path": "tests/DOMSerializer/Nodes/XSSTest.php",
    "chars": 427,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('text should not get rendered as html', function () {\n    $document = [\n        'type' ="
  },
  {
    "path": "tests/DOMSerializer/WrongFormatTest.php",
    "chars": 2969,
    "preview": "<?php\n\nnamespace Tiptap\\Tests\\HTMLOutput;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\n"
  },
  {
    "path": "tests/Editor/DescendantsTest.php",
    "chars": 3279,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('descendants() loops through all nodes recursively', function () {\n    $document = [\n   "
  },
  {
    "path": "tests/Editor/GetDocumentTest.php",
    "chars": 557,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getDocument() returns a PHP array', function () {\n    $html = \"<p>Example Text</p>\";\n\n "
  },
  {
    "path": "tests/Editor/GetHTMLTest.php",
    "chars": 544,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getHTML() returns HTML', function () {\n    $input = [\n        'type' => 'doc',\n        "
  },
  {
    "path": "tests/Editor/GetJSONTest.php",
    "chars": 310,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getJSON() returns JSON', function () {\n    $html = \"<p>Example</p>\";\n\n    $result = (ne"
  },
  {
    "path": "tests/Editor/GetTextTest.php",
    "chars": 861,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getText() returns plain text', function () {\n    $html = \"<h1>Heading</h1><p>Paragraph<"
  },
  {
    "path": "tests/Editor/SanitizeTest.php",
    "chars": 3893,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('unknown nodes are removed from the document', function () {\n    $document = [\n        '"
  },
  {
    "path": "tests/Editor/SetContentTest.php",
    "chars": 2642,
    "preview": "<?php\n\nuse Tiptap\\Editor;\n\ntest('json strings are detected', function () {\n    $result = (new Editor)->setContent('{\n   "
  },
  {
    "path": "tests/Pest.php",
    "chars": 1508,
    "preview": "<?php\n\n/*\n|--------------------------------------------------------------------------\n| Test Case\n|---------------------"
  },
  {
    "path": "tests/Schema/GetTopNodeTest.php",
    "chars": 230,
    "preview": "<?php\n\nuse Tiptap\\Core\\Schema;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('document is the top node', function () {\n    $sc"
  },
  {
    "path": "tests/Schema/PriorityTest.php",
    "chars": 245,
    "preview": "<?php\n\nuse Tiptap\\Core\\Schema;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('paragraph is the default node', function () {\n  "
  },
  {
    "path": "tests/Utils/HTMLTest.php",
    "chars": 269,
    "preview": "<?php\n\nuse Tiptap\\Utils\\HTML;\n\ntest('classes are merged properly', function () {\n    $attributes = [\n        ['class' =>"
  }
]

About this extraction

This page contains the full source code of the ueberdosis/tiptap-php GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 150 files (287.1 KB), approximately 64.1k tokens, and a symbol index with 244 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!