Git Product home page Git Product logo

tiptap-php's Introduction

Tiptap for PHP

Latest Version on Packagist GitHub Tests Action Status Total Downloads License Chat Sponsor

A PHP package to work with Tiptap content. You can transform Tiptap-compatible JSON to HTML, and the other way around, sanitize your content, or just modify it.

Installation

You can install the package via composer:

composer require ueberdosis/tiptap-php

Usage

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

Convert Tiptap HTML to JSON

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

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

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

You can get a JSON string in PHP, too.

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

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

Convert Tiptap JSON to HTML

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

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

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

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

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

Syntax highlighting for code blocks with highlight.php

The default CodeBlock extension doesn’t add syntax highlighting to your code blocks. However, if you want to add syntax highlighting to your code blocks, there’s a special CodeBlockHighlight extension.

Swapping our the default one works like that:

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

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

This is still unstyled. You need to load a CSS file to add colors to the output, for example like that:

<link rel="stylesheet" href="//unpkg.com/@highlightjs/[email protected]/styles/default.min.css">

Boom, syntax highlighting! By the way, this is powered by the amazing scrivo/highlight.php.

Syntax highlighting for code blocks with Shiki (Requires Node.js)

There is an alternate syntax highlighter that utilizes Shiki. Shiki is a beautiful syntax highlighter powered by the same language engine that many code editors use. The major differences from the CodeBlockHighlight extensions are, 1) you must install the shiki npm package, 2) Shiki code highlighting works by injecting inline styles so pulling in a external css file is not required, 3) you can use most VS Code themes to highlight your code.

To use the Shiki extension, first install the npm package

npm install shiki

Then follow the example below:

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

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

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

Under the hood the Shiki extension utilizes Shiki PHP by Spatie, so please see the documentation for additional details and considerations.

Convert content to plain text

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

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

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

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

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

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

Sanitize content

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

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

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

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

Modifying the content

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

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

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

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

Configuration

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

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

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

Extensions

By default, the StarterKit is loaded, but you can pass a custom array of extensions aswell.

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

Configure extensions

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

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

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

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

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

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

Extend existing extensions

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

<?php

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

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

Custom extensions

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

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

<?php

use Tiptap\Core\Node;

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

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

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

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

Extension priority

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

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

Testing

composer test

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

composer test-watch

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

tiptap-php's People

Contributors

faltjo avatar hanspagel avatar jacksleight avatar martindines avatar timoisik avatar wyattcast44 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

tiptap-php's Issues

Sanitize removes links (<a>) and underline (<u>)

Hi, I have installed the package just to sanitize the content generated by tiptap in the frontend. And I notice that when sanitizing it removes the links (a tags) and underlines (u tags), how is it possible to avoid this behavior?

Ordered List incompatible with Tiptap JS

I save content to my database with tiptap editor, the output json from the editor is this:

'{"type":"doc","content":[{"type":"orderedList","attrs":{"start":2},"content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Test text here "}]}]}]}]}';

However, when this is passed into tiptap-php with setContent and then getHTML, the ol element has no order attribute. If I modify the json above to have order instead of start it works as expected. I believe this to be a bug in tiptap-php and not tiptap js.

This is re-produceable with the below code:

Incorrect:

 $content = '{"type":"doc","content":[{"type":"orderedList","attrs":{"start":2},"content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Test text here "}]}]}]}]}';
 echo (new Editor())
     ->setContent($content)
     ->getHTML();

Correct:

 $content = '{"type":"doc","content":[{"type":"orderedList","attrs":{"order":2},"content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Test text here "}]}]}]}]}';
 echo (new Editor())
     ->setContent($content)
     ->getHTML();

Issue with syntax for renderHTML of custom extension with nested HTML

Hi there,

Here's my situation:

  • I have some Tiptap content stored in my database as JSON
  • I need to render this content as HTML using this PHP library (Laravel project) in order to send the content in an email notification
  • I am trying to recreate the rendered HTML markup a custom Mention extension that works in my Vue/JS version of Tiptap

Here is my working Javascript version of the renderHTML function:

return [
  "span",
  {
    ...HTMLAttributes,
    "data-id": node.attrs.id,
    "data-type": "mention",
    "data-label": node.attrs.label.full_name,
    contenteditable: false,
    class: "mention",
  },
  ["img", { src: `${node.attrs.label.avatar}`, class: "mention__avatar" }],
  ` ${node.attrs.label.first_name}`,
]

When I try and do something similar in the PHP version:

return [
    'span',
    HTML::mergeAttributes(
        [
            'class' => 'mention',
            'data-id' => $node->attrs->id,
            'data-type' => 'mention',
            'data-label' => $user->full_name,
        ],
        $this->options['HTMLAttributes'],
        $HTMLAttributes,
    ),
    ['img', [
            'src' => 'https://canary.imgix.net/' . $user->avatar . '?auto=format&dpr=2&w=72&h=72&fit=facearea&faceindex=1&facepad=3&q=90',
            'class' => 'mention__avatar',
        ]
    ],
    $user->first_name
];

I don't get the same output... The outer span tag renders correctly with the appropriate data attributes, and the image tag also renders and shows the user's avatar.

The issue then is what happens after that image tag - as I simply cannot get the user's name to render.

If I have it as per the code above, then what happens is it outputs an additional html tag after the img tag, with that tag being the user's first name. For example, if the user is named "Dave", then it will render a <dave></dave> tag pair.

It may perhaps simply be a syntax thing that I'm missing, but I've tried a number of other combinations:

  1. Changing the last part to strval(" " . $user->first_name) to try and return a string with a space in front of it - however this causes and error
  2. I've tried adding another span tag after the image tag like so:
return [
    'span',
    HTML::mergeAttributes(
        [
            'class' => 'mention',
            'data-id' => $node->attrs->id,
            'data-type' => 'mention',
            'data-label' => $user->full_name,
        ],
        $this->options['HTMLAttributes'],
        $HTMLAttributes,
    ),
    ['img', [
            'src' => 'https://canary.imgix.net/' . $user->avatar . '?auto=format&dpr=2&w=72&h=72&fit=facearea&faceindex=1&facepad=3&q=90',
            'class' => 'mention__avatar',
        ]
    ],
    'span',
    ['class' => 'mention__name'],
    $user->first_name,
];

which actually does render the extra span tag, including adding the mention__name class to that span... but then throws an error that it's expecting the last part to be an array, not a string.

  1. I've also tried then putting both the image tag and the name string inside of an array to make this combined content array be the third part of the returned array, but this also causes other errors.
  2. I also tried other combinations of elements such as making the parent a div, or wrapping the name in a p tag thinking it may be a semantics thing, but still got the same sort of errors

So my thinking is that either:

  • I'm missing some simple syntax issue in order to render something like <span class="mention"><img src="url" class="mention__avatar"> Name</span>
  • OR, the renderHTML function for the PHP version of Tiptap wasn't written to accomodate this kind of nesting of content

Any help or tips would be greatly appreciated.

Theme is not applied

Because the theme variable is still hard-coded, the selected theme is never applied.

File CodeBlockShiki.php and method renderHTML()

$content = Shiki::highlight(
      code: $code,
      language: $language,
      theme: 'nord',
  );

Should be

$content = Shiki::highlight(
      code: $code,
      language: $language,
      theme: $this->options['theme'],
  );

Strike is deprecated

Hello,

The strike element has been deprecated and I don't think it should be part of the available options or at least, it shouldn‘t be the default tag for the rendering.

Add ability to be able to limit output by number of characters

I'd like to be able to limit the output to a specific number of characters while retaining JSON or HTML. For instance, I'd like to be able to limit the characters output to 50 without risking cutting off an HTML end tag if it cuts in the middle of a word. Maybe something like $output->getHTML()->limit(50, '...') - with the first value being number of characters, and second being what you'd append to the end of the string should it cut off any characters.

Overlapping marks generate invalid HTML

Hello,

there seems to be an issue with the Tiptap JSON to HTML conversion when using overlapping styles like this:

image

JSON input:

{
  "type": "doc",
  "content": [
    {
      "type": "paragraph",
      "content": [
        {
          "type": "text",
          "marks": [
            {
              "type": "bold"
            }
          ],
          "text": "lorem "
        },
        {
          "type": "text",
          "marks": [
            {
              "type": "bold"
            },
            {
              "type": "italic"
            }
          ],
          "text": "ipsum"
        },
        {
          "type": "text",
          "marks": [
            {
              "type": "italic"
            }
          ],
          "text": " dolor"
        }
      ]
    }
  ]
}

HTML output:

<p><strong>lorem <em>ipsum</strong> dolor</em></p>

Expected HTML output:

<p><strong>lorem <em>ipsum</em></strong><em> dolor</em></p>

For easy reproduction:

(new Tiptap\Editor)
  ->setContent([
      "type" => "doc",
      "content" => [
          [
              "type" => "paragraph",
              "content" => [
                  [
                      "type" => "text",
                      "marks" => [
                          [
                              "type" => "bold"
                          ]
                      ],
                      "text" => "lorem "
                  ],
                  [
                      "type" => "text",
                      "marks" => [
                          [
                              "type" => "bold"
                          ],
                          [
                              "type" => "italic"
                          ]
                      ],
                      "text" => "ipsum"
                  ],
                  [
                      "type" => "text",
                      "marks" => [
                          [
                              "type" => "italic"
                          ]
                      ],
                      "text" => " dolor"
                  ]
              ]
          ]
      ]
  ])
  ->getHTML();

Thanks in advance :)

Array to string conversion

Hi there,

Silly question but when I trying to get JSON from the following HTML, it throws Array to string conversion in ../vendor/ueberdosis/tiptap-php/src/Core/DOMParser.php on line 144.

<p><iframe frameborder="0" height="385" src="https://www.youtube.com/embed/xxx" width="590"></iframe></p>
<p><iframe frameborder="0" height="385" src="https://www.youtube.com/embed/yyy" width="590"></iframe></p>

Suggest new package when using the old ones

Hi!

I've been using the old PHP packages and not had the time to update yet.

However, every time I run composer update I get an abandoned message - which makes sense!

Package ueberdosis/html-to-prosemirror is abandoned, you should avoid using it. No replacement was suggested.
Package ueberdosis/prosemirror-php is abandoned, you should avoid using it. No replacement was suggested.
Package ueberdosis/prosemirror-to-html is abandoned, you should avoid using it. No replacement was suggested.

I know its in the README now, but could we maybe also suggest this new package from composer aswell?

Did the initial work, however I cannot add PR's to archived repositories:
ueberdosis/prosemirror-to-html@main...phh:patch-1
ueberdosis/html-to-prosemirror@main...phh:patch-1
ueberdosis/prosemirror-php@main...phh:patch-1

Include the link mark in the starter kit

One issue I was coming across when using this package was that the link mark isn't used in the default starter kit that gets loaded when Editor is instantiated, even though it's available in the Marks folder.

I got around this by using my own StarterKit class with link included (in addOptions and addExtensions), but I was curious why it's not included by default?

Add an ability to specify `content`

Thanks for this package!

I want to have an ability to specify content for the Document node explicitly.

I want it to be block+ emailFooter.

If I'm not missing something, currently it's impossible to specify content like in the JS package.

Might be related to this issue #2

Update scrivo/highlight.php to v9.18.1.10

Hi! I can't update my project due to having packages that separately depend on this (which depends on scrivo/highlight.php to be at v9.18.1.9) and another package which depends on it being at v9.18.1.10. Thus concluding the best solution for tiptap-php to use the newest version of highlight.

This change seems to not interfere at all as per the changelog. I'll be more than happy to create the tiny pull request to make this happen. :)

Add support for addAttributes `default`

In JavaScript, we can set a default value for custom attributes. Let’s add that to the PHP package, too!

public static function addAttributes() {
    return [
        'color' => [
            'default' => 'red',
        ],
    ];
}

Importing html with nested lists causes invalid doc structure

See my comment #2 (comment)

If a <li> tag contains a <p> and a sublist <ul>, the ListItem Node wraps the content with an additional <p> tag.

The resulting json doc can be loaded into tiptap but as soon as you edit a list item, an Invalid content for node paragraph is thrown because there is now a <p> tag containing a <p> and <ul> tag which is not valid, because a <p> tag can only contain phrasing content.

Get rid of the wrapper() method

I’d like to get rid of the wrapper() method. It’s used - for example - to move the content of list items in a paragraph.

I’m afraid we’d need to add broad support for the content attribute from ProseMirror to achieve that though.

Better doc for beginners and fast start

Hello, it would be cool to have an "example" directory with an example page that displays a tiptap form built with tiptap-php.

I just discovered Tiptap and I don't know if tiptap requires the javascript library, or what to do with tiptap-php to have an interactive hello world editor that I can interact with.

Thanks.

spatie/shiki-php requires php 8.0

Hi, The spatie/shiki-php package requires php 8.0 but I would like to use the package in php 7.4 so right now I cant use this package. Do you have a workaround for this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.