1
0
Fork 0

Merge branch 'develop' into joost

This commit is contained in:
joostdecock 2023-06-20 19:28:48 +02:00
commit 7f7ceeb0e2
21 changed files with 1058 additions and 470 deletions

View file

@ -27,6 +27,7 @@ module.exports = {
extends: 'eslint:recommended',
env: {
es2021: true,
node: true,
},
// Required when using experimental EcmaScript features
parser: '@babel/eslint-parser',

View file

@ -192,12 +192,12 @@ yuri:
backend:
_:
'@aws-sdk/client-sesv2': '3.352.0'
'@aws-sdk/client-sesv2': '3.354.0'
'@prisma/client': &prisma '4.15.0'
'bcryptjs': '2.4.3'
'cors': '2.8.5'
'crypto': '1.0.1'
'dotenv': '16.1.4'
'dotenv': '16.3.1'
'express': '4.18.2'
'js-yaml': *jsyaml
'lodash.get': *_get
@ -213,7 +213,7 @@ backend:
dev:
'chai': *chai
'chai-http': '4.4.0'
'esbuild': '0.18.2'
'esbuild': '0.18.5'
'mocha': *mocha
'mocha-steps': '1.3.0'
'nodemon': '2.0.22'
@ -228,7 +228,7 @@ dev:
'@next/bundle-analyzer': &next '13.4.6'
'@tailwindcss/typography': &tailwindTypography '0.5.9'
'algoliasearch': '4.17.2'
'daisyui': &daisyui '3.1.0'
'daisyui': &daisyui '3.1.1'
'lodash.get': *_get
'lodash.orderby': &_orderby '4.6.0'
'lodash.set': *_set
@ -238,7 +238,7 @@ dev:
'react-dom': *react
'react-hotkeys-hook': &reactHotkeysHook '4.4.0'
'react-instantsearch-dom': &reactInstantsearchDom '6.40.0'
'react-instantsearch-hooks-web': '6.44.2'
'react-instantsearch-hooks-web': '6.44.3'
'react-markdown': &reactMarkdown '8.0.7'
'react-swipeable': &reactSwipeable '7.0.1'
'react-timeago': &reactTimeago '7.1.0'
@ -314,6 +314,8 @@ org:
'@mdx-js/mdx': *mdx
'@mdx-js/react': *mdx
'@mdx-js/runtime': *mdxRuntime
'@portabletext/react': '^1.0.6'
'@sanity/client': '^6.1.2'
'@tailwindcss/typography': *tailwindTypography
'algoliasearch': *algoliasearch
'react-copy-to-clipboard': 5.1.0
@ -323,6 +325,7 @@ org:
'lodash.set': *_set
'luxon': '3.3.0'
'next': *next
'next-sanity': '^4.3.3'
'react-dropzone': '14.2.3'
'react-hotkeys-hook': *reactHotkeysHook
'react-instantsearch-dom': *reactInstantsearchDom
@ -344,8 +347,8 @@ org:
sanity:
_:
'@sanity/vision': &sanity '3.12.0'
'easymde': '2.16.0'
'@sanity/vision': &sanity '3.12.1'
'easymde': '2.18.0'
'react': *react
'react-dom': *react
'react-is': *react

View file

@ -28,12 +28,12 @@
},
"peerDependencies": {},
"dependencies": {
"@aws-sdk/client-sesv2": "3.352.0",
"@aws-sdk/client-sesv2": "3.354.0",
"@prisma/client": "4.15.0",
"bcryptjs": "2.4.3",
"cors": "2.8.5",
"crypto": "1.0.1",
"dotenv": "16.1.4",
"dotenv": "16.3.1",
"express": "4.18.2",
"js-yaml": "4.1.0",
"lodash.get": "4.4.2",
@ -50,7 +50,7 @@
"devDependencies": {
"chai": "4.3.7",
"chai-http": "4.4.0",
"esbuild": "0.18.2",
"esbuild": "0.18.5",
"mocha": "10.2.0",
"mocha-steps": "1.3.0",
"nodemon": "2.0.22",

View file

@ -36,7 +36,7 @@
"@next/bundle-analyzer": "13.4.6",
"@tailwindcss/typography": "0.5.9",
"algoliasearch": "4.17.2",
"daisyui": "3.1.0",
"daisyui": "3.1.1",
"lodash.get": "4.4.2",
"lodash.orderby": "4.6.0",
"lodash.set": "4.3.2",
@ -46,7 +46,7 @@
"react-dom": "18.2.0",
"react-hotkeys-hook": "4.4.0",
"react-instantsearch-dom": "6.40.0",
"react-instantsearch-hooks-web": "6.44.2",
"react-instantsearch-hooks-web": "6.44.3",
"react-markdown": "8.0.7",
"react-swipeable": "7.0.1",
"react-timeago": "7.1.0",

View file

@ -38,7 +38,7 @@
"d3-dispatch": "3.0.1",
"d3-drag": "3.0.0",
"d3-selection": "3.0.0",
"daisyui": "3.1.0",
"daisyui": "3.1.1",
"i18next": "22.5.1",
"lodash.get": "4.4.2",
"lodash.orderby": "4.6.0",

View 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>
)
}

View file

@ -0,0 +1,30 @@
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(() => {
const runEffect = async () => {
const code = await compile(mdxStr, {
outputFormat: 'function-body',
development: false,
})
const evaled = await run(code, runtime)
setMdxModule(() => evaled.default)
}
runEffect()
}, [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

View 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>
)
}

View file

@ -0,0 +1,32 @@
import { createClient } from 'next-sanity'
import { siteConfig } from 'site/site.config.mjs'
let sanityClient
export const sanityLoader = ({ query, language, type, slug, order }) => {
sanityClient =
sanityClient ||
createClient({
projectId: 'hl5bw8cj',
dataset: 'site-content',
apiVersion: '2023-06-17',
token: process.env.SANITY_TOKEN,
useCdn: false,
})
if (!query) {
query = `*[_type == "${type}${language}"`
if (slug) query += ` && slug.current == "${slug}"`
query += ']'
}
if (order) {
query += ` | order(${order})`
}
return sanityClient.fetch(query)
}
export const sanityImage = (image, dataset = 'site-content') => {
const [, assetName, origSize, format] = image.asset._ref.split('-')
return `https://cdn.sanity.io/images/${siteConfig.sanity.projectt}/${dataset}/${assetName}-${origSize}.${format}`
}

View file

@ -34,15 +34,18 @@
"@mdx-js/mdx": "2.3.0",
"@mdx-js/react": "2.3.0",
"@mdx-js/runtime": "2.0.0-next.9",
"@portabletext/react": "^1.0.6",
"@sanity/client": "^6.1.2",
"@tailwindcss/typography": "0.5.9",
"algoliasearch": "4.17.2",
"react-copy-to-clipboard": "5.1.0",
"daisyui": "3.1.0",
"daisyui": "3.1.1",
"lodash.get": "4.4.2",
"lodash.orderby": "4.6.0",
"lodash.set": "4.3.2",
"luxon": "3.3.0",
"next": "13.4.6",
"next-sanity": "^4.3.3",
"react-dropzone": "14.2.3",
"react-hotkeys-hook": "4.4.0",
"react-instantsearch-dom": "6.40.0",

View file

@ -0,0 +1,67 @@
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'
const namespaces = [...sanityNs]
const BlogPostPage = (props) => {
return <SanityPageWrapper {...props} namespaces={namespaces} />
}
/*
* getStaticProps() is used to fetch data at build-time.
*
* On this page, it is loading the blog content from strapi.
*
* This, in combination with getStaticPaths() below means this
* page will be used to render/generate all blog 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: 'blog', language: locale, slug })
.then((data) => data[0])
.catch((err) => console.log(err))
return {
props: {
post: {
slug,
body: post.body,
title: post.title,
date: post.date,
caption: post.caption,
image: sanityImage(post.image),
},
// FIXME load the author separately
author: {
displayname: post.author,
// slug: post.author.slug,
// about: post.author.about,
// image: strapiImage(post.author.picture, ['small']),
// about: post.author.about,
},
...(await serverSideTranslations(locale, namespaces)),
},
}
}
export const getStaticPaths = async () => {
const paths = await sanityLoader({ language: 'en', type: 'blog' })
.then((data) => data.map((post) => `/blog/${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 BlogPostPage

View file

@ -1,34 +1,111 @@
// Dependencies
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { sanityLoader, sanityImage } from 'site/components/sanity/utils.mjs'
// Hooks
import { useTranslation } from 'next-i18next'
// Components
import Link from 'next/link'
import { TimeAgo } from 'shared/components/wrappers/mdx.mjs'
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { V3Wip } from 'shared/components/v3-wip.mjs'
// Translation namespaces used on this page
const namespaces = [...new Set(['designs', ...pageNs])]
const textShadow = {
style: {
textShadow:
'1px 1px 1px #000000, -1px -1px 1px #000000, 1px -1px 1px #000000, -1px 1px 1px #000000, 2px 2px 1px #000000',
},
}
const Preview = ({ post, t }) => (
<div className="shadow rounded-lg">
<Link href={`blog/${post.slug}`} className="hover:underline">
<div
className="bg-base-100 w-full h-full overflow-hidden shadow flex flex-column items-center rounded-lg"
style={{
backgroundImage: `url(${post.image})`,
backgroundSize: 'cover',
}}
>
<div className="text-right my-2 w-full">
<div
className={`
bg-neutral text-neutral-content bg-opacity-40 text-right
px-4 py-1
lg:px-8 lg:py-4
`}
>
<h5
className={`
text-neutral-content
text-xl font-bold
md:text-2xl md:font-normal
xl:text-3xl
`}
{...textShadow}
>
{post.title}
</h5>
<p
className={`
hidden md:block
m-0 p-1 -mt-2
text-neutral-content
leading-normal text-sm font-normal
opacity-70
`}
{...textShadow}
>
<TimeAgo date={post.date} t={t} /> by <strong>{post.author}</strong>
</p>
</div>
</div>
</div>
</Link>
</div>
)
/*
* 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 BlogIndexPage = ({ page }) => (
<PageWrapper {...page}>
<div className="max-w-2xl">
<V3Wip />
</div>
</PageWrapper>
)
const BlogIndexPage = ({ page, posts }) => {
const { t } = useTranslation()
return (
<PageWrapper {...page}>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3 max-w-7xl lg:pr-4 xl:pr-6">
{posts.map((post) => (
<Preview post={post} t={t} key={post.slug} />
))}
</div>
</PageWrapper>
)
}
export default BlogIndexPage
export async function getStaticProps({ locale }) {
const posts = await sanityLoader({ language: locale, type: 'blog' }).catch((err) =>
console.log(err)
)
return {
props: {
posts: posts.map((post) => ({
slug: post.slug.current,
title: post.title,
date: post.date,
// FIXME get the authors separately
author: post.author,
image: sanityImage(post.image) + '?fit=clip&w=400',
})),
...(await serverSideTranslations(locale, namespaces)),
page: {
locale,
// title: 'Freesewing Blog',
path: ['blog'],
},
},

View file

@ -0,0 +1,71 @@
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'
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

View file

@ -1,34 +1,97 @@
// Dependencies
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
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'
// 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'],
},
},

View file

@ -7,6 +7,9 @@ export const siteConfig = {
bugsnag: {
key: '1b3a900d6ebbfd071975e39b534e1ff5',
},
sanity: {
project: process.env.SANITY_PROJECT || 'hl5bw8cj',
},
languages: ['en', 'es', 'de', 'fr', 'nl'],
site: 'FreeSewing.org',
}

View file

@ -20,12 +20,12 @@
},
"peerDependencies": {},
"dependencies": {
"@sanity/vision": "3.12.0",
"easymde": "2.16.0",
"@sanity/vision": "3.12.1",
"easymde": "2.18.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"sanity": "3.12.0",
"sanity": "3.12.1",
"styled-components": "5.3.11",
"sanity-plugin-markdown": "4.1.0"
},

View file

@ -16,7 +16,7 @@ import { useTranslation } from 'next-i18next'
//import { PrevNext } from '../mdx/prev-next.mjs'
//
//
const TimeAgo = ({ date, t }) => {
export const TimeAgo = ({ date, t }) => {
const i = Interval.fromDateTimes(DateTime.fromISO(date), DateTime.now())
.toDuration(['hours', 'days', 'months', 'years'])
.toObject()
@ -101,9 +101,21 @@ const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale
</div>
)
export const MdxWrapper = ({ MDX = false, frontmatter = {}, components = {}, children = [] }) => {
export const PlainMdxWrapper = ({ MDX = false, components = {}, children, site = 'org' }) => {
const allComponents = { ...baseComponents(site), ...components }
return <div className="searchme">{MDX ? <MDX components={allComponents} /> : children}</div>
}
export const MdxWrapper = ({
MDX = false,
frontmatter = {},
components = {},
children = [],
site = 'org',
}) => {
const { t } = useTranslation('docs')
const allComponents = { ...baseComponents, ...components }
const { locale, slug } = useContext(NavigationContext)
const updates = docUpdates[slug] || {}
@ -116,7 +128,7 @@ export const MdxWrapper = ({ MDX = false, frontmatter = {}, components = {}, chi
updated={updates.u}
{...{ locale, slug, t }}
/>
<div className="searchme">{MDX ? <MDX components={allComponents} /> : children}</div>
<PlainMdxWrapper {...{ MDX, components, children }} />
</div>
)
}

View file

@ -4,6 +4,7 @@ import React, { useState, useEffect, useContext } from 'react'
// Hooks
import { useTheme } from 'shared/hooks/use-theme.mjs'
// Components
import Head from 'next/head'
import { SwipeWrapper } from 'shared/components/wrappers/swipes.mjs'
import { LayoutWrapper, ns as layoutNs } from 'site/components/wrappers/layout.mjs'
import { DocsLayout, ns as docsNs } from 'site/components/layouts/docs.mjs'
@ -80,6 +81,11 @@ export const PageWrapper = (props) => {
// Return wrapper
return (
<SwipeWrapper>
{pageTitle && (
<Head>
<meta property="og:title" content={pageTitle} key="title" />
</Head>
)}
<div
data-theme={currentTheme} // This facilitates CSS selectors
key={currentTheme} // This forces the data-theme update

View file

@ -0,0 +1,21 @@
import { compile, run } from '@mdx-js/mdx'
import * as runtime from 'react/jsx-runtime' // Production.
import { useState, useEffect } 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
}

View file

@ -24,7 +24,7 @@
"d3-dispatch": "3.0.1",
"d3-drag": "3.0.0",
"d3-selection": "3.0.0",
"daisyui": "3.1.0",
"daisyui": "3.1.1",
"feed": "4.2.2",
"file-saver": "2.0.5",
"front-matter": "4.0.2",

926
yarn.lock

File diff suppressed because it is too large Load diff