remarkjs / react-markdown

Markdown component for React
https://remarkjs.github.io/react-markdown/
MIT License
13.31k stars 879 forks source link

Support custom link attributes through markdown #540

Closed codler closed 3 years ago

codler commented 3 years ago

Subject of the feature

Support custom attributes through markdown

Example syntax

[link](url){:target="_blank"}

Problem

https://stackoverflow.com/a/4705645/304894 Im my case I want to be able to set different rel attribute from markdown

Expected behavior

I would get the attributes from renderers

ChristianMurphy commented 3 years ago

By default react-markdown supports CommonMark only. However, syntax extensions/plugins can be used to add new syntax features https://github.com/remarkjs/remark/blob/main/doc/plugins.md The particular syntax you are looking for appears to be https://github.com/arobase-che/remark-attr, which can work with react-markdown but needs an update to do so https://github.com/arobase-che/remark-attr/issues/22

david-sharer commented 3 years ago

For anyone else who runs into this, I got this working with a custom component for my scenario (I needed call-to-action button-links). All buttons that have \ as the first-child [**text here**](... "custom:attribute title text") are rendered as buttons and their title text is parsed for custom attribute strings. Highly limited, but worked for me! image

// typescript component rendering
import React, { ReactElement } from "react";
import ReactMarkdown from "react-markdown";
import { Components, ReactBaseProps, ReactMarkdownProps } from "react-markdown/src/ast-to-react";
import remarkGfm from "remark-gfm";
import { first, fromPairs } from "lodash";
import { Button } from "@material-ui/core";

const components: Components = {
  a: ({
    node,
    children,
    href,
    title,
    ...props
  }: ReactBaseProps & ReactMarkdownProps & { href: string; title: string }) => {
    // if the first child of the button is a bold element, make this a CTA button
    if ((first(children) as ReactElement)?.type === "strong") {
      const splitTitle = title.split(' ');
      const titleAttrEntries = splitTitle.filter(t => t.includes(':'));
      const remainingTitleEntries = splitTitle.filter(t => !t.includes(':'));
      const finalTitle = remainingTitleEntries.join(' ');
      const titleAttrPairs = titleAttrEntries.map(te => te.split(':'))
      const titleAttrs = fromPairs(titleAttrPairs);
      return (
        <Button variant="contained" href={href} title={finalTitle} {...props} {...titleAttrs}>
          {children}
        </Button>
      );
    }

    return <a href={href} title={title} children={children} {...props}></a>;
  },
};

export function renderMarkdown(markdown: string) {
  return (
    <ReactMarkdown
      plugins={[remarkGfm]}
      children={markdown}
      components={components}/>
  );
}
<!-- markdown -->
[Regular Link](/where_to_go/)

[**CTA Button Link**](/where_to_go/ "color:primary any:attribute things without colons will be treated as the title")

output image

Ram-sys179 commented 1 year ago

@david-sharer Looks like the ReactBaseProps is deprecated??

yuri2peter commented 1 year ago

{:target="_blank"}

For anyone else who runs into this, I got this working with a custom component for my scenario (I needed call-to-action button-links). All buttons that have \<strong> as the first-child [**text here**](... "custom:attribute title text") are rendered as buttons and their title text is parsed for custom attribute strings. Highly limited, but worked for me! image

// typescript component rendering
import React, { ReactElement } from "react";
import ReactMarkdown from "react-markdown";
import { Components, ReactBaseProps, ReactMarkdownProps } from "react-markdown/src/ast-to-react";
import remarkGfm from "remark-gfm";
import { first, fromPairs } from "lodash";
import { Button } from "@material-ui/core";

const components: Components = {
  a: ({
    node,
    children,
    href,
    title,
    ...props
  }: ReactBaseProps & ReactMarkdownProps & { href: string; title: string }) => {
    // if the first child of the button is a bold element, make this a CTA button
    if ((first(children) as ReactElement)?.type === "strong") {
      const splitTitle = title.split(' ');
      const titleAttrEntries = splitTitle.filter(t => t.includes(':'));
      const remainingTitleEntries = splitTitle.filter(t => !t.includes(':'));
      const finalTitle = remainingTitleEntries.join(' ');
      const titleAttrPairs = titleAttrEntries.map(te => te.split(':'))
      const titleAttrs = fromPairs(titleAttrPairs);
      return (
        <Button variant="contained" href={href} title={finalTitle} {...props} {...titleAttrs}>
          {children}
        </Button>
      );
    }

    return <a href={href} title={title} children={children} {...props}></a>;
  },
};

export function renderMarkdown(markdown: string) {
  return (
    <ReactMarkdown
      plugins={[remarkGfm]}
      children={markdown}
      components={components}/>
  );
}
<!-- markdown -->
[Regular Link](/where_to_go/)

[**CTA Button Link**](/where_to_go/ "color:primary any:attribute things without colons will be treated as the title")

output image

Very useful, thanks