[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.{yml,yaml}]\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and documentation with \"export-ignore\".\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/phpunit.xml.dist   export-ignore\n/tests              export-ignore\n/.editorconfig      export-ignore\n/.php_cs            export-ignore\n/.github            export-ignore\n/psalm.xml          export-ignore\n\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contribution guide before creating an issue or pull request.\n\n## Etiquette\n\nThis project is open source, and as such, the maintainers give their free time to build and maintain the source code\nheld within. They make the code freely available in the hope that it will be of use to other developers. It would be\nextremely unfair for them to suffer abuse or anger for their hard work.\n\nPlease be considerate towards maintainers when raising issues or presenting pull requests. Let's show the\nworld that developers are civilized and selfless people.\n\nIt's the duty of the maintainer to ensure that all submissions to the project are of sufficient\nquality 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.\n\n## Viability\n\nWhen requesting or submitting new features, first consider whether it might be useful to others. Open\nsource projects are used by many developers, who may have entirely different needs to your own. Think about\nwhether or not your feature is likely to be used by other users of the project.\n\n## Procedure\n\nBefore filing an issue:\n\n- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.\n- Check to make sure your feature suggestion isn't already present within the project.\n- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.\n- Check the pull requests tab to ensure that the feature isn't already in progress.\n\nBefore submitting a pull request:\n\n- Check the codebase to ensure that your feature doesn't already exist.\n- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.\n\n## Requirements\n\nIf the project maintainer has any additional requirements, you will find them listed here.\n\n- **[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).\n\n- **Add tests!** - Your patch won't be accepted if it doesn't have tests.\n\n- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.\n\n- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.\n\n- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.\n\n- **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.\n\n**Happy coding**!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ntitle: \"[Bug]: \"\ndescription: Found a bug? Report it here to help us improve.\nlabels:\n  - \"bug\"\nbody:\n  - type: markdown\n    attributes:\n      value: \"### Please provide details to help us diagnose the bug.\"\n  - type: input\n    id: php_version\n    attributes:\n      label: PHP Version\n      description: The version of PHP you are using.\n      placeholder: e.g. 8.0\n    validations:\n      required: true\n  - type: input\n    id: version\n    attributes:\n      label: Version\n      description: Specify the version of Tiptap PHP you are using.\n      placeholder: 1.0.0\n    validations:\n      required: true\n  - type: textarea\n    id: problem\n    attributes:\n      label: Bug Description\n      description: Provide a clear and concise description of what the bug is.\n      placeholder: \"The issue occurs when...\"\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: |\n        ### Additional Information\n        Please provide any additional information that may help us understand the issue.\n  - type: textarea\n    id: expectation\n    attributes:\n      label: Expected Behavior\n      description: Describe what you expected to happen.\n    validations:\n      required: true\n  - type: textarea\n    id: context\n    attributes:\n      label: Additional Context (Optional)\n      description: \"Add any other context about the problem here, such as screenshots or videos.\"\n  - type: checkboxes\n    attributes:\n      label: Dependency Updates\n      description: \"Have you updated your dependencies? This can often resolve issues.\"\n      options:\n        - label: Yes, I've updated all my dependencies.\n          required: true\n  - type: markdown\n    attributes:\n      value: \"Thank you for helping us improve our open-source projects by reporting this issue!\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Request a feature\ndescription: Share ideas for new features\ntitle: \"[Feature Request]: \"\nlabels:\n  - \"enhancement\"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ## Feature Request\n\n        Thank you for considering contributing to Tiptap PHP! We welcome your ideas and suggestions for new features.\n  - type: textarea\n    id: feature_description\n    attributes:\n      label: \"Feature Description\"\n      description: \"Please provide a clear and concise description of the feature you would like to see.\"\n      placeholder: \"e.g. I would like to have a new command to...\"\n    validations:\n      required: true\n  - type: textarea\n    id: additional_context\n    attributes:\n      label: \"Additional Context\"\n      description: \"Please provide any additional context or information that may be helpful in understanding your request.\"\n      placeholder: \"e.g. use case, related features, etc.\"\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\nIf you discover any security related issues, please email humans@tiptap.dev instead of using the issue tracker.\n"
  },
  {
    "path": ".github/workflows/php-cs-fixer.yml",
    "content": "name: Check & fix styling\n\non:\n  push:\n    branches:\n      - main\n      - develop\n      - next\n      - release/*\n  pull_request:\n    branches:\n      - main\n      - develop\n      - next\n  workflow_dispatch:\n\njobs:\n  php-cs-fixer:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.head_ref }}\n\n      - name: Run PHP CS Fixer\n        uses: docker://oskarstark/php-cs-fixer-ga\n        with:\n          args: --config=.php_cs.dist.php --allow-risky=yes\n\n      - name: Commit changes\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          commit_message: Fix styling\n"
  },
  {
    "path": ".github/workflows/psalm.yml",
    "content": "name: Psalm\n\non:\n  push:\n    branches:\n      - main\n      - develop\n      - next\n      - release/*\n  pull_request:\n    branches:\n      - main\n      - develop\n      - next\n  workflow_dispatch:\n\njobs:\n  psalm:\n    name: psalm\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: \"8.3\"\n          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick\n          coverage: none\n\n      - name: Cache composer dependencies\n        uses: actions/cache@v4\n        with:\n          path: vendor\n          key: composer-${{ hashFiles('composer.lock') }}\n\n      - name: Run composer install\n        run: composer install -n --prefer-dist\n\n      - name: Run psalm\n        run: ./vendor/bin/psalm --output-format=github\n"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - main\n      - develop\n      - next\n      - release/*\n  pull_request:\n    branches:\n      - main\n      - develop\n      - next\n  workflow_dispatch:\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: true\n      matrix:\n        os: [ubuntu-latest, windows-latest]\n        php: [8.1, 8.2, 8.3]\n        stability: [prefer-lowest, prefer-stable]\n        node-version: [22]\n\n    name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo\n          coverage: none\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v2.5.1\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Load cached dependencies\n        uses: actions/cache@v4\n        id: cache\n        with:\n          path: |\n            **/node_modules\n          key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}\n\n      - name: Install dependencies\n        id: install-dependencies\n        if: steps.cache.outputs.cache-hit != 'true'\n        run: npm install\n\n      - name: Setup problem matchers\n        run: |\n          echo \"::add-matcher::${{ runner.tool_cache }}/php.json\"\n          echo \"::add-matcher::${{ runner.tool_cache }}/phpunit.json\"\n\n      - name: Install dependencies\n        run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction\n\n      - name: Execute tests\n        run: vendor/bin/pest\n"
  },
  {
    "path": ".gitignore",
    "content": ".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-fixer.cache\nnode_modules"
  },
  {
    "path": ".php_cs.dist.php",
    "content": "<?php\n\n$finder = Symfony\\Component\\Finder\\Finder::create()\n    ->in([\n        __DIR__ . '/src',\n        __DIR__ . '/tests',\n    ])\n    ->name('*.php')\n    ->notName('*.blade.php')\n    ->ignoreDotFiles(true)\n    ->ignoreVCS(true);\n\nreturn (new PhpCsFixer\\Config())\n    ->setRules([\n        '@PSR2' => true,\n        'array_syntax' => ['syntax' => 'short'],\n        'ordered_imports' => ['sort_algorithm' => 'alpha'],\n        'no_unused_imports' => true,\n        'not_operator_with_successor_space' => true,\n        'trailing_comma_in_multiline' => true,\n        'phpdoc_scalar' => true,\n        'unary_operator_spaces' => true,\n        'binary_operator_spaces' => true,\n        'blank_line_before_statement' => [\n            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],\n        ],\n        'phpdoc_single_line_var_spacing' => true,\n        'phpdoc_var_without_name' => true,\n        'method_argument_space' => [\n            'on_multiline' => 'ensure_fully_multiline',\n            'keep_multiple_spaces_after_comma' => true,\n        ],\n        'single_trait_insert_per_statement' => true,\n    ])\n    ->setFinder($finder);\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    \"configurations\": [\n      {\n        \"type\": \"php\",\n        \"request\": \"launch\",\n        \"name\": \"Run Test\",\n        \"program\": \"${workspaceFolder}/vendor/bin/pest\",\n        \"args\": [\n            \"--filter\",\n            \"${input:testFilter}\"\n        ],\n        \"runtimeArgs\": [\n            \"-dxdebug.mode=debug\",\n            \"-dxdebug.start_with_request=trigger\"\n        ],\n        \"cwd\": \"${workspaceFolder}\",\n        \"port\": 9003\n      }\n    ],\n    \"inputs\": [\n      {\n        \"type\": \"promptString\",\n        \"id\": \"testFilter\",\n        \"description\": \"Filter by test\",\n        \"default\": \"\"\n      }\n    ]\n  }"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) überdosis <humans@tiptap.dev>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Tiptap for PHP\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/ueberdosis/tiptap-php.svg?style=flat-square)](https://packagist.org/packages/ueberdosis/tiptap-php)\n[![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)\n[![Total Downloads](https://img.shields.io/packagist/dt/ueberdosis/tiptap-php.svg?style=flat-square)](https://packagist.org/packages/ueberdosis/tiptap-php)\n[![License](https://img.shields.io/packagist/l/ueberdosis/tiptap-php?style=flat-square)](https://packagist.org/packages/ueberdosis/tiptap-php)\n[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true)](https://discord.gg/WtJ49jGshW)\n[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)\n\nA 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.\n\n## Installation\nYou can install the package via composer:\n\n```bash\ncomposer require ueberdosis/tiptap-php\n```\n\n## Usage\nThe PHP package mimics large parts of the JavaScript package. If you know your way around Tiptap, the PHP syntax will feel familiar to you.\n\n### Convert Tiptap HTML to JSON\nLet’s start by converting a HTML snippet to a PHP array with a Tiptap-compatible structure:\n\n```php\n(new \\Tiptap\\Editor)\n    ->setContent('<p>Example Text</p>')\n    ->getDocument();\n\n// Returns:\n// ['type' => 'doc', 'content' => …]\n```\n\nYou can get a JSON string in PHP, too.\n\n```php\n(new \\Tiptap\\Editor)\n    ->setContent('<p>Example Text</p>')\n    ->getJSON();\n\n// Returns:\n// {\"type\": \"doc\", \"content\": …}\n```\n\n### Convert Tiptap JSON to HTML\nThe other way works aswell. Just pass a JSON string or an PHP array to generate the HTML.\n\n```php\n(new \\Tiptap\\Editor)\n    ->setContent([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ]\n            ]\n        ],\n    ])\n    ->getHTML();\n\n// Returns:\n// <h1>Example Text</h1>\n```\n\nThis doesn’t fully adhere to the ProseMirror schema. Some things are supported too, for example aren’t marks allowed in a `CodeBlock`.\n\nIf you need better schema support, create an issue with the feature you’re missing.\n\n### Syntax highlighting for code blocks with [highlight.php](https://github.com/scrivo/highlight.php)\nThe 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.\n\nSwapping our the default one works like that:\n\n```php\n(new \\Tiptap\\Editor([\n    'extensions' => [\n        new \\Tiptap\\Extensions\\StarterKit([\n            'codeBlock' => false,\n        ]),\n        new \\Tiptap\\Nodes\\CodeBlockHighlight(),\n    ],\n]))\n->setContent('<pre><code>&lt;?php phpinfo()</code></pre>')\n->getHTML();\n\n// Returns:\n// <pre><code class=\"hljs php\"><span class=\"hljs-meta\">&lt;?php</span> phpinfo()</code></pre>\n```\n\nThis is still unstyled. You need to [load a CSS file](https://highlightjs.org/download/) to add colors to the output, for example like that:\n\n```html\n<link rel=\"stylesheet\" href=\"//unpkg.com/@highlightjs/cdn-assets@11.4.0/styles/default.min.css\">\n```\n\nBoom, syntax highlighting! By the way, this is powered by the amazing [scrivo/highlight.php](https://github.com/scrivo/highlight.php).\n\n### Syntax highlighting for code blocks with [Shiki](https://github.com/shikijs/shiki) (Requires Node.js)\nThere 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:\n\n1. you must install the `shiki` npm package.\n2. Shiki code highlighting works by injecting inline styles so pulling in a external css file is not required.\n3. you can use most VS Code themes to highlight your code.\n\nTo use the Shiki extension, first install the npm package\n\n```bash\nnpm install shiki\n```\n\nThen follow the example below:\n\n```php\n(new \\Tiptap\\Editor([\n    'extensions' => [\n        new \\Tiptap\\Extensions\\StarterKit([\n            'codeBlock' => false,\n        ]),\n        new \\Tiptap\\Nodes\\CodeBlockShiki(),\n    ],\n]))\n->setContent('<pre><code>&lt;?php phpinfo()</code></pre>')\n->getHTML();\n```\n\nTo configure the theme or default language for code blocks pass additonal configuration into the constructor as show below:\n\n```php\n(new \\Tiptap\\Editor([\n    'extensions' => [\n        new \\Tiptap\\Extensions\\StarterKit([\n            'codeBlock' => false,\n        ]),\n        new \\Tiptap\\Nodes\\CodeBlockShiki([\n            'theme' => 'github-dark', // default: nord, see https://github.com/shikijs/shiki/blob/main/docs/themes.md\n            'defaultLanguage' => 'php', // default: html, see https://github.com/shikijs/shiki/blob/main/docs/languages.md\n            'guessLanguage' => true, // default: true, if the language isn’t passed, it tries to guess the language with highlight.php\n        ]),\n    ],\n]))\n->setContent('<pre><code>&lt;?php phpinfo()</code></pre>')\n->getHTML();\n```\n\nUnder 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.\n\n### Convert content to plain text\nContent can also be transformed to plain text, for example to put it into a search index.\n\n```php\n(new \\Tiptap\\Editor)\n    ->setContent('<h1>Heading</h1><p>Paragraph</p>')\n    ->getText();\n\n// Returns:\n// \"Heading\n//\n// Paragraph\"\n```\n\nWhat’s coming between blocks can be configured, too.\n\n```php\n(new \\Tiptap\\Editor)\n    ->setContent('<h1>Heading</h1><p>Paragraph</p>')\n    ->getText([\n        'blockSeparator' => \"\\n\",\n    ]);\n\n// Returns:\n// \"Heading\n// Paragraph\"\n```\n\n### Sanitize content\nA 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.\n\nIt’ll return the same format you’re using as the input format.\n\n```php\n(new \\Tiptap\\Editor)\n    ->sanitize('<p>Example Text<script>alert(\"HACKED!\")</script></p>');\n\n// Returns:\n// '<p>Example Text</p>'\n```\n\n### Modifying the content\nWith 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.\n\n> 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.\n\n```php\n$editor->descendants(function (&$node) {\n    if ($node->type !== 'heading') {\n        return;\n    }\n\n    $node->attrs->level = 1;\n});\n```\n\n### Configuration\nPass 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.\n\n```php\nnew \\Tiptap\\Editor([\n    'content' => '<p>Example Text</p>',\n    'extensions' => [\n        new \\Tiptap\\Extensions\\StarterKit,\n    ],\n])\n```\n\nThe `StarterKit` is loaded by default. If you just want to use that, there’s no need to set it.\n\n### Extensions\nBy default, the [`StarterKit`](https://tiptap.dev/api/extensions/starter-kit) is loaded, but you can pass a custom array of extensions aswell.\n\n```php\nnew \\Tiptap\\Editor([\n    'extensions' => [\n        new \\Tiptap\\Extensions\\StarterKit,\n        new \\Tiptap\\Marks\\Link,\n    ],\n])\n```\n\n### Configure extensions\nSome 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.\n\n```php\nnew \\Tiptap\\Editor([\n    'extensions' => [\n        // …\n        new \\Tiptap\\Nodes\\Heading([\n            'levels' => [1, 2, 3],\n        ]),\n    ],\n])\n```\n\nYou can pass custom HTML attributes through the configuration, too.\n\n```php\nnew \\Tiptap\\Editor([\n    'extensions' => [\n        // …\n        new \\Tiptap\\Nodes\\Heading([\n            'HTMLAttributes' => [\n                'class' => 'my-custom-class',\n            ],\n        ]),\n    ],\n])\n```\n\nFor the `StarterKit`, it’s slightly different, but works as you are used to from the JavaScript package.\n\n```php\nnew \\Tiptap\\Editor([\n    'extensions' => [\n        new Tiptap\\Extensions\\StarterKit([\n            'codeBlock' => false,\n            'heading' => [\n                'HTMLAttributes' => [\n                    'class' => 'my-custom-class',\n                ],\n            ]\n        ]),\n    ],\n])\n```\n\n### Extend existing extensions\nIf you need to change minor details of the supported extensions, you can just extend an extension.\n\n```php\n<?php\n\nclass CustomBold extends \\Tiptap\\Marks\\Bold\n{\n    public function renderHTML($mark)\n    {\n        // Renders <b> instead of <strong>\n        return ['b', 0]\n    }\n}\n\nnew \\Tiptap\\Editor([\n    'extensions' => [\n        new Paragraph,\n        new Text,\n        new CustomBold,\n    ],\n])\n```\n\n#### Custom extensions\nYou 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.\n\nMake sure to dig through the extensions in this repository to learn more about the PHP extension API.\n\n```php\n<?php\n\nuse Tiptap\\Core\\Node;\n\nclass CustomNode extends Node\n{\n    public static $name = 'customNode';\n    \n    public static $priority = 100;\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'my-custom-tag[data-id]',\n            ],\n            [\n                'tag' => 'my-custom-tag',\n                'getAttrs' => function ($DOMNode) {\n                    return ! \\Tiptap\\Utils\\InlineStyle::hasAttribute($DOMNode, [\n                        'background-color' => '#000000',\n                    ]) ? null : false;\n                },\n            ],\n            [\n                'style' => 'background-color',\n                'getAttrs' => function ($value) {\n                    return (bool) preg_match('/^(black)$/', $value) ? null : false;\n                },\n            ],\n        ];\n    }\n\n    public function renderHTML($node)\n    {\n        return ['my-custom-tag', ['class' => 'foobar'], 0]\n    }\n}\n```\n\n#### Extension priority\n\nExtensions are evaluated in the order of descending priority. By default, all Nodes, Marks, and Extensions, have a priority value of `100`.\n\nPriority 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).\n\n## Testing\n```bash\ncomposer test\n```\n\nYou can install nodemon (`npm install -g nodemon`) to keep the test suite running and watch for file changes:\n\n```bash\ncomposer test-watch\n```\n\n## Contributing\nPlease see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n- [Hans Pagel](https://github.com/hanspagel)\n- [All Contributors](../../contributors)\n\n## License\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"ueberdosis/tiptap-php\",\n    \"description\": \"A PHP package to work with Tiptap output\",\n    \"keywords\": [\n        \"ueberdosis\",\n        \"tiptap\",\n        \"prosemirror\"\n    ],\n    \"homepage\": \"https://github.com/ueberdosis/tiptap-php\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Hans Pagel\",\n            \"email\": \"humans@tiptap.dev\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^8.0\",\n        \"scrivo/highlight.php\": \"^9.18\",\n        \"spatie/shiki-php\": \"^2.0\"\n    },\n    \"require-dev\": {\n        \"friendsofphp/php-cs-fixer\": \"^3.5\",\n        \"pestphp/pest\": \"^1.21\",\n        \"phpunit/phpunit\": \"^9.5\",\n        \"vimeo/psalm\": \"^4.3\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Tiptap\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Tiptap\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"scripts\": {\n        \"psalm\": \"vendor/bin/psalm\",\n        \"psalm-watch\": \"nodemon --exec './vendor/bin/psalm || exit 1' --ext php\",\n        \"test\": \"./vendor/bin/pest\",\n        \"test-watch\": \"nodemon --exec './vendor/bin/pest || exit 1' --ext php\",\n        \"test-coverage\": \"./vendor/bin/pest --coverage-html coverage\",\n        \"format\": \"vendor/bin/php-cs-fixer fix --allow-risky=yes --config=.php_cs.dist.php\"\n    },\n    \"config\": {\n        \"sort-packages\": true,\n        \"allow-plugins\": {\n            \"pestphp/pest-plugin\": true\n        }\n    },\n    \"minimum-stability\": \"dev\",\n    \"prefer-stable\": true\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tiptap-php\",\n  \"private\": true,\n  \"description\": \"This package.json has all Node dependencies for the local development of the package.\",\n  \"homepage\": \"https://github.com/ueberdosis/tiptap-php\",\n  \"devDependencies\": {\n    \"shiki\": \"^2.0.0\"\n  }\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n    backupGlobals=\"false\"\n    backupStaticAttributes=\"false\"\n    bootstrap=\"vendor/autoload.php\"\n    colors=\"true\"\n    convertErrorsToExceptions=\"true\"\n    convertNoticesToExceptions=\"true\"\n    convertWarningsToExceptions=\"true\"\n    processIsolation=\"false\"\n    stopOnFailure=\"false\"\n    executionOrder=\"random\"\n    failOnWarning=\"true\"\n    failOnRisky=\"true\"\n    failOnEmptyTestSuite=\"true\"\n    beStrictAboutOutputDuringTests=\"true\"\n    verbose=\"false\"\n>\n    <testsuites>\n        <testsuite name=\"VendorName Test Suite\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <coverage>\n        <include>\n            <directory suffix=\".php\">./src</directory>\n        </include>\n        <report>\n            <html outputDirectory=\"build/coverage\"/>\n            <text outputFile=\"build/coverage.txt\"/>\n            <clover outputFile=\"build/logs/clover.xml\"/>\n        </report>\n    </coverage>\n    <logging>\n        <junit outputFile=\"build/report.junit.xml\"/>\n    </logging>\n</phpunit>\n"
  },
  {
    "path": "psalm.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"3\"\n    findUnusedVariablesAndParams=\"true\"\n    resolveFromConfigFile=\"true\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns=\"https://getpsalm.org/schema/config\"\n    xsi:schemaLocation=\"https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd\"\n>\n    <projectFiles>\n        <directory name=\"src\"/>\n        <ignoreFiles>\n            <directory name=\"vendor\"/>\n        </ignoreFiles>\n    </projectFiles>\n</psalm>\n"
  },
  {
    "path": "src/Core/DOMParser.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nuse DOMDocument;\nuse DOMElement;\nuse Tiptap\\Utils\\InlineStyle;\nuse Tiptap\\Utils\\Minify;\n\nclass DOMParser\n{\n    protected $DOM;\n\n    protected $schema;\n\n    protected $storedMarks = [];\n\n    public function __construct($schema)\n    {\n        $this->schema = $schema;\n    }\n\n    public function process(string $value): array\n    {\n        $this->setDocument($value);\n\n        $content = $this->processChildren(\n            $this->getDocumentBody()\n        );\n\n        return [\n            'type' => $this->schema->topNode::$name,\n            'content' => $content,\n        ];\n    }\n\n    private function setDocument(string $value): DOMParser\n    {\n        libxml_use_internal_errors(true);\n\n        $this->DOM = new DOMDocument;\n        /**\n         * @psalm-suppress ArgumentTypeCoercion\n         */\n        $this->DOM->loadHTML(\n            $this->makeValidXMLDocument(\n                $this->minify($value)\n            )\n        );\n\n        return $this;\n    }\n\n    private function minify(string $value): string\n    {\n        return (new Minify)->process($value);\n    }\n\n    private function makeValidXMLDocument($value): string\n    {\n        return '<?xml encoding=\"utf-8\" ?>' . $value;\n    }\n\n    private function getDocumentBody(): DOMElement\n    {\n        return $this->DOM->getElementsByTagName('body')->item(0);\n    }\n\n    private function processChildren($node): array\n    {\n        $nodes = [];\n\n        foreach ($node->childNodes as $child) {\n            if ($class = $this->getNodeFor($child)) {\n                $item = $this->parseAttributes($class, $child);\n\n                if ($item === null) {\n                    if ($child->hasChildNodes()) {\n                        $nodes = array_merge($nodes, $this->processChildren($child));\n                    }\n\n                    continue;\n                }\n\n                if ($child->hasChildNodes()) {\n                    $item = array_merge($item, [\n                        'content' => $this->processChildren($child),\n                    ]);\n                }\n\n                if (count($this->storedMarks)) {\n                    $item = array_merge($item, [\n                        'marks' => $this->storedMarks,\n                    ]);\n                }\n\n                array_push($nodes, $item);\n            } elseif ($class = $this->getMarkFor($child)) {\n                array_push($this->storedMarks, $this->parseAttributes($class, $child));\n\n                if ($child->hasChildNodes()) {\n                    $nodes = array_merge($nodes, $this->processChildren($child));\n                }\n\n                array_pop($this->storedMarks);\n            } elseif ($child->hasChildNodes()) {\n                $nodes = array_merge($nodes, $this->processChildren($child));\n            }\n        }\n\n\n        // If similar nodes with different text follow each other,\n        // we can merge them into a single node.\n        return $this->mergeSimilarNodes($nodes);\n    }\n\n    private function isMultidimensionalArray($array)\n    {\n        foreach ($array as $value) {\n            if (is_array($value)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    private function mergeSimilarNodes($nodes)\n    {\n        $result = [];\n\n        /**\n         * @psalm-suppress UnusedFunctionCall\n         */\n        array_reduce($nodes, function ($carry, $node) use (&$result) {\n            // Ignore multidimensional arrays\n            if ($this->isMultidimensionalArray($node) || $this->isMultidimensionalArray($carry)) {\n                $result[] = $node;\n\n                return $node;\n            }\n\n            // Check if text is the only difference\n            $differentKeys = array_keys(array_diff($carry, $node));\n            if ($differentKeys != ['text']) {\n                $result[] = $node;\n\n                return $node;\n            }\n\n            // Merge it!\n            $result[count($result) - 1]['text'] .= $node['text'];\n\n            return $result[count($result) - 1];\n        }, []);\n\n        return $result;\n    }\n\n\n    private function getNodeFor($item)\n    {\n        return $this->getExtensionFor($item, $this->schema->nodes);\n    }\n\n    private function getMarkFor($item)\n    {\n        return $this->getExtensionFor($item, $this->schema->marks);\n    }\n\n    private function getExtensionFor($node, $classes)\n    {\n        $parseRules = [];\n\n        foreach ($classes as $class) {\n            $classParseRules = $this->getClassParseRules($class, $node);\n            $parseRules = array_merge($parseRules, $classParseRules);\n        }\n\n        usort($parseRules, fn ($parseRuleA, $parseRuleB) => $parseRuleB['priority'] - $parseRuleA['priority']);\n\n        foreach ($parseRules as $parseRule) {\n            if ($this->checkParseRule($parseRule, $node)) {\n                return $parseRule['class'];\n            }\n        }\n\n        return false;\n    }\n\n    private function getClassParseRules($class, $node): array\n    {\n        $parseRules = $class->parseHTML($node);\n\n        if (! is_array($parseRules)) {\n            return [];\n        }\n        $classParseRules = [];\n        foreach ($parseRules as $parseRule) {\n            $parseRule['class'] = $class;\n            $parseRule['priority'] = $parseRule['priority'] ?? 50;\n            $classParseRules[] = $parseRule;\n        }\n\n        return $classParseRules;\n    }\n\n    private function checkParseRule($parseRule, $DOMNode): bool\n    {\n        // ['tag' => 'span[type=\"mention\"]']\n        if (isset($parseRule['tag'])) {\n            if (preg_match('/([a-zA-Z-]*)\\[([a-z-]+)(=\"?([a-zA-Z]*)\"?)?\\]$/', $parseRule['tag'], $matches)) {\n                $tag = $matches[1];\n                $attribute = $matches[2];\n                if (isset($matches[4])) {\n                    $value = $matches[4];\n                }\n            } else {\n                $tag = $parseRule['tag'];\n            }\n\n            if ($tag !== $DOMNode->nodeName) {\n                return false;\n            }\n\n            if (isset($attribute) && ! $DOMNode->hasAttribute($attribute)) {\n                return false;\n            }\n\n            if (isset($attribute) && isset($value) && $DOMNode->getAttribute($attribute) !== $value) {\n                return false;\n            }\n        }\n\n        // ['style' => 'font-weight=italic']\n        if (isset($parseRule['style'])) {\n            if (preg_match('/([a-zA-Z-]*)(=\"?([a-zA-Z-]*)\"?)?$/', $parseRule['style'], $matches)) {\n                $style = $matches[1];\n\n                if (isset($matches[3])) {\n                    $value = $matches[3];\n                }\n            } else {\n                $style = $parseRule['style'];\n            }\n\n            if (! InlineStyle::hasAttribute($DOMNode, $style)) {\n                return false;\n            }\n\n            if (isset($value) && InlineStyle::getAttribute($DOMNode, $style) !== $value) {\n                return false;\n            }\n        }\n\n        // ['getAttrs' => function($DOMNode) { … }]\n        if (isset($parseRule['getAttrs'])) {\n            if (isset($parseRule['style']) && InlineStyle::hasAttribute($DOMNode, $parseRule['style'])) {\n                $parameter = InlineStyle::getAttribute($DOMNode, $parseRule['style']);\n            } else {\n                $parameter = $DOMNode;\n            }\n\n            if ($parseRule['getAttrs']($parameter) === false) {\n                return false;\n            }\n        }\n\n        if (\n            ! is_array($parseRule)\n            || ! count($parseRule)\n            || (\n                ! isset($parseRule['tag'])\n                && ! isset($parseRule['style'])\n                && ! isset($parseRule['getAttrs'])\n            )) {\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * @return (array|mixed|string)[]|null\n     *\n     * @psalm-return array{type: mixed, text?: string, attrs?: array}|null\n     */\n    private function parseAttributes($class, $DOMNode): ?array\n    {\n        $item = [\n            'type' => $class::$name,\n        ];\n\n        if ($class::$name === 'text') {\n            $text = ltrim($DOMNode->nodeValue, \"\\n\");\n\n            if ($text === '') {\n                return null;\n            }\n\n            $item = array_merge($item, [\n                'text' => $text,\n            ]);\n        }\n\n        $parseRules = $class->parseHTML();\n\n        if (! is_array($parseRules)) {\n            return $item;\n        }\n\n        foreach ($parseRules as $parseRule) {\n            if (! $this->checkParseRule($parseRule, $DOMNode)) {\n                continue;\n            }\n\n            $attributes = $parseRule['attrs'] ?? [];\n            if (count($attributes)) {\n                if (! isset($item['attrs'])) {\n                    $item['attrs'] = [];\n                }\n\n                $item['attrs'] = array_merge($item['attrs'], $attributes);\n            }\n\n            if (isset($parseRule['getAttrs'])) {\n                if (isset($parseRule['style']) && InlineStyle::hasAttribute($DOMNode, $parseRule['style'])) {\n                    $parameter = InlineStyle::getAttribute($DOMNode, $parseRule['style']);\n                } else {\n                    $parameter = $DOMNode;\n                }\n\n                $attributes = $parseRule['getAttrs']($parameter);\n\n                if (! is_array($attributes)) {\n                    continue;\n                }\n\n                if (! isset($item['attrs'])) {\n                    $item['attrs'] = [];\n                }\n\n                $item['attrs'] = array_merge($item['attrs'], $attributes);\n            }\n        }\n\n        /**\n         * public function addAttributes()\n         * {\n         *     return [\n         *         'href' => [\n         *             'parseHTML' => function ($DOMNode) {\n         *                 $attrs['href'] = $DOMNode->getAttribute('href');\n         *             }\n         *         ],\n         *     ];\n         * }\n         */\n        foreach ($this->schema->getAttributeConfigurations($class) as $attribute => $configuration) {\n            if (isset($configuration['parseHTML'])) {\n                $value = $configuration['parseHTML']($DOMNode);\n            } else {\n                $value = $DOMNode->getAttribute($attribute) ?: null;\n            }\n\n            if ($value !== null) {\n                $item['attrs'][$attribute] = $value;\n            }\n        }\n\n        return $item;\n    }\n}\n"
  },
  {
    "path": "src/Core/DOMSerializer.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nuse DOMDocument;\nuse stdClass;\nuse Tiptap\\Utils\\HTML;\n\nclass DOMSerializer\n{\n    protected $document;\n\n    protected $schema;\n\n    public function __construct($schema)\n    {\n        $this->schema = $schema;\n    }\n\n    private function renderNode($node, $previousNode = null, $nextNode = null, &$markStack = []): string\n    {\n        $html = [];\n        $markTagsToClose = [];\n\n        if (isset($node->marks)) {\n            foreach ($node->marks as $mark) {\n                foreach ($this->schema->marks as $class) {\n                    $renderClass = $class;\n\n                    if (! $this->isMarkOrNode($mark, $renderClass)) {\n                        continue;\n                    }\n\n                    if (! $this->markShouldOpen($mark, $previousNode)) {\n                        continue;\n                    }\n\n                    $html[] = $this->renderOpeningTag($renderClass, $mark);\n                    # push recently created mark tag to the stack\n                    $markStack[] = [$renderClass, $mark];\n                }\n            }\n        }\n\n        foreach ($this->schema->nodes as $extension) {\n            if (! $this->isMarkOrNode($node, $extension)) {\n                continue;\n            }\n\n            $html[] = $this->renderOpeningTag($extension, $node);\n\n            break;\n        }\n\n        // [\"content\" => …]\n        $lastKey = array_key_last($html);\n        $lastElement = $html[$lastKey] ?? null;\n        if (! is_null($lastKey) && isset($lastElement['content'])) {\n            $html[$lastKey] = $lastElement['content'];\n        }\n        // child nodes\n        elseif (isset($node->content)) {\n            $nestedNodeMarkStack = [];\n            foreach ($node->content as $index => $nestedNode) {\n                $previousNestedNode = $node->content[$index - 1] ?? null;\n                $nextNestedNode = $node->content[$index + 1] ?? null;\n\n                $html[] = $this->renderNode($nestedNode, $previousNestedNode, $nextNestedNode, $nestedNodeMarkStack);\n            }\n        }\n        // renderText($node)\n        elseif (isset($extension) && method_exists($extension, 'renderText')) {\n            $html[] = $extension->renderText($node);\n        }\n        // text\n        elseif (isset($node->text)) {\n            $html[] = htmlspecialchars($node->text, ENT_QUOTES, 'UTF-8');\n        }\n\n        foreach ($this->schema->nodes as $extension) {\n            if (! $this->isMarkOrNode($node, $extension)) {\n                continue;\n            }\n\n            $html[] = $this->renderClosingTag($extension->renderHTML($node));\n        }\n\n        if (isset($node->marks)) {\n            foreach (array_reverse($node->marks) as $mark) {\n                foreach ($this->schema->marks as $extension) {\n                    if (! $this->isMarkOrNode($mark, $extension)) {\n                        continue;\n                    }\n\n                    if (! $this->markShouldClose($mark, $nextNode)) {\n                        continue;\n                    }\n\n                    # remember which mark tags to close\n                    $markTagsToClose[] = [$extension, $mark];\n                }\n            }\n            # close mark tags and reopen when necessary\n            $html = array_merge($html, $this->closeAndReopenTags($markTagsToClose, $markStack));\n        }\n\n        return join($html);\n    }\n\n    private function closeAndReopenTags(array $markTagsToClose, array &$markStack): array\n    {\n        $markTagsToReopen = [];\n        $closingTags = $this->closeMarkTags($markTagsToClose, $markStack, $markTagsToReopen);\n        $reopeningTags = $this->reopenMarkTags($markTagsToReopen, $markStack);\n\n        return array_merge($closingTags, $reopeningTags);\n    }\n\n    private function closeMarkTags($markTagsToClose, &$markStack, &$markTagsToReopen): array\n    {\n        $html = [];\n        while (! empty($markTagsToClose)) {\n            # close mark tag from the top of the stack\n            $markTag = array_pop($markStack);\n            $markExtension = $markTag[0];\n            $mark = $markTag[1];\n            $html[] = $this->renderClosingTag($markExtension->renderHTML($mark));\n\n            # check if the last closed tag is overlapping and has to be reopened\n            # find the first matching mark to close\n            $foundIndex = null;\n            foreach ($markTagsToClose as $index => $markToClose) {\n                if ($markExtension == $markToClose[0] && $mark == $markToClose[1]) {\n                    $foundIndex = $index;\n\n                    break;\n                }\n            }\n\n            if ($foundIndex === null) {\n                $markTagsToReopen[] = $markTag;\n            } else {\n                # specific mark tag does not have to be reopened, but deleted from the 'to close' list\n                unset($markTagsToClose[$foundIndex]);\n                $markTagsToClose = array_values($markTagsToClose); // Re-index array\n            }\n        }\n\n        return $html;\n    }\n\n    private function reopenMarkTags($markTagsToReopen, &$markStack): array\n    {\n        $html = [];\n        # reopen the overlapping mark tags and push them to the stack\n        foreach (array_reverse($markTagsToReopen) as $markTagToOpen) {\n            $renderClass = $markTagToOpen[0];\n            $mark = $markTagToOpen[1];\n            $html[] = $this->renderOpeningTag($renderClass, $mark);\n            $markStack[] = [$renderClass, $mark];\n        }\n\n        return $html;\n    }\n\n    private function isMarkOrNode($markOrNode, $renderClass): bool\n    {\n        return isset($markOrNode->type) && $markOrNode->type === $renderClass::$name;\n    }\n\n    private function markShouldOpen($mark, $previousNode): bool\n    {\n        return $this->nodeHasMark($previousNode, $mark);\n    }\n\n    private function markShouldClose($mark, $nextNode): bool\n    {\n        return $this->nodeHasMark($nextNode, $mark);\n    }\n\n    private function nodeHasMark($node, $mark): bool\n    {\n        if (! $node) {\n            return true;\n        }\n\n        if (! property_exists($node, 'marks')) {\n            return true;\n        }\n\n        // The other node has same mark\n        foreach ($node->marks as $otherMark) {\n            if ($mark == $otherMark) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    private function renderOpeningTag($extension, $nodeOrMark, $renderHTML = false)\n    {\n        /**\n         * public function addAttributes()\n         * {\n         *     return [\n         *        'color' => [\n         *            'renderHTML' => function ($attributes) {\n         *                return [\n         *                    'style' => \"color: {$attributes['color']}\",\n         *                ];\n         *            }\n         *        ],\n         *    ];\n         * }\n         */\n        $HTMLAttributes = [];\n\n        foreach ($this->schema->getAttributeConfigurations($extension) as $attribute => $configuration) {\n            // 'rendered' => false\n            if (isset($configuration['rendered']) && $configuration['rendered'] === false) {\n                continue;\n            }\n\n            // 'default' => 'foobar'\n            if (! isset($nodeOrMark->attrs->{$attribute}) && isset($configuration['default'])) {\n                if (! isset($nodeOrMark->attrs)) {\n                    $nodeOrMark->attrs = new stdClass;\n                }\n\n                $nodeOrMark->attrs->{$attribute} = $configuration['default'];\n            }\n\n            // 'renderHTML' => fn($attributes) …\n            if (isset($configuration['renderHTML'])) {\n                $value = $configuration['renderHTML']($nodeOrMark->attrs ?? new stdClass);\n            } else {\n                $value = [\n                    $attribute => $nodeOrMark->attrs->{$attribute} ?? null,\n                ];\n            }\n\n            if ($value !== null) {\n                $HTMLAttributes = HTML::mergeAttributes($HTMLAttributes, $value);\n            }\n        }\n\n        // Remove empty attributes\n        $HTMLAttributes = array_filter($HTMLAttributes, fn ($HTMLAttribute) => $HTMLAttribute !== null);\n\n        if ($renderHTML === false) {\n            $renderHTML = $extension->renderHTML($nodeOrMark, $HTMLAttributes);\n        }\n\n        // [\"content\" => …]\n        if (isset($renderHTML['content'])) {\n            return $renderHTML;\n        }\n\n        // null\n        if (is_null($renderHTML)) {\n            return '';\n        }\n\n        // ['table', ['tbody', 0]]\n        // ['table', ['class' => 'foobar'], ['tbody', 0]]\n        if (is_array($renderHTML)) {\n            $html = [];\n\n            foreach ($renderHTML as $index => $renderInstruction) {\n                // ['div', …]\n                if (is_string($renderInstruction)) {\n                    if (is_integer($index) && $nextTag = $renderHTML[$index + 1] ?? null) {\n                        // ['table', ['class' => 'custom-class']]\n                        if (! in_array(0, $nextTag, true)) {\n                            if (is_array($nextTag) && $this->isAnAttributeArray($nextTag)) {\n                                $attributes = HTML::renderAttributes($nextTag);\n                            } else {\n                                $attributes = '';\n                            }\n\n                            // <a href=\"#\">\n                            $html[] = \"<{$renderInstruction}{$attributes}>\";\n                        } else {\n                            $html[] = \"<{$renderInstruction}>\";\n                        }\n                    } else {\n                        $html[] = \"<{$renderInstruction}>\";\n                    }\n\n                    // ['div', 'span']\n                    if (isset($nextTag) && is_array($nextTag) && ! in_array(0, $nextTag, true)) {\n                        if (! $this->isAnAttributeArray($nextTag)) {\n                            $html[] = $this->renderOpeningTag($extension, $nodeOrMark, $nextTag);\n                            $html[] = $this->renderClosingTag($nextTag);\n                        }\n                    }\n\n                    // ['div', ?, 'span']\n                    if (is_integer($index) && $nextTag = $renderHTML[$index + 2] ?? null) {\n                        if (! in_array(0, $nextTag, true)) {\n                            if (! $this->isAnAttributeArray($nextTag)) {\n                                $html[] = $this->renderOpeningTag($extension, $nodeOrMark, $nextTag);\n                                $html[] = $this->renderClosingTag($nextTag);\n                            }\n                        }\n                    }\n\n                    continue;\n                }\n                // ['tbody', 0]\n                elseif (is_array($renderInstruction) && in_array(0, $renderInstruction, true)) {\n                    $html[] = $this->renderOpeningTag($extension, $nodeOrMark, $renderInstruction);\n                }\n                // ['class' => 'foobar']\n                elseif (is_array($renderInstruction)) {\n                    continue;\n                }\n            }\n\n            return join($html);\n        }\n\n        throw new \\Exception('[renderOpeningTag] Failed to use renderHTML: ' . json_encode($renderHTML));\n    }\n\n    private function isAnAttributeArray($items): bool\n    {\n        if (! is_array($items)) {\n            return false;\n        }\n\n        $keys = array_keys($items);\n\n        return $keys !== array_keys($keys);\n    }\n\n    private function isSelfClosing($tag): bool\n    {\n        $dom = new DOMDocument('1.0', 'utf-8');\n        $element = $dom->createElement($tag, 'test');\n        $dom->appendChild($element);\n        $rendered = $dom->saveHTML();\n\n        return substr_count($rendered, $tag) === 1;\n    }\n\n    /**\n     * @return null|string\n     */\n    private function renderClosingTag($renderHTML)\n    {\n        // null\n        if (is_null($renderHTML)) {\n            return '';\n        }\n\n        // [\"content\" => …]\n        if (isset($renderHTML['content'])) {\n            return;\n        }\n\n        // ['table', ['tbody']]\n        if (is_array($renderHTML)) {\n            $html = [];\n\n            foreach (array_reverse($renderHTML) as $renderInstruction) {\n                // 'div'\n                if (is_string($renderInstruction)) {\n                    if ($this->isSelfClosing($renderInstruction)) {\n                        return null;\n                    }\n\n                    $html[] = \"</{$renderInstruction}>\";\n                }\n                // ['div', 0]\n                elseif (is_array($renderInstruction) && in_array(0, $renderInstruction, true)) {\n                    $html[] = $this->renderClosingTag($renderInstruction);\n                }\n            }\n\n            return join($html);\n        }\n\n        throw new \\Exception('[renderClosingTag] Failed to use renderHTML: ' . json_encode($renderHTML));\n    }\n\n    public function process(array $value): string\n    {\n        $html = [];\n\n        // transform document to object\n        $this->document = json_decode(json_encode($value));\n\n        $content = is_array($this->document->content) ? $this->document->content : [];\n\n        $markStack = [];\n\n        foreach ($content as $index => $node) {\n            $previousNode = $content[$index - 1] ?? null;\n            $nextNode = $content[$index + 1] ?? null;\n\n            $html[] = $this->renderNode($node, $previousNode, $nextNode, $markStack);\n        }\n\n        return join($html);\n    }\n}\n"
  },
  {
    "path": "src/Core/Extension.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Extension\n{\n    public static $name;\n\n    public static $priority = 100;\n\n    public $options = [];\n\n    public function __construct(array $options = [])\n    {\n        $this->options = array_merge($this->addOptions(), $options);\n    }\n\n    public function addOptions()\n    {\n        return [];\n    }\n\n    public function addGlobalAttributes()\n    {\n        return [];\n    }\n\n    public function addExtensions()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Core/JSONSerializer.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nclass JSONSerializer\n{\n    protected $document;\n\n    public function process(array $value): string\n    {\n        $this->document = json_decode(json_encode($value));\n\n        return json_encode($this->document);\n    }\n}\n"
  },
  {
    "path": "src/Core/Mark.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Mark extends Extension\n{\n    public static $priority = 100;\n\n    public function addAttributes()\n    {\n        return [];\n    }\n\n    public function renderHTML($mark)\n    {\n        return null;\n    }\n\n    public function parseHTML()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Core/Node.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Node extends Extension\n{\n    public static $priority = 100;\n\n    public static $topNode = false;\n\n    public static $marks = '_';\n\n    public function addAttributes()\n    {\n        return [];\n    }\n\n    public function parseHTML()\n    {\n        return [];\n    }\n\n    public function renderHTML($node)\n    {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Core/Schema.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nclass Schema\n{\n    public array $allExtensions = [];\n\n    public array $nodes = [];\n    public array $marks = [];\n    public array $extensions = [];\n\n    public $defaultNode;\n    public $topNode;\n\n    public array $globalAttributes = [];\n\n    public function __construct(array $extensions = [])\n    {\n        $this->allExtensions = $this->loadExtensions($extensions);\n        usort($this->allExtensions, fn ($a, $b) => $b::$priority - $a::$priority);\n\n        $this->nodes = array_filter($this->allExtensions, function ($extension) {\n            return is_subclass_of($extension, \\Tiptap\\Core\\Node::class);\n        });\n\n        $this->marks = array_filter($this->allExtensions, function ($extension) {\n            return is_subclass_of($extension, \\Tiptap\\Core\\Mark::class);\n        });\n\n        $this->extensions = array_filter($this->allExtensions, function ($extension) {\n            return is_subclass_of($extension, \\Tiptap\\Core\\Extension::class);\n        });\n\n        $this->defaultNode = reset($this->nodes);\n        $this->topNode = current(array_filter($this->nodes, fn ($node) => $node::$topNode));\n\n        return $this;\n    }\n\n    private function loadExtensions($extensions = [])\n    {\n        foreach ($extensions as $extension) {\n            if (method_exists($extension, 'addExtensions') && count($extension->addExtensions())) {\n                $extensions = array_merge(\n                    $extensions,\n                    $this->loadExtensions($extension->addExtensions()),\n                );\n            }\n\n            if (method_exists($extension, 'addGlobalAttributes')) {\n                $globalAttributes = $extension->addGlobalAttributes();\n\n                foreach ($globalAttributes as $globalAttributeConfiguration) {\n                    foreach ($globalAttributeConfiguration['types'] ?? [] as $type) {\n                        $this->globalAttributes[$type] = array_merge(\n                            $this->globalAttributes[$type] ?? [],\n                            $globalAttributeConfiguration['attributes']\n                        );\n                    }\n                }\n            }\n        }\n\n        return $extensions;\n    }\n\n    public function apply($document)\n    {\n        if (! is_array($document['content'])) {\n            return $document;\n        }\n\n        $document['content'] = array_map(function ($node) {\n            foreach ($this->allExtensions as $extension) {\n                if (! isset($node['type']) || $node['type'] !== $extension::$name) {\n                    continue;\n                }\n\n                if (property_exists($extension, 'marks')) {\n                    if ($extension::$marks === '') {\n                        $node = $this->filterMarks($node);\n\n                        unset($node['marks']);\n                    }\n\n                    // TODO: Support for multiple marks is missing\n                }\n\n                break;\n            }\n\n            return $node;\n        }, $document['content']);\n\n        return $document;\n    }\n\n    public function filterMarks(&$node)\n    {\n        unset($node['marks']);\n\n        if (isset($node['content'])) {\n            $node['content'] = array_map(function ($child) {\n                return $this->filterMarks($child);\n            }, $node['content']);\n        }\n\n        return $node;\n    }\n\n    public function getAttributeConfigurations($class): array\n    {\n        return array_merge(\n            $this->globalAttributes[$class::$name] ?? [],\n            $class->addAttributes(),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Core/TextSerializer.php",
    "content": "<?php\n\nnamespace Tiptap\\Core;\n\nclass TextSerializer\n{\n    protected $document;\n\n    protected $schema;\n\n    protected $configuration = [\n        'blockSeparator' => \"\\n\\n\",\n    ];\n\n    public function __construct($schema, $configuration = [])\n    {\n        $this->schema = $schema;\n        $this->configuration = array_merge($this->configuration, $configuration);\n    }\n\n    public function process(array $value): string\n    {\n        $html = [];\n\n        // transform document to object\n        $this->document = json_decode(json_encode($value));\n\n        $content = is_array($this->document->content) ? $this->document->content : [];\n\n        foreach ($content as $node) {\n            $html[] = $this->renderNode($node);\n        }\n\n        return join($this->configuration['blockSeparator'], $html);\n    }\n\n    private function renderNode($node): string\n    {\n        $text = [];\n\n        if (isset($node->content)) {\n            foreach ($node->content as $nestedNode) {\n                $text[] = $this->renderNode($nestedNode);\n            }\n        } elseif (isset($node->text)) {\n            $text[] = htmlspecialchars($node->text, ENT_QUOTES, 'UTF-8');\n        }\n\n        return join($this->configuration['blockSeparator'], $text);\n    }\n}\n"
  },
  {
    "path": "src/Editor.php",
    "content": "<?php\n\nnamespace Tiptap;\n\nuse Exception;\nuse Tiptap\\Core\\DOMParser;\nuse Tiptap\\Core\\DOMSerializer;\nuse Tiptap\\Core\\JSONSerializer;\nuse Tiptap\\Core\\Schema;\nuse Tiptap\\Core\\TextSerializer;\nuse Tiptap\\Extensions\\StarterKit;\n\nclass Editor\n{\n    protected $document;\n\n    public $schema;\n\n    public $configuration = [\n        'content' => null,\n        'extensions' => [],\n    ];\n\n    public function __construct(array $configuration = [])\n    {\n        if (! isset($configuration['extensions'])) {\n            $configuration['extensions'] = [\n                new StarterKit,\n            ];\n        }\n\n        $this->configuration = array_merge_recursive($this->configuration, $configuration);\n        $this->schema = new Schema($this->configuration['extensions']);\n\n        if (isset($configuration['content'])) {\n            $this->setContent($configuration['content']);\n        }\n    }\n\n    /**\n     * @return static\n     */\n    public function setContent($value): self\n    {\n        if ($this->getContentType($value) === 'HTML') {\n            $this->document = (new DOMParser($this->schema))->process($value);\n        } elseif ($this->getContentType($value) === 'Array') {\n            $this->document = json_decode(json_encode($value), true);\n        } elseif ($this->getContentType($value) === 'JSON') {\n            $this->document = json_decode($value, true);\n        }\n\n        $this->document = $this->schema->apply($this->document);\n\n        return $this;\n    }\n\n    public function getDocument()\n    {\n        return $this->document;\n    }\n\n    public function getJSON(): string\n    {\n        return (new JSONSerializer)->process($this->document);\n    }\n\n    public function getHTML(): string\n    {\n        return (new DOMSerializer($this->schema))->process($this->document);\n    }\n\n    public function getText($configuration = []): string\n    {\n        return (new TextSerializer($this->schema, $configuration))->process($this->document);\n    }\n\n    public function sanitize($value)\n    {\n        if ($this->getContentType($value) === 'HTML') {\n            return $this->setContent($value)->getHTML();\n        } elseif ($this->getContentType($value) === 'Array') {\n            return $this->setContent($value)->getDocument();\n        } elseif ($this->getContentType($value) === 'JSON') {\n            return $this->setContent($value)->getJSON();\n        }\n    }\n\n    public function getContentType($value): string\n    {\n        if (is_string($value)) {\n            try {\n                /**\n                 * @psalm-suppress UnusedFunctionCall\n                 */\n                json_decode($value, true, 512, JSON_THROW_ON_ERROR);\n\n                return 'JSON';\n            } catch (Exception $exception) {\n                return 'HTML';\n            }\n        }\n\n        if (is_array($value)) {\n            return 'Array';\n        }\n\n        throw new Exception('Unknown format passed to setContent(). Try passing HTML, JSON or an Array.');\n    }\n\n    public function descendants($closure): Editor\n    {\n        // Transform the document to an object\n        $node = json_decode(json_encode($this->document));\n\n        $this->walkThroughNodes($node, $closure);\n\n        // Store the updated document.\n        $this->setContent(json_decode(json_encode($node), true));\n\n        return $this;\n    }\n\n    /**\n     * @return void\n     */\n    private function walkThroughNodes(&$node, $closure)\n    {\n        // Skip, if it’s just text.\n        if ($node->type === 'text') {\n            return;\n        }\n\n        // Call the closure.\n        $closure($node);\n\n        // Skip, if there are no children.\n        if (! isset($node->content)) {\n            return;\n        }\n\n        // Make sure content is an Array.\n        $content = is_array($node->content) ? $node->content : [];\n\n        // Loop through all children.\n        foreach ($content as $child) {\n            $this->walkThroughNodes($child, $closure);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Extensions/Color.php",
    "content": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Color extends Extension\n{\n    public static $name = 'color';\n\n    public function addOptions()\n    {\n        return [\n            'types' => ['textStyle'],\n        ];\n    }\n\n    public function addGlobalAttributes()\n    {\n        return [\n            [\n                'types' => $this->options['types'],\n                'attributes' => [\n                    'color' => [\n                        'default' => null,\n                        'parseHTML' => function ($DOMNode) {\n                            $attribute = InlineStyle::getAttribute($DOMNode, 'color');\n\n                            if ($attribute === null) {\n                                return null;\n                            }\n\n                            return preg_replace('/[\\'\"]+/', '', $attribute);\n                        },\n                        'renderHTML' => function ($attributes) {\n                            $color = $attributes?->color ?? null;\n\n                            if ($color === null) {\n                                return null;\n                            }\n\n                            return ['style' => \"color: {$color}\"];\n                        },\n                    ],\n                ],\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Extensions/FontFamily.php",
    "content": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass FontFamily extends Extension\n{\n    public static $name = 'fontFamily';\n\n    public function addOptions()\n    {\n        return [\n            'types' => ['textStyle'],\n        ];\n    }\n\n    public function addGlobalAttributes()\n    {\n        return [\n            [\n                'types' => $this->options['types'],\n                'attributes' => [\n                    'fontFamily' => [\n                        'default' => null,\n                        'parseHTML' => function ($DOMNode) {\n                            $attribute = InlineStyle::getAttribute($DOMNode, 'font-family');\n\n                            if ($attribute === null) {\n                                return null;\n                            }\n\n                            return $attribute;\n                        },\n                        'renderHTML' => function ($attributes) {\n                            $fontFamily = $attributes?->fontFamily ?? null;\n\n                            if ($fontFamily === null) {\n                                return null;\n                            }\n\n                            return ['style' => \"font-family: {$fontFamily}\"];\n                        },\n                    ],\n                ],\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Extensions/StarterKit.php",
    "content": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\n\nclass StarterKit extends Extension\n{\n    public static $name = 'starterKit';\n\n    public function addOptions()\n    {\n        return [\n            'document' => [],\n            'blockquote' => [],\n            'bulletList' => [],\n            'codeBlock' => [],\n            'hardBreak' => [],\n            'heading' => [],\n            'horizontalRule' => [],\n            'listItem' => [],\n            'orderedList' => [],\n            'paragraph' => [],\n            'text' => [],\n            'bold' => [],\n            'code' => [],\n            'italic' => [],\n            'strike' => [],\n        ];\n    }\n\n    public function addExtensions()\n    {\n        return array_filter([\n            $this->options['document'] !== false\n                ? new \\Tiptap\\Nodes\\Document($this->options['document'])\n                : null,\n            $this->options['blockquote'] !== false\n                ? new \\Tiptap\\Nodes\\Blockquote($this->options['blockquote'])\n                : null,\n            $this->options['bulletList'] !== false\n                ? new \\Tiptap\\Nodes\\BulletList($this->options['bulletList'])\n                : null,\n            $this->options['codeBlock'] !== false\n                ? new \\Tiptap\\Nodes\\CodeBlock($this->options['codeBlock'])\n                : null,\n            $this->options['hardBreak'] !== false\n                ? new \\Tiptap\\Nodes\\HardBreak($this->options['hardBreak'])\n                : null,\n            $this->options['heading'] !== false\n                ? new \\Tiptap\\Nodes\\Heading($this->options['heading'])\n                : null,\n            $this->options['horizontalRule'] !== false\n                ? new \\Tiptap\\Nodes\\HorizontalRule($this->options['horizontalRule'])\n                : null,\n            $this->options['listItem'] !== false\n                ? new \\Tiptap\\Nodes\\ListItem($this->options['listItem'])\n                : null,\n            $this->options['orderedList'] !== false\n                ? new \\Tiptap\\Nodes\\OrderedList($this->options['orderedList'])\n                : null,\n            $this->options['paragraph'] !== false\n                ? new \\Tiptap\\Nodes\\Paragraph($this->options['paragraph'])\n                : null,\n            $this->options['text'] !== false\n                ? new \\Tiptap\\Nodes\\Text($this->options['text'])\n                : null,\n            $this->options['bold'] !== false\n                ? new \\Tiptap\\Marks\\Bold($this->options['bold'])\n                : null,\n            $this->options['code'] !== false\n                ? new \\Tiptap\\Marks\\Code($this->options['code'])\n                : null,\n            $this->options['italic'] !== false\n                ? new \\Tiptap\\Marks\\Italic($this->options['italic'])\n                : null,\n            $this->options['strike'] !== false\n                ? new \\Tiptap\\Marks\\Strike($this->options['strike'])\n                : null,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Extensions/TextAlign.php",
    "content": "<?php\n\nnamespace Tiptap\\Extensions;\n\nuse Tiptap\\Core\\Extension;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass TextAlign extends Extension\n{\n    public static $name = 'textAlign';\n\n    public function addOptions()\n    {\n        return [\n            'types' => [],\n            'alignments' => ['left', 'center', 'right', 'justify'],\n            'defaultAlignment' => 'left',\n        ];\n    }\n\n    public function addGlobalAttributes()\n    {\n        return [\n            [\n              'types' => $this->options['types'],\n              'attributes' => [\n                'textAlign' => [\n                    'default' => $this->options['defaultAlignment'],\n                    'parseHTML' => fn ($DOMNode) =>\n                        InlineStyle::getAttribute($DOMNode, 'text-align') ?? $this->options['defaultAlignment'],\n                    'renderHTML' => function ($attributes) {\n                        if ($attributes->textAlign === $this->options['defaultAlignment']) {\n                            return null;\n                        }\n\n                        return ['style' => \"text-align: {$attributes->textAlign}\"];\n                    },\n                ],\n              ],\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Bold.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Bold extends Mark\n{\n    public static $name = 'bold';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'strong',\n            ],\n            [\n                'tag' => 'b',\n                'getAttrs' => function ($DOMNode) {\n                    return ! InlineStyle::hasAttribute($DOMNode, [\n                        'font-weight' => 'normal',\n                    ]) ? null : false;\n                },\n            ],\n            [\n                'style' => 'font-weight',\n                'getAttrs' => function ($value) {\n                    return (bool) preg_match('/^(bold(er)?|[5-9]\\d{2,})$/', $value) ? null : false;\n                },\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return [\n            'strong',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Code.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Code extends Mark\n{\n    public static $name = 'code';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'code',\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['code', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Highlight.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Highlight extends Mark\n{\n    public static $name = 'highlight';\n\n    public function addOptions()\n    {\n        return [\n            'multicolor' => false,\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'mark',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        if (! $this->options['multicolor']) {\n            return [];\n        }\n\n        return [\n            'color' => [\n                'parseHTML' => function ($DOMNode) {\n                    if ($color = $DOMNode->getAttribute('data-color')) {\n                        return $color;\n                    }\n\n                    return InlineStyle::getAttribute($DOMNode, 'background-color') ?: null;\n                },\n                'renderHTML' => function ($attributes) {\n                    if (! $attributes->color) {\n                        return null;\n                    }\n\n                    return [\n                        'data-color' => $attributes->color,\n                        'style' => \"background-color: {$attributes->color}\",\n                    ];\n                },\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return [\n            'mark',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Italic.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\nuse Tiptap\\Utils\\InlineStyle;\n\nclass Italic extends Mark\n{\n    public static $name = 'italic';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'em',\n            ],\n            [\n                'tag' => 'i',\n                'getAttrs' => function ($DOMNode) {\n                    return ! InlineStyle::hasAttribute($DOMNode, [\n                        'font-style' => 'normal',\n                    ]) ? null : false;\n                },\n            ],\n            [\n                'style' => 'font-style=italic',\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['em', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Link.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Link extends Mark\n{\n    public static $name = 'link';\n\n    // Port of the DOMPurify helper used by Tiptap’s Link extension\n    // https://github.com/ueberdosis/tiptap/blob/next/packages/extension-link/src/link.ts#L161\n    const ATTR_WHITESPACE = '/[\\x00-\\x20\\x{00A0}\\x{1680}\\x{180E}\\x{2000}-\\x{2029}\\x{205F}\\x{3000}]/u';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [\n                'target' => '_blank',\n                'rel' => 'noopener noreferrer nofollow',\n            ],\n            'allowedProtocols' => [\n                'http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp',\n            ],\n            'isAllowedUri' => fn ($uri) => $this->isAllowedUri($uri),\n        ];\n    }\n\n    public function isAllowedUri($uri)\n    {\n        if ($uri === null || $uri === '') {\n            return true;\n        }\n\n        $sanitised = preg_replace(self::ATTR_WHITESPACE, '', $uri);\n\n        $pattern = '/^(?:(?:' . implode('|', array_map('preg_quote', $this->options['allowedProtocols']))\n        . '):|[^a-z]|[a-z0-9+.\\-]+(?:[^a-z+.\\-:]|$))/i';\n\n        return (bool) preg_match($pattern, $sanitised);\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'a[href]',\n                'getAttrs' => function ($DOMNode) {\n                    $href = $DOMNode->getAttribute('href');\n\n                    if (\n                        $href === '' ||\n                        ! $this->options['isAllowedUri']($href)\n                    ) {\n                        return false;\n                    }\n\n                    return null;\n                },\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'href' => [],\n            'target' => [],\n            'rel' => [],\n            'class' => [],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        $isAllowed = $this->options['isAllowedUri']($HTMLAttributes['href'] ?? '');\n\n        if (! $isAllowed) {\n            $HTMLAttributes['href'] = '';\n        }\n\n        $attributes = HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes);\n\n        if (isset($mark->attrs)) {\n            foreach ((array) $mark->attrs as $key => $value) {\n                if ($value === null) {\n                    unset($attributes[$key]);\n                }\n            }\n        }\n\n        return [\n            'a',\n            $attributes,\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Strike.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Strike extends Mark\n{\n    public static $name = 'strike';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 's',\n            ],\n            [\n                'tag' => 'del',\n            ],\n            [\n                'tag' => 'strike',\n            ],\n            [\n                'style' => 'text-decoration=line-through',\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['s', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Subscript.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Subscript extends Mark\n{\n    public static $name = 'subscript';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'sub',\n            ],\n            [\n                'style' => 'vertical-align=sub',\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['sub', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Superscript.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Superscript extends Mark\n{\n    public static $name = 'superscript';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'sup',\n            ],\n            [\n                'style' => 'vertical-align=super',\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['sup', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Marks/TextStyle.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass TextStyle extends Mark\n{\n    public static $name = 'textStyle';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'span',\n                'getAttrs' => function ($DOMNode) {\n                    return $DOMNode->hasAttribute('style') ? null : false;\n                },\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['span', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Marks/Underline.php",
    "content": "<?php\n\nnamespace Tiptap\\Marks;\n\nuse Tiptap\\Core\\Mark;\nuse Tiptap\\Utils\\HTML;\n\nclass Underline extends Mark\n{\n    public static $name = 'underline';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'u',\n            ],\n            [\n                'style' => 'text-decoration=underline',\n            ],\n        ];\n    }\n\n    public function renderHTML($mark, $HTMLAttributes = [])\n    {\n        return ['u', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Blockquote.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Blockquote extends Node\n{\n    public static $name = 'blockquote';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'blockquote',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['blockquote', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/BulletList.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass BulletList extends Node\n{\n    public static $name = 'bulletList';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'ul',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['ul', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/CodeBlock.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass CodeBlock extends Node\n{\n    public static $name = 'codeBlock';\n\n    public static $marks = '';\n\n    public function addOptions()\n    {\n        return [\n            'languageClassPrefix' => 'language-',\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'pre',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'language' => [\n                'parseHTML' => function ($DOMNode) {\n                    if (! ($DOMNode->childNodes[0] instanceof \\DOMElement)) {\n                        return null;\n                    }\n\n                    return preg_replace(\n                        \"/^\" . $this->options['languageClassPrefix']. \"/\",\n                        \"\",\n                        $DOMNode->childNodes[0]->getAttribute('class')\n                    ) ?: null;\n                },\n                'rendered' => false,\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'pre',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            [\n                'code',\n                [\n                    'class' => $node->attrs->language ?? null\n                        ? $this->options['languageClassPrefix'] . $node->attrs->language\n                        : null,\n                ],\n                0,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/CodeBlockHighlight.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse DomainException;\nuse Highlight\\Highlighter;\nuse Tiptap\\Utils\\HTML;\n\nclass CodeBlockHighlight extends CodeBlock\n{\n    public function addOptions()\n    {\n        return [\n            'languageClassPrefix' => 'hljs ',\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        $code = $node->content[0]->text ?? '';\n\n        try {\n            $highlighter = new Highlighter();\n\n            if ($node->attrs->language ?? null) {\n                $result = $highlighter->highlight($node->attrs->language, $code);\n            } else {\n                $result = $highlighter->highlightAuto($code);\n            }\n\n            $mergedAttributes = HTML::mergeAttributes(\n                [\n                    'class' => $this->options['languageClassPrefix'] . $result->language,\n                ],\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n            );\n\n            $renderedAttributes = HTML::renderAttributes($mergedAttributes);\n\n            $content = \"<pre><code\" . $renderedAttributes . \">\";\n            $content .= $result->value;\n            $content .= \"</code></pre>\";\n        } catch (DomainException $exception) {\n            $mergedAttributes = HTML::mergeAttributes(\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n            );\n\n            $renderedAttributes = HTML::renderAttributes($mergedAttributes);\n\n            $content = \"<pre><code\" . $renderedAttributes . \">\";\n            $content .= htmlentities($code);\n            $content .= \"</code></pre>\";\n        }\n\n        return [\n            'content' => $content,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/CodeBlockShiki.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse DomainException;\nuse Exception;\nuse Highlight\\Highlighter;\nuse Spatie\\ShikiPhp\\Shiki;\nuse Tiptap\\Utils\\HTML;\n\nclass CodeBlockShiki extends CodeBlock\n{\n    public function addOptions()\n    {\n        return [\n            'languageClassPrefix' => 'language-',\n            'HTMLAttributes' => [],\n            'defaultLanguage' => 'html',\n            'theme' => 'nord',\n            'guessLanguage' => true,\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        $code = $node->content[0]->text ?? '';\n\n        // Language is set\n        if ($node->attrs->language === null) {\n            $language = $node->attrs->language;\n        }\n        // Auto-detect the language\n        elseif ($this->options['guessLanguage']) {\n            try {\n                $highlighter = new Highlighter();\n                $result = $highlighter->highlightAuto($code);\n                $language = $result->language;\n            } catch (Exception $exception) {\n                //\n            }\n        }\n\n        // Use the default language\n        if (! isset($language)) {\n            $language = $this->options['defaultLanguage'];\n        }\n\n        try {\n            $content = Shiki::highlight($code, $language, 'nord');\n        } catch (DomainException $exception) {\n            $mergedAttributes = HTML::mergeAttributes(\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n            );\n\n            $renderedAttributes = HTML::renderAttributes($mergedAttributes);\n\n            $content = \"<pre><code\" . $renderedAttributes . \">\";\n            $content .= htmlentities($code);\n            $content .= \"</code></pre>\";\n        }\n\n        return [\n            'content' => $content,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Details.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Details extends Node\n{\n    public static $name = 'details';\n\n    public function addOptions()\n    {\n        return [\n            'persist' => false,\n            'openClassName' => 'is-open',\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'details',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        if (! $this->options['persist']) {\n            return [];\n        }\n\n        return [\n            'open' => [\n                'default' => false,\n                'parseHTML' => fn ($DOMNode) => $DOMNode->hasAttribute('open'),\n                'renderHTML' => fn ($attributes) => $attributes->open ? ['open' => 'open'] : [],\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'details',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/DetailsContent.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass DetailsContent extends Node\n{\n    public static $name = 'detailsContent';\n\n    public function addOptions(): array\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML(): array\n    {\n        return [\n            [\n                'tag' => 'div[data-type]',\n                'getAttrs' => fn ($value): bool => (bool) $value == 'detailsContent',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = []): array\n    {\n        return [\n            'div',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes, ['data-type' => 'detailsContent']),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/DetailsSummary.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass DetailsSummary extends Node\n{\n    public static $name = 'detailsSummary';\n\n    public function addOptions(): array\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML(): array\n    {\n        return [\n            [\n                'tag' => 'summary',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = []): array\n    {\n        return [\n            'summary',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Document.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\n\nclass Document extends Node\n{\n    public static $name = 'doc';\n\n    public static $topNode = true;\n}\n"
  },
  {
    "path": "src/Nodes/HardBreak.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass HardBreak extends Node\n{\n    public static $name = 'hardBreak';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'br',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['br', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Heading.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Heading extends Node\n{\n    public static $name = 'heading';\n\n    public function addOptions()\n    {\n        return [\n            'levels' => [1, 2, 3, 4, 5, 6],\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return array_map(function ($level) {\n            return [\n                'tag' => \"h{$level}\",\n                'attrs' => [\n                    'level' => $level,\n                ],\n            ];\n        }, $this->options['levels']);\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        $hasLevel = in_array($node->attrs->level, $this->options['levels']);\n\n        $level = $hasLevel ?\n            $node->attrs->level :\n            $this->options['levels'][0];\n\n        return [\n            \"h{$level}\",\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/HorizontalRule.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass HorizontalRule extends Node\n{\n    public static $name = 'horizontalRule';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'hr',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['hr', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Image.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Image extends Node\n{\n    public static $name = 'image';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'img[src]',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'src' => [],\n            'alt' => [],\n            'title' => [],\n            'width' => [],\n            'height' => [],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['img', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/ListItem.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass ListItem extends Node\n{\n    public static $name = 'listItem';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'li',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['li', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Mention.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Mention extends Node\n{\n    public static $name = 'mention';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n            'renderLabel' => fn () => null,\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'span[data-type=\"' . self::$name . '\"]',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'id' => [\n                'parseHTML' => fn ($DOMNode) => $DOMNode->getAttribute('data-id') ?: null,\n                'renderHTML' => fn ($attributes) => ['data-id' => $attributes->id ?? null],\n            ],\n        ];\n    }\n\n    public function renderText($node)\n    {\n        return $this->options['renderLabel']($node);\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'span',\n            HTML::mergeAttributes(\n                ['data-type' => self::$name],\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n            ),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/OrderedList.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass OrderedList extends Node\n{\n    public static $name = 'orderedList';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'ol',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'start' => [\n                'parseHTML' => fn ($DOMNode) => (int) $DOMNode->getAttribute('start') ?: null,\n                'renderHTML' => fn ($attributes) => ($attributes->start ?? null) ? ['start' => $attributes->start] : null,\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['ol', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Paragraph.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Paragraph extends Node\n{\n    public static $name = 'paragraph';\n\n    public static $priority = 1000;\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'p',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['p', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Table.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass Table extends Node\n{\n    public static $name = 'table';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'table',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'table',\n            HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes),\n            ['tbody', 0],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/TableCell.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TableCell extends Node\n{\n    public static $name = 'tableCell';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'td',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'rowspan' => [\n                'parseHTML' => fn ($DOMNode) => intval($DOMNode->getAttribute('rowspan')) ?: null,\n            ],\n            'colspan' => [\n                'parseHTML' => fn ($DOMNode) => intval($DOMNode->getAttribute('colspan')) ?: null,\n            ],\n            'colwidth' => [\n                'parseHTML' => function ($DOMNode) {\n                    $colwidth = $DOMNode->getAttribute('data-colwidth');\n\n                    if (! $colwidth) {\n                        return null;\n                    }\n\n                    $widths = array_map(function ($w) {\n                        return intval($w);\n                    }, explode(',', $colwidth));\n\n                    return $widths;\n                },\n                'renderHTML' => function ($attributes) {\n                    if (! isset($attributes->colwidth)) {\n                        return null;\n                    }\n\n                    return [\n                        'data-colwidth' => join(',', $attributes->colwidth),\n                    ];\n                },\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'td',\n            HTML::mergeAttributes(\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n            ),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/TableHeader.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Utils\\HTML;\n\nclass TableHeader extends TableCell\n{\n    public static $name = 'tableHeader';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'th',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'th',\n            HTML::mergeAttributes(\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n            ),\n            0,\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/TableRow.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TableRow extends Node\n{\n    public static $name = 'tableRow';\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'tr',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['tr', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/TaskItem.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TaskItem extends Node\n{\n    public static $name = 'taskItem';\n\n    public static $priority = 1000;\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'checked' => [\n                'default' => false,\n                'renderHTML' => fn ($attributes) => [\n                    'data-checked' => $attributes->checked ?? null,\n                ],\n            ],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'li[data-type=\"' . self::$name . '\"]',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return [\n            'li',\n            HTML::mergeAttributes(\n                $this->options['HTMLAttributes'],\n                $HTMLAttributes,\n                ['data-type' => self::$name],\n            ),\n            [\n                'label',\n                [\n                    'input',\n                    [\n                        'type' => 'checkbox',\n                        'checked' => $node->attrs->checked ?? null\n                        ? 'checked'\n                        : null,\n                    ],\n                ],\n                ['span'],\n            ],\n            [\n                'div',\n                0,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/TaskList.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\nuse Tiptap\\Utils\\HTML;\n\nclass TaskList extends Node\n{\n    public static $name = 'taskList';\n\n    public static $priority = 1000;\n\n    public function addOptions()\n    {\n        return [\n            'HTMLAttributes' => [],\n        ];\n    }\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'ul[data-type=\"' . self::$name . '\"]',\n            ],\n        ];\n    }\n\n    public function renderHTML($node, $HTMLAttributes = [])\n    {\n        return ['ul', HTML::mergeAttributes(\n            $this->options['HTMLAttributes'],\n            $HTMLAttributes,\n            ['data-type' => self::$name],\n        ), 0];\n    }\n}\n"
  },
  {
    "path": "src/Nodes/Text.php",
    "content": "<?php\n\nnamespace Tiptap\\Nodes;\n\nuse Tiptap\\Core\\Node;\n\nclass Text extends Node\n{\n    public static $name = 'text';\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => '#text',\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "src/Utils/HTML.php",
    "content": "<?php\n\nnamespace Tiptap\\Utils;\n\nclass HTML\n{\n    /**\n     * Merge an associative array of attributes,\n     * and make sure to merge classes and inline styles.\n     */\n    public static function mergeAttributes()\n    {\n        $args = func_get_args();\n\n        $attributes = array_shift($args);\n\n        foreach ($args as $moreAttributes) {\n            foreach ($moreAttributes as $key => $value) {\n                // class=\"foo bar\"\n                if ($key === 'class') {\n                    $attributes['class'] = trim(($attributes['class'] ?? '') . ' ' . $value);\n\n                    continue;\n                }\n\n                // style=\"color: red;\"\n                if ($key === 'style') {\n                    $style = rtrim($attributes['style'] ?? '', '; ') . '; ' . rtrim($value ?? '', ';') . '; ';\n                    $attributes['style'] = ltrim(trim($style), '; ');\n\n                    continue;\n                }\n\n                $attributes[$key] = $value;\n            }\n        }\n\n        return $attributes;\n    }\n\n    /**\n     * Render an associative array of attributes\n     * as a HTML string.\n     */\n    public static function renderAttributes(array $attrs): string\n    {\n        // Make boolean values a string, so they can be rendered in HTML\n        $attrs = array_map(function ($attribute) {\n            if ($attribute === true) {\n                return 'true';\n            }\n\n            if ($attribute === false) {\n                return 'false';\n            }\n\n            return $attribute;\n        }, $attrs);\n\n        $attributes = [];\n\n        // class=\"custom\"\n        foreach (array_filter($attrs) as $name => $value) {\n            $escapedValue = htmlentities($value);\n\n            $attributes[] = \" {$name}=\\\"{$escapedValue}\\\"\";\n        }\n\n        return join($attributes);\n    }\n}\n"
  },
  {
    "path": "src/Utils/InlineStyle.php",
    "content": "<?php\n\nnamespace Tiptap\\Utils;\n\nuse Exception;\n\nclass InlineStyle\n{\n    /**\n     * @return string[]\n     *\n     * @psalm-return array<string, string>\n     */\n    public static function get($DOMNode): array\n    {\n        $results = [];\n\n        if (! method_exists($DOMNode, 'getAttribute')) {\n            return [];\n        }\n\n        $style = $DOMNode->getAttribute('style');\n\n        preg_match_all(\n            \"/([\\w-]+)\\s*:\\s*([^;]+)\\s*;?/\",\n            $style,\n            $matches,\n            PREG_SET_ORDER\n        );\n\n        foreach ($matches as $match) {\n            $results[$match[1]] = $match[2];\n        }\n\n        return $results;\n    }\n\n    public static function hasAttribute($DOMNode, $value): bool\n    {\n        $styles = self::get($DOMNode);\n\n        if (is_string($value)) {\n            return in_array($value, array_keys($styles));\n        }\n\n        if (is_array($value)) {\n            return array_diff($value, $styles) == [];\n        }\n\n        throw new Exception('Can’t compare inline styles to ' . json_encode($value));\n    }\n\n    public static function getAttribute($DOMNode, $attribute): ?string\n    {\n        return self::get($DOMNode)[$attribute] ?? null;\n    }\n}\n"
  },
  {
    "path": "src/Utils/Minify.php",
    "content": "<?php\n\nnamespace Tiptap\\Utils;\n\nclass Minify\n{\n    protected $_replacementHash;\n    protected $_placeholders = [];\n    protected $_html;\n\n    public function process($html): string\n    {\n        $this->_html = str_replace(\"\\r\\n\", \"\\n\", trim($html));\n\n        $hash = isset($_SERVER['REQUEST_TIME']) ? (string) $_SERVER['REQUEST_TIME'] : (string) time();\n        $this->_replacementHash = 'MINIFYHTML' . md5($hash);\n\n        // replace PREs with placeholders\n        $this->_html = preg_replace_callback('/\\\\s*<pre(\\\\b[^>]*?>[\\\\s\\\\S]*?<\\\\/pre>)\\\\s*/iu', [$this, '_removePreCB'], $this->_html);\n\n        // trim each line.\n        $this->_html = preg_replace('/^\\\\s+|\\\\s+$/mu', '', $this->_html);\n\n        // remove ws around block/undisplayed elements\n        $this->_html = preg_replace('/\\\\s+(<\\\\/?(?:area|article|aside|base(?:font)?|blockquote|body'\n            . '|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'\n            . '|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'\n            . '|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'\n            . '|ul|video)\\\\b[^>]*>)/iu', '$1', $this->_html);\n\n        // fill placeholders\n        $this->_html = str_replace(\n            array_keys($this->_placeholders),\n            array_values($this->_placeholders),\n            $this->_html\n        );\n\n        return $this->_html;\n    }\n\n    protected function _removePreCB($m): string\n    {\n        return $this->_reservePlace(\"<pre{$m[1]}\");\n    }\n\n    protected function _reservePlace($content): string\n    {\n        $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';\n        $this->_placeholders[$placeholder] = $content;\n\n        return $placeholder;\n    }\n}\n"
  },
  {
    "path": "tests/DOMParser/EmojiTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('emojis are transformed correctly', function () {\n    $html = \"<p>🔥</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"🔥\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/EmptyNodesTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('parsing must not fail on empty nodes', function () {\n    $html = '<p><img /></p><p><img /></p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [],\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/EmptyTextNodesTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('output_must_not_have_empty_text_nodes()', function () {\n    $html = \"<em><br />\\n</em>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'hardBreak',\n                'marks' => [\n                    [\n                        'type' => 'italic',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Extensions/ColorTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\Color;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('color is parsed correctly', function () {\n    $html = '<p><span style=\"color: red;\">red text</span></p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextStyle(),\n                new Color(),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'textStyle',\n                                'attrs' => [\n                                    'color' => 'red',\n                                ],\n                            ],\n                        ],\n                        'text' => 'red text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('color extension respects the types option', function () {\n    $html = '<h1 style=\"color: red;\">red heading</h1>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new Color([\n                'types' => ['heading'],\n            ]),\n        ],\n    ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 1,\n                    'color' => 'red',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'red heading',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Extensions/FontFamilyTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\DOMParser\\Extensions;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\FontFamily;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('font family is parsed correctly', function () {\n    $html = '<p><span style=\"font-family: Arial;\">Arial text</span></p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextStyle(),\n                new FontFamily(),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'textStyle',\n                                'attrs' => [\n                                    'fontFamily' => 'Arial',\n                                ],\n                            ],\n                        ],\n                        'text' => 'Arial text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('multiple font family values are parsed correctly', function () {\n    $html = '<p><span style=\"font-family: Helvetica Neue, Arial, \\'Times New Roman\\', &quot;Open Sans&quot;, sans-serif;\">Multiple fonts</span></p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextStyle(),\n                new FontFamily(),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'textStyle',\n                                'attrs' => [\n                                    'fontFamily' => 'Helvetica Neue, Arial, \\'Times New Roman\\', \"Open Sans\", sans-serif',\n                                ],\n                            ],\n                        ],\n                        'text' => 'Multiple fonts',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('font family extension respects the types option', function () {\n    $html = '<h1 style=\"font-family: Times New Roman;\">Times New Roman heading</h1>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new FontFamily([\n                'types' => ['heading'],\n            ]),\n        ],\n    ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 1,\n                    'fontFamily' => 'Times New Roman',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Times New Roman heading',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Extensions/TextAlignTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Extensions\\TextAlign;\n\ntest('text align is parsed correctly', function () {\n    $html = '<p style=\"text-align: center;\">Example Text</p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextAlign([\n                    'types' => ['paragraph'],\n                ]),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'attrs' => [\n                    'textAlign' => 'center',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('text align uses default value', function () {\n    $html = '<p>Example Text</p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextAlign([\n                    'types' => ['paragraph'],\n                ]),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'attrs' => [\n                    'textAlign' => 'left',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('default text align is configureable', function () {\n    $html = '<p>Example Text</p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextAlign([\n                    'types' => ['paragraph'],\n                    'defaultAlignment' => 'center',\n                ]),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'attrs' => [\n                    'textAlign' => 'center',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/KeepContentOfUnknownTagsTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('keeps content of unknown tags', function () {\n    $html = \"<p>Example <x-unknown-tag>Text</x-unknown-tag></p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"Example Text\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('keeps content of unknown tags even if it has known tags', function () {\n    $html = '<p>Example <x-unknown-tag><b>Text</b></x-unknown-tag></p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"Example \",\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => \"Text\",\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/BoldTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('b gets rendered correctly', function () {\n    $html = '<p><b>Example</b> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('strong gets rendered correctly', function () {\n    $html = '<p><strong>Example</strong> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('b with font weight normal is ignored', function () {\n    $html = '<p><b style=\"font-weight: normal;\">Example</b> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('span with font weight bold is parsed', function () {\n    $html = '<p><span style=\"font-weight: bold;\">Example</span> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('span with font weight 500 is parsed', function () {\n    $html = '<p><span style=\"font-weight: 500;\">Example</span> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/CodeTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('code gets rendered correctly', function () {\n    $html = '<p><code>Example Text</code></p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'code',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/CustomMarkTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\n\nclass CustomMark extends \\Tiptap\\Core\\Mark\n{\n    public static $name = 'custom';\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'span',\n            ],\n        ];\n    }\n\n    public function addAttributes()\n    {\n        return [\n            'foo' => [\n                'parseHTML' => fn ($DOMNode) => $DOMNode->getAttribute('data-foo') ?: null,\n            ],\n            'fruit' => [],\n        ];\n    }\n}\n\ntest('b and strong get rendered correctly', function () {\n    $html = '<p><span data-foo=\"bar\" fruit=\"banana\">Example</span> text</p>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new CustomMark,\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'custom',\n                                'attrs' => [\n                                    'foo' => 'bar',\n                                    'fruit' => 'banana',\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/HighlightTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('mark gets rendered correctly', function () {\n    $html = '<p><mark>Example</mark> Text</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new \\Tiptap\\Extensions\\StarterKit,\n            new \\Tiptap\\Marks\\Highlight,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'highlight',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('color is ignored by default', function () {\n    $html = '<p><mark>Example</mark> Text</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new \\Tiptap\\Extensions\\StarterKit,\n            new \\Tiptap\\Marks\\Highlight,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'highlight',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('color is parsed from data attribute', function () {\n    $html = '<p><mark data-color=\"red\">Example</mark> Text</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new \\Tiptap\\Extensions\\StarterKit,\n            new \\Tiptap\\Marks\\Highlight([\n                'multicolor' => true,\n            ]),\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'highlight',\n                                'attrs' => [\n                                    'color' => 'red',\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('color is parsed from the background color inline style', function () {\n    $html = '<p><mark style=\"background-color: #ffcc00\">Example</mark> Text</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new \\Tiptap\\Extensions\\StarterKit,\n            new \\Tiptap\\Marks\\Highlight([\n                'multicolor' => true,\n            ]),\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'highlight',\n                                'attrs' => [\n                                    'color' => '#ffcc00',\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/ItalicTest.php",
    "content": "<?php\n\n\nuse Tiptap\\Editor;\n\ntest('i gets rendered correctly', function () {\n    $html = '<p><i>Example</i> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('em gets rendered correctly', function () {\n    $html = '<p><em>Example</em> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('i with font style normal is ignored', function () {\n    $html = '<p><i style=\"font-style: normal;\">Example</i> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('span with font style italic is parsed', function () {\n    $html = '<p><span style=\"font-style: italic;\">Example</span> Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/LinkTest.php",
    "content": "<?php\n\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('link gets rendered correctly', function () {\n    $html = '<a href=\"https://tiptap.dev\">Example Link</a>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('link_mark_has_support_for_rel', function () {\n    $html = '<a href=\"https://tiptap.dev\" rel=\"noopener\">Example Link</a>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'rel' => 'noopener',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('link_mark_has_support_for_class', function () {\n    $html = '<a class=\"tiptap\" href=\"https://tiptap.dev\">Example Link</a>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'class' => 'tiptap',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('link_mark_has_support_for_target', function () {\n    $html = '<a href=\"https://tiptap.dev\" target=\"_blank\">Example Link</a>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'target' => '_blank',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\nfunction getValidUrls()\n{\n    return [\n        'https://example.com',\n        'http://example.com',\n        '/same-site/index.html',\n        '../relative.html',\n        'mailto:info@example.com',\n        'ftp://info@example.com',\n    ];\n}\n\nfunction getInvalidUrls()\n{\n    // Copied from https://github.com/ueberdosis/tiptap/blob/next/tests/cypress/integration/extensions/link.spec.ts\n\n    return [\n        // A standard JavaScript protocol\n        \"javascript:alert(window.origin)\",\n    \n        // The protocol is not case sensitive\n        \"jAvAsCrIpT:alert(window.origin)\",\n    \n        // Characters \\x01-\\x20 are allowed before the protocol\n        \"\\x00javascript:alert(window.origin)\",\n        \"\\x01javascript:alert(window.origin)\",\n        \"\\x02javascript:alert(window.origin)\",\n        \"\\x03javascript:alert(window.origin)\",\n        \"\\x04javascript:alert(window.origin)\",\n        \"\\x05javascript:alert(window.origin)\",\n        \"\\x06javascript:alert(window.origin)\",\n        \"\\x07javascript:alert(window.origin)\",\n        \"\\x08javascript:alert(window.origin)\",\n        \"\\x09javascript:alert(window.origin)\",\n        \"\\x0ajavascript:alert(window.origin)\",\n        \"\\x0bjavascript:alert(window.origin)\",\n        \"\\x0cjavascript:alert(window.origin)\",\n        \"\\x0djavascript:alert(window.origin)\",\n        \"\\x0ejavascript:alert(window.origin)\",\n        \"\\x0fjavascript:alert(window.origin)\",\n        \"\\x10javascript:alert(window.origin)\",\n        \"\\x11javascript:alert(window.origin)\",\n        \"\\x12javascript:alert(window.origin)\",\n        \"\\x13javascript:alert(window.origin)\",\n        \"\\x14javascript:alert(window.origin)\",\n        \"\\x15javascript:alert(window.origin)\",\n        \"\\x16javascript:alert(window.origin)\",\n        \"\\x17javascript:alert(window.origin)\",\n        \"\\x18javascript:alert(window.origin)\",\n        \"\\x19javascript:alert(window.origin)\",\n        \"\\x1ajavascript:alert(window.origin)\",\n        \"\\x1bjavascript:alert(window.origin)\",\n        \"\\x1cjavascript:alert(window.origin)\",\n        \"\\x1djavascript:alert(window.origin)\",\n        \"\\x1ejavascript:alert(window.origin)\",\n        \"\\x1fjavascript:alert(window.origin)\",\n    \n        // Characters \\x09,\\x0a,\\x0d are allowed inside the protocol\n        \"java\\x09script:alert(window.origin)\",\n        \"java\\x0ascript:alert(window.origin)\",\n        \"java\\x0dscript:alert(window.origin)\",\n    \n        // Characters \\x09,\\x0a,\\x0d are allowed after protocol name before the colon\n        \"javascript\\x09:alert(window.origin)\",\n        \"javascript\\x0a:alert(window.origin)\",\n        \"javascript\\x0d:alert(window.origin)\",\n    ];\n}\n\nfunction getJsonContent($url)\n{\n    return [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Click me',\n                        'marks' => [\n                            [\n                                'type' => 'link',\n                                'attrs' => [\n                                    'href' => $url,\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n}\n\nfunction getHtmlContent($url)\n{\n    return '<p><a href=\"' . $url . '\">Click me</a></p>';\n}\n\ntest('link_mark_does_output_href_tag_for_valid_JSON_schemas', function () {\n    foreach (getValidUrls() as $url) {\n        $content = getJsonContent($url);\n\n        $editor = (new Editor([\n            'content' => $content,\n            'extensions' => [\n                new StarterKit,\n                new Link,\n            ],\n        ]));\n\n        $result = $editor->getHTML();\n        expect($result)->toContain($url);\n    }\n});\n\ntest('link_mark_does_not_output_href_tag_for_valid_JSON_schemas', function () {\n    foreach (getInvalidUrls() as $url) {\n        $content = getJsonContent($url);\n\n        $editor = (new Editor([\n            'content' => $content,\n            'extensions' => [\n                new StarterKit,\n                new Link,\n            ],\n        ]));\n\n        $result = $editor->getHTML();\n        expect($result)->not->toContain($url);\n    }\n});\n\ntest('link_mark_does_output_href_tag_for_valid_HTML_schemas', function () {\n    foreach (getValidUrls() as $url) {\n        $content = getHtmlContent($url);\n\n        $editor = (new Editor([\n            'content' => $content,\n            'extensions' => [\n                new StarterKit,\n                new Link,\n            ],\n        ]));\n\n        $result = $editor->getHTML();\n        expect($result)->toContain($url);\n    }\n});\n\ntest('link_mark_does_not_output_href_tag_for_valid_HTML_schemas', function () {\n    foreach (getInvalidUrls() as $url) {\n        $content = getHtmlContent($url);\n\n        $editor = (new Editor([\n            'content' => $content,\n            'extensions' => [\n                new StarterKit,\n                new Link,\n            ],\n        ]));\n\n        $result = $editor->getJson();\n        expect($result)->not->toContain($url);\n    }\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/NestedMarksTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('nested marks are rendered correctly', function () {\n    $html = '<strong>only bold <em>bold and italic</em> only bold</strong>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'only bold ',\n                'marks' => [\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'text',\n                'text' => 'bold and italic',\n                'marks' => [\n                    [\n                        'type' => 'bold',\n                    ],\n                    [\n                        'type' => 'italic',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'text',\n                'text' => ' only bold',\n                'marks' => [\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/StrikeTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('strike and s del get rendered correctly', function () {\n    $html = '<p><strike>Example text using strike</strike> and <s>example text using s</s> and <del>example text using del</del></p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example text using strike',\n                        'marks' => [\n                            [\n                                'type' => 'strike',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' and ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'example text using s',\n                        'marks' => [\n                            [\n                                'type' => 'strike',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' and ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'example text using del',\n                        'marks' => [\n                            [\n                                'type' => 'strike',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('inline style is parsed correctly', function () {\n    $html = '<p><span style=\"text-decoration: line-through\">Example Text</span></p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'strike',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/SubscriptTest.php",
    "content": "<?php\n\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Subscript;\n\ntest('subscript gets rendered correctly', function () {\n    $html = '<p><sub>Example Text</sub></p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Subscript,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'subscript',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('inline style is parsed correctly', function () {\n    $html = '<p><span style=\"vertical-align: sub;\">Example Text</span></p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Subscript,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'subscript',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/SuperscriptTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Superscript;\n\ntest('superscript gets rendered correctly', function () {\n    $html = '<p><sup>Example Text</sup></p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Superscript,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'superscript',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('inline style is parsed correctly', function () {\n    $html = '<p><span style=\"vertical-align: super;\">Example Text</span></p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Superscript,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'superscript',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/TextStyleTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('span gets rendered correctly', function () {\n    $html = '<p><span style=\"color: red\">Example</span> Text</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new TextStyle,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'textStyle',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('span without inline style is ignored', function () {\n    $html = '<p><span>Example</span> Text</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new TextStyle,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Marks/UnderlineTest.php",
    "content": "<?php\n\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Underline;\n\ntest('underline gets rendered correctly', function () {\n    $html = '<p><u>Example Text</u></p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Underline,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'underline',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('inline style is parsed correctly', function () {\n    $html = '<p><span style=\"text-decoration: underline;\">Example Text</span></p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Underline,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'underline',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/MarksInNodesTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('paragraph with marks gets rendered correctly', function () {\n    $html = \"<p>Example <strong><em>Text</em></strong>.</p>\";\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Text',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => '.',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('complex markup gets rendered correctly', function () {\n    $html = '\n        <h1>Headline 1</h1>\n        <p>\n            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>.\n        </p>\n    ';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => '1',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Headline 1',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Some text. ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Bold Text',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => '. ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Italic Text',\n                        'marks' => [\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => '. ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Bold and italic Text',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => '. Here is a ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Link',\n                        'marks' => [\n                            [\n                                'type' => 'link',\n                                'attrs' => [\n                                    'href' => 'https://tiptap.dev',\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => '.',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('multiple lists gets rendered correctly', function () {\n    $html = '\n        <h2>Headline 2</h2>\n        <ol>\n            <li><p>ordered list item</p></li>\n            <li><p>ordered list item</p></li>\n            <li><p>ordered list item</p></li>\n        </ol>\n        <ul>\n            <li><p>unordered list item</p></li>\n            <li><p>unordered list item with <a href=\"https://tiptap.dev\"><strong>link</strong></a></p></li>\n            <li><p>unordered list item</p></li>\n        </ul>\n        <p>Some Text.</p>\n    ';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' =>\n        [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => '2',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Headline 2',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'orderedList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'ordered list item',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'ordered list item',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'ordered list item',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'unordered list item',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'unordered list item with ',\n                                    ],\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'link',\n                                        'marks' => [\n                                            [\n                                                'type' => 'link',\n                                                'attrs' => [\n                                                    'href' => 'https://tiptap.dev',\n                                                ],\n                                            ],\n                                            [\n                                                'type' => 'bold',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'unordered list item',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Some Text.',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/MultipleMarksTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('multiple marks are rendered correctly', function () {\n    $html = '<p><strong><em>Example Text</em></strong></p>';\n\n    $result = (new Editor)->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/BlockquoteTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('blockquote gets rendered correctly', function () {\n    $html = '<blockquote><p>Paragraph</p></blockquote>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'blockquote',\n                'content' => [\n                    [\n                        'type' => 'paragraph',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Paragraph',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/BulletListTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('bulletList gets rendered correctly', function () {\n    $html = '<ul><li><p>Example</p></li><li><p>Text</p></li></ul>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('bulletlistItem with text only gets wrapped in paragraph', function () {\n    $html = '<ul><li><p>Example</p></li><li><p>Text <em>Test</em></p></li></ul>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Text ',\n                                    ],\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Test',\n                                        'marks' => [\n                                            [\n                                                'type' => 'italic',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('listItems with space get rendered correctly', function () {\n    $html = '<ul><li><p> </p></li></ul>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('listItems content get rendered correctly', function () {\n    $html = '<ul><li><p>Tiptap</p></li></ul>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Tiptap',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/CodeBlockTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('codeBlock gets rendered correctly', function () {\n    $html = '<pre><code>Example Text</code></pre>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('codeBlock with language gets rendered correctly', function () {\n    $html = '<pre><code class=\"language-css\">body { display: none }</code></pre>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'attrs' => [\n                    'language' => 'css',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'body { display: none }',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('language class prefix is configureable', function () {\n    $html = '<pre><code class=\"custom-language-prefix-css\">body { display: none }</code></pre>';\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit([\n                    'codeBlock' => [\n                        'languageClassPrefix' => 'custom-language-prefix-',\n                    ],\n                ]),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'attrs' => [\n                    'language' => 'css',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'body { display: none }',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('code block and inline code are rendered correctly', function () {\n    $html = '<p><code>Example Text</code></p><pre><code>body { display: none }</code></pre>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'code',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n            [\n                'type' => 'codeBlock',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'body { display: none }',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('it handles code blocks without a code tag', function () {\n    $html = '<pre>body { display: none }</pre>';\n\n    $result = (new Editor)->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'body { display: none }',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/DetailsTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Details;\nuse Tiptap\\Nodes\\DetailsContent;\nuse Tiptap\\Nodes\\DetailsSummary;\n\ntest('details gets rendered correctly', function () {\n    $html = '<details><summary>Summary</summary><div data-type=\"detailsContent\"><p>Content</p></div></details>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details,\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('details open attribute is ignored by default', function () {\n    $html = '<details open><summary>Summary</summary><div data-type=\"detailsContent\"><p>Content</p></div></details>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details,\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('details open attribute is parsed when persist is enabled', function () {\n    $html = '<details open><summary>Summary</summary><div data-type=\"detailsContent\"><p>Content</p></div></details>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details(['persist' => true]),\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'attrs' => [\n                    'open' => true,\n                ],\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('details without open attribute sets open to false when persist is enabled', function () {\n    $html = '<details><summary>Summary</summary><div data-type=\"detailsContent\"><p>Content</p></div></details>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details(['persist' => true]),\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'attrs' => [\n                    'open' => false,\n                ],\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/HardBreakTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('break gets rendered correctly', function () {\n    $html = '<p>Hard <br />Break</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Hard ',\n                    ],\n                    [\n                        'type' => 'hardBreak',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Break',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\n\ntest('multiple nodes get rendered correctly', function () {\n    $html = '<p>Example</p><p>Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/HeadingTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('h1 is rendered correctly', function () {\n    $html = '<h1>Example Text</h1>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 1,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('h2 is rendered correctly', function () {\n    $html = '<h2>Example Text</h2>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 2,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('h7 is ignored', function () {\n    $html = '<h7>Example Text</h7>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/HighPriorityParagraph.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\DOMParser\\Nodes;\n\nuse Tiptap\\Core\\Node;\n\nclass HighPriorityParagraph extends Node\n{\n    public static $name = 'highPriorityParagraph';\n\n    public function parseHTML()\n    {\n        return [\n            [\n                'tag' => 'p',\n                'priority' => 60,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/DOMParser/Nodes/HorizontalRuleTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('hr gets rendered correctly', function () {\n    $html = '<p>Horizontal</p><hr /><p>Rule</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Horizontal',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'horizontalRule',\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Rule',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/ImageTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Image;\n\ntest('image gets rendered correctly', function () {\n    $html = '<img src=\"https://example.com/eggs.png\" alt=\"The Finished Dish\" title=\"Eggs in a dish\" />';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Image,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'image',\n                'attrs' => [\n                    'alt' => 'The Finished Dish',\n                    'src' => 'https://example.com/eggs.png',\n                    'title' => 'Eggs in a dish',\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('image gets rendered correctly when title is missing', function () {\n    $html = '<img src=\"https://example.com/eggs.png\" alt=\"The Finished Dish\" />';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Image,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'image',\n                'attrs' => [\n                    'alt' => 'The Finished Dish',\n                    'src' => 'https://example.com/eggs.png',\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('image gets rendered correctly when alt is missing', function () {\n    $html = '<img src=\"https://example.com/eggs.png\" title=\"Eggs in a dish\" />';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Image,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'image',\n                'attrs' => [\n                    'src' => 'https://example.com/eggs.png',\n                    'title' => 'Eggs in a dish',\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/MentionTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Mention;\n\ntest('user mention gets rendered correctly', function () {\n    $html = '<p>Hey <span data-type=\"mention\" data-id=\"123\"></span>, was geht?</p>';\n\n    $output = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Mention,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($output)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Hey ',\n                    ],\n                    [\n                        'type' => 'mention',\n                        'attrs' => [\n                            'id' => 123,\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ', was geht?',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/OrderedListTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('orderedList gets rendered correctly', function () {\n    $html = '<ol><li><p>Example</p></li><li><p>Text</p></li></ol>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'orderedList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('orderedList has correct offset', function () {\n    $html = '<ol start=\"3\"><li><p>Example</p></li><li><p>Text</p></li></ol>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'orderedList',\n                'attrs' => [\n                    'start' => 3,\n                ],\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/ParagraphTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('simple text gets rendered correctly', function () {\n    $html = '<p>Example Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\n\ntest('multiple nodes get rendered correctly', function () {\n    $html = '<p>Example</p><p>Text</p>';\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/Nodes/TableTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\JSONOutput\\Nodes;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Table;\nuse Tiptap\\Nodes\\TableCell;\nuse Tiptap\\Nodes\\TableHeader;\nuse Tiptap\\Nodes\\TableRow;\n\ntest('table gets rendered correctly', function () {\n    $html =\n        '<table><tbody>' .\n            '<tr>' .\n                '<th><p>text in header cell</p></th>' .\n                '<th colspan=\"2\" data-colwidth=\"100,0\"><p>text in header cell with colspan 2</p></th>' .\n            '</tr>' .\n            '<tr>' .\n                '<td rowspan=\"2\"><p>paragraph 1 in cell with rowspan 2</p><p>paragraph 2 in cell with rowspan 2</p></td>' .\n                '<td><p>foo</p></td>' .\n                '<td><p>bar</p></td>' .\n            '</tr>' .\n            '<tr>' .\n                '<td><p>foo</p></td>' .\n                '<td><p>bar</p></td>' .\n            '</tr>' .\n        '</tbody></table>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Table,\n            new TableRow,\n            new TableCell,\n            new TableHeader,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'table',\n                'content' => [\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableHeader',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'text in header cell',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableHeader',\n                                'attrs' => [\n                                    'colspan' => 2,\n                                    'colwidth' => [\n                                        100,\n                                        0,\n                                    ],\n                                ],\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'text in header cell with colspan 2',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableCell',\n                                'attrs' => [\n                                    'rowspan' => 2,\n                                ],\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'paragraph 1 in cell with rowspan 2',\n                                            ],\n                                        ],\n                                    ],\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'paragraph 2 in cell with rowspan 2',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'foo',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'bar',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'foo',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'bar',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/ParseHTMLPriorityTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Tests\\DOMParser\\Nodes\\HighPriorityParagraph;\n\ntest('priority for parsing HTML', function () {\n    $html = '<p>Example</p>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new HighPriorityParagraph,\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'highPriorityParagraph',\n                'content' => [\n                    ['type' => 'text', 'text' => 'Example'],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/SpecialCharacterTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('emojis are transformed correctly()', function () {\n    $html = \"<p>🔥</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"🔥\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('extended emojis are transformed correctly()', function () {\n    $html = \"<p>👩‍👩‍👦</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"👩‍👩‍👦\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('umlauts are transformed correctly()', function () {\n    $html = \"<p>äöüÄÖÜß</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"äöüÄÖÜß\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('html entities are transformed correctly()', function () {\n    $html = \"<p>&lt;</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"<\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/TaskListTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\TaskItem;\nuse Tiptap\\Nodes\\TaskList;\n\ntest('task list gets parsed correctly', function () {\n    $html = '<ul data-type=\"taskList\"><li data-type=\"taskItem\"><p>Example Text</p></li></ul>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new TaskList(),\n            new TaskItem(),\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'taskList',\n                'content' => [\n                    [\n                        'type' => 'taskItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('bullet lists are still parsed correctly', function () {\n    $html = '<ul><li><p>Example Text</p></li></ul>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new TaskList(),\n            new TaskItem(),\n        ],\n    ]))->setContent($html)->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMParser/WhitespaceTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('whitespace at the beginning is stripped', function () {\n    $html = \"<p>\\nExample\\n Text</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"Example\\nText\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('whitespace in codeBlocks is ignored', function () {\n    $html = \"<p>\\n\" .\n            \"    Example Text\\n\" .\n            \"</p>\\n\" .\n            \"<pre><code>\\n\" .\n            \"Line of Code\\n\" .\n            \"    Line of Code 2\\n\" .\n            \"Line of Code</code></pre>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'codeBlock',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => \"Line of Code\\n    Line of Code 2\\nLine of Code\",\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/DOMSerializer/ExampleJsonTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('example json gets rendered correctly', function () {\n    $document = '{\n        \"type\":\"doc\",\n        \"content\":[\n           {\n              \"type\":\"heading\",\n              \"attrs\":{\n                 \"level\":2\n              },\n              \"content\":[\n                 {\n                    \"type\":\"text\",\n                    \"text\":\"Export HTML or JSON\"\n                 }\n              ]\n           },\n           {\n              \"type\":\"paragraph\",\n              \"content\":[\n                 {\n                    \"type\":\"text\",\n                    \"text\":\"You are able to export your data as \"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"marks\":[\n                       {\n                          \"type\":\"code\"\n                       }\n                    ],\n                    \"text\":\"HTML\"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"text\":\" or \"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"marks\":[\n                       {\n                          \"type\":\"code\"\n                       }\n                    ],\n                    \"text\":\"JSON\"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"text\":\". To pass \"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"marks\":[\n                       {\n                          \"type\":\"code\"\n                       }\n                    ],\n                    \"text\":\"HTML\"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"text\":\" to the editor use the \"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"marks\":[\n                       {\n                          \"type\":\"code\"\n                       }\n                    ],\n                    \"text\":\"content\"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"text\":\" slot. To pass \"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"marks\":[\n                       {\n                          \"type\":\"code\"\n                       }\n                    ],\n                    \"text\":\"JSON\"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"text\":\" to the editor use the \"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"marks\":[\n                       {\n                          \"type\":\"code\"\n                       }\n                    ],\n                    \"text\":\"doc\"\n                 },\n                 {\n                    \"type\":\"text\",\n                    \"text\":\" prop.\"\n                 }\n              ]\n           }\n        ]\n     }';\n\n    $result = (new Editor)->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<h2>Export HTML or JSON</h2><p>You are able to export your data as <code>HTML</code> or <code>JSON</code>. To pass <code>HTML</code> to the editor use the <code>content</code> slot. To pass <code>JSON</code> to the editor use the <code>doc</code> prop.</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Extensions/ColorTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\Color;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('color is rendered correctly', function () {\n    $json = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'textStyle',\n                                'attrs' => [\n                                    'color' => 'red',\n                                ],\n                            ],\n                        ],\n                        'text' => 'red text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new TextStyle(),\n            new Color(),\n        ],\n    ]))\n        ->setContent($json)\n        ->getHTML();\n\n    expect($result)->toEqual('<p><span style=\"color: red;\">red text</span></p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Extensions/FontFamilyTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\DOMSerializer\\Extensions;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\FontFamily;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('font family is rendered correctly', function () {\n    $json = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'textStyle',\n                                'attrs' => [\n                                    'fontFamily' => 'Helvetica, Arial, sans-serif',\n                                ],\n                            ],\n                        ],\n                        'text' => 'custom font text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new TextStyle(),\n            new FontFamily(),\n        ],\n    ]))\n        ->setContent($json)\n        ->getHTML();\n\n    expect($result)->toEqual('<p><span style=\"font-family: Helvetica, Arial, sans-serif;\">custom font text</span></p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Extensions/TextAlignTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Extensions\\TextAlign;\n\ntest('text align is rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'attrs' => [\n                    'textAlign' => 'center',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextAlign([\n                    'types' => ['paragraph'],\n                ]),\n            ],\n        ]))\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p style=\"text-align: center;\">Example Text</p>');\n});\n\ntest('default text align isn’t rendered', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'attrs' => [\n                    'textAlign' => 'left',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextAlign([\n                    'types' => ['paragraph'],\n                ]),\n            ],\n        ]))\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p>Example Text</p>');\n});\n\ntest('default text align is configureable', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'attrs' => [\n                    'textAlign' => 'center',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result =\n        (new Editor([\n            'extensions' => [\n                new StarterKit,\n                new TextAlign([\n                    'types' => ['paragraph'],\n                    'defaultAlignment' => 'center',\n                ]),\n            ],\n        ]))\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p>Example Text</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/InputTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Image;\n\ntest('array gets rendered to html', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('Example Text');\n});\n\ntest('json gets rendered to html', function () {\n    $document = json_encode([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n            ],\n        ],\n    ]);\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('Example Text');\n});\n\ntest('encoding is correct', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Äffchen',\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('Äffchen');\n});\n\ntest('quotes are not escaped', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => '\"Example Text\"',\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('&quot;Example Text&quot;');\n});\n\ntest('escaped attribute values', function () {\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Image,\n        ],\n    ]))->setContent([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'image',\n                'attrs' => [\n                    'src' => '\"><script type=\"text/javascript\">alert(1);</script><img src=\"',\n                ],\n            ],\n        ],\n    ])->getHTML();\n\n    expect($result)->toEqual('<img src=\"&quot;&gt;&lt;script type=&quot;text/javascript&quot;&gt;alert(1);&lt;/script&gt;&lt;img src=&quot;\">');\n});\n\ntest('reasonable attribute names', function () {\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Image,\n        ],\n    ]))->setContent([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'image',\n                'attrs' => [\n                    'onerror' => 'alert(1)',\n                    'src' => '',\n                ],\n            ],\n        ],\n    ])->getHTML();\n\n    expect($result)->toEqual('<img>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/BoldTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('bold mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<strong>Example Text</strong>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/CodeTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('code mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'code',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<code>Example Text</code>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/HighlightTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('mark doesn’t allow specific colors by default', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'highlight',\n                                'attrs' => [\n                                    'color' => 'red',\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new \\Tiptap\\Extensions\\StarterKit,\n            new \\Tiptap\\Marks\\Highlight,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<p><mark>Example</mark> Text</p>');\n});\n\ntest('mark allows specific colors when configured', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'highlight',\n                                'attrs' => [\n                                    'color' => 'red',\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new \\Tiptap\\Extensions\\StarterKit,\n            new \\Tiptap\\Marks\\Highlight([\n                'multicolor' => true,\n            ]),\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<p><mark data-color=\"red\" style=\"background-color: red;\">Example</mark> Text</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/ItalicTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('italic mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'italic',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<em>Example Text</em>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/LinkTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('link mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://tiptap.dev\">Example Link</a>');\n});\n\ntest('link mark has support for rel', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'rel' => 'noopener',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_blank\" rel=\"noopener\" href=\"https://tiptap.dev\">Example Link</a>');\n});\n\ntest('link mark has support for class', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'rel' => 'noopener',\n                            'class' => 'tiptap',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_blank\" rel=\"noopener\" href=\"https://tiptap.dev\" class=\"tiptap\">Example Link</a>');\n});\n\ntest('link mark has support for target', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'target' => '_self',\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_self\" rel=\"noopener noreferrer nofollow\" href=\"https://tiptap.dev\">Example Link</a>');\n});\n\ntest('link with marks generates clean output', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://example.com',\n                        ],\n                    ],\n                ],\n                'text' => 'Example ',\n            ],\n            [\n                'type' => 'text',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://example.com',\n                        ],\n                    ],\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n                'text' => 'Link',\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://example.com\">Example <strong>Link</strong></a>');\n});\n\ntest('link with marks inside node generates clean output', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'link',\n                                'attrs' => [\n                                    'href' => 'https://example.com',\n                                ],\n                            ],\n                        ],\n                        'text' => 'Example ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'marks' => [\n                            [\n                                'type' => 'link',\n                                'attrs' => [\n                                    'href' => 'https://example.com',\n                                ],\n                            ],\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                        'text' => 'Link',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<p><a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://example.com\">Example <strong>Link</strong></a></p>');\n});\n\ntest('link mark can disable rel', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'rel' => null,\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_blank\" href=\"https://tiptap.dev\">Example Link</a>');\n});\n\ntest('link mark can disable target', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                            'target' => null,\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a rel=\"noopener noreferrer nofollow\" href=\"https://tiptap.dev\">Example Link</a>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/StrikeTest.php",
    "content": "<?php\n\n\nuse Tiptap\\Editor;\n\ntest('strike gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'strike',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<s>Example Text</s>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/SubscriptTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\Marks;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Subscript;\n\ntest('subscript mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'subscript',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Subscript,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<sub>Example Text</sub>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/SuperscriptTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\Marks;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Superscript;\n\ntest('superscript mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'superscript',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Superscript,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<sup>Example Text</sup>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Marks/UnderlineTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\Marks;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Underline;\n\ntest('underline mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => 'Example Text',\n                'marks' => [\n                    [\n                        'type' => 'underline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Underline,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<u>Example Text</u>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/MultipleMarksTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\HTMLOutput\\Mix;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\TextStyle;\n\ntest('multiple marks get rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                            [\n                                'type' => 'italic',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)->setContent($document)->getHTML();\n    expect($result)->toEqual('<p><strong><em>Example Text</em></strong></p>');\n});\n\n\ntest('multiple marks get rendered correctly, with additional mark at the first node', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'marks' => [\n                    [\n                        'type' => 'italic',\n                    ],\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n                'text' => 'lorem ',\n            ],\n            [\n                'type' => 'text',\n                'marks' => [\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n                'text' => 'ipsum',\n            ],\n        ],\n    ];\n    $result = (new Editor)->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<em><strong>lorem </strong></em><strong>ipsum</strong>');\n});\n\n\ntest('multiple marks get rendered correctly, with additional mark at the last node', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'marks' => [\n                    [\n                        'type' => 'italic',\n                    ],\n                ],\n                'text' => 'lorem ',\n            ],\n            [\n                'type' => 'text',\n                'marks' => [\n                    [\n                        'type' => 'italic',\n                    ],\n                    [\n                        'type' => 'bold',\n                    ],\n                ],\n                'text' => 'ipsum',\n            ],\n        ],\n    ];\n    $result = (new Editor)->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<em>lorem <strong>ipsum</strong></em>');\n});\n\n\ntest('multiple marks get rendered correctly, when overlapping marks exist', function () {\n    $document = [\n        \"type\" => \"doc\",\n        \"content\" => [\n            [\n                \"type\" => \"paragraph\",\n                \"content\" => [\n                    [\n                        \"type\" => \"text\",\n                        \"marks\" => [\n                            [\n                                \"type\" => \"bold\",\n                            ],\n                        ],\n                        \"text\" => \"lorem \",\n                    ],\n                    [\n                        \"type\" => \"text\",\n                        \"marks\" => [\n                            [\n                                \"type\" => \"bold\",\n                            ],\n                            [\n                                \"type\" => \"italic\",\n                            ],\n                        ],\n                        \"text\" => \"ipsum\",\n                    ],\n                    [\n                        \"type\" => \"text\",\n                        \"marks\" => [\n                            [\n                                \"type\" => \"italic\",\n                            ],\n                        ],\n                        \"text\" => \" dolor\",\n                    ],\n                    [\n                        \"type\" => \"text\",\n                        \"text\" => \" sit\",\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p><strong>lorem <em>ipsum</em></strong><em> dolor</em> sit</p>');\n});\n\n\ntest('multiple marks get rendered correctly, when overlapping passage with multiple marks exist', function () {\n    $document = [\n        \"type\" => \"doc\",\n        \"content\" => [\n            [\n                \"type\" => \"paragraph\",\n                \"content\" => [\n                    [\n                        \"type\" => \"text\",\n                        \"marks\" => [\n                            [\n                                \"type\" => \"bold\",\n                            ],\n                            [\n                                \"type\" => \"strike\",\n                            ],\n                        ],\n                        \"text\" => \"lorem \",\n                    ],\n                    [\n                        \"type\" => \"text\",\n                        \"marks\" => [\n                            [\n                                \"type\" => \"italic\",\n                            ],\n                            [\n                                \"type\" => \"bold\",\n                            ],\n                            [\n                                \"type\" => \"strike\",\n                            ],\n                        ],\n                        \"text\" => \"ipsum\",\n                    ],\n                    [\n                        \"type\" => \"text\",\n                        \"marks\" => [\n                            [\n                                \"type\" => \"strike\",\n                            ],\n                            [\n                                \"type\" => \"italic\",\n                            ],\n                        ],\n                        \"text\" => \" dolor\",\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p><strong><s>lorem <em>ipsum</em></s></strong><s><em> dolor</em></s></p>');\n});\n\ntest('renders duplicate mark types as nested elements', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                            [\n                                'type' => 'textStyle',\n                            ],\n                            [\n                                'type' => 'textStyle',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ' Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new TextStyle,\n        ],\n    ]))\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p><strong><span><span>Example</span></span></strong> Text</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/BlockquoteTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('blockquote node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'blockquote',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Quote',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<blockquote>Example Quote</blockquote>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/BulletListTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('bulletList node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'first list item',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<ul><li>first list item</li></ul>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/CodeBlockHighlightTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\CodeBlockHighlight;\n\ntest('codeBlockHighlight adds syntax highlighting to code blocks', function () {\n    $html = '<pre><code>body { display: none }</code></pre>';\n\n    $result = (new Editor([\n            'extensions' => [\n                new StarterKit([\n                    'codeBlock' => false,\n                ]),\n                new CodeBlockHighlight(),\n            ],\n        ]))\n        ->setContent($html)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code class=\"hljs css\"><span class=\"hljs-selector-tag\">body</span> { <span class=\"hljs-attribute\">display</span>: none }</code></pre>');\n});\n\ntest('codeBlockHighlight uses the specified language', function () {\n    $html = '<pre><code class=\"hljs php\">&lt;?php phpinfo()</code></pre>';\n\n    $result = (new Editor([\n            'extensions' => [\n                new StarterKit([\n                    'codeBlock' => false,\n                ]),\n                new CodeBlockHighlight(),\n            ],\n        ]))\n        ->setContent($html)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code class=\"hljs php\"><span class=\"hljs-meta\">&lt;?php</span> phpinfo()</code></pre>');\n});\n\ntest('codeBlockHighlight uses the configured languageClassPrefix', function () {\n    $html = '<pre><code class=\"foo php\">&lt;?php phpinfo()</code></pre>';\n\n    $result = (new Editor([\n            'extensions' => [\n                new StarterKit([\n                    'codeBlock' => false,\n                ]),\n                new CodeBlockHighlight([\n                    'languageClassPrefix' => 'foo ',\n                ]),\n            ],\n        ]))\n        ->setContent($html)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code class=\"foo php\"><span class=\"hljs-meta\">&lt;?php</span> phpinfo()</code></pre>');\n});\n\ntest('codeBlockHighlight falls back to just a pre and code tag', function () {\n    $html = '<pre><code class=\"WRONG PREFIX php\">&lt;?php phpinfo()</code></pre>';\n\n    $result = (new Editor([\n            'extensions' => [\n                new StarterKit([\n                    'codeBlock' => false,\n                ]),\n                new CodeBlockHighlight(),\n            ],\n        ]))\n        ->setContent($html)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code>&lt;?php phpinfo()</code></pre>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/CodeBlockShikiTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\CodeBlockShiki;\n\ntest('editor can be created with codeBlockShiki extension', function () {\n    $editor = (new Editor([\n        'extensions' => [\n            new StarterKit([\n                'codeBlock' => false,\n            ]),\n            new CodeBlockShiki,\n        ],\n    ]));\n\n    expect($editor->configuration['extensions'][1])\n        ->toBeInstanceOf(CodeBlockShiki::class);\n});\n\ntest('default theme can be set for codeBlockShiki extension', function () {\n    $editor = (new Editor([\n        'extensions' => [\n            new StarterKit([\n                'codeBlock' => false,\n            ]),\n            new CodeBlockShiki([\n                'theme' => 'mojave',\n            ]),\n        ],\n    ]));\n\n    expect($editor->configuration['extensions'][1]->options['theme'])\n        ->toEqual('mojave');\n});\n\ntest('default language can be set for codeBlockShiki extension', function () {\n    $editor = (new Editor([\n        'extensions' => [\n            new StarterKit([\n                'codeBlock' => false,\n            ]),\n            new CodeBlockShiki([\n                'defaultLanguage' => 'css',\n            ]),\n        ],\n    ]));\n\n    expect($editor->configuration['extensions'][1]->options['defaultLanguage'])\n        ->toEqual('css');\n});\n\ntest('code block and inline code are rendered correctly', function () {\n    $html = '<p><code>Example Text</code></p><pre><code class=\"language-css\">body { display: none }</code></pre>';\n\n    $result = (new Editor([\n        'extensions' => [\n                new StarterKit([\n                    'codeBlock' => false,\n                ]),\n                new CodeBlockShiki([\n                    'defaultLanguage' => 'css',\n                ]),\n            ],\n        ]))\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                        'marks' => [\n                            [\n                                'type' => 'code',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n            [\n                'type' => 'codeBlock',\n                'attrs' => [\n                    'language' => 'css',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'body { display: none }',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('html result is properly rendered', function () {\n    $html = '<p><code>Example Text</code></p><pre><code class=\"language-css\">body { display: none }</code></pre>';\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit([\n                'codeBlock' => false,\n            ]),\n            new CodeBlockShiki([\n                'defaultLanguage' => 'css',\n            ]),\n        ],\n    ]))\n        ->setContent($html)\n        ->getHtml();\n\n    // expect($result)\n    //     ->toEqual('<p><code>Example Text</code></p><pre class=\"shiki\" style=\"background-color: #2e3440ff\"><code><span class=\"line\"><span style=\"color:#81A1C1\">body</span><span style=\"color:#D8DEE9FF\"> </span><span style=\"color:#ECEFF4\">{</span><span style=\"color:#D8DEE9FF\"> </span><span style=\"color:#D8DEE9\">display</span><span style=\"color:#ECEFF4\">:</span><span style=\"color:#D8DEE9FF\"> </span><span style=\"color:#81A1C1\">none</span><span style=\"color:#D8DEE9FF\"> </span><span style=\"color:#ECEFF4\">}</span></span></code></pre>');\n\n    // Build a regex pattern that allows an optional whitespace (\\s?) after the colon in each style attribute.\n    $expectedPattern = '/^<p><code>Example Text<\\/code><\\/p>'\n        . '<pre class=\"shiki\" style=\"background-color: #2e3440ff\">'\n        . '<code><span class=\"line\">'\n        . '<span style=\"color:\\s?#81A1C1\">body<\\/span>'\n        . '<span style=\"color:\\s?#D8DEE9FF\">\\s?<\\/span>'\n        . '<span style=\"color:\\s?#ECEFF4\">{<\\/span>'\n        . '<span style=\"color:\\s?#D8DEE9FF\">\\s?<\\/span>'\n        . '<span style=\"color:\\s?#D8DEE9\">display<\\/span>'\n        . '<span style=\"color:\\s?#ECEFF4\">:<\\/span>'\n        . '<span style=\"color:\\s?#D8DEE9FF\">\\s?<\\/span>'\n        . '<span style=\"color:\\s?#81A1C1\">none<\\/span>'\n        . '<span style=\"color:\\s?#D8DEE9FF\">\\s?<\\/span>'\n        . '<span style=\"color:\\s?#ECEFF4\">}<\\/span>'\n        . '<\\/span><\\/code><\\/pre>$/';\n\n    expect($result)->toMatch($expectedPattern);\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/CodeBlockTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('codeBlock node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code>Example Text</code></pre>');\n});\n\ntest('codeBlock language is rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'attrs' => [\n                    'language' => 'css',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code class=\"language-css\">Example Text</code></pre>');\n});\n\ntest('codeBlock language prefix is configureable', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'codeBlock',\n                'attrs' => [\n                    'language' => 'css',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n            'extensions' => [\n                new StarterKit([\n                    'codeBlock' => [\n                        'languageClassPrefix' => 'custom-language-prefix-',\n                    ],\n                ]),\n            ],\n        ]))\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<pre><code class=\"custom-language-prefix-css\">Example Text</code></pre>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/DetailsTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Details;\nuse Tiptap\\Nodes\\DetailsContent;\nuse Tiptap\\Nodes\\DetailsSummary;\n\ntest('details node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary Text',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details,\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<details><summary>Summary Text</summary><div data-type=\"detailsContent\"><p>Content Text</p></div></details>');\n});\n\ntest('details node with open true renders open attribute when persist is enabled', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'attrs' => [\n                    'open' => true,\n                ],\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary Text',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details(['persist' => true]),\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<details open=\"open\"><summary>Summary Text</summary><div data-type=\"detailsContent\"><p>Content Text</p></div></details>');\n});\n\ntest('details node with open false does not render open attribute when persist is enabled', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'details',\n                'attrs' => [\n                    'open' => false,\n                ],\n                'content' => [\n                    [\n                        'type' => 'detailsSummary',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'Summary Text',\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'detailsContent',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Content Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Details(['persist' => true]),\n            new DetailsSummary,\n            new DetailsContent,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<details><summary>Summary Text</summary><div data-type=\"detailsContent\"><p>Content Text</p></div></details>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/HardBreakNodeTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('self closing node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'some text',\n                    ],\n                    [\n                        'type' => 'hardBreak',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'some more text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p>some text<br>some more text</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/HeadingTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('heading node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 2,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h2>Example Headline</h2>';\n\n    expect((new Editor)->setContent($document)->getHTML())->toEndWith($html);\n});\n\ntest('forbidden heading levels are transformed to a heading with an allowed level', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 7,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h1>Example Headline</h1>';\n\n    expect((new Editor)->setContent($document)->getHTML())->toEqual($html);\n});\n\ntest('depending on the configuration heading levels are allowed', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 3,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h3>Example Headline</h3>';\n\n    expect((new Editor([\n        'extensions' => [\n            new \\Tiptap\\Nodes\\Heading(['levels' => [1, 2, 3]]),\n        ],\n    ]))->setContent($document)->getHTML())->toEqual($html);\n});\n\ntest('depending on the configuration heading levels are transformed', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 4,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h1>Example Headline</h1>';\n\n    expect((new Editor([\n        'extensions' => [\n            new \\Tiptap\\Nodes\\Heading(['levels' => [1, 2, 3]]),\n        ],\n    ]))->setContent($document)->getHTML())->toEqual($html);\n});\n\ntest('configured HTMLAttributes are rendered to HTML', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 1,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h1 class=\"custom-heading-class\">Example Headline</h1>';\n\n    expect((new Editor([\n        'extensions' => [\n            new \\Tiptap\\Nodes\\Heading(['HTMLAttributes' => [\n                'class' => 'custom-heading-class',\n            ]]),\n        ],\n    ]))->setContent($document)->getHTML())->toEqual($html);\n});\n\ntest('custom attributes are rendered too', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 1,\n                    'color' => 'red',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h1 class=\"custom-heading-class\" style=\"color: red;\">Example Headline</h1>';\n\n    class CustomHeading extends \\Tiptap\\Nodes\\Heading\n    {\n        public function addAttributes()\n        {\n            return [\n                'color' => [\n                    'renderHTML' => function ($attributes) {\n                        if (! isset($attributes->color)) {\n                            return null;\n                        }\n\n                        return [\n                            'style' => \"color: {$attributes->color}\",\n                        ];\n                    },\n                ],\n            ];\n        }\n    }\n\n    $result = (new Editor([\n        'extensions' => [\n            new CustomHeading([\n                'HTMLAttributes' => [\n                    'class' => 'custom-heading-class',\n                ],\n            ]),\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual($html);\n});\n\ntest('inline styles are merged properly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 1,\n                    'color' => 'red',\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Headline',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $html = '<h1 style=\"color: white; background-color: red;\">Example Headline</h1>';\n\n    class AnotherCustomHeading extends \\Tiptap\\Nodes\\Heading\n    {\n        public function addAttributes()\n        {\n            return [\n                'color' => [\n                    'renderHTML' => function ($attributes) {\n                        if (! isset($attributes->color)) {\n                            return null;\n                        }\n\n                        return [\n                            'style' => \"background-color: {$attributes->color}\",\n                        ];\n                    },\n                ],\n            ];\n        }\n    }\n\n    $result = (new Editor([\n        'extensions' => [\n            new AnotherCustomHeading([\n                'HTMLAttributes' => [\n                    'style' => 'color: white; ',\n                ],\n            ]),\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual($html);\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/HorizontalRuleNodeTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('self closing node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'some text',\n                    ],\n                ],\n            ],\n            [\n                'type' => 'horizontalRule',\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'some more text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p>some text</p><hr><p>some more text</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/ImageTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Image;\n\ntest('image node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'image',\n                'attrs' => [\n                    'alt' => 'an image',\n                    'src' => 'image/source',\n                    'title' => 'The image title',\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Image,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<img src=\"image/source\" alt=\"an image\" title=\"The image title\">');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/MentionTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\Mention;\n\ntest('user mention is serialized correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Hey ',\n                    ],\n                    [\n                        'type' => 'mention',\n                        'attrs' => [\n                            'id' => 123,\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ', was geht?',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $output = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Mention,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($output)->toEqual('<p>Hey <span data-type=\"mention\" data-id=\"123\"></span>, was geht?</p>');\n});\n\ntest('label can be customized', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Hey ',\n                    ],\n                    [\n                        'type' => 'mention',\n                        'attrs' => [\n                            'id' => 123,\n                        ],\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => ', was geht?',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $output = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Mention([\n                'renderLabel' => fn ($node) => '@Philipp',\n            ]),\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($output)->toEqual('<p>Hey <span data-type=\"mention\" data-id=\"123\">@Philipp</span>, was geht?</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/OrderedListTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('orderedList node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'orderedList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'first list item',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<ol><li>first list item</li></ol>');\n});\n\ntest('function orderedList has offset', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'orderedList',\n                'attrs' => [\n                    'start' => 3,\n                ],\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'text',\n                                'text' => 'first list item',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<ol start=\"3\"><li>first list item</li></ol>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/ParagraphTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('paragraph node gets rendered correctly()', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Paragraph',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getHTML();\n\n    expect($result)->toEqual('<p>Example Paragraph</p>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/TableTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Nodes\\Paragraph;\nuse Tiptap\\Nodes\\Table;\nuse Tiptap\\Nodes\\TableCell;\nuse Tiptap\\Nodes\\TableHeader;\nuse Tiptap\\Nodes\\TableRow;\nuse Tiptap\\Nodes\\Text;\n\ntest('simple table node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'table',\n                'content' => [\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableHeader',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'text in header cell',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new Table,\n            new TableRow,\n            new TableCell,\n            new TableHeader,\n            new Paragraph,\n            new Text,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<table><tbody><tr><th><p>text in header cell</p></th></tr></tbody></table>');\n});\n\ntest('table node gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'table',\n                'content' => [\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableHeader',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'text in header cell',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableHeader',\n                                'attrs' => [\n                                    'colspan' => 2,\n                                    'colwidth' => [\n                                        100,\n                                        0,\n                                    ],\n                                ],\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'text in header cell with colspan 2',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableCell',\n                                'attrs' => [\n                                    'rowspan' => 2,\n                                ],\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'paragraph 1 in cell with rowspan 2',\n                                            ],\n                                        ],\n                                    ],\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'paragraph 2 in cell with rowspan 2',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'foo',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'bar',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'tableRow',\n                        'content' => [\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'foo',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                            [\n                                'type' => 'tableCell',\n                                'content' => [\n                                    [\n                                        'type' => 'paragraph',\n                                        'content' => [\n                                            [\n                                                'type' => 'text',\n                                                'text' => 'bar',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new Table,\n            new TableRow,\n            new TableCell,\n            new TableHeader,\n            new Paragraph,\n            new Text,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    $html =\n        '<table><tbody>' .\n            '<tr>' .\n                '<th><p>text in header cell</p></th>' .\n                '<th colspan=\"2\" data-colwidth=\"100,0\"><p>text in header cell with colspan 2</p></th>' .\n            '</tr>' .\n            '<tr>' .\n                '<td rowspan=\"2\"><p>paragraph 1 in cell with rowspan 2</p><p>paragraph 2 in cell with rowspan 2</p></td>' .\n                '<td><p>foo</p></td>' .\n                '<td><p>bar</p></td>' .\n            '</tr>' .\n            '<tr>' .\n                '<td><p>foo</p></td>' .\n                '<td><p>bar</p></td>' .\n            '</tr>' .\n        '</tbody></table>';\n\n    expect($result)->toEqual($html);\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/TaskListTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Nodes\\TaskItem;\nuse Tiptap\\Nodes\\TaskList;\n\ntest('task list gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'taskList',\n                'content' => [\n                    [\n                        'type' => 'taskItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new TaskList(),\n            new TaskItem(),\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<ul data-type=\"taskList\"><li data-checked=\"false\" data-type=\"taskItem\"><label><input type=\"checkbox\"><span></span></label><div><p>Example Text</p></div></li></ul>');\n});\n\ntest('task item status is rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'taskList',\n                'content' => [\n                    [\n                        'type' => 'taskItem',\n                        'attrs' => [\n                            'checked' => true,\n                        ],\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example Text',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit(),\n            new TaskList(),\n            new TaskItem(),\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<ul data-type=\"taskList\"><li data-checked=\"true\" data-type=\"taskItem\"><label><input type=\"checkbox\" checked=\"checked\"><span></span></label><div><p>Example Text</p></div></li></ul>');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/Nodes/XSSTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('text should not get rendered as html', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'text',\n                'text' => '<script>alert(1)</script>',\n            ],\n        ],\n    ];\n\n    $result = (new Editor)->setContent($document)->getHTML();\n\n    expect($result)->toEqual('&lt;script&gt;alert(1)&lt;/script&gt;');\n});\n"
  },
  {
    "path": "tests/DOMSerializer/WrongFormatTest.php",
    "content": "<?php\n\nnamespace Tiptap\\Tests\\HTMLOutput;\n\nuse Tiptap\\Editor;\nuse Tiptap\\Extensions\\StarterKit;\nuse Tiptap\\Marks\\Link;\n\ntest('node content is string gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => 'test',\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toBeEmpty();\n});\n\ntest('node content is empty array gets rendered correctly 1', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toBeEmpty();\n});\n\ntest('node content is empty array gets rendered correctly 2', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [], [],\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toBeEmpty();\n});\n\ntest('node content contains empty array gets rendered correctly 3', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [],\n            'test',\n            [],\n            '',\n            [],\n            [\n                'type' => 'codeBlock',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n            [],\n            [],\n            [],\n            '',\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<pre><code>Example Text</code></pre>');\n});\n\ntest('node content contains empty array empty mark gets rendered correctly', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [],\n            'test',\n            [],\n            '',\n            [],\n            [\n                'type' => 'text',\n                'text' => 'Example Link',\n                'marks' => [\n                    [],\n                    '',\n                    'test',\n                    [\n                        'type' => 'link',\n                        'attrs' => [\n                            'href' => 'https://tiptap.dev',\n                        ],\n                    ],\n                ],\n            ],\n            [],\n            [],\n            [],\n            '',\n        ],\n    ];\n\n    $result = (new Editor([\n        'extensions' => [\n            new StarterKit,\n            new Link,\n        ],\n    ]))->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://tiptap.dev\">Example Link</a>');\n});\n"
  },
  {
    "path": "tests/Editor/DescendantsTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('descendants() loops through all nodes recursively', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'bulletList',\n                'content' => [\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Example',\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                    [\n                        'type' => 'listItem',\n                        'content' => [\n                            [\n                                'type' => 'paragraph',\n                                'content' => [\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Text ',\n                                    ],\n                                    [\n                                        'type' => 'text',\n                                        'text' => 'Test',\n                                        'marks' => [\n                                            [\n                                                'type' => 'italic',\n                                            ],\n                                        ],\n                                    ],\n                                ],\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $editor = (new Editor)->setContent($document);\n\n    $result = [];\n\n    $editor->descendants(function ($node) use (&$result) {\n        $result[] = $node->type;\n    });\n\n    expect($result)->toEqual([\n        'doc',\n        'bulletList',\n        'listItem',\n        'paragraph',\n        'listItem',\n        'paragraph',\n        'paragraph',\n    ]);\n});\n\ntest('updating node attributes in descendants() works', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'heading',\n                'attrs' => [\n                    'level' => 2,\n                ],\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $editor = (new Editor)->setContent($document);\n\n    // Set the level for all headings to 1\n    $html = $editor->descendants(function (&$node) {\n        if ($node->type !== 'heading') {\n            return;\n        }\n\n        $node->attrs->level = 1;\n    })->getHTML();\n\n    expect($html)->toEqual('<h1>Example Text</h1>');\n});\n"
  },
  {
    "path": "tests/Editor/GetDocumentTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getDocument() returns a PHP array', function () {\n    $html = \"<p>Example Text</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/Editor/GetHTMLTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getHTML() returns HTML', function () {\n    $input = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($input)\n        ->getHTML();\n\n    expect($result)->toEqual('<p>Example Text</p>');\n});\n"
  },
  {
    "path": "tests/Editor/GetJSONTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getJSON() returns JSON', function () {\n    $html = \"<p>Example</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getJSON();\n\n    expect($result)->toEqual('{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\",\"content\":[{\"type\":\"text\",\"text\":\"Example\"}]}]}');\n});\n"
  },
  {
    "path": "tests/Editor/GetTextTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('getText() returns plain text', function () {\n    $html = \"<h1>Heading</h1><p>Paragraph</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getText();\n\n    expect($result)->toEqual(\"Heading\\n\\nParagraph\");\n});\n\ntest('getText() only returns one blockSeparator between blocks', function () {\n    $html = \"<h1>Heading</h1><p>Paragraph</p><ul><li><p>ListItem</p></li></ul>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getText();\n\n    expect($result)->toEqual(\"Heading\\n\\nParagraph\\n\\nListItem\");\n});\n\ntest('the blockSeparator is configureable', function () {\n    $html = \"<h1>Heading</h1><p>Paragraph</p>\";\n\n    $result = (new Editor)\n        ->setContent($html)\n        ->getText([\n            'blockSeparator' => \"\\n\",\n        ]);\n\n    expect($result)->toEqual(\"Heading\\nParagraph\");\n});\n"
  },
  {
    "path": "tests/Editor/SanitizeTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('unknown nodes are removed from the document', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\n\ntest('unknown nodes are removed from the document with the sanitized method', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)->sanitize($document);\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('unknown HTML tags are removed', function () {\n    $document = '<p>Example Text<script>alert(\"HACKED\");</script></p>';\n\n    $result = (new Editor)->setContent($document)->getHTML();\n\n    expect($result)->toEqual('<p>Example Text</p>');\n});\n\ntest('unknown HTML tags are removed with the sanitize method', function () {\n    $document = '<p>Example Text<script>alert(\"HACKED\");</script></p>';\n\n    $result = (new Editor)->sanitize($document);\n\n    expect($result)->toEqual('<p>Example Text</p>');\n});\n\ntest('unknown nodes are removed from the JSON', function () {\n    $document = json_encode([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getJSON();\n\n    expect($result)->toEqual(json_encode([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]));\n});\n\ntest('unknown nodes are removed from the json with the sanitized method', function () {\n    $document = json_encode([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n\n    $result = (new Editor)->sanitize($document);\n\n    expect($result)->toEqual(json_encode([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'foo',\n                'content' => [\n                    [\n                        'type' => 'foo',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]));\n});\n"
  },
  {
    "path": "tests/Editor/SetContentTest.php",
    "content": "<?php\n\nuse Tiptap\\Editor;\n\ntest('json strings are detected', function () {\n    $result = (new Editor)->setContent('{\n        \"type\": \"doc\",\n        \"content\": [\n            {\n                \"type\": \"paragraph\",\n                \"content\": [\n                    {\n                        \"type\": \"text\",\n                        \"text\": \"Example Text\"\n                    }\n                ]\n            }\n        ]\n    }')->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\n\ntest('arrays are detected', function () {\n    $document = [\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ];\n\n    $result = (new Editor)\n        ->setContent($document)\n        ->getDocument();\n\n    expect($result)->toEqual($document);\n});\n\n\ntest('html is detected', function () {\n    $result = (new Editor)\n        ->setContent('<p>Example <strong>Text</strong></p>')\n        ->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example ',\n                    ],\n                    [\n                        'type' => 'text',\n                        'text' => 'Text',\n                        'marks' => [\n                            [\n                                'type' => 'bold',\n                            ],\n                        ],\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n\ntest('content can be passed to the configuration', function () {\n    $result = (new Editor([\n        'content' => '<p>Example Text</p>',\n    ]))->getDocument();\n\n    expect($result)->toEqual([\n        'type' => 'doc',\n        'content' => [\n            [\n                'type' => 'paragraph',\n                'content' => [\n                    [\n                        'type' => 'text',\n                        'text' => 'Example Text',\n                    ],\n                ],\n            ],\n        ],\n    ]);\n});\n"
  },
  {
    "path": "tests/Pest.php",
    "content": "<?php\n\n/*\n|--------------------------------------------------------------------------\n| Test Case\n|--------------------------------------------------------------------------\n|\n| The closure you provide to your test functions is always bound to a specific PHPUnit test\n| case class. By default, that class is \"PHPUnit\\Framework\\TestCase\". Of course, you may\n| need to change it using the \"uses()\" function to bind a different classes or traits.\n|\n*/\n\n// uses(Tests\\TestCase::class)->in('Feature');\n\n/*\n|--------------------------------------------------------------------------\n| Expectations\n|--------------------------------------------------------------------------\n|\n| When you're writing tests, you often need to check that values meet certain conditions. The\n| \"expect()\" function gives you access to a set of \"expectations\" methods that you can use\n| to assert different things. Of course, you may extend the Expectation API at any time.\n|\n*/\n\nexpect()->extend('toBeOne', function () {\n    return $this->toBe(1);\n});\n\n/*\n|--------------------------------------------------------------------------\n| Functions\n|--------------------------------------------------------------------------\n|\n| While Pest is very powerful out-of-the-box, you may have some testing code specific to your\n| project that you don't want to repeat in every file. Here you can also expose helpers as\n| global functions to help you to reduce the number of lines of code in your test files.\n|\n*/\n\nfunction something()\n{\n    // ..\n}\n"
  },
  {
    "path": "tests/Schema/GetTopNodeTest.php",
    "content": "<?php\n\nuse Tiptap\\Core\\Schema;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('document is the top node', function () {\n    $schema = new Schema([\n        new StarterKit,\n    ]);\n\n    expect($schema->topNode::$name)->toEqual('doc');\n});\n"
  },
  {
    "path": "tests/Schema/PriorityTest.php",
    "content": "<?php\n\nuse Tiptap\\Core\\Schema;\nuse Tiptap\\Extensions\\StarterKit;\n\ntest('paragraph is the default node', function () {\n    $schema = new Schema([\n        new StarterKit,\n    ]);\n\n    expect($schema->defaultNode::$name)->toEqual('paragraph');\n});\n"
  },
  {
    "path": "tests/Utils/HTMLTest.php",
    "content": "<?php\n\nuse Tiptap\\Utils\\HTML;\n\ntest('classes are merged properly', function () {\n    $attributes = [\n        ['class' => 'a'],\n        ['class' => 'b'],\n    ];\n\n    $result = HTML::mergeAttributes(...$attributes);\n\n    expect($result)->toEqual(['class' => 'a b']);\n});\n"
  }
]