Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions packages/richtext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,32 @@
"release:dry": "release-it --dry-run"
},
"dependencies": {
"markdown-it": "^14.1.0",
"markdown-it-github": "^0.5.0",
"node-html-parser": "^7.0.1"
"@tiptap/core": "^3.10.2",
"@tiptap/extension-blockquote": "^3.10.2",
"@tiptap/extension-bold": "^3.10.2",
"@tiptap/extension-code": "^3.10.2",
"@tiptap/extension-code-block": "^3.10.2",
"@tiptap/extension-details": "^3.10.2",
"@tiptap/extension-document": "^3.10.2",
"@tiptap/extension-emoji": "^3.10.2",
"@tiptap/extension-hard-break": "^3.10.2",
"@tiptap/extension-heading": "^3.10.2",
"@tiptap/extension-highlight": "^3.10.2",
"@tiptap/extension-horizontal-rule": "^3.10.2",
"@tiptap/extension-image": "^3.10.2",
"@tiptap/extension-italic": "^3.10.2",
"@tiptap/extension-link": "^3.10.2",
"@tiptap/extension-list": "^3.10.2",
"@tiptap/extension-paragraph": "^3.10.2",
"@tiptap/extension-strike": "^3.10.2",
"@tiptap/extension-subscript": "^3.10.2",
"@tiptap/extension-superscript": "^3.10.2",
"@tiptap/extension-table": "^3.10.2",
"@tiptap/extension-text": "^3.10.2",
"@tiptap/extension-text-style": "^3.10.2",
"@tiptap/extension-underline": "^3.10.2",
"@tiptap/html": "^3.10.2",
"markdown-it": "^14.1.0"
},
"devDependencies": {
"@arethetypeswrong/core": "^0.18.2",
Expand Down
153 changes: 42 additions & 111 deletions packages/richtext/src/html-parser.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest';
import { htmlToStoryblokRichtext } from './html-parser';
import { BlockTypes } from './types';
import Heading from '@tiptap/extension-heading';

describe('htmlToStoryblokRichtext', () => {
it('parses headings', () => {
Expand Down Expand Up @@ -62,10 +62,10 @@ describe('htmlToStoryblokRichtext', () => {
type: 'doc',
content: [
{
type: 'bullet_list',
type: 'bulletList',
content: [
{
type: 'list_item',
type: 'listItem',
content: [
{
type: 'paragraph',
Expand All @@ -76,7 +76,7 @@ describe('htmlToStoryblokRichtext', () => {
],
},
{
type: 'list_item',
type: 'listItem',
content: [
{
type: 'paragraph',
Expand All @@ -99,10 +99,10 @@ describe('htmlToStoryblokRichtext', () => {
type: 'doc',
content: [
{
type: 'ordered_list',
type: 'orderedList',
content: [
{
type: 'list_item',
type: 'listItem',
content: [
{
type: 'paragraph',
Expand All @@ -113,7 +113,7 @@ describe('htmlToStoryblokRichtext', () => {
],
},
{
type: 'list_item',
type: 'listItem',
content: [
{
type: 'paragraph',
Expand Down Expand Up @@ -183,7 +183,7 @@ describe('htmlToStoryblokRichtext', () => {
type: 'doc',
content: [
{
type: 'code_block',
type: 'codeBlock',
attrs: { language: 'js' },
content: [
{ type: 'text', text: 'const foo = "bar";\nconsole.log(foo);\n' },
Expand All @@ -206,7 +206,7 @@ describe('htmlToStoryblokRichtext', () => {
type: 'tableRow',
content: [
{
type: 'tableCell',
type: 'tableHeader',
content: [
{
type: 'paragraph',
Expand All @@ -216,7 +216,7 @@ describe('htmlToStoryblokRichtext', () => {
attrs: { colspan: 1, rowspan: 1, colwidth: null },
},
{
type: 'tableCell',
type: 'tableHeader',
content: [
{
type: 'paragraph',
Expand All @@ -226,7 +226,7 @@ describe('htmlToStoryblokRichtext', () => {
attrs: { colspan: 1, rowspan: 1, colwidth: null },
},
{
type: 'tableCell',
type: 'tableHeader',
content: [
{
type: 'paragraph',
Expand Down Expand Up @@ -389,43 +389,6 @@ describe('htmlToStoryblokRichtext', () => {
});
});

it('distinguishes between anchor and link', () => {
const result = htmlToStoryblokRichtext(
'<a id="some-id">Anchor</a><a href="/home">Link</a>',
);
expect(result).toEqual({
type: 'doc',
content: [
{
text: 'Anchor',
type: 'text',
marks: [
{
type: 'anchor',
attrs: {
anchor: 'some-id',
},
content: [],
},
],
},
{
text: 'Link',
type: 'text',
marks: [
{
type: 'link',
attrs: {
href: '/home',
},
content: [],
},
],
},
],
});
});

it('parses strikethrough (delete, s) marks', () => {
const html = '<p>This is <del>deleted text</del> <s>deleted text</s>.</p>';
const result = htmlToStoryblokRichtext(html);
Expand Down Expand Up @@ -495,7 +458,7 @@ describe('htmlToStoryblokRichtext', () => {
],
},
{
type: 'horizontal_rule',
type: 'horizontalRule',
},
{
type: 'paragraph',
Expand All @@ -517,7 +480,7 @@ describe('htmlToStoryblokRichtext', () => {
type: 'paragraph',
content: [
{ type: 'text', text: 'Line with a hard break here.' },
{ type: 'hard_break' },
{ type: 'hardBreak' },
{ type: 'text', text: 'Next line after break.' },
],
},
Expand Down Expand Up @@ -548,8 +511,8 @@ describe('htmlToStoryblokRichtext', () => {
type: 'text',
text: 'bold and italic',
marks: [
{ type: 'italic' },
{ type: 'bold' },
{ type: 'italic' },
],
},
],
Expand All @@ -566,8 +529,8 @@ describe('htmlToStoryblokRichtext', () => {
type: 'text',
text: 'italic',
marks: [
{ type: 'italic' },
{ type: 'bold' },
{ type: 'italic' },
],
},
],
Expand Down Expand Up @@ -658,10 +621,10 @@ describe('htmlToStoryblokRichtext', () => {
},
},
{
type: 'italic',
type: 'bold',
},
{
type: 'bold',
type: 'italic',
},
],
text: 'bold and italic link',
Expand Down Expand Up @@ -775,10 +738,11 @@ describe('htmlToStoryblokRichtext', () => {
{
type: 'link',
attrs: {
class: null,
href: '/home',
rel: 'noopener noreferrer',
target: '_blank',
},
content: [],
},
],
},
Expand Down Expand Up @@ -812,13 +776,14 @@ describe('htmlToStoryblokRichtext', () => {
{
type: 'link',
attrs: {
class: null,
custom: {
'data-supported-custom-attribute': 'whatever',
},
href: '/home',
rel: 'noopener noreferrer',
target: '_blank',
},
content: [],
},
],
},
Expand All @@ -830,7 +795,7 @@ describe('htmlToStoryblokRichtext', () => {

it('preserves styleOptions on inline elements', () => {
const resultStyleOptions = htmlToStoryblokRichtext(
'<p>foo <span class="style-1 invalid-style">bar</span></p><p>baz <span class="style-2">qux</span></p><p>corge <span class="style-3">grault</span> <a href="/home" class="style-1">Home</a></p>',
'<p>foo <span class="style-1 invalid-style">bar</span></p><p>baz <span class="style-2">qux</span></p><p>corge <span class="style-3">grault</span> <a href="/home" class="style-1 invalid-style">Home</a></p>',
{
styleOptions: [
{ name: 'Style1', value: 'style-1' },
Expand All @@ -856,7 +821,6 @@ describe('htmlToStoryblokRichtext', () => {
attrs: {
class: 'style-1',
},
content: [],
},
],
text: 'bar',
Expand All @@ -878,7 +842,6 @@ describe('htmlToStoryblokRichtext', () => {
attrs: {
class: 'style-2',
},
content: [],
},
],
text: 'qux',
Expand All @@ -890,33 +853,20 @@ describe('htmlToStoryblokRichtext', () => {
content: [
{
type: 'text',
text: 'corge ',
},
{
type: 'text',
text: 'grault',
},
{
type: 'text',
text: ' ',
text: 'corge grault ',
},
{
text: 'Home',
type: 'text',
marks: [
{
type: 'link',
attrs: {
href: '/home',
},
content: [],
},
{
type: 'styled',
attrs: {
class: 'style-1',
href: '/home',
rel: null,
target: null,
},
content: [],
},
],
},
Expand Down Expand Up @@ -945,36 +895,21 @@ describe('htmlToStoryblokRichtext', () => {
expect(warn).toHaveBeenCalledWith(expect.stringContaining('[StoryblokRichText] - `class` "unsupported" on `<span>` can not be transformed to rich text.'));
});

it('throws an error when transformation is not supported', () => {
const unsupportedElements = [
'<div>Hello world!</div>',
'<iframe src="https://example.com"></iframe>',
'<script>alert("test")</script>',
];
for (const element of unsupportedElements) {
expect(() => htmlToStoryblokRichtext(element))
.toThrowError(/No resolver specified for tag "(div|iframe|script)"!/);
}
});

it('throws an error when the source HTML is invalid', () => {
const html = '<ul><li>Not closed!<p></ul>';
expect(() => htmlToStoryblokRichtext(html))
.toThrowError('Invalid HTML: The provided string could not be parsed. Common causes include unclosed or mismatched tags!');
});

it('allows using custom resolvers', () => {
const html = '<h2>Custom Heading</h2><div>Custom Div</div>';
it('allows using custom tip tap extensions', () => {
const html = '<h2>Custom Heading</h2>';
const result = htmlToStoryblokRichtext(html, {
resolvers: {
h2: (_, content) => ({
type: BlockTypes.HEADING,
attrs: { level: 99 },
content,
}),
div: (_, content) => ({
type: BlockTypes.PARAGRAPH,
content,
tipTapExtensions: {
heading: Heading.extend({
addAttributes() {
return {
...this.parent?.(),
level: {
parseHTML: () => {
return 99;
},
},
};
},
}),
},
});
Expand All @@ -988,10 +923,6 @@ describe('htmlToStoryblokRichtext', () => {
{ type: 'text', text: 'Custom Heading' },
],
},
{
type: 'paragraph',
content: [{ type: 'text', text: 'Custom Div' }],
},
],
});
});
Expand Down Expand Up @@ -1024,10 +955,10 @@ describe('htmlToStoryblokRichtext', () => {
],
},
{
type: 'horizontal_rule',
type: 'horizontalRule',
},
{
type: 'code_block',
type: 'codeBlock',
attrs: {},
content: [{ type: 'text', text: 'Hello\nworld' }],
},
Expand Down
Loading
Loading