Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: index.js:178 Uncaught RangeError: Duplicate use of selection JSON ID cell #5913

Open
1 task done
zoltanszogyenyi opened this issue Dec 4, 2024 · 11 comments
Open
1 task done
Labels
Category: Open Source The issue or pull reuqest is related to the open source packages of Tiptap. Info: Wont Fix The issue or pullrequest will not be fixed Type: Bug The issue or pullrequest is related to a bug

Comments

@zoltanszogyenyi
Copy link

Affected Packages

core, tables

Version(s)

2.6.6

Bug Description

Hey everyone,

We created a WYSIWYG component based on the Tip Tap library:

https://flowbite.com/docs/plugins/wysiwyg/#editing-tables

Everything worked perfectly and even though we haven't changed the code or versions, importing the table component throws an error that it seems that we can't fix on our end as it seems to be internal:

index.js:178 Uncaught RangeError: Duplicate use of selection JSON ID cell
    at l.jsonID (index.js:178:19)
    at index.js:741:11

If I remove the import table from the code then the code works, but obviously the table actions won't get rendered.

This is the JS code on our end:


import { Editor } from 'https://esm.sh/@tiptap/[email protected]';
import StarterKit from 'https://esm.sh/@tiptap/[email protected]';
import Table from 'https://esm.sh/@tiptap/[email protected]';
import TableCell from 'https://esm.sh/@tiptap/[email protected]';
import TableHeader from 'https://esm.sh/@tiptap/[email protected]';
import TableRow from 'https://esm.sh/@tiptap/[email protected]';

const TipTapExtensionTableCell = TableCell.extend({
	addAttributes() {
		return {
			...this.parent?.(),
			backgroundColor: {
				default: null,
				renderHTML: (attributes) => {
					if (!attributes.backgroundColor) {
						return {}
					}

					return {
						style: 'background-color: ' + attributes.backgroundColor,
					}
				},
				parseHTML: (element) => {
					return element.style.backgroundColor.replace(/['"]+/g, '')
				},
			},
		}
	},
});

window.addEventListener('load', function() {
    if (document.getElementById("wysiwyg-tables-example")) {

    // tip tap editor setup
    const editor = new Editor({
        element: document.querySelector('#wysiwyg-tables-example'),
        extensions: [
            StarterKit,
            Table.configure({
                resizable: true,
            }),
            TableRow,
            TableHeader,
            TableCell,
            TipTapExtensionTableCell
        ],
        content: '<p>Understanding global <strong>population growth trends</strong> is essential for analyzing the development and future of nations. Population growth rates provide insights into economic prospects, resource allocation, and potential challenges for countries worldwide.</p><p>Here is an example of population data:</p><div class=tableWrapper><table style=min-width:75px><col><col><col><tr><th colspan=1 rowspan=1><p>Country<th colspan=1 rowspan=1><p>Population<th colspan=1 rowspan=1><p>Growth rate<tr><td colspan=1 rowspan=1><p>United States<td colspan=1 rowspan=1><p>333 million<td colspan=1 rowspan=1><p>0.4%<tr><td colspan=1 rowspan=1><p>China<td colspan=1 rowspan=1><p>1.41 billion<td colspan=1 rowspan=1><p>0%<tr><td colspan=1 rowspan=1><p>Germany<td colspan=1 rowspan=1><p>83.8 million<td colspan=1 rowspan=1><p>0.7%<tr><td colspan=1 rowspan=1><p>India<td colspan=1 rowspan=1><p>1.42 billion<td colspan=1 rowspan=1><p>1.0%<tr><td colspan=1 rowspan=1><p>Brazil<td colspan=1 rowspan=1><p>214 million<td colspan=1 rowspan=1><p>0.6%<tr><td colspan=1 rowspan=1><p>Indonesia<td colspan=1 rowspan=1><p>273 million<td colspan=1 rowspan=1><p>1.1%<tr><td colspan=1 rowspan=1><p>Pakistan<td colspan=1 rowspan=1><p>231 million<td colspan=1 rowspan=1><p>2.0%<tr><td colspan=1 rowspan=1><p>Nigeria<td colspan=1 rowspan=1><p>223 million<td colspan=1 rowspan=1><p>2.5%</table></div><p>Learn more about global population trends from reliable sources like the <a href=https://www.worldpopulationreview.com>World Population Review</a>.</p>',
        editorProps: {
            attributes: {
                class: 'format lg:format-lg dark:format-invert focus:outline-none format-blue max-w-none',
            },
        }
    });

    // set up custom event listeners for the buttons
    document.getElementById('addTableButton').addEventListener('click', () => {
        editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
    });

    // add column before
    document.getElementById('addColumnBeforeButton').addEventListener('click', () => {
        editor.chain().focus().addColumnBefore().run();
    });

    // add column after
    document.getElementById('addColumnAfterButton').addEventListener('click', () => {
        editor.chain().focus().addColumnAfter().run();
    });

    // remove column
    document.getElementById('removeColumnButton').addEventListener('click', () => {
        editor.chain().focus().deleteColumn().run();
    });

    // add row before
    document.getElementById('addRowBeforeButton').addEventListener('click', () => {
        editor.chain().focus().addRowBefore().run();
    });

    // add row after
    document.getElementById('addRowAfterButton').addEventListener('click', () => {
        editor.chain().focus().addRowAfter().run();
    });

    // remove row
    document.getElementById('removeRowButton').addEventListener('click', () => {
        editor.chain().focus().deleteRow().run();
    });

    // delete table
    document.getElementById('deleteTableButton').addEventListener('click', () => {
        editor.chain().focus().deleteTable().run();
    });

    // merge cells
    document.getElementById('mergeCellsButton').addEventListener('click', () => {
        editor.chain().focus().mergeCells().run();
    });

    // split cells
    document.getElementById('splitCellsButton').addEventListener('click', () => {
        editor.chain().focus().splitCell().run();
    });

    // merge or split
    document.getElementById('mergeOrSplitButton').addEventListener('click', () => {
        editor.chain().focus().mergeOrSplit().run();
    });

    // toggle header column
    document.getElementById('toggleHeaderColumnButton').addEventListener('click', () => {
        editor.chain().focus().toggleHeaderColumn().run();
    });

    // toggle header row
    document.getElementById('toggleHeaderRowButton').addEventListener('click', () => {
        editor.chain().focus().toggleHeaderRow().run();
    });

    // toggle header cell
    document.getElementById('toggleHeaderCellButton').addEventListener('click', () => {
        editor.chain().focus().toggleHeaderCell().run();
    });

    const cellAttributeModal = FlowbiteInstances.getInstance('Modal', 'cell-attribute-modal');
    
    document.getElementById('addCellAttributeForm').addEventListener('submit', (e) => {

        e.preventDefault();
        
        const attributeName = document.getElementById('attribute-name').value;
        const attributeValue = document.getElementById('attribute-value').value;

        if (attributeName && attributeValue) {
            const result = editor.commands.setCellAttribute(attributeName ? attributeName : '', attributeValue ? attributeValue : '');
            document.getElementById('addCellAttributeForm').reset();
            cellAttributeModal.hide();
        }
    });

    // fix tables
    document.getElementById('fixTablesButton').addEventListener('click', () => {
       editor.commands.fixTables();
    });

    // go to previous cell
    document.getElementById('previousCellButton').addEventListener('click', () => {
        editor.chain().focus().goToPreviousCell().run();
    });

    // go to the next cell
    document.getElementById('nextCellButton').addEventListener('click', () => {
        editor.chain().focus().goToNextCell().run();
    });
}
})

Any idea why this happens? We're importing via CDN, all the other examples work on our site.

Browser Used

Chrome

Code Example URL

No response

Expected Behavior

It should work and not throw an error.

Additional Context (Optional)

No response

Dependency Updates

  • Yes, I've updated all my dependencies.
@zoltanszogyenyi zoltanszogyenyi added Category: Open Source The issue or pull reuqest is related to the open source packages of Tiptap. Type: Bug The issue or pullrequest is related to a bug labels Dec 4, 2024
@nperez0111
Copy link
Contributor

This is likely because you are trying to import from esm.sh and the table-cell extension is being included multiple times. There are known issues with the packaging with ESM because it cannot handle peer dependencies in the way that we are using them

@zoltanszogyenyi
Copy link
Author

zoltanszogyenyi commented Dec 4, 2024

Hey @nperez0111,

Thanks for the feedback, but this code worked perfectly one week ago and we haven't changed a thing.

Every other import ESM example works, you can check the link here:

https://flowbite.com/docs/plugins/wysiwyg/#default-text-editor

So this seems to be a table specific error.

I've been debugging this for over 2 hours and there's nothing wrong with our code AFAIK.

@nperez0111
Copy link
Contributor

nperez0111 commented Dec 4, 2024

Peer deps would be resolved separately since you don't have a lockfile to make sure it (and other deps) stays on a specific version. We make extensive use of peer dep ranges which would allow new versions of tiptap packages and 3rd party deps like prosemirror sneak in.

@nperez0111
Copy link
Contributor

Case in point, looking at your example, you have 3 versions of @tiptap/core being imported in your lib.

image

The only way to ensure all of them are locked in version together would be to import the packages specifically (every single dep) or, to bundle it separately. I would not rely on something like ESM.sh, it is quite the anti-pattern nowadays

@zoltanszogyenyi
Copy link
Author

But all our versions that are imported via ESM.sh are locked for v2.6.6, we're not importing different versions deliberately.

@nperez0111 nperez0111 added the Info: Wont Fix The issue or pullrequest will not be fixed label Dec 4, 2024
@nperez0111
Copy link
Contributor

That is not "locked", you are requesting a package's version. But, that package has dependencies & peer dependencies that too need to be resolved. So, esm.sh tries to resolve them for you. But, if you look at a package, it species a range as it's dependency version:

"@tiptap/core": "^2.7.0"
in this case ^2.7.0 means resolve the latest version that is in the major range 2, so, you get 2.10.1 because that is the latest we have released.

esm.sh cannot lock all of the dependencies in the tree, because it needs to refresh to satisfy those ranges. This is the purpose of a lockfile, to freeze a tree of dependencies in a known working state that can then be updated all at once.

By using esm.sh you cannot make these guarantees. It is an anti-pattern

@zoltanszogyenyi
Copy link
Author

Is there any way we can make this work via CDN?

@nperez0111
Copy link
Contributor

Again, this is an anti-pattern. I cannot make a recommendation for you because the approach is fundamentally flawed.

@zoltanszogyenyi
Copy link
Author

zoltanszogyenyi commented Dec 4, 2024

Thanks, I'll see what I can do.

Probably move the packages locally, too bad, CDN would've been a super comfortable approach.

@zoltanszogyenyi
Copy link
Author

zoltanszogyenyi commented Dec 4, 2024

Actually, I do have a question:

Is Tip Tap hosted on other CDN platforms other than ESM?

I'm thinking that from there I can in fact actually lock in the versions.

For example, at Flowbite we serve our dist JS via CDN too and it doesn't have to resolve packages since it's the final code that you import to make things work:

https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.5.2/flowbite.js

Thanks!

@nperez0111
Copy link
Contributor

Tiptap doesn't explicitly host themselves on ESM.sh, ESM.sh just happens to be able to import any NPM package and serve it. There could be a way of doing this with ESM.sh or another provider to lock it to a hash or something, but I don't know of how that all works.

I would recommend bundling the packages you need for yourself and serve 1 JS file with all the dependencies in 1 file if you must. You could even bundle it into your library so it doesn't need to make extra network requests.

But, this is outside what Tiptap can do, and again, this is an anti-pattern, so I cannot support it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Open Source The issue or pull reuqest is related to the open source packages of Tiptap. Info: Wont Fix The issue or pullrequest will not be fixed Type: Bug The issue or pullrequest is related to a bug
Projects
None yet
Development

No branches or pull requests

2 participants