Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f128bb6
fix(richtext)!: HTML and Markdown parsing for links and edge cases
maoberlehner Nov 7, 2025
104c75f
Merge branch 'main' into bugfix/richtext-html-parser
alexjoverm Feb 9, 2026
b1048b3
fix(richtext): convert 6 needed nodes to underscode case
alexjoverm Feb 9, 2026
055c4b6
Merge branch 'main' into bugfix/richtext-html-parser
alexjoverm Feb 12, 2026
6fc767c
chore: upgrade playground
alexjoverm Feb 12, 2026
8994436
chore: refactor marks and nodes into a centralized extensions helpers
alexjoverm Feb 12, 2026
c5855b8
chore: revamp the Richtext renderer to use extensions instead of
alexjoverm Feb 12, 2026
fb1be37
test: add edge cases to HTML tests from the registered support issues
alexjoverm Feb 12, 2026
7c6a64a
chore: Update packages/richtext/src/extensions/nodes.ts
alexjoverm Feb 12, 2026
3a32d14
chore: Update packages/richtext/playground/vanilla/.env
alexjoverm Feb 12, 2026
28fc859
chore: revert ignore
alexjoverm Feb 12, 2026
34d6d86
feat: add tiptapExtensions for modern overrides
alexjoverm Feb 12, 2026
e7c0dce
feat: generate linear list of richtext content
dipankarmaikap Feb 18, 2026
7bd2283
Merge branch main' into bugfix/richtext-html-parser
alexjoverm Feb 23, 2026
98e0811
feat!: remove custom resolvers in favor of tiptap extensions
alexjoverm Feb 23, 2026
bdc2fcd
chore: update playgrounds
alexjoverm Feb 23, 2026
df697eb
chore(richtext): add extra tests to cover both-ways functionality
alexjoverm Feb 24, 2026
2ca560c
test(richtext): clean up and purge all unnecessary tests
alexjoverm Feb 24, 2026
74555c2
qs: apply bugbot fixes
alexjoverm Feb 24, 2026
cbf6784
qs: add name and type as context
alexjoverm Feb 24, 2026
47bfdf3
test: ensure the right nodes, marks and extensions are the ones to be
alexjoverm Feb 24, 2026
457ce44
fix: mailto duplication issue
alexjoverm Feb 24, 2026
5c6bdaa
chore: update lockfile
alexjoverm Feb 24, 2026
603133e
qs: fix null outputs, add globals exclusions
alexjoverm Feb 24, 2026
c1963b2
qs: fix textStyle node, fix playgrounds
alexjoverm Feb 24, 2026
a654352
qs: lint playgrounds
alexjoverm Feb 24, 2026
6b0bfad
qs: add thead/tbody support, fix _id issue
alexjoverm Feb 24, 2026
a5c1dec
qs: fix id issue
alexjoverm Feb 24, 2026
a4edafb
chore: normalize null/undefined values, clean up personal data in tests
alexjoverm Feb 25, 2026
109b338
fix: prevent empty values on href, add asTag for better typing
alexjoverm Feb 25, 2026
d97f3c9
chore: fix test:types race condition by run it after build
alexjoverm Feb 25, 2026
45388cd
chore: revert
alexjoverm Feb 25, 2026
63438e4
chore: astro richtext playground update
dipankarmaikap Feb 26, 2026
7e651a5
refactor(astro): drop experimental richTextToHTML
dipankarmaikap Feb 26, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ vite.config.ts.timestamp-*
.cursor
.zed
.rules
.claude
CLAUDE.md
claude-output
.storyblok
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
2 changes: 1 addition & 1 deletion packages/richtext/playground/vanilla/.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VITE_STORYBLOK_TOKEN= <your_storyblok_token>
VITE_STORYBLOK_TOKEN=2cDRejDf3yJ9XZFte2Hzqwtt
202 changes: 165 additions & 37 deletions packages/richtext/playground/vanilla/public/test.html
Original file line number Diff line number Diff line change
@@ -1,33 +1,118 @@
<h1 id="heading-1">Heading 1</h1>
<p>This is a paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
<h2 id="heading-2">Heading 2</h2>
<p>Another paragraph.</p>
<blockquote>
<p>This is a blockquote example.</p>
</blockquote>
<p>Here is some <code>inline code</code> in a sentence.</p>
<p>Here is a <a href="https://www.storyblok.com/">Storyblok link</a> in a sentence.</p>
<p>Strikethrough example: <del>deleted text</del></p>
<p>Line with a hard break here.<br />Next line after break.</p>
<hr />
<pre><code><span class="hljs-keyword">const</span> foo = <span class="hljs-string">'bar'</span><span class="hljs-comment">;</span>
console.<span class="hljs-built_in">log</span>(foo)<span class="hljs-comment">;</span>
</code></pre>
<!-- Richtext Kitchen Sink — every supported node and mark type -->

<!-- ── Headings (h1–h6) ─────────────────────────────────────────────── -->

<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>

<!-- ── Paragraphs ────────────────────────────────────────────────────── -->

<p>This is a plain paragraph.</p>
<p>This paragraph has <strong>bold</strong>, <em>italic</em>, <u>underlined</u>, <s>strikethrough</s>, and <code>inline code</code> text.</p>
<p>This paragraph has <sup>superscript</sup> and <sub>subscript</sub> text.</p>
<p>This paragraph has <mark>highlighted</mark> text.</p>

<!-- ── Combined marks ────────────────────────────────────────────────── -->

<p><strong><em>Bold and italic combined.</em></strong></p>
<p><strong><em><u>Bold, italic, and underlined combined.</u></em></strong></p>
<p><strong>Bold with <em>partial italic</em> inside.</strong></p>

<!-- ── Links ─────────────────────────────────────────────────────────── -->

<p>A <a href="https://www.storyblok.com/" target="_blank">URL link (new tab)</a> in a paragraph.</p>
<p>A <a href="/about" data-uuid="abc-123" data-linktype="story">story link</a> in a paragraph.</p>
<p>A <a href="/about" data-uuid="abc-123" data-anchor="section-1" data-linktype="story">story link with anchor</a> in a paragraph.</p>
<p>An <a href="mailto:info@storyblok.com" data-linktype="email">email link</a> in a paragraph.</p>
<p>An <a href="https://a.storyblok.com/f/000000/sample.pdf" data-linktype="asset">asset link</a> in a paragraph.</p>
<p>A <a href="tel:+44 3457 911 911">telephone link</a> in a paragraph.</p>
<p><strong><a href="https://example.com">Bold link</a></strong> and <em><a href="https://example.com">italic link</a></em>.</p>
<p><a href="https://example.com"><strong>Bold</strong>, normal, and <em>italic</em> inside link</a>.</p>

<!-- ── Link inside heading (Porsche migration case) ──────────────────── -->

<h2>Heading with <a href="https://example.com">a link</a> inside</h2>

<!-- ── Anchor mark ───────────────────────────────────────────────────── -->

<p><span id="my-anchor">Text with an anchor mark.</span></p>

<!-- ── Bullet list ───────────────────────────────────────────────────── -->

<ul>
<li>Bullet item 1</li>
<li>Bullet item 2 with <strong>bold</strong></li>
<li>Bullet item 3
<ul>
<li>Nested bullet item A</li>
<li>Nested bullet item B</li>
</ul>
</li>
</ul>

<!-- ── Ordered list ──────────────────────────────────────────────────── -->

<ol>
<li>Ordered item 1</li>
<li>Ordered item 2 with <em>italic</em></li>
<li>Ordered item 3
<ol>
<li>Nested ordered item A</li>
</ol>
</li>
</ol>

<!-- ── Mixed nested list ─────────────────────────────────────────────── -->

<ul>
<li>Unordered list item 1</li>
<li>Unordered list item 2</li>
<li><p>Unordered list item 3</p></li>
<li><p>Ordered list item 1</p></li>
<li>Ordered list item 2</li>
<li>Ordered list item 3</li>
<li>Bullet parent
<ol>
<li>Ordered child 1</li>
<li>Ordered child 2</li>
</ol>
</li>
</ul>
<p>
<img
src="https://a.storyblok.com/f/279818/710x528/c53330ed26/tresjs-doge.jpg"
alt="Alt text for image"
title="Image Title"
/>
</p>

<!-- ── Blockquote ────────────────────────────────────────────────────── -->

<blockquote>
<p>This is a blockquote with <em>italic</em> text inside.</p>
</blockquote>

<blockquote>
<blockquote>
<p>Nested blockquote.</p>
</blockquote>
</blockquote>

<!-- ── Code block ────────────────────────────────────────────────────── -->

<pre><code class="language-javascript">function greet(name) {
return `Hello, ${name}!`;
}

console.log(greet('Storyblok'));</code></pre>

<pre><code>Code block without a language.</code></pre>

<!-- ── Horizontal rule ───────────────────────────────────────────────── -->

<hr>

<!-- ── Hard break ────────────────────────────────────────────────────── -->

<p>Line one<br>Line two (after hard break)<br>Line three</p>

<!-- ── Image ─────────────────────────────────────────────────────────── -->

<img src="https://a.storyblok.com/f/279818/710x528/c53330ed26/tresjs-doge.jpg" alt="Placeholder image" title="A sample image">

<!-- ── Table ─────────────────────────────────────────────────────────── -->

<table>
<thead>
<tr>
Expand All @@ -38,19 +123,62 @@ <h2 id="heading-2">Heading 2</h2>
</thead>
<tbody>
<tr>
<td>R1C1</td>
<td>R1C2</td>
<td>R1C3</td>
<td>Cell 1</td>
<td><strong>Bold cell</strong></td>
<td>Cell 3</td>
</tr>
<tr>
<td><strong>R2C1</strong></td>
<td><em>R2C2</em></td>
<td>R2C3</td>
<td><em>Italic cell</em></td>
<td><a href="https://example.com">Link cell</a></td>
<td><code>Code cell</code></td>
</tr>
<tr>
<td>R3C1</td>
<td>R3C2</td>
<td>R3C3</td>
<td colspan="2">Colspan 2 cell</td>
<td>Normal cell</td>
</tr>
</tbody>
</table>

<!-- ── Table without thead ───────────────────────────────────────────── -->

<table>
<tbody>
<tr>
<td>Body-only A</td>
<td>Body-only B</td>
</tr>
</tbody>
</table>

<!-- ── Details / Accordion ───────────────────────────────────────────── -->

<details>
<summary>Click to expand</summary>
<p>Hidden content inside a details element.</p>
</details>

<!-- ── Realistic CMS migration (Porsche-like) ────────────────────────── -->

<h1>Product Overview</h1>
<p>Welcome to our <strong>product page</strong>. For more details, visit <a href="https://example.com/docs" target="_blank">our documentation</a>.</p>
<h2>Features</h2>
<ul>
<li>Fast performance with <code>O(1)</code> lookups</li>
<li>Built-in <em>type safety</em></li>
<li>Comprehensive API</li>
</ul>
<h2>Pricing</h2>
<table>
<thead>
<tr><th>Plan</th><th>Price</th></tr>
</thead>
<tbody>
<tr><td>Free</td><td>$0/mo</td></tr>
<tr><td>Pro</td><td><strong>$29/mo</strong></td></tr>
</tbody>
</table>
<h2>Contact</h2>
<p>Email us at <a href="mailto:support@example.com">support@example.com</a> or call <a href="tel:+1-555-0123">+1-555-0123</a>.</p>
<hr>
<blockquote><p>The best product I've ever used! — <em>Customer Review</em></p></blockquote>
<pre><code class="language-bash">npm install @example/product</code></pre>
Loading
Loading