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

Possibility to pass additional props to MDX custom components through the code blocks #69

Closed
FatehAK opened this issue Feb 7, 2023 · 12 comments

Comments

@FatehAK
Copy link

FatehAK commented Feb 7, 2023

Hi, currently when using MDX custom components and rehype-pretty-code, the following props are passed through to the component:

style: { backgroundColor: '#282c34' },
'data-language': 'js',
'data-theme': 'default',

Is is possible to pass additional props to the component? For example:

Consider a code fence:

```js copyEnabled
if (entry[0].boundingClientRect.y < 0) {
    logo.dataset.spin = 'false';
} else {
  if (!isSpinning) logo.dataset.spin = 'true';
  isSpinning = false;
}

Here copyEnabled is custom metadata. This copyEnabled metadata should be available as a prop to the MDX custom component.

This way we can conditionally control the components through markdown very easily.

@atomiks
Copy link
Collaborator

atomiks commented Feb 7, 2023

This would be interesting. So like a custom configuration object to enable conditional logic, like showLineNumbers but for anything? This issue is possibly the same thing?: #60

@FatehAK
Copy link
Author

FatehAK commented Feb 7, 2023

Yep exactly! This would give user full control over rendering of the code fence.

@atomiks
Copy link
Collaborator

atomiks commented Feb 9, 2023

The question is what the syntax should be, especially ensuring things are kept forwards-compatible.

For example, all these are possible:

  • /.../ string highlight
  • {n-n} line highlight
  • showLineNumbers adds data-line-numbers to <code>
  • title="..." adds a separate new block
```js /hello/ /etc/ {4,6-10} showLineNumbers title="..."

Let's say we can feed the existing special syntax as props to <pre> and <code> as well:

/.../ => `stringHighlights` === ['hello', 'etc']
{n-n} => `lineHighlights` === [4, 6, 7, 8, 9, 10]
showLineNumbers  => `showLineNumbers` === true
title="..." => `title` === "..."

If we split the metastring by whitespaces, and if the library wanted to add another "unique" bit of syntax, it could break?

So I guess we could add a new special config, props whose syntax matches JSX attributes?:

```js props={copyEnabled key="value"}

Which would pass copyEnabled: true and key: "value" as props to the pre and code MDX components.

Also considering this is meant to work with plain MD, I don't know if you can even pass props properly unless using data-* attributes. So it might have to be different.

@FatehAK
Copy link
Author

FatehAK commented Feb 9, 2023

Very true and valid points. Since the plugin's main use-case is plain markdown, adding this feature introduces a lot of complexity for little gain.

I thought of something similar to your props approach initially as well where you introduce a new prop meta which would contain all the extra props. Just to try it out I patched the package by adding a new property to the pre tag in the toFragment call:

...other code

pre.properties['data-language'] = lang;
pre.properties['data-theme'] = mode;
pre.properties.meta = my_transformed_meta_string; // (a comma separated string of key=val)

This did work well and I was able to get the prop meta in my custom MDX component for the pre tag. But the problem is for plain markdown the pre tag would have this unnecessary meta attribute added in HTML.

I think in the long run it may be better to have a MDX only plugin which can handle this somehow but even in MDX you will face this issue if you aren't using a custom component for pre.

@FatehAK
Copy link
Author

FatehAK commented Feb 9, 2023

Another idea would be handle this at the config level when defining the plugin. A boolean indicating whether we are using a custom MDX component for our pre tags.

Drawbacks with this would be when people try to mix MDX pre custom components and normal pre tags.

@satya164
Copy link

I had used https://github.com/remcohaszing/rehype-mdx-code-props in another project but unfortunately I couldn't get it to work in combination with rehype-pretty-code. It just didn't find any of the metadata.

@atomiks
Copy link
Collaborator

atomiks commented Nov 11, 2023

Was it placed last?

Note This plugin transforms the hast (HTML) nodes into JSX. After running this plugin, they can no longer be processed by other plugins. To combine it with other plugins, such as syntax highlighting plugins, rehype-mdx-code-props must run last.

@satya164
Copy link

Was it placed last?

Note This plugin transforms the hast (HTML) nodes into JSX. After running this plugin, they can no longer be processed by other plugins. To combine it with other plugins, such as syntax highlighting plugins, rehype-mdx-code-props must run last.

Yup

@satya164
Copy link

Looks like node.data.meta doesn't exist after rehype-pretty-code processes the code, so rehype-mdx-code-props doesn't work.

So I wrote a custom rehype plugin to add data attributes based on the meta:

import { visit } from 'unist-util-visit';

export default function codeblockMeta() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (
        node.tagName === 'pre' &&
        node.children?.length === 1 &&
        node.children[0].tagName === 'code'
      ) {
        const codeblock = node.children[0];
        const meta = codeblock.data?.meta;

        if (meta) {
          const attributes = meta.split(' ').reduce((acc, attribute) => {
            let key, value;

            if (attribute.includes('=')) {
              [key, value] = attribute.split('=');
              value = value.replace(/^"(.+(?="$))"$/, '$1');
            } else {
              key = attribute;
              value = true;
            }

            return Object.assign(acc, { [`data-${key}`]: value });
          }, {});

          Object.assign(node.properties, attributes);
        }
      }
    });
  };
}

This plugin should be included before rehype-pretty-code as to be able to read node.data.meta.

But unfortunately this breaks when using rehype-pretty-code, because it overwrites the properties here when it converts the pre to a div.

As a workaround, I added the following after the Object.assign:

let properties = node.properties;

Object.defineProperty(node, 'properties', {
  set(value) {
    Object.assign(properties, value);
  },
  get() {
    return properties;
  },
});

Super hacky but it ensures that the previous data attributes aren't overwritten. Though there's the other issue that these don't exist on the pre anymore as the pre was converted to a div and is now a wrapper element.

@atomiks
Copy link
Collaborator

atomiks commented Dec 19, 2023

@satya164 hopefully fixes it? #139

@satya164
Copy link

@atomiks thank you! i'll try it out

@atomiks
Copy link
Collaborator

atomiks commented Dec 19, 2023

Should be fixed now to use with https://github.com/remcohaszing/rehype-mdx-code-props

@atomiks atomiks closed this as completed Dec 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants