diff --git a/sites/org/components/layouts/post.mjs b/sites/org/components/layouts/post.mjs index 3b1ecd74182..2f050ff3223 100644 --- a/sites/org/components/layouts/post.mjs +++ b/sites/org/components/layouts/post.mjs @@ -19,36 +19,32 @@ export const ns = [navNs, 'docs'] const isEndSlug = (slug) => slug.split('/').length === 1 -export const PostLayout = ({ children = [], slug, frontmatter, locale }) => { - const { siteNav } = useNavigation({ ignoreControl: true }) +export const PostLayout = ({ children = [], slug, frontmatter, locale }) => ( + <> + + + + + + - return ( - <> - - - - - - - - -
- -

{frontmatter.title}

-
- -
-
- {children} - -
- - -
+ +
+ +

{frontmatter.title}

+
- - - - ) -} +
+ {children} + +
+ + +
+ +
+
+ + +) diff --git a/sites/org/components/mdx/posts/utils.mjs b/sites/org/components/mdx/posts/utils.mjs index 5ef4c4a88c1..faa1a57af8c 100644 --- a/sites/org/components/mdx/posts/utils.mjs +++ b/sites/org/components/mdx/posts/utils.mjs @@ -1,22 +1,23 @@ import { localePath } from 'shared/utils.mjs' -const preGenerate = 6 -export const numPerPage = 12 +import { siteConfig as config } from 'site/site.config.mjs' -export const getPostSlugPaths = (order) => { +export const getPostSlugPaths = (posts) => { const paths = [] - for (const lang in order) { - for (let i = 0; i < preGenerate; i++) { - paths.push(localePath(lang, `${order[lang][i]}`)) - } + for (const lang in posts) { + paths.push( + ...Object.keys(posts[lang]) + .slice(0, config.posts.preGenerate) + .map((slug) => localePath(lang, slug)) + ) } return paths } -export const getPostIndexPaths = (order, type) => { +export const getPostIndexPaths = (posts, type) => { const paths = [] - for (const language in order) { + for (const language in posts) { paths.push(localePath(language, `${type}/page/1`)) paths.push(localePath(language, `${type}/page/2`)) } @@ -24,13 +25,18 @@ export const getPostIndexPaths = (order, type) => { return paths } -export const getPostIndexProps = (locale, params, order, postInfo) => { - const pageNum = parseInt(params.page) - const numLocPages = Math.ceil(order[locale].length / numPerPage) +export const getPostIndexProps = (pagenr, posts, meta) => { + const pageNum = parseInt(pagenr) + const numLocPages = Math.ceil(Object.keys(posts).length / config.posts.perPage) if (pageNum > numLocPages) return false - const postSlugs = order[locale].slice(numPerPage * (pageNum - 1), numPerPage * pageNum) - const posts = postSlugs.map((s) => ({ ...postInfo[locale][s], s })) + const pagePosts = Object.entries(posts) + .slice(config.posts.perPage * (pageNum - 1), config.posts.perPage * pageNum) + .map(([slug, post]) => ({ + s: slug, + ...post, + ...meta[slug], + })) - return { posts, current: pageNum, total: numLocPages } + return { posts: pagePosts, current: pageNum, total: numLocPages } } diff --git a/sites/org/pages/blog/[slug].mjs b/sites/org/pages/blog/[slug].mjs index 0aa095e42e0..72a7f94da23 100644 --- a/sites/org/pages/blog/[slug].mjs +++ b/sites/org/pages/blog/[slug].mjs @@ -1,4 +1,5 @@ -import { order } from 'site/prebuild/blog-paths.mjs' +import { pages as posts } from 'site/prebuild/blog.mjs' +import { meta } from 'site/prebuild/blog-meta.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' @@ -68,7 +69,7 @@ export async function getStaticProps({ params, locale }) { export const getStaticPaths = async () => { return { - paths: getPostSlugPaths(order), + paths: getPostSlugPaths(posts), fallback: 'blocking', } } diff --git a/sites/org/pages/blog/index.mjs b/sites/org/pages/blog/index.mjs deleted file mode 100644 index 00662c6073e..00000000000 --- a/sites/org/pages/blog/index.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// Dependencies -import { serverSideTranslations } from 'next-i18next/serverSideTranslations' -// Hooks -import { useTranslation } from 'next-i18next' -// Components -import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' -import { Popout } from 'shared/components/popout.mjs' - -// Translation namespaces used on this page -const namespaces = pageNs - -const BlogIndexPage = ({ page, slug }) => { - const { t } = useTranslation() - - return ( - - Implement blog view - - ) -} - -export default BlogIndexPage - -export async function getStaticProps({ locale }) { - return { - props: { - ...(await serverSideTranslations(locale, namespaces)), - slug: 'blog', - page: { - locale, - path: ['blog'], - }, - }, - } -} diff --git a/sites/org/pages/blog/page/[page].mjs b/sites/org/pages/blog/page/[page].mjs index bc15271e869..fa57b7e9c62 100644 --- a/sites/org/pages/blog/page/[page].mjs +++ b/sites/org/pages/blog/page/[page].mjs @@ -1,6 +1,7 @@ // Dependencies import { serverSideTranslations } from 'next-i18next/serverSideTranslations' -import { postInfo, order } from 'site/prebuild/blog-paths.mjs' +import { pages as posts } from 'site/prebuild/blog.mjs' +import { meta } from 'site/prebuild/blog-meta.mjs' import { getPostIndexPaths, getPostIndexProps } from 'site/components/mdx/posts/utils.mjs' // Hooks import { useTranslation } from 'next-i18next' @@ -80,7 +81,7 @@ const BlogIndexPage = ({ posts, page, current, total }) => {
{posts.map((post) => ( - + ))}
@@ -91,7 +92,7 @@ const BlogIndexPage = ({ posts, page, current, total }) => { export default BlogIndexPage export async function getStaticProps({ locale, params }) { - const props = getPostIndexProps(locale, params, order, postInfo) + const props = getPostIndexProps(params.page, posts[locale], meta) if (props === false) return { notFound: true } @@ -110,7 +111,7 @@ export async function getStaticProps({ locale, params }) { export const getStaticPaths = async () => { return { - paths: getPostIndexPaths(order, 'blog'), + paths: getPostIndexPaths(posts, 'blog'), fallback: 'blocking', } } diff --git a/sites/org/site.config.mjs b/sites/org/site.config.mjs index 453896d2f4f..c1d0d415da4 100644 --- a/sites/org/site.config.mjs +++ b/sites/org/site.config.mjs @@ -20,4 +20,8 @@ export const siteConfig = { languagesWip: [], site: 'FreeSewing.org', tld: 'org', + posts: { + preGenerate: 6, + perPage: 12, + }, } diff --git a/sites/shared/prebuild/markdown.mjs b/sites/shared/prebuild/markdown.mjs index e1e733a27b3..733b30a02cf 100644 --- a/sites/shared/prebuild/markdown.mjs +++ b/sites/shared/prebuild/markdown.mjs @@ -1,6 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { exec } from 'node:child_process' +import orderBy from 'lodash.orderby' /* * Shared header to include in written .mjs files @@ -25,14 +26,23 @@ const stripQuotes = (str) => { /* * This is the fast and low-tech way to some frontmatter from all files in a folder */ -const loadFolderFrontmatter = async (key, site, folder, transform = false) => { - const prefix = site === 'org' ? 'docs/' : '' +const loadFolderFrontmatter = async (key, site, folder, transform = false, lang = false) => { + const prefix = site === 'org' ? `${folder}/` : '' /* * Figure out what directory to spawn the child process in */ const cwd = await path.resolve(process.cwd(), '..', '..', 'markdown', site, folder) let list = false - const grep = exec('grep ^' + key + ': -RIsm 1 ' + cwd, { cwd }, (error, stdout, stderr) => { + /* + * When going through a small number of files in a flat directory (eg. blog posts) a + * recursive grep through all files is faster. + * But the biggest task is combing through all the org documentation and for this + * it's much faster to first run find to limit the number of files to open + */ + const cmd = `find ${cwd} -type f -name "${ + lang ? lang : '*' + }.md" -exec grep "^${key}:" -ism 1 {} +` + const grep = exec(cmd, { cwd }, (error, stdout, stderr) => { if (error) { console.error(`exec error: ${error}`) return @@ -61,7 +71,7 @@ const loadFolderFrontmatter = async (key, site, folder, transform = false) => { * Trim some of the irrelevant path info prior to splitting on '.md:{key}:' */ const chunks = match - .split(`markdown/${site}/${site === 'org' ? 'docs/' : ''}`) + .split(`markdown/${site}/${site === 'dev' ? '' : folder + '/'}`) .pop() .split(`.md:${key}:`) if (chunks.length === 2 && chunks[0].length > 1) { @@ -89,13 +99,14 @@ const loadFolderFrontmatter = async (key, site, folder, transform = false) => { /* * Merges in order key on those slugs that have it set */ -const mergeOrder = (titles, order) => { +const mergeOrder = (titles, order, withSlug = false) => { const pages = {} for (const lang in titles) { pages[lang] = {} for (const [slug, t] of Object.entries(titles[lang])) { pages[lang][slug] = { t } - if (order[slug]) pages[lang][slug] = order[slug] + if (order.en[slug]) pages[lang][slug].o = order.en[slug] + if (withSlug) pages[lang][slug].s = slug } } @@ -121,7 +132,8 @@ const formatDate = (date, slug, lang) => { const loadDocs = async (site) => { const folder = site === 'org' ? 'docs' : '.' const titles = await loadFolderFrontmatter('title', site, folder) - const order = await loadFolderFrontmatter('order', site, folder) + // Order is the same for all languages, so only grab EN files + const order = await loadFolderFrontmatter('order', site, folder, false, 'en') return mergeOrder(titles, order) } @@ -131,19 +143,81 @@ const loadDocs = async (site) => { */ const loadBlog = async () => { const titles = await loadFolderFrontmatter('title', 'org', 'blog') - const order = await loadFolderFrontmatter('date', 'org', 'blog', formatDate) + // Order is the same for all languages, so only grab EN files + const order = await loadFolderFrontmatter('date', 'org', 'blog', formatDate, 'en') + // Author is the same for all languages, so only grab EN files + const authors = await loadFolderFrontmatter('author', 'org', 'blog', false, 'en') + // Image is the same for all languages, so only grab EN files + const images = await loadFolderFrontmatter('image', 'org', 'blog', false, 'en') - return mergeOrder(titles, order) + // Merge titles and order for EN + const merged = {} + for (const slug in titles.en) + merged[slug] = { + t: titles.en[slug], + o: order.en[slug], + s: slug, + a: authors.en[slug], + i: images.en[slug], + } + // Order based on post data (descending) + const ordered = orderBy(merged, 'o', 'desc') + + // Apply same order to all languages + const posts = {} + const meta = {} + const languages = Object.keys(titles).filter((lang) => lang !== 'em') + + for (const lang of Object.keys(titles)) { + posts[lang] = {} + for (const post of ordered) { + posts[lang][post.s] = { t: post.t } + if (lang === 'en') meta[post.s] = { a: post.a, d: post.o, i: post.i } + } + } + + return { posts, meta } } /* * Loads all showcase posts, titles and order */ const loadShowcase = async () => { - const titles = await loadFolderFrontmatter('title', 'org', 'blog') - const order = await loadFolderFrontmatter('date', 'org', 'blog') + const titles = await loadFolderFrontmatter('title', 'org', 'showcase') + // Order is the same for all languages, so only grab EN files + const order = await loadFolderFrontmatter('date', 'org', 'showcase', formatDate, 'en') + // Author is the same for all languages, so only grab EN files + const makers = await loadFolderFrontmatter('maker', 'org', 'showcase', false, 'en') + // Image is the same for all languages, so only grab EN files + const images = await loadFolderFrontmatter('image', 'org', 'showcase', false, 'en') - return mergeOrder(titles, order) + // Merge titles and order for EN + const merged = {} + for (const slug in titles.en) + merged[slug] = { + t: titles.en[slug], + o: order.en[slug], + s: slug, + m: makers.en[slug], + i: images.en[slug], + } + // Order based on post data (descending) + const ordered = orderBy(merged, 'o', 'desc') + + // Apply same order to all languages + const posts = {} + const meta = {} + const languages = Object.keys(titles).filter((lang) => lang !== 'em') + + for (const lang of Object.keys(titles)) { + posts[lang] = {} + for (const post of ordered) { + posts[lang][post.s] = { t: post.t } + if (lang === 'en') meta[post.s] = { m: post.m, d: post.o, i: post.i } + } + } + + return { posts, meta } } /* @@ -151,7 +225,8 @@ const loadShowcase = async () => { */ const loadNewsletter = async () => { const titles = await loadFolderFrontmatter('title', 'org', 'newsletter') - const order = await loadFolderFrontmatter('edition', 'org', 'newsletter') + // Order is the same for all languages, so only grab EN files + const order = await loadFolderFrontmatter('edition', 'org', 'newsletter', false, 'en') return mergeOrder(titles, order) } @@ -177,6 +252,16 @@ export const pages = { ${Object.keys(pages).join(',')} }` ) } +/* + * Write out a single prebuild file + */ +const writeFile = async (filename, exportname, site, content) => { + fs.writeFileSync( + path.resolve('..', site, 'prebuild', `${filename}.mjs`), + `${header}export const ${exportname} = ${JSON.stringify(content)}` + ) +} + /* * Main method that does what needs doing for the docs */ @@ -192,9 +277,11 @@ export const prebuildPosts = async (store) => { store.posts = { blog: await loadBlog(), showcase: await loadShowcase(), - newsletter: await loadNewsletter(), + newsletter: { posts: await loadNewsletter() }, } - await writeFiles('blog', 'org', store.posts.blog) - await writeFiles('showcase', 'org', store.posts.showcase) + await writeFiles('blog', 'org', store.posts.blog.posts) + await writeFiles('showcase', 'org', store.posts.showcase.posts) await writeFiles('newsletter', 'org', store.posts.newsletter) + await writeFile('blog-meta', 'meta', 'org', store.posts.blog.meta) + await writeFile('showcase-meta', 'meta', 'org', store.posts.showcase.meta) } diff --git a/sites/shared/prebuild/navigation.mjs b/sites/shared/prebuild/navigation.mjs index 4ed0bec936f..14d3ec93898 100644 --- a/sites/shared/prebuild/navigation.mjs +++ b/sites/shared/prebuild/navigation.mjs @@ -80,7 +80,7 @@ export const prebuildNavigation = async (store) => { // Handle posts if (posts) { for (const type in posts) { - for (const [slug, post] of Object.entries(posts[type][lang])) { + for (const [slug, post] of Object.entries(posts[type].posts[lang])) { set(sitenav, [lang, ...slug.split('/')], { t: post.t, o: post.o, s: slug }) } }