diff --git a/sites/org/components/layouts/post.mjs b/sites/org/components/layouts/post.mjs index 62ce471d6bd..0c3d1090bed 100644 --- a/sites/org/components/layouts/post.mjs +++ b/sites/org/components/layouts/post.mjs @@ -19,8 +19,10 @@ import { PrevNext } from 'shared/components/prev-next.mjs' export const ns = [navNs, 'docs'] //navNs +/** checks for a slug that isn't a post, to prevent a prev or next button to it */ const isEndSlug = (slug) => slug.split('/').length === 1 +/** layout for a page that displays a blog, showcase or newsletter */ export const PostLayout = ({ children = [], slug, frontmatter, locale }) => { const { siteNav } = useNavigation({ ignoreControl: true }) diff --git a/sites/org/components/mdx/posts/utils.mjs b/sites/org/components/mdx/posts/utils.mjs index 5ef4c4a88c1..95ff0b415c3 100644 --- a/sites/org/components/mdx/posts/utils.mjs +++ b/sites/org/components/mdx/posts/utils.mjs @@ -2,21 +2,32 @@ import { localePath } from 'shared/utils.mjs' const preGenerate = 6 export const numPerPage = 12 -export const getPostSlugPaths = (order) => { +/** + * get pre-generated paths for each language for post slug pages + * @param {Object} sortedPaths a dictionary keyed by locale of post paths sorted by date published + * @return {Sting[]} paths for the latest 6 posts in all locales + */ +export const getPostSlugPaths = (sortedPaths) => { const paths = [] - for (const lang in order) { + for (const lang in sortedPaths) { for (let i = 0; i < preGenerate; i++) { - paths.push(localePath(lang, `${order[lang][i]}`)) + paths.push(localePath(lang, `${sortedPaths[lang][i]}`)) } } return paths } -export const getPostIndexPaths = (order, type) => { +/** + * get pre-generated paths for each language for post index pages + * @param {Object} sortedPaths a dictionary keyed by locale of post paths sorted by date published + * @param {String} type post type: blog, showcase, or newsletter + * @return {String[]} paths for the first two pages of posts in all locales + */ +export const getPostIndexPaths = (sortedPaths, type) => { const paths = [] - for (const language in order) { + for (const language in sortedPaths) { paths.push(localePath(language, `${type}/page/1`)) paths.push(localePath(language, `${type}/page/2`)) } @@ -24,12 +35,23 @@ export const getPostIndexPaths = (order, type) => { return paths } -export const getPostIndexProps = (locale, params, order, postInfo) => { +/** + * get static props for a post index page + * @param {String} locale the locale + * @param {Object} params path params + * @param {Object} sortedPaths [description] + * @param {Object} postInfo data on all posts, loaded from prebuild + * @return {Object} props page props + * @return {Object[]} props.posts the posts to link to on the page + * @return {Number} props.current the current page number + * @return {Number} props.total the total number of pages + */ +export const getPostIndexProps = (locale, params, sortedPaths, postInfo) => { const pageNum = parseInt(params.page) - const numLocPages = Math.ceil(order[locale].length / numPerPage) + const numLocPages = Math.ceil(sortedPaths[locale].length / numPerPage) if (pageNum > numLocPages) return false - const postSlugs = order[locale].slice(numPerPage * (pageNum - 1), numPerPage * pageNum) + const postSlugs = sortedPaths[locale].slice(numPerPage * (pageNum - 1), numPerPage * pageNum) const posts = postSlugs.map((s) => ({ ...postInfo[locale][s], s })) return { posts, current: pageNum, total: numLocPages } diff --git a/sites/org/pages/blog/[slug].mjs b/sites/org/pages/blog/[slug].mjs index 0aa095e42e0..feedf83e0a5 100644 --- a/sites/org/pages/blog/[slug].mjs +++ b/sites/org/pages/blog/[slug].mjs @@ -53,6 +53,11 @@ const BlogPage = ({ locale, slug, page }) => { export async function getStaticProps({ params, locale }) { const { slug } = params + // if the slug isn't present in the prebuilt order, return 404 + if (order[locale].indexOf(`blog/${slug}`) === -1) { + return { notFound: true } + } + return { props: { slug, @@ -66,6 +71,21 @@ export async function getStaticProps({ params, locale }) { } } +/* + * getStaticPaths() is used to specify for which routes (think URLs) + * this page should be used to generate the result. + * + * On this page, it is returning a truncated list of routes (think URLs) for all + * the mdx blog (markdown) content. + * That list comes from prebuild/blog-paths.mjs, which is built in the prebuild step + * and contains paths, titles, imageUrls, and intro for all blog posts. + * + * the fallback: 'blocking' property means that + * any pages that haven't been pre-generated + * will generate and cache the first time someone visits them + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ export const getStaticPaths = async () => { return { paths: getPostSlugPaths(order), diff --git a/sites/org/pages/blog/page/[page].mjs b/sites/org/pages/blog/page/[page].mjs index bc15271e869..22a0742e6d0 100644 --- a/sites/org/pages/blog/page/[page].mjs +++ b/sites/org/pages/blog/page/[page].mjs @@ -68,6 +68,7 @@ const Preview = ({ post, t }) => ( ) + /* * Each page MUST be wrapped in the PageWrapper component. * You also MUST spread props.page into this wrapper component @@ -90,9 +91,20 @@ const BlogIndexPage = ({ posts, page, current, total }) => { export default BlogIndexPage +/* + * getStaticProps() is used to fetch data at build-time. + * + * On this page, it fetches data for the blogs to be linked to on this page + * + * This, in combination with getStaticPaths() below means this + * page will be used to link to all blogs. + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ export async function getStaticProps({ locale, params }) { const props = getPostIndexProps(locale, params, order, postInfo) + // if there shouldn't be a page with these params, return 404 if (props === false) return { notFound: true } return { @@ -108,6 +120,21 @@ export async function getStaticProps({ locale, params }) { } } +/* + * getStaticPaths() is used to specify for which routes (think URLs) + * this page should be used to generate the result. + * + * On this page, it is returning a truncated list of routes (think URLs) for pages that list and link to + * the mdx blog (markdown) content. + * That list comes from prebuild/blog-paths.mjs, which is built in the prebuild step + * and contains paths, titles, imageUrls, and intro for all blog posts. + * + * the fallback: 'blocking' property means that + * any pages that haven't been pre-generated + * will generate and cache the first time someone visits them + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ export const getStaticPaths = async () => { return { paths: getPostIndexPaths(order, 'blog'), diff --git a/sites/org/pages/docs/[...slug].mjs b/sites/org/pages/docs/[...slug].mjs index df78f5fda3e..f3bb017d0a7 100644 --- a/sites/org/pages/docs/[...slug].mjs +++ b/sites/org/pages/docs/[...slug].mjs @@ -12,27 +12,13 @@ import { DocsLayout, ns as layoutNs } from 'site/components/layouts/docs.mjs' import { loaders } from 'shared/components/dynamic-docs/org.mjs' export const ns = [...pageNs, layoutNs] -/* - * PLEASE READ THIS BEFORE YOU TRY TO REFACTOR THIS PAGE - * - * You will notice that this page has a page component for each language - * and that those components are 95% identical. So you may be thinking: - * - * This is not DRY, let me refactor this real quick - * - * Before you do so, please reflect on these topics: - * - * - Do you know the pitfalls of dynamic imports in Webpack? - * - Do you know how much documentation we have? - * - Do you know we support 5 languages? - * - * If you do know all of these thigns, and you think you can improve this page. Go ahead. - * - * If you are not sure, then I would recommend you find something else to work on, unless - * you consider this a learning opportunity. - * - * joost - * + +/** + * a page to display documentation markdown + * 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. */ export const Page = ({ page, locale, frontmatter, MDX }) => ( ( ) const DocsPage = ({ page, locale, slug }) => { + // get the appropriate loader for the locale, and load the mdx for this page const loader = useCallback(() => loaders[locale](slug), [locale, slug]) // State const { frontmatter, MDX } = useDynamicMdx(loader) @@ -97,6 +84,6 @@ export async function getStaticPaths() { ...somePaths.map((key) => `/fr/${key}`), ...somePaths.map((key) => `/nl/${key}`), ], - fallback: 'blocking', + fallback: false, } } diff --git a/sites/org/pages/showcase/[slug].mjs b/sites/org/pages/showcase/[slug].mjs index be680c20f2e..52bd77906ed 100644 --- a/sites/org/pages/showcase/[slug].mjs +++ b/sites/org/pages/showcase/[slug].mjs @@ -9,14 +9,21 @@ import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' const namespaces = [...layoutNs, ...postNs, ...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 ShowcasePage = ({ locale, slug, page }) => { + // function to load the correct markdown const loader = useCallback( () => import(`orgmarkdown/showcase/${slug}/${locale}.md`), [slug, locale] ) const { frontmatter, MDX } = useDynamicMdx(loader) - if (!MDX) return null + return ( { /* * getStaticProps() is used to fetch data at build-time. * - * On this page, it is loading the showcase content from strapi. + * On this page, it 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 showcase content. @@ -49,6 +56,11 @@ const ShowcasePage = ({ locale, slug, page }) => { export async function getStaticProps({ params, locale }) { const { slug } = params + // if the slug isn't present in the prebuilt order, return 404 + if (order[locale].indexOf(`showcase/${slug}`) === -1) { + return { notFound: true } + } + return { props: { slug, @@ -62,6 +74,21 @@ export async function getStaticProps({ params, locale }) { } } +/* + * getStaticPaths() is used to specify for which routes (think URLs) + * this page should be used to generate the result. + * + * On this page, it is returning a truncated list of routes (think URLs) for all + * the mdx showcase (markdown) content. + * That list comes from prebuild/showcase-paths.mjs, which is built in the prebuild step + * and contains paths, titles, imageUrls, and intro for all showcase posts. + * + * the fallback: 'blocking' property means that + * any pages that haven't been pre-generated + * will generate and cache the first time someone visits them + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ export const getStaticPaths = async () => { return { paths: getPostSlugPaths(order), diff --git a/sites/org/pages/showcase/page/[page].mjs b/sites/org/pages/showcase/page/[page].mjs index 270c2bd3be3..5fbbaca2a87 100644 --- a/sites/org/pages/showcase/page/[page].mjs +++ b/sites/org/pages/showcase/page/[page].mjs @@ -58,6 +58,12 @@ const Posts = ({ posts }) => { ) } +/* + * 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 ShowcaseIndexPage = ({ posts, page, current, total }) => { const { t } = useTranslation() @@ -74,9 +80,20 @@ const ShowcaseIndexPage = ({ posts, page, current, total }) => { export default ShowcaseIndexPage +/* + * getStaticProps() is used to fetch data at build-time. + * + * On this page, it fetches data for the showcases to be linked to on this page + * + * This, in combination with getStaticPaths() below means this + * page will be used to link to all showcases. + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ export async function getStaticProps({ locale, params }) { const props = getPostIndexProps(locale, params, order, postInfo) + // if there shouldn't be a page with these params, return 404 if (props === false) return { notFound: true } return { @@ -92,6 +109,21 @@ export async function getStaticProps({ locale, params }) { } } +/* + * getStaticPaths() is used to specify for which routes (think URLs) + * this page should be used to generate the result. + * + * On this page, it is returning a truncated list of routes (think URLs) for pages that list and link to + * the mdx showcase (markdown) content. + * That list comes from prebuild/showcase-paths.mjs, which is built in the prebuild step + * and contains paths, titles, imageUrls, and intro for all showcase posts. + * + * the fallback: 'blocking' property means that + * any pages that haven't been pre-generated + * will generate and cache the first time someone visits them + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ export const getStaticPaths = async () => { return { paths: getPostIndexPaths(order, 'showcase'), diff --git a/sites/shared/components/prev-next.mjs b/sites/shared/components/prev-next.mjs index ba22d11e3a0..bd2060e0e5e 100644 --- a/sites/shared/components/prev-next.mjs +++ b/sites/shared/components/prev-next.mjs @@ -31,14 +31,32 @@ const NextPage = ({ t, s }) => ) +/** + * get slug at the given index, or null if it should not be displayed + * @param {Number} index the index of the page to get + * @param {String[]} slugLut the lut for navigation slugs + * @param {Object} siteNav nav data + * @param {Boolen | Function} shouldHide should this element be hidden? + * Function arguments should accept the slug that this element would display and return a boolean + * @return {String | null} the slug for the page, or null if it shouldn't display + */ const getItemWithCaveat = (index, slugLut, siteNav, shouldHide) => { + // boolean shouldHide, return null if (shouldHide === true) return null + // function shouldHide, return null if it returns true if (typeof shouldHide === 'function' && shouldHide(slugLut[index])) return null + // get the slug at the index return get(siteNav, slugLut[index].split('/')) } +/** + * Previous and Next buttons + * @param {String} options.slug the current slug + * @param {Boolean | Function} options.noPrev should the previous button be hidden? You can pass a function that accepts the slug for the previous button and returns a boolean, or just pass a boolean + * @param {Boolean | Function} options.noNext should the next button be hidden? You can pass a function that accepts the slug for the next button and returns a boolean, or just pass a boolean + */ export const PrevNext = ({ slug, noPrev = false, noNext = false }) => { // Grab site navigation and slug lookup table from the useNavigatin hook const { siteNav, slugLut } = useNavigation() diff --git a/sites/shared/hooks/use-dynamic-mdx.mjs b/sites/shared/hooks/use-dynamic-mdx.mjs index 75aba0693d3..e8e8d592dc6 100644 --- a/sites/shared/hooks/use-dynamic-mdx.mjs +++ b/sites/shared/hooks/use-dynamic-mdx.mjs @@ -2,6 +2,14 @@ import { components } from 'shared/components/mdx/index.mjs' import { Loading } from 'shared/components/spinner.mjs' import { useState, useEffect } from 'react' +/** + * Dynamically load and compile mdx + * @param {Function} loader an import function to use to load the mdx + * @param {String} site the site whose component set will be used in rendering the mdx + * @return {Object} props + * @return {React.component} props.MDX an component to render the loaded MDX + * @return {Object} props.frontmatter the frontmatter loaded from the markdown + */ export const useDynamicMdx = (loader, site = 'org') => { // State const [frontmatter, setFrontmatter] = useState({ title: `freeSewing.${site}` }) diff --git a/sites/shared/prebuild/posts.mjs b/sites/shared/prebuild/posts.mjs index aa2a80b6cdf..35dcb137dcc 100644 --- a/sites/shared/prebuild/posts.mjs +++ b/sites/shared/prebuild/posts.mjs @@ -26,8 +26,6 @@ export const prebuildPosts = async (site) => { sorted[lang] = Object.keys(resultPages[lang]).sort( (a, b) => resultPages[lang][a].o - resultPages[lang][b].o ) - // get rid of the index page - sorted[lang].shift() } writeOps.push(