1
0
Fork 0

refactor blog and showcase implementation

This commit is contained in:
Enoch Riese 2023-07-18 21:27:36 -06:00
parent 6653e6f5b7
commit 2768adc36c
14 changed files with 266 additions and 297 deletions

View file

@ -18,32 +18,38 @@ import { Toc } from 'shared/components/mdx/toc.mjs'
import { MdxMetaData } from 'shared/components/mdx/meta.mjs'
import { PrevNext } from 'shared/components/prev-next.mjs'
export const ns = [navNs] //navNs
export const ns = [navNs, 'docs'] //navNs
export const DocsLayout = ({ children = [], slug, frontmatter }) => {
export const FrontmatterHead = ({ frontmatter }) => (
<Head>
<meta property="og:title" content={frontmatter.title} key="title" />
<meta property="og:type" content="article" key="type" />
<meta property="og:description" content={frontmatter.intro || frontmatter.title} key="type" />
<meta
property="og:article:author"
content={frontmatter.author || frontmatter.maker || 'Joost De Cock'}
key="author"
/>
<meta
property="og:image"
content={`https://canary.backend.freesewing.org/og-img/en/org/${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:url" content={`https://freesewing.org/${slug}`} key="url" />
<meta property="og:locale" content={locale || 'en'} key="locale" />
<meta property="og:site_name" content="freesewing.org" key="site" />
<title>{frontmatter.title + '- FreeSewing.org'}</title>
</Head>
)
export const DocsLayout = ({ children = [], slug, frontmatter, locale }) => {
const { siteNav } = useNavigation({ ignoreControl: true })
return (
<>
<Head>
<meta property="og:title" content={frontmatter.title} key="title" />
<meta property="og:type" content="article" key="type" />
<meta property="og:description" content={``} key="type" />
<meta property="og:article:author" content="Joost De Cock" key="author" />
<meta
property="og:image"
content={`https://canary.backend.freesewing.org/og-img/en/org/${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:url" content={`https://freesewing.org/${slug}`} key="url" />
<meta property="og:locale" content="en" key="locale" />
<meta property="og:site_name" content="freesewing.org" key="site" />
<title>{frontmatter.title + '- FreeSewing.org'}</title>
</Head>
<BaseLayout>
<BaseLayoutLeft>
<MainSections {...{ siteNav, slug }} />

View file

@ -0,0 +1,55 @@
// Hooks
import { useNavigation } from 'site/hooks/use-navigation.mjs'
// Components
import { FrontmatterHead } from './docs.mjs'
import {
BaseLayout,
BaseLayoutLeft,
BaseLayoutProse,
BaseLayoutRight,
} from 'shared/components/base-layout.mjs'
import {
NavLinks,
Breadcrumbs,
MainSections,
ns as navNs,
} from 'shared/components/navigation/sitenav.mjs'
import { Toc } from 'shared/components/mdx/toc.mjs'
import { PrevNext } from 'shared/components/prev-next.mjs'
export const ns = [navNs, 'docs'] //navNs
const isEndSlug = (slug) => slug.split('/').length === 1
export const PostLayout = ({ children = [], slug, frontmatter }) => {
const { siteNav } = useNavigation({ ignoreControl: true })
return (
<>
<BaseLayout>
<BaseLayoutLeft>
<MainSections {...{ siteNav, slug }} />
<NavLinks {...{ siteNav, slug }} />
</BaseLayoutLeft>
<BaseLayoutProse>
<div className="w-full">
<Breadcrumbs {...{ siteNav, slug }} />
<h1 className="break-words searchme">{frontmatter.title}</h1>
<div className="block xl:hidden">
<Toc toc={frontmatter.toc} wrap />
</div>
</div>
{children}
<PrevNext slug={slug} noPrev={isEndSlug} noNext={isEndSlug} />
</BaseLayoutProse>
<BaseLayoutRight>
<div className="hidden xl:block">
<Toc toc={frontmatter.toc} wrap />
</div>
</BaseLayoutRight>
</BaseLayout>
</>
)
}

View file

@ -0,0 +1,55 @@
import { PageLink } from 'shared/components/page-link.mjs'
import { Lightbox } from 'shared/components/lightbox.mjs'
import { ImageWrapper } from 'shared/components/wrappers/img.mjs'
import { Author } from './author.mjs'
import { TimeAgo } from 'shared/components/mdx/meta.mjs'
import { useTranslation } from 'next-i18next'
import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs'
export const ns = ['common', 'posts']
export const PostArticle = ({ slug, frontmatter, MDX, page }) => {
const { t } = useTranslation('common')
return (
<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={frontmatter.date} t={t} /> [{frontmatter.date}]
</div>
<div>
{frontmatter.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">
{frontmatter.author || frontmatter.maker || 'FIXME: No displayname'}
</a>
</div>
</div>
<figure>
<Lightbox>
<ImageWrapper>
<img src={frontmatter.image} alt={frontmatter.caption} className="shadow m-auto" />
</ImageWrapper>
<figcaption
className="text-center mb-8 prose m-auto"
dangerouslySetInnerHTML={{ __html: frontmatter.caption }}
/>
</Lightbox>
</figure>
<div className="strapi prose lg:prose-lg mb-12 m-auto">
<MdxWrapper>{MDX}</MdxWrapper>
</div>
<div className="max-w-prose text-lg lg:text-xl">
<Author author={frontmatter.author || frontmatter.maker} />
</div>
</article>
)
}

View file

@ -1,4 +1,3 @@
import { SanityMdxWrapper } from './mdx-wrapper.mjs'
import { useTranslation } from 'next-i18next'
export const Author = ({ author = '' }) => {

View file

@ -0,0 +1,37 @@
import { localePath } from 'shared/utils.mjs'
const preGenerate = 6
export const numPerPage = 12
export const getPostSlugPaths = (order) => {
const paths = []
for (const lang in order) {
for (let i = 0; i < preGenerate; i++) {
const slug = order[lang][i]
paths.push(localePath(lang, `${order[lang][i]}`))
}
}
return paths
}
export const getPostIndexPaths = (order, type) => {
const paths = []
for (const language in order) {
paths.push(localePath(language, `${type}/page/1`))
paths.push(localePath(language, `${type}/page/2`))
}
return paths
}
export const getPostIndexProps = (locale, params, order, postInfo) => {
const pageNum = parseInt(params.page)
const numLocPages = Math.ceil(order[locale].length / numPerPage)
if (pageNum > numLocPages) return false
const postSlugs = order[locale].slice(numPerPage * (pageNum - 1), numPerPage * pageNum)
const posts = postSlugs.map((s) => ({ ...postInfo[locale][s], s }))
return { posts, current: pageNum, total: numLocPages }
}

View file

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

@ -1,114 +0,0 @@
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/mdx/meta.mjs'
import { useTranslation } from 'next-i18next'
import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs'
import { DocsLayout, ns as layoutNs } from 'site/components/layouts/docs.mjs'
import { PrevNext } from 'shared/components/prev-next.mjs'
export const ns = ['common', 'posts', ...pageNs]
const preGenerate = 6
export const SanityPageWrapper = ({ slug, frontmatter, MDX, namespaces, page }) => {
const { t } = useTranslation(namespaces)
return (
<PageWrapper {...page} title={frontmatter.title} slug={slug}>
<Head>
<meta property="og:type" content="article" key="type" />
<meta
property="og:description"
content={frontmatter.intro || frontmatter.title}
key="description"
/>
<meta property="og:article:author" content={frontmatter.author} key="author" />
<meta property="og:url" content={`https://freesewing.org/blog/${slug}`} key="url" />
<meta
property="og:image"
content={`https://canary.backend.freesewing.org/og-img/en/dev/blog/${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={frontmatter.date} t={t} /> [{frontmatter.date}]
</div>
<div>
{frontmatter.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">
{frontmatter.author || frontmatter.maker || 'FIXME: No displayname'}
</a>
</div>
</div>
<figure>
<Lightbox>
<ImageWrapper>
<img src={frontmatter.image} alt={frontmatter.caption} className="shadow m-auto" />
</ImageWrapper>
<figcaption
className="text-center mb-8 prose m-auto"
dangerouslySetInnerHTML={{ __html: frontmatter.caption }}
/>
</Lightbox>
</figure>
<div className="strapi prose lg:prose-lg mb-12 m-auto">
<MdxWrapper>{MDX}</MdxWrapper>
</div>
<div className="max-w-prose text-lg lg:text-xl">
<Author author={frontmatter.author || frontmatter.maker} />
</div>
</article>
<PrevNext
slug={page?.path.join('/')}
noPrev={(s) => s.split('/').length === 1}
noNext={(s) => s.split('/').length === 1}
/>
</PageWrapper>
)
}
const pathStr = '../../../../markdown/org/'
export const getSanityStaticPaths = (type) => {
return async () => {
const paths = []
const allPathsMod = await import(`../../prebuild/${type}-paths.mjs`)
for (const lang in allPathsMod.order) {
const lPath = lang === 'en' ? '' : `/${lang}`
let i = 0,
counter = 0
while (i < preGenerate && counter < 20) {
counter++
const slug = allPathsMod.order[lang][i]
if (!slug) continue
paths.push(`${lPath}/${allPathsMod.order[lang][i]}`)
i++
}
}
return {
paths: paths,
fallback: 'blocking',
}
}
}

View file

@ -1,43 +0,0 @@
import { createClient } from '@sanity/client'
import { siteConfig } from 'site/site.config.mjs'
let sanityClient
const cache = {}
export const sanityLoader = async ({ query, language, type, slug, order, filters = '' }) => {
sanityClient =
sanityClient ||
createClient({
projectId: siteConfig.sanity.project,
dataset: siteConfig.sanity.dataset,
apiVersion: siteConfig.sanity.apiVersion,
useCdn: true,
})
if (!query) {
query = `*[_type == "${type}${language}"`
if (slug) query += ` && slug.current == "${slug}"`
query += ']'
}
if (order) {
query += ` | order(${order})`
}
query += filters
if (cache[query]) return cache[query]
const result = await sanityClient.fetch(query)
cache[query] = result
return result
}
export const sanityImage = (image, dataset = 'site-content') => {
const [, assetName, origSize, format] = image.asset._ref.split('-')
return `https://cdn.sanity.io/images/${siteConfig.sanity.project}/${dataset}/${assetName}-${origSize}.${format}`
}
export const sanitySiteImage = (image) => sanityImage(image, 'site-content')
export const sanityUserImage = (image) => sanityImage(image, 'user-content')
export const numPerPage = 12

View file

@ -1,36 +1,49 @@
import {
SanityPageWrapper,
getSanityStaticPaths,
ns as sanityNs,
} from 'site/components/sanity/page-wrapper.mjs'
import { order } from 'site/prebuild/blog-paths.mjs'
import { getPostSlugPaths } from 'site/components/mdx/posts/utils.mjs'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useDynamicMdx } from 'shared/hooks/use-dynamic-mdx.mjs'
import { useCallback } from 'react'
import { PostLayout, ns as layoutNs } from 'site/components/layouts/post.mjs'
import { PostArticle, ns as postNs } from 'site/components/mdx/posts/article.mjs'
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
const namespaces = [...sanityNs]
const namespaces = [...layoutNs, ...postNs, ...pageNs]
const BlogPostPage = ({ slug, page, locale }) => {
/*
* 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 BlogPage = ({ locale, slug, page }) => {
// function to load the correct markdown
const loader = useCallback(() => import(`orgmarkdown/blog/${slug}/${locale}.md`), [slug, locale])
// load the markdown
const { frontmatter, MDX } = useDynamicMdx(loader)
if (!MDX) return null
return (
<SanityPageWrapper
{...{
frontmatter,
MDX,
namespaces,
page,
slug,
}}
/>
<PageWrapper
{...page}
locale={locale}
title={frontmatter.title}
layout={(props) => <PostLayout {...props} {...{ slug: page.path.join('/'), frontmatter }} />}
>
<PostArticle
{...{
slug,
frontmatter,
MDX,
page,
}}
/>
</PageWrapper>
)
}
/*
* getStaticProps() is used to fetch data at build-time.
*
* On this page, it is loading the blog content from strapi.
* On this page, it passes the name of the bundle to be loaded on the client.
*
* This, in combination with getStaticPaths() below means this
* page will be used to render/generate all blog content.
@ -43,8 +56,8 @@ export async function getStaticProps({ params, locale }) {
return {
props: {
slug,
...(await serverSideTranslations(locale, namespaces)),
locale,
...(await serverSideTranslations(locale, namespaces)),
page: {
locale,
path: ['blog', slug],
@ -53,6 +66,11 @@ export async function getStaticProps({ params, locale }) {
}
}
export const getStaticPaths = getSanityStaticPaths('blog')
export const getStaticPaths = async () => {
return {
paths: getPostSlugPaths(order, 'blog'),
fallback: 'blocking',
}
}
export default BlogPostPage
export default BlogPage

View file

@ -1,5 +1,7 @@
// Dependencies
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { postInfo, order } from 'site/prebuild/blog-paths.mjs'
import { getPostIndexPaths, getPostIndexProps } from 'site/components/mdx/posts/utils.mjs'
// Hooks
import { useTranslation } from 'next-i18next'
// Components
@ -7,13 +9,10 @@ import Link from 'next/link'
import { TimeAgo } from 'shared/components/mdx/meta.mjs'
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { Pagination } from 'shared/components/navigation/pagination.mjs'
import { postInfo, order } from 'site/prebuild/blog-paths.mjs'
// Translation namespaces used on this page
const namespaces = [...new Set(['designs', 'sections', ...pageNs])]
export const numPerPage = 12
const textShadow = {
style: {
textShadow:
@ -92,42 +91,26 @@ const BlogIndexPage = ({ posts, page, current, total }) => {
export default BlogIndexPage
export async function getStaticProps({ locale, params }) {
const pageNum = parseInt(params.page)
const postSlugs = order[locale].slice(numPerPage * (pageNum - 1), numPerPage * pageNum)
const posts = postSlugs.map((s) => ({ ...postInfo[locale][s], s }))
const numLocPages = Math.ceil(order[locale].length / numPerPage)
const props = getPostIndexProps(locale, params, order, postInfo)
if (pageNum > numLocPages) {
return {
notFound: true,
}
}
if (props === false) return { notFound: true }
return {
props: {
// designs,
posts,
current: pageNum,
total: numLocPages,
...props,
...(await serverSideTranslations(locale, namespaces)),
page: {
locale,
path: ['blog'],
path: ['showcase'],
},
},
}
}
export const getStaticPaths = async () => {
const paths = []
for (const language in order) {
const lPath = language === 'en' ? '' : `/${language}`
paths.push(`${lPath}/blog/page/1`)
paths.push(`${lPath}/blog/page/2`)
}
return {
paths,
paths: getPostIndexPaths(order, 'blog'),
fallback: 'blocking',
}
}

View file

@ -1,13 +1,13 @@
import {
SanityPageWrapper,
getSanityStaticPaths,
ns as sanityNs,
} from 'site/components/sanity/page-wrapper.mjs'
import { order } from 'site/prebuild/showcase-paths.mjs'
import { getPostSlugPaths } from 'site/components/mdx/posts/utils.mjs'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useDynamicMdx } from 'shared/hooks/use-dynamic-mdx.mjs'
import { useCallback } from 'react'
import { PostLayout, ns as layoutNs } from 'site/components/layouts/post.mjs'
import { PostArticle, ns as postNs } from 'site/components/mdx/posts/article.mjs'
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
const namespaces = [...sanityNs]
const namespaces = [...layoutNs, ...postNs, ...pageNs]
const ShowcasePage = ({ locale, slug, page }) => {
const loader = useCallback(
@ -18,15 +18,21 @@ const ShowcasePage = ({ locale, slug, page }) => {
const { frontmatter, MDX } = useDynamicMdx(loader)
if (!MDX) return null
return (
<SanityPageWrapper
{...{
frontmatter,
MDX,
namespaces,
page,
slug,
}}
/>
<PageWrapper
{...page}
locale={locale}
title={frontmatter.title}
layout={(props) => <PostLayout {...props} {...{ slug: page.path.join('/'), frontmatter }} />}
>
<PostArticle
{...{
slug,
frontmatter,
MDX,
page,
}}
/>
</PageWrapper>
)
}
@ -56,6 +62,11 @@ export async function getStaticProps({ params, locale }) {
}
}
export const getStaticPaths = getSanityStaticPaths('showcase')
export const getStaticPaths = async () => {
return {
paths: getPostSlugPaths(order, 'showcase'),
fallback: 'blocking',
}
}
export default ShowcasePage

View file

@ -1,14 +1,13 @@
// Dependencies
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { postInfo, order } from 'site/prebuild/showcase-paths.mjs'
import { getPostIndexPaths, getPostIndexProps } from 'site/components/mdx/posts/utils.mjs'
// Hooks
import { useTranslation } from 'next-i18next'
// Components
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import Link from 'next/link'
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { Pagination } from 'shared/components/navigation/pagination.mjs'
import { postInfo, order } from 'site/prebuild/showcase-paths.mjs'
export const numPerPage = 12
// Translation namespaces used on this page
const namespaces = [...new Set(['common', 'designs', ...pageNs])]
@ -76,23 +75,14 @@ const ShowcaseIndexPage = ({ posts, page, current, total }) => {
export default ShowcaseIndexPage
export async function getStaticProps({ locale, params }) {
const pageNum = parseInt(params.page)
const postSlugs = order[locale].slice(numPerPage * (pageNum - 1), numPerPage * pageNum)
const posts = postSlugs.map((s) => ({ ...postInfo[locale][s], s }))
const numLocPages = Math.ceil(order[locale].length / numPerPage)
const props = getPostIndexProps(locale, params, order, postInfo)
if (pageNum > numLocPages) {
return {
notFound: true,
}
}
if (props === false) return { notFound: true }
return {
props: {
// designs,
posts,
current: pageNum,
total: numLocPages,
...props,
...(await serverSideTranslations(locale, namespaces)),
page: {
locale,
@ -103,15 +93,8 @@ export async function getStaticProps({ locale, params }) {
}
export const getStaticPaths = async () => {
const paths = []
for (const language in order) {
const lPath = language === 'en' ? '' : `/${language}`
paths.push(`${lPath}/showcase/page/1`)
paths.push(`${lPath}/showcase/page/2`)
}
return {
paths,
paths: getPostIndexPaths(order),
fallback: 'blocking',
}
}

View file

@ -18,9 +18,11 @@ const run = async () => {
const SITE = process.env.SITE || 'lab'
await prebuildDesigns()
if (['org', 'dev'].includes(SITE)) {
if (!FAST) await prebuildGitData(SITE)
if (!FAST) {
await prebuildGitData(SITE)
await prebuildCrowdin()
}
const docPages = await prebuildDocs(SITE)
await prebuildCrowdin()
const postPages = await prebuildPosts(SITE)
prebuildNavigation(docPages, postPages, SITE)
if (!FAST && process.env.GENERATE_OG_IMAGES) {

View file

@ -349,3 +349,10 @@ export const maxPovDepthSlug = (slug, site) => {
* In that case, this will return true
*/
export const isSlugPart = (part, slug) => slug.slice(0, part.length) === part
/*
* Makes a properly formated path for the given locale
* (i.e. skips adding 'en' to localized paths)
* Expects a slug with no leading slash
* */
export const localePath = (locale, slug) => (locale === 'en' ? '/' : `/${locale}/`) + slug