I'm using Storybook to document some html components.
However, in order to create a story in mdx I have to use the Story component, meaning I can't use raw html and instead have to return my markup as a string:
import { Story } from '#storybook/addon-docs/blocks';
<Story name="Simple Button">
{`<button class="my-button">Download Now</button>`}
</Story>
Which renders the source code as a string:
Is there a way I can return clean markup or have the source show as true html? The source showing quotes and lack of syntax highlighting makes for poor documentation.
It seems that since v6.0.0-alpha.22 you can render any custom source code in Storybook.
Using the Canvas block's params object you can set custom source for your story.
import { Story, Canvas } from '#storybook/addon-docs/blocks';
<Canvas>
<Story name="custom source" height="100px" parameters={{ docs: { source: { code: `<button class="my-button">Download Now</button>` } } }}>
{`<button class="my-button">Download Now</button>`}
</Story>
</Canvas>
This renders the button in the story, and the source code uses raw HTML:
Related
What I'm trying to do
A simple way to render the content of a markdown file when it's passed as a string to another component using .compiledContent (or even using .rawContnent)? Or even a better way than this as obviously usually in Astro we can use the <Content /> Component, but from my knowledge, I can't pass a component or this functionality to another component without using a <slot /> in the parent component.
I have some JS for the parent component and using a <slot/> instead of passing the props to the component would change things, so hopefully looking for a solution with using this.
My setup
Data stored in /src/data/experience as markdown files with a year and a description formatted as markdown in the content section of each file
A component called Tabs.astro which takes props of headings and contents which are both lists of strings
A page /src/pages/experience.astro with the Tabs component in it which is displaying this data
I take the below code to get the data from the markdown files and pass the years and descriptions to the Tab component.
experience.astro
---
import Tabs from "../components/Tabs.astro";
const jobs = await Astro.glob("../data/experience/*.md");
const years = jobs.map((job) => job.frontmatter.year);
const descriptions = jobs.map((job) => job.compiledContent);
---
<!-- My component taking the data to be rendered -->
<Tabs headings={years} contents={descriptions} />
Tabs.astro
And the component renders the info like so
<!-- Tabs -->
<div class="tabs">
<ul class="tabs-header">
{
headings.map((heading) => (
<li>{heading}</li>
))
}
</ul>
<ul class="tabs-content">
{contents.map((content) => <li class="tab">{content}</li>)}
</ul>
</div>
My current solution
At the moment using .compiledContent gets me the correct HTML, however it is all in a string so the HTML doesn't actually render.
What I'm looking for
Is there a native way in Astro to pass markdown as a prop to a component?
If not is there a manual and recommended way in Astro to convert a markdown string and sanitise it to protect against XSS attacks? (if this is a risk in Astro when rendered statically?)
If not what are your most recommended ways to render markdown and sanitise it in JS?
Thanks so much for your time and help! I'm loving using Astro
p.s Also happy to concede and just use a <slot/> in my component if needed... ;)
Astro has a set:html directive you can use in combination with a Fragment like this
<Fragment set:html={post.compiledContent()}/>
After a bit of struggling with this myself, the current solution from the Astro docs for a single file without looping is the following.
Import your file with {Content as YourAliasName} from '../yourPath/yourFileName.md'
Then just use it as a tag <YourAliasName />
Example from the docs for reference:
---
import {Content as PromoBanner} from '../components/promoBanner.md';
---
<PromoBanner />
https://docs.astro.build/en/guides/markdown-content/#the-content-component
I have blog page on multiple languages and I use react-i18next library for translation.
I have one component called BlogPostPage where I show each post when it's opened, inside the component there is part for showing blog text like this:
import { useTranslation } from "react-i18next";
const [t] = useTranslation(["translation1", "overview"]);
..........
<Typography mb={2} component="p" variant="subtitle1">
{t(`text${state.id}1`)}
</Typography>
and my json translation file looks like this
{
"text51":"<h4>Welcome to our application</h4>",
}
So I want to put html code inside translation text since different post has different html code it really needs to be in json file and not in the component... is there any way that can be done?
Output of my code is:
<h4>Welcome to our application</h4>
Use the Trans component: https://react.i18next.com/latest/trans-component
<Trans i18nKey="text51">
<h4>Welcome to our application</h4>
</Trans>
With <0> instead of <h4>
"text51": "<0>Welcome to our application</0>"
Your issue is that react will try to escape any html it find in strings like this. If you absolutely need to do this you can use the following method:
<div dangerouslySetInnerHTML={{__html: t(`text${state.id}1`)}} />
Beware that there is a reason that react calls this prop dangerouslySetInnerHTML, and doing this is very much an anti-pattern.
I have a page built with React (NextJS) and I am pulling some markup string content from Wordpress and inserting it into my JSX, like so:
...
<div className="wrapper">
<p
className="text-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
></p>
</div>
...
Now, the markup possibly contains links and I want to open all those links on new tab. So I tried:
...
<div className="wrapper">
<base target="_blank" />
<p
className="text-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
></p>
</div>
...
and all links in the markup are opened on new tab so, great. But the problem is that all other links in the page including those outside the div.wrapper element are opened in new tabs (since <base /> is scoped to the entire page) and I'll like to prevent this.
Since I can't use multiple <base /> on the same page, the other option I'm aware of is to loop through anchor tags of interest with document.querySelector(".wrapper a") and add the target attribute to all of them but, in React it's an anti-pattern to modify the DOM directly.
So I'm not sure how best to proceed. What do I do?
You can use DOMParser API to achieve that.
Here's a little snippet
const parser = new DOMParser();
const htmlText = `<div>Url link</div>`;
let content = parser.parseFromString(htmlText, "text/html");
const anchors = content.getElementsByTagName('a');
Array.from(anchors).forEach(a => {
a.setAttribute("target", "_blank");
})
console.log(content.body.innerHTML); // Here it is your new string
The code may need to be improved a bit, I've just typed this out of MDN example and I didn't have time to test it. Does this work?
Well first of all base element should only be inserted in html head element, and not inside the body html element, you could do that imperatively or using the react-helmet library - if you still need to use it.
dangerouslySetInnerHTML is in itself an imperative pice of code, but sometimes its the only possible solutions for a certain use cases, now regarding the links you could either do it using imperative code in a useEffect or componentDidMount, or you code use react-html-parser which will enable you to modify dom elements in a more declarative fashion - i say in a more declarative fashion because while its a react component in practice its still more imperative than its declarative in nature, but still better than custom code running in useEffect or componentDidMount
I have a DatoCms site using GatsbyJS, which includes a markdown editor field using a markdown editor.
This is formatted as bullet points on the back end.
However, on screen it renders as this
How do I make the text render correctly as bullet points?
Here is the code:
import React from 'react'
import { Link, graphql } from 'gatsby'
import Masonry from 'react-masonry-component'
import Img from 'gatsby-image'
import Layout from "../components/layout"
import SEO from '../components/SEO'
const SkillsPage = ({ data : { skillsPage }}) => (
<Layout>
<SEO/>
<div className="showcase">
<h1 className="sheet__title">{skillsPage.title}</h1>
<div>
{skillsPage.skills}
</div>
</div>
</Layout>
)
export default SkillsPage
export const query = graphql`
query SkillsPageQuery {
skillsPage: datoCmsSkillPage {
title
skills
}
}
`
And the HTML output:
It seems related to styles issue. You can try to display them as a block or within a flex container.
One thing I've faced recently using DatoCMS and its WYSIWYG is that paragraphs are not displayed properly, not inheriting the line breaks. It can be easily fixed by using the CSS property white-space: pre-line to them to fix it. Maybe it fixes this issue too.
More information about white-space property can be found in MDN documentation.
With the new information provided, your issue is the way you render the content. You should use dangerouslySetInnerHTML to print automatically what's inside the WYSIWYG (markdown or rich text)
<div dangerouslySetInnerHTML={{ __html: skillsPage.skills }} />
After this, if you have display or layout issues, you can check for the CSS properties that I've explained.
If you had an object/array to loop (array of skills) you should print them by:
{skillsPage.skills.map(skill => <li key={skill}>{skill}</li)}
More information about dangerouslySetInnerHTML from the official React documentation.
I'm curious what's the best way to use a regular JavaScript library (not written as a React component) inside a React environment.
For example, let's say there's a JavaScript library that embeds a simple widget to my webpage. The instructions are as follows:
Include the loading tag in the header.
Embed the snippet anywhere you want.
In a normal webpage, I would do the following:
<head>
<script src="http://karaoke.com/source.js"></script>
</head>
<body>
<h1>Hello look at my cool widget library</h1>
<karaoke width="600" height="400" />
</body>
How do I achieve the same effect where I have a React component like this?
class MainView extends Component {
render() {
return (
<div>
<h1>I want to show my karaoke widget here, but how?</h1>
</div>
);
}
}
The main purpose of JSX is to feel like HTML. The main purpose of render in a React component is to render that "fake" (virtual) HTML. If your external widget is also a React component, the solution is straightforward:
class MainView extends Component {
render() {
return (
<div>
<h1>Hello look at my cool widget library</h1>
<Widget width="600" height="400" />
</div>
);
}
}
All you have to make sure is that the code above runs after your widget script is loaded (so the Widget component is present). The exact solution to this would depend on your build system.
If your external library is not a React component, then you cannot use a custom widget tag and you must render actual HTML.
The big trick is to not return from render but manually render ourselves after the widget initializes what it needs:
class MainView extends Component {
render() {
// don't render anything
return <div/>;
},
componentDidMount() {
// find the DOM node for this component
const node = ReactDOM.findDOMNode(this);
// widget does stuff
$(node).activateMyCoolWidget();
// start a new React render tree with widget node
ReactDOM.render(<div>{this.props.children}</div>, node);
}
});
Take a look at portals for more details.