shared components and utils for sanity pages
This commit is contained in:
parent
39655daf6a
commit
e9d21ddd62
11 changed files with 350 additions and 136 deletions
|
@ -27,6 +27,7 @@ module.exports = {
|
|||
extends: 'eslint:recommended',
|
||||
env: {
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
// Required when using experimental EcmaScript features
|
||||
parser: '@babel/eslint-parser',
|
||||
|
|
43
sites/org/components/sanity/author.mjs
Normal file
43
sites/org/components/sanity/author.mjs
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { SanityMdxWrapper } from './mdx-wrapper.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
export const Author = ({ author = {} }) => {
|
||||
const { t } = useTranslation(['posts'])
|
||||
return (
|
||||
<div id="author" className="flex flex-col lg:flex-row m-auto p-2 items-center">
|
||||
<div className="theme-gradient w-40 h-40 p-2 rounded-full aspect-square hidden lg:block">
|
||||
<div
|
||||
className={`
|
||||
w-lg bg-cover bg-center rounded-full aspect-square
|
||||
hidden lg:block
|
||||
`}
|
||||
style={{ backgroundImage: `url(${author.image})` }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="theme-gradient p-2 rounded-full aspect-square w-40 h-40 lg:hidden m-auto">
|
||||
<img
|
||||
className={`block w-full h-full mx-auto rounded-full`}
|
||||
src={author.image}
|
||||
alt={author.displayname}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
text-center p-2 px-4 rounded-r-lg bg-opacity-50
|
||||
lg:text-left
|
||||
`}
|
||||
>
|
||||
<p
|
||||
className="text-xl"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('xMadeThis', { x: author.displayname }),
|
||||
}}
|
||||
/>
|
||||
<div className="prose mdx">
|
||||
<SanityMdxWrapper MDX={author.about} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
29
sites/org/components/sanity/mdx-wrapper.mjs
Normal file
29
sites/org/components/sanity/mdx-wrapper.mjs
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { compile, run } from '@mdx-js/mdx'
|
||||
import * as runtime from 'react/jsx-runtime' // Production.
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { PlainMdxWrapper } from 'shared/components/wrappers/mdx.mjs'
|
||||
|
||||
export const useEvaledMdx = (mdxStr = '') => {
|
||||
const [mdxModule, setMdxModule] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
const code = await compile(mdxStr, {
|
||||
outputFormat: 'function-body',
|
||||
development: false,
|
||||
})
|
||||
const evaled = await run(code, runtime)
|
||||
setMdxModule(() => evaled.default)
|
||||
})()
|
||||
}, [mdxStr])
|
||||
|
||||
return mdxModule
|
||||
}
|
||||
|
||||
export const MdxEvalWrapper = ({ MDX = false, components = {}, site = 'org' }) => {
|
||||
const evaled = useEvaledMdx(MDX)
|
||||
return <PlainMdxWrapper {...{ MDX: evaled, components, site }} />
|
||||
}
|
||||
|
||||
export const SanityMdxWrapper = MdxEvalWrapper
|
80
sites/org/components/sanity/page-wrapper.mjs
Normal file
80
sites/org/components/sanity/page-wrapper.mjs
Normal file
|
@ -0,0 +1,80 @@
|
|||
import Head from 'next/head'
|
||||
import { PageLink } from 'shared/components/page-link.mjs'
|
||||
import { Lightbox } from 'shared/components/lightbox.mjs'
|
||||
import { ImageWrapper } from 'shared/components/wrappers/img.mjs'
|
||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||
import { Author } from './author.mjs'
|
||||
import { TimeAgo } from 'shared/components/wrappers/mdx.mjs'
|
||||
import { SanityMdxWrapper } from './mdx-wrapper.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
export const ns = ['common', 'posts', ...pageNs]
|
||||
|
||||
export const SanityPageWrapper = ({
|
||||
post = {},
|
||||
author = {},
|
||||
page = {},
|
||||
namespaces = ['common'],
|
||||
}) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
return (
|
||||
<PageWrapper title={post.title} {...page}>
|
||||
<Head>
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
<meta property="og:description" content={post.intro || post.title} key="description" />
|
||||
<meta property="og:article:author" content={author.displayname} key="author" />
|
||||
<meta property="og:url" content={`https://freesewing.org/blog/${post.slug}`} key="url" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://canary.backend.freesewing.org/og-img/en/dev/blog/${post.slug}`}
|
||||
key="image"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:locale" content="en_US" key="locale" />
|
||||
<meta property="og:site_name" content="freesewing.org" key="site" />
|
||||
</Head>
|
||||
<article className="mb-12 px-8 max-w-7xl">
|
||||
<div className="flex flex-row justify-between text-sm mb-1 mt-2">
|
||||
<div>
|
||||
<TimeAgo date={post.date} t={t} /> [{post.date}]
|
||||
</div>
|
||||
<div>
|
||||
{post.designs?.map((design) => (
|
||||
<PageLink
|
||||
href={`/showcase/designs/${design}`}
|
||||
txt={design}
|
||||
key={design}
|
||||
className="px-2 capitalize"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
By{' '}
|
||||
<a href="#maker" className="text-secondary hover:text-secondary-focus">
|
||||
{author.displayname || 'FIXME: No displayname'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<figure>
|
||||
<Lightbox>
|
||||
<ImageWrapper>
|
||||
<img src={post.image} alt={post.caption} className="shadow m-auto" />
|
||||
</ImageWrapper>
|
||||
<figcaption
|
||||
className="text-center mb-8 prose m-auto"
|
||||
dangerouslySetInnerHTML={{ __html: post.caption }}
|
||||
/>
|
||||
</Lightbox>
|
||||
</figure>
|
||||
<div className="strapi prose lg:prose-lg mb-12 m-auto">
|
||||
<SanityMdxWrapper MDX={post.body} />
|
||||
</div>
|
||||
<div className="max-w-prose text-lg lg:text-xl">
|
||||
<Author author={author} />
|
||||
</div>
|
||||
</article>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
|
@ -3,7 +3,7 @@ import { createClient, groq } from 'next-sanity'
|
|||
const sanityId = process.env.SANITY_PROJECT || 'hl5bw8cj'
|
||||
|
||||
let sanityClient
|
||||
export const sanityLoader = ({ query, language, type, slug }) => {
|
||||
export const sanityLoader = ({ query, language, type, slug, order }) => {
|
||||
sanityClient =
|
||||
sanityClient ||
|
||||
createClient({
|
||||
|
@ -20,6 +20,10 @@ export const sanityLoader = ({ query, language, type, slug }) => {
|
|||
query += ']'
|
||||
}
|
||||
|
||||
if (order) {
|
||||
query += ` | order(${order})`
|
||||
}
|
||||
|
||||
return sanityClient.fetch(query)
|
||||
}
|
||||
|
|
@ -1,113 +1,12 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||
import { PlainMdxWrapper, TimeAgo } from 'shared/components/wrappers/mdx.mjs'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import Head from 'next/head'
|
||||
import { Lightbox } from 'shared/components/lightbox.mjs'
|
||||
import { ImageWrapper } from 'shared/components/wrappers/img.mjs'
|
||||
import { SanityPageWrapper, ns as sanityNs } from 'site/components/sanity/page-wrapper.mjs'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { sanityLoader, sanityImage } from 'shared/strapi/loader.js'
|
||||
import { strapiImage } from 'shared/utils.mjs'
|
||||
|
||||
const strapi = 'https://posts.freesewing.org'
|
||||
const namespaces = [...sanityNs]
|
||||
|
||||
const Author = ({ author }) => {
|
||||
return (
|
||||
<div id="author" className="flex flex-col lg:flex-row m-auto p-2 items-center">
|
||||
<div className="theme-gradient w-40 h-40 p-2 rounded-full aspect-square hidden lg:block">
|
||||
<div
|
||||
className={`
|
||||
w-lg bg-cover bg-center rounded-full aspect-square
|
||||
hidden lg:block
|
||||
`}
|
||||
style={{ backgroundImage: `url(${strapi}${author?.image?.sizes?.small?.url})` }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="theme-gradient p-2 rounded-full aspect-square w-40 h-40 lg:hidden m-auto">
|
||||
<img
|
||||
className={`block w-full h-full mx-auto rounded-full`}
|
||||
src={`${strapi}${author?.image?.sizes.small.url}`}
|
||||
alt={author?.displayname}
|
||||
width={author?.image?.sizes.small.w}
|
||||
height={author?.image?.sizes.small.h}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
text-center p-2 px-4 rounded-r-lg bg-opacity-50
|
||||
lg:text-left
|
||||
`}
|
||||
>
|
||||
<p className="text-xl">
|
||||
<span className="font-semibold"> {author?.displayname}</span>
|
||||
<span className="text-sm pl-2 opacity-70">Wrote this</span>
|
||||
</p>
|
||||
<div className="prose mdx">
|
||||
<PlainMdxWrapper compile includeMeta={false}>
|
||||
{author.about}
|
||||
</PlainMdxWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const BlogPostPage = ({ post, author }) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
return (
|
||||
<PageWrapper title={post?.title}>
|
||||
<Head>
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
<meta property="og:description" content={post.intro || post.title} key="description" />
|
||||
<meta property="og:article:author" content={author.displayname} key="author" />
|
||||
<meta property="og:url" content={`https://freesewing.org/blog/${post.slug}`} key="url" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://canary.backend.freesewing.org/og-img/en/dev/blog/${post.slug}`}
|
||||
key="image"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:locale" content="en_US" key="locale" />
|
||||
<meta property="og:site_name" content="freesewing.dev" key="site" />
|
||||
</Head>
|
||||
<article className="mb-12">
|
||||
<div className="flex flex-row justify-between text-sm mb-1 mt-2">
|
||||
<span>
|
||||
<TimeAgo date={post.date} t={t} /> [{post.date}]
|
||||
</span>
|
||||
<span>
|
||||
By{' '}
|
||||
<a href="#author" className="text-secondary hover:text-secondary-focus">
|
||||
{author.displayname || 'FIXME: No displayname'}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<figure>
|
||||
<Lightbox>
|
||||
<ImageWrapper>
|
||||
<img src={post.image} alt={post.caption} className="shadow m-auto" />
|
||||
</ImageWrapper>
|
||||
<figcaption
|
||||
className="text-center mb-8 prose m-auto mt-1"
|
||||
dangerouslySetInnerHTML={{ __html: post.caption }}
|
||||
/>
|
||||
</Lightbox>
|
||||
</figure>
|
||||
<div className="strapi prose lg:prose-lg mb-12 m-auto">
|
||||
<PlainMdxWrapper compile includeMeta={false}>
|
||||
{post.body}
|
||||
</PlainMdxWrapper>
|
||||
</div>
|
||||
<div className="max-w-prose text-lg lg:text-xl">
|
||||
<Author author={author} />
|
||||
</div>
|
||||
</article>
|
||||
</PageWrapper>
|
||||
)
|
||||
const BlogPostPage = (props) => {
|
||||
return <SanityPageWrapper {...props} namespaces={namespaces} />
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -122,7 +21,7 @@ const BlogPostPage = ({ post, author }) => {
|
|||
*/
|
||||
export async function getStaticProps({ params, locale }) {
|
||||
const { slug } = params
|
||||
const post = await sanityLoader({ type: 'blog', language: locale, slug, withImage: true })
|
||||
const post = await sanityLoader({ type: 'blog', language: locale, slug })
|
||||
.then((data) => data[0])
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
|
@ -144,7 +43,7 @@ export async function getStaticProps({ params, locale }) {
|
|||
// image: strapiImage(post.author.picture, ['small']),
|
||||
// about: post.author.about,
|
||||
},
|
||||
...(await serverSideTranslations(locale)),
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { sanityLoader, sanityImage } from 'shared/sanity.mjs'
|
||||
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
|
||||
// Hooks
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Components
|
||||
|
|
72
sites/org/pages/showcase/[slug].mjs
Normal file
72
sites/org/pages/showcase/[slug].mjs
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { SanityPageWrapper, ns as sanityNs } from 'site/components/sanity/page-wrapper.mjs'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const namespaces = [...sanityNs]
|
||||
|
||||
const ShowcasePage = (props) => {
|
||||
return <SanityPageWrapper {...props} namespaces={namespaces} />
|
||||
}
|
||||
|
||||
/*
|
||||
* getStaticProps() is used to fetch data at build-time.
|
||||
*
|
||||
* On this page, it is loading the showcase content from strapi.
|
||||
*
|
||||
* This, in combination with getStaticPaths() below means this
|
||||
* page will be used to render/generate all showcase content.
|
||||
*
|
||||
* To learn more, see: https://nextjs.org/docs/basic-features/data-fetching
|
||||
*/
|
||||
export async function getStaticProps({ params, locale }) {
|
||||
const { slug } = params
|
||||
const post = await sanityLoader({ type: 'showcase', language: locale, slug })
|
||||
.then((data) => data[0])
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
const designs = [post.design1 || null]
|
||||
if (post.design2 && post.design2.length > 2) designs.push(post.design2)
|
||||
if (post.design3 && post.design3.length > 2) designs.push(post.design3)
|
||||
|
||||
return {
|
||||
props: {
|
||||
post: {
|
||||
slug,
|
||||
body: post.body,
|
||||
title: post.title,
|
||||
date: post.date,
|
||||
caption: post.caption,
|
||||
image: sanityImage(post.image[0]),
|
||||
designs,
|
||||
},
|
||||
// FIXME load the author separately
|
||||
author: {
|
||||
displayname: post.maker,
|
||||
// slug: post.maker.slug,
|
||||
// image: strapiImage(post.maker.picture, ['small']),
|
||||
// ...(await mdxCompiler(post.maker.about)),
|
||||
},
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const getStaticPaths = async () => {
|
||||
const paths = await sanityLoader({ language: 'en', type: 'showcase' })
|
||||
.then((data) => data.map((post) => `/showcase/${post.slug.current}`))
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
return {
|
||||
paths: [
|
||||
...paths,
|
||||
...paths.map((p) => `/de${p}`),
|
||||
...paths.map((p) => `/es${p}`),
|
||||
...paths.map((p) => `/fr${p}`),
|
||||
...paths.map((p) => `/nl${p}`),
|
||||
],
|
||||
fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
export default ShowcasePage
|
|
@ -1,34 +1,99 @@
|
|||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo } from 'react'
|
||||
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
|
||||
// Components
|
||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||
import { V3Wip } from 'shared/components/v3-wip.mjs'
|
||||
import Link from 'next/link'
|
||||
import { TimeAgo } from 'shared/components/wrappers/mdx.mjs'
|
||||
|
||||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(['showcase', ...pageNs])]
|
||||
const namespaces = [...new Set(['common', 'designs', ...pageNs])]
|
||||
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const DesignsPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
export const PreviewTile = ({ img, slug, title }) => (
|
||||
<Link href={`/showcase/${slug}`} className="text-center">
|
||||
<span
|
||||
style={{ backgroundImage: `url(${img})`, backgroundSize: 'cover' }}
|
||||
className={`
|
||||
rounded-full inline-block border-base-100
|
||||
w-40 h-40
|
||||
md:w-56 md:h-56
|
||||
`}
|
||||
></span>
|
||||
<p>{title}</p>
|
||||
</Link>
|
||||
)
|
||||
|
||||
export default DesignsPage
|
||||
// const DesignPosts = ({ design, posts }) => {
|
||||
// const { t } = useTranslation(['patterns'])
|
||||
// return (
|
||||
// <div className='py-2'>
|
||||
// <h2>
|
||||
// <Link href={`/showcase/designs/${design}`}>
|
||||
// <a className="hover:text-secondary-focus hover:underline">{t(`${design}.t`)}</a>
|
||||
// </Link>
|
||||
// </h2>
|
||||
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
// FIXME paginate
|
||||
const Posts = ({ posts }) => (
|
||||
<div className="grid grid-cols-1 gap-4 xl:gap-8 lg:grid-cols-2 xl:grid-cols-3 lg:pr-4 xl:pr-8">
|
||||
{posts.map((post) => (
|
||||
<PreviewTile img={post.image} slug={post.slug} title={post.title} key={post.slug} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
const ShowcaseIndexPage = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const { posts } = props
|
||||
// const designKeys = useMemo(() => Object.keys(designs).sort(), [designs])
|
||||
return (
|
||||
<PageWrapper title={t('showcase')} {...props.page}>
|
||||
<Posts posts={posts} />
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowcaseIndexPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
const posts = await sanityLoader({
|
||||
language: locale,
|
||||
type: 'showcase',
|
||||
order: 'date desc',
|
||||
}).catch((err) => console.log(err))
|
||||
|
||||
const designs = {}
|
||||
const propPosts = []
|
||||
posts.forEach((post) => {
|
||||
// for (const design of post.designs) {
|
||||
// if (typeof designs[design] === 'undefined') designs[design] = []
|
||||
// designs[design].push(post)
|
||||
// }
|
||||
|
||||
propPosts.push({
|
||||
slug: post.slug.current,
|
||||
title: post.title,
|
||||
date: post.date,
|
||||
// FIXME get the authors separately
|
||||
author: post.maker,
|
||||
image: sanityImage(post.image[0]) + '?fit=clip&w=400',
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
props: {
|
||||
posts: propPosts,
|
||||
designs,
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
// title: 'Freesewing Blog',
|
||||
path: ['showcase'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -102,20 +102,20 @@ const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale
|
|||
</div>
|
||||
)
|
||||
|
||||
export const PlainMdxWrapper = ({ MDX = false, components = {}, compile, children }) => {
|
||||
const allComponents = { ...baseComponents, ...components }
|
||||
const compiledMdx = MDX ? (
|
||||
<MDX components={allComponents} />
|
||||
) : compile ? (
|
||||
<ReactMarkdown components={allComponents}>{children}</ReactMarkdown>
|
||||
) : (
|
||||
children
|
||||
)
|
||||
export const PlainMdxWrapper = ({ MDX = false, components = {}, children, site = 'org' }) => {
|
||||
const allComponents = { ...baseComponents(site), ...components }
|
||||
const CompiledMdx = MDX ? <MDX components={allComponents} /> : children || ''
|
||||
|
||||
return <div className="searchme">{compiledMdx}</div>
|
||||
return <div className="searchme">{MDX ? <MDX components={allComponents} /> : children}</div>
|
||||
}
|
||||
|
||||
export const MdxWrapper = ({ MDX = false, frontmatter = {}, components = {}, children = [] }) => {
|
||||
export const MdxWrapper = ({
|
||||
MDX = false,
|
||||
frontmatter = {},
|
||||
components = {},
|
||||
children = [],
|
||||
site = 'org',
|
||||
}) => {
|
||||
const { t } = useTranslation('docs')
|
||||
|
||||
const { locale, slug } = useContext(NavigationContext)
|
||||
|
|
21
sites/shared/hooks/use-evaled-mdx.mjs
Normal file
21
sites/shared/hooks/use-evaled-mdx.mjs
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { compile, run } from '@mdx-js/mdx'
|
||||
import * as runtime from 'react/jsx-runtime' // Production.
|
||||
|
||||
import { useState, useEffect, Fragment } from 'react'
|
||||
|
||||
export const useEvaledMdx = (mdxStr = '') => {
|
||||
const [mdxModule, setMdxModule] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
const code = await compile(mdxStr, {
|
||||
outputFormat: 'function-body',
|
||||
development: false,
|
||||
})
|
||||
const evaled = await run(code, runtime)
|
||||
setMdxModule(() => evaled.default)
|
||||
})()
|
||||
}, [mdxStr])
|
||||
|
||||
return mdxModule
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue