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

Option to pass content as prop #129

Open
yohlime opened this issue Feb 1, 2024 · 7 comments · Fixed by #168
Open

Option to pass content as prop #129

yohlime opened this issue Feb 1, 2024 · 7 comments · Fixed by #168
Labels
has workaround The workaround exists, thus further improvements needed

Comments

@yohlime
Copy link

yohlime commented Feb 1, 2024

It turns out that Astro has a built-in component that provides syntax highlighting for code blocks (<Code />). However, the string that needs to be converted should be passed as a prop, making it incompatible with your current implementation.

The following won't work:

---
import { Code } from "astro:components";
---
<RichTextRenderer
  content={text}
  schema={{
    nodes: {
      ...,
      code_block: ({ attrs }) => ({
        component: Code,
        props: {
          lang: attrs.class?.split("-")[1],
          theme: "solarized-dark",
        },
      }),
      ...
    },
    ...
  }}
  ...
/>

I've implemented a workaround by introducing a "metaprop" called contentPropName to specify the prop name to which the content will be passed. For instance, the following will render correctly because the component requires the content to be passed to a prop called code:

---
import { Code } from "astro:components";
---
<RichTextRenderer
  content={text}
  schema={{
    nodes: {
      ...,
      code_block: ({ attrs }) => ({
        component: Code,
        props: {
          lang: attrs.class?.split("-")[1],
          theme: "solarized-dark",
+         contentPropName: "code",
        },
      }),
      ...
    },
    ...
  }}
  ...
/>

I can create a pull request for these changes, or perhaps you have other suggestions?

@edvinasjurele
Copy link
Collaborator

@atsuyaourt Thank you for pointing this out.

I see the problem, currently RichTextRenderer.astro only allows content be passed as children <Component {...props}>{resolvedContent}</Component>;.

The solution provided is one of the ways, though I am not super sure if this is the most intuitive way. However, adding some JSDoc comments as a clear explanation should help the users out. We should not forget an example in demo app with this Astro native Code component.

Let me take a look into alternatives.

@Edo-San
Copy link
Contributor

Edo-San commented Mar 7, 2024

I stumbled upon the exact same issue! 😅

Some random thoughts

Using slots is the easiest way to make the implementation recursive. Aside from that, the "leaves" of the recursion tree might need to be rendered via props. If you render a node mapping its content to any Component props, I'd say that 99% of the time it's where recursion stops and you're rendering an atom component. I think that's what we're talking about, right now.

In order for this to be possible:

  • The resolverFn should accept the whole node as a param so that one could map portions of the content to different props, if needed
  • There should be some way to determine if we want to render the content in a slot or as props at the RichTextRenderer level 🤔 Or maybe the content will always be slotted, but the Component will either 1) rely only on props and ignore the slot 2) use named slots, if there's the need to slot something

WDYT?

@edvinasjurele
Copy link
Collaborator

edvinasjurele commented Mar 7, 2024

Hey guys, we surely need to solve this somehow 💪

I was thinking of either going the way the contentPropName way or alternatively just passing content things to resolverFn to give full flexibility. However, this "leaves" the recursion tree which may potentially impose an issue of not rendering stuff down the tree. We must think about this aspect, and provide examples in the demo app, but I am also pretty certain such cases might be about the handling of nodes at the very end of the tree.

Maybe

<RichTextRenderer
  text={{
    type: "doc";
    content: ...; // the passed content
  }}
  schema={{ ... }} // same schema
/>

There is also <Astro.self (ref) to call file itself when user has rich text configuration like in the demo https://github.com/atsuyaourt/storyblok-rich-text-astro-renderer/blob/main/demo/src/storyblok/RichText.astro (did not go into it that deep, so have to doublecheck, but from top of my head)

@edvinasjurele
Copy link
Collaborator

Hey @atsuyaourt and @Edo-San check the #168

Thanks @Edo-San a lot for the help and POC!

@yohlime
Copy link
Author

yohlime commented Mar 8, 2024

Thank you so much for these insights! I agree that exposing the whole Node object is a better approach to solving this issue. The PR looks good to me, and I can't wait to try it asap!

@edvinasjurele
Copy link
Collaborator

edvinasjurele commented Mar 26, 2024

Getting back to this issue, it seems the node object expose that we recently implemented, does not entirely help in all scenarios due to when content is handled manually we are escaping the rendering cycle of RichTextRenderer and we must somehow handle that content ourselves:

heading: ({ attrs: { level }, content }) => ({
  component: Text,
  props: {
    variant: `h${level}`,
    text: content?.[0].text,  // here we cannot handle content array easily in some case, thus this works only for some minor or end-of-tree nodes which does not contain complicated nodes.
  },
}),

However in complicated nodes like bullet_list / ordered_list, where list_item can be anything, not only text but also other block nodes (ref). see types:

type ListItem = {
  type: "list_item";
  content?: Array<
    Paragraph | Blok | BulletList | OrderedList | HorizontalRule | Image | Emoji
  >;
};

type BulletList = {
  type: "bullet_list";
  content?: Array<ListItem>;
};

export type OrderedList = {
  type: "ordered_list";
  attrs: {
    order: number;
  };
  content?: Array<ListItem>;
};

This implies that @atsuyaourt proposed idea of having contentPropName field would make sense here, as it would not touch the content directly, but rather leave an indication how to handle content changing from slot strategy to prop.

We have to give this ticket a second go and maybe potentially add this handling possibility too 🤔 Re-opening ticket for now.

@edvinasjurele edvinasjurele reopened this Mar 26, 2024
@edvinasjurele edvinasjurele added the has workaround The workaround exists, thus further improvements needed label Mar 26, 2024
@edvinasjurele
Copy link
Collaborator

edvinasjurele commented Mar 26, 2024

As a workaround for now, we do have a custom RichTextRenderer.astro in the repository with extending of:

if (type === 'bullet_list' || type === 'ordered_list') {
  return <Component {...props} items={resolvedContent} />;
}        

and then resolvers configuration is:

bullet_list: ({ type }) => {
    return {
      type,
      component: List,
      props: {
        styleType: 'disc',
      },
    };
  },
  ordered_list: ({ type }) => {
    return {
      type,
      component: List,
      props: {
        styleType: 'numbered',
      },
    };
  },
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has workaround The workaround exists, thus further improvements needed
Projects
None yet
3 participants