ueberdosis / tiptap

The headless rich text editor framework for web artisans.
https://tiptap.dev
MIT License
27.52k stars 2.29k forks source link

[Bug]: Cannot parse generated html from YouTube extension #4089

Open ben-bourdin451 opened 1 year ago

ben-bourdin451 commented 1 year ago

Which packages did you experience the bug in?

extension-youtube, html

What Tiptap version are you using?

^2.0.0

What’s the bug you are facing?

Using generateHTML & generateJSON utilities from @tiptap/html doesn't work with the YouTube extension. Sample easily replicable test case:

import { generateJSON, generateHTML } from '@tiptap/html';
import Document from '@tiptap/extension-document';
import Text from '@tiptap/extension-text';

it('can convert from & to html', async () => {
    const extensions = [Document, Text, Youtube];
    const html = `<p>Tiptap now supports YouTube embeds! Awesome!</p>
      <div data-youtube-video>
        <iframe src="https://www.youtube.com/watch?v=cqHqLQgVCgY"></iframe>
      </div>`;
    const json = generateJSON(html, extensions);
    expect(generateHTML(json, extensions)).to.equal(html);
});

Test fails with:

Unknown CSS selector type descendant div[data-youtube-video] iframe [
  { type: 'tag', name: 'div', namespace: null },
  {
    type: 'attribute',
    name: 'data-youtube-video',
    action: 'exists',
    value: '',
    namespace: null,
    ignoreCase: null
  },
  { type: 'descendant' },
  { type: 'tag', name: 'iframe', namespace: null }
]

TypeError: Cannot read properties of null (reading 'includes')
❯ getEmbedUrlFromYoutubeUrl ../../node_modules/@tiptap/extension-youtube/src/utils.ts:56:11
❯ Object.renderHTML ../../node_modules/@tiptap/extension-youtube/src/youtube.ts:138:22
❯ Object.schema.toDOM [as youtube] ../../node_modules/@tiptap/core/src/helpers/getSchemaByResolvedExtensions.ts:100:32
❯ DOMSerializer.serializeNodeInner ../../node_modules/prosemirror-model/dist/index.js:3280:100
❯ ../../node_modules/prosemirror-model/dist/index.js:3272:34
❯ Fragment.forEach ../../node_modules/prosemirror-model/dist/index.js:255:13
❯ DOMSerializer.serializeFragment ../../node_modules/prosemirror-model/dist/index.js:3246:18
❯ getHTMLFromFragment ../../node_modules/@tiptap/html/src/getHTMLFromFragment.ts:5:53
❯ Module.generateHTML ../../node_modules/@tiptap/html/src/generateHTML.ts:10:10
❯ src/index.spec.ts:47:12

What browser are you using?

Chrome

Code example

No response

What did you expect to happen?

I would expect the above test to pass.

Anything to add? (optional)

Looks like this could be an issue with zeed-dom and how it parses the html. I would happily open an issue there however since I couldn't find much information relating to a similar issue (only this seems close) it's hard to tell exactly what the issue is.

Did you update your dependencies?

Are you sponsoring us?

dahalaamosh commented 9 months ago

Facing same issue in

"@tiptap/extension-youtube": "^2.1.16", "@tiptap/html": "^2.2.2",

After running generateJSON() using @tiptap/html in the generated html when trying to use TiptapTransformer.toYDoc() getting this error -

Unknown CSS selector type descendant div[data-youtube-video] iframe [ { type: 'tag', name: 'div', namespace: null }, { type: 'attribute', name: 'data-youtube-video', action: 'exists', value: '', namespace: null, ignoreCase: null }, { type: 'descendant' }, { type: 'tag', name: 'iframe', namespace: null }

ben-bourdin451 commented 9 months ago

Quick follow up on this issue, it seems like it is due to the underlying HTML parsing library used, so not an issue with TipTap or Prosemirror.

Switching to a different library than zeed-dom in the following snippet fixed the issue

export function generateJSON(html: string, extensions: Extensions): Record<string, any> {
  const schema = getSchema(extensions)
  const dom = parseHTML(html) as unknown as Node

  return DOMParser.fromSchema(schema).parse(dom).toJSON()
}
haydenbleasel commented 7 months ago

@ben-bourdin451 What library did you end up using instead of zeed-dom?

haydenbleasel commented 7 months ago

Still digging into it, opened an issue in zeed-dom: https://github.com/holtwick/zeed-dom/issues/10

Update: I re-implemented the latest version of zeed-dom locally and it looks like it works, despite throwing a warning. dom returns a _VDocumentFragment containing the iframe, leading me to think the problem is in the prosemirror-model DOMParser.

ben-bourdin451 commented 7 months ago

@ben-bourdin451 What library did you end up using instead of zeed-dom?

we ended up using linkedom

vekunz commented 4 months ago

Is there any recommendation on what to do, until this bug is fixed? I want to use YouTube Videos in my documents and need to store them as HTML files. I tried linkedom as a drop-in replacement, but then no content at all was loaded from the HTML file.

Philipinho commented 4 months ago

@vekunz I switched to using happy-dom.

generateHTML

import { Extensions, getSchema, JSONContent } from '@tiptap/core';
import { DOMSerializer, Node } from '@tiptap/pm/model';
import { Window } from 'happy-dom';

export function generateHTML(doc: JSONContent, extensions: Extensions): string {
  const schema = getSchema(extensions);
  const contentNode = Node.fromJSON(schema, doc);

  const window = new Window();

  const fragment = DOMSerializer.fromSchema(schema).serializeFragment(
    contentNode.content,
    {
      document: window.document as unknown as Document,
    },
  );

  const serializer = new window.XMLSerializer();
  // @ts-ignore
  return serializer.serializeToString(fragment as unknown as Node);
}

Edit: The happy-dom based generateJSON function has issues with closing tags. I now use the @tiptap/html for generateJSON and then the happy-dom for generateHTML. This should be enough until there is an official fix.

SaadK94 commented 1 month ago

Importing generateJSON from @tiptap/core instead of @tiptap/html solved the issue for me.

Philipinho commented 1 month ago

@SaadK94, @tiptap/core only works client side.