-
Notifications
You must be signed in to change notification settings - Fork 26
fix(richtext)!: HTML and Markdown parsing for links and edge cases #369
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
Changes from 32 commits
f128bb6
104c75f
b1048b3
055c4b6
6fc767c
8994436
c5855b8
fb1be37
7c6a64a
3a32d14
28fc859
34d6d86
e7c0dce
7bd2283
98e0811
bdc2fcd
df697eb
2ca560c
74555c2
cbf6784
47bfdf3
457ce44
5c6bdaa
603133e
c1963b2
a654352
6b0bfad
a5c1dec
a4edafb
109b338
d97f3c9
45388cd
63438e4
7e651a5
3e37dc7
9d0e62a
e961ced
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| import { | ||
| ComponentBlok, | ||
| richTextResolver, | ||
| type StoryblokRichTextNode, | ||
| type StoryblokRichTextResolvers, | ||
| type StoryblokRichTextOptions, | ||
| } from '@storyblok/js'; | ||
| import { experimental_AstroContainer } from 'astro/container'; | ||
| import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'; | ||
|
|
@@ -13,13 +14,13 @@ let container: null | experimental_AstroContainer = null; | |
| * @experimental Converts a Storyblok RichText field into an HTML string. | ||
| * | ||
| * This API is still under development and may change in future releases. | ||
| * It also relies on Astro’s experimental | ||
| * It also relies on Astro's experimental | ||
| * [experimental_AstroContainer](https://docs.astro.build/en/reference/container-reference/) feature. | ||
| * | ||
| * @async | ||
| * @param {StoryblokRichTextNode} richTextField - The root RichText node to convert. | ||
| * @param {StoryblokRichTextResolvers} [customResolvers] - Optional custom resolvers | ||
| * for customizing how specific nodes or marks are transformed into HTML. | ||
| * @param {StoryblokRichTextOptions['tiptapExtensions']} [tiptapExtensions] - Optional custom | ||
| * tiptap extensions for customizing how specific nodes or marks are rendered. | ||
| * @returns {Promise<string>} A promise that resolves to the HTML string representation | ||
| * of the provided RichText content. | ||
| * | ||
|
|
@@ -36,7 +37,7 @@ let container: null | experimental_AstroContainer = null; | |
| */ | ||
| export const richTextToHTML = async ( | ||
| richTextField: StoryblokRichTextNode, | ||
| customResolvers?: StoryblokRichTextResolvers, | ||
| tiptapExtensions?: StoryblokRichTextOptions['tiptapExtensions'], | ||
| ): Promise<string> => { | ||
| // Create Astro container only once | ||
| if (!container) { | ||
|
|
@@ -45,17 +46,11 @@ export const richTextToHTML = async ( | |
|
|
||
| // Collect async render results keyed by placeholder ID | ||
| const asyncReplacements: Promise<{ id: string; result: string }>[] = []; | ||
| // Build the resolvers object | ||
| const resolvers: StoryblokRichTextResolvers = { | ||
| // Handle async components | ||
| blok: (node) => { | ||
| const componentBody = node.attrs?.body; | ||
| if (!Array.isArray(componentBody)) { | ||
| return ''; | ||
| } | ||
|
|
||
| return componentBody | ||
| .map((blok) => { | ||
| const resolver = richTextResolver<string>({ | ||
| tiptapExtensions: { | ||
| blok: ComponentBlok.configure({ | ||
| renderComponent: (blok: Record<string, unknown>, _id?: string) => { | ||
| if (!blok || typeof blok !== 'object' || !container) { | ||
| return ''; | ||
| } | ||
|
|
@@ -77,15 +72,11 @@ export const richTextToHTML = async ( | |
|
|
||
| asyncReplacements.push(promise); | ||
| return placeholder; | ||
| }) | ||
| .join('\n'); | ||
| }, | ||
| }), | ||
| ...tiptapExtensions, | ||
| }, | ||
|
|
||
| // Add custom resolvers if provided | ||
| ...customResolvers, | ||
| }; | ||
|
|
||
| const resolver = richTextResolver({ resolvers }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Modified file is dead code after export removalLow Severity
Additional Locations (1) |
||
| }); | ||
|
|
||
| let html = resolver.render(richTextField); | ||
| // Wait for all async renders | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,13 +26,13 @@ | |
| "type": "paragraph", | ||
| "content": [ | ||
| { | ||
| "text": "hola@alvarosaburido.dev", | ||
| "text": "jane@example.com", | ||
| "type": "text", | ||
| "marks": [ | ||
| { | ||
| "type": "link", | ||
| "attrs": { | ||
| "href": "hola@alvarosaburido.dev", | ||
| "href": "jane@example.com", | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was checking if
Nice!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, honestly it's a big coincidence hahahaha. Great to know! |
||
| "uuid": null, | ||
| "anchor": null, | ||
| "target": null, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| <script setup lang="ts"> | ||
| import { ref } from 'vue'; | ||
| import type { SbBlokData } from '@storyblok/vue'; | ||
|
|
||
| interface EmojiRandomizerBlok extends SbBlokData { | ||
| label?: string; | ||
| } | ||
|
|
||
| interface Props { | ||
| blok: EmojiRandomizerBlok; | ||
| } | ||
|
|
||
| defineProps<Props>(); | ||
|
|
||
| // List of fun emojis to randomly choose from | ||
| const emojis = ['😊', '🎉', '🚀', '✨', '🌈', '🎨', '🎸', '🎮', '🍕', '🌺']; | ||
|
|
||
| // Reactive state to track current emoji | ||
| const currentEmoji = ref<string>( | ||
| emojis[Math.floor(Math.random() * emojis.length)], | ||
| ); | ||
|
|
||
| /** | ||
| * Generates a new random emoji different from the current one | ||
| */ | ||
| const randomizeEmoji = (): void => { | ||
| let newEmoji: string; | ||
| do { | ||
| newEmoji = emojis[Math.floor(Math.random() * emojis.length)]; | ||
| } while (newEmoji === currentEmoji.value); | ||
|
|
||
| currentEmoji.value = newEmoji; | ||
| }; | ||
| </script> | ||
|
|
||
| <template> | ||
| <div | ||
| v-editable="blok" | ||
| class="flex flex-col items-center gap-6 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg" | ||
| data-test="emoji-randomizer" | ||
| > | ||
| <!-- Large emoji display --> | ||
| <div class="text-6xl"> | ||
| {{ currentEmoji }} | ||
| </div> | ||
|
|
||
| <!-- Randomize button --> | ||
| <button | ||
| class="px-6 py-3 rounded-lg bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white font-small transition-colors duration-200 dark:bg-blue-600 dark:hover:bg-blue-700 dark:active:bg-blue-800" | ||
| @click="randomizeEmoji" | ||
| > | ||
| {{ blok.label || 'Randomize Emoji' }} | ||
| </button> | ||
| </div> | ||
| </template> |


Uh oh!
There was an error while loading. Please reload this page.