From 66414de0ef8e957bf9438c13bc619525e245cd76 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:23:11 +1000 Subject: [PATCH 01/18] feat: added learn to header with route to featured-analysis (#331) --- routes/constants.ts | 1 + site-config/brc-analytics/local/config.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/routes/constants.ts b/routes/constants.ts index c6cf07fe..64f64492 100644 --- a/routes/constants.ts +++ b/routes/constants.ts @@ -4,6 +4,7 @@ export const ROUTES = { CONFIGURE_WORKFLOW: "/data/assemblies/{entityId}/{trsId}", GENOME: "/data/assemblies/{entityId}", GENOMES: "/data/assemblies", + LEARN: "/learn/featured-analyses", ORGANISMS: "/data/organisms", PRIORITY_PATHOGEN: "/data/[entityListType]/[entityId]", PRIORITY_PATHOGENS: "/data/priority-pathogens", diff --git a/site-config/brc-analytics/local/config.ts b/site-config/brc-analytics/local/config.ts index 0ef31cfa..f3538561 100644 --- a/site-config/brc-analytics/local/config.ts +++ b/site-config/brc-analytics/local/config.ts @@ -89,6 +89,7 @@ export function makeConfig( undefined, [ { label: "About", url: ROUTES.ABOUT }, + { label: "Learn", url: ROUTES.LEARN }, { label: "Organisms", url: ROUTES.ORGANISMS }, { label: "Assemblies", url: ROUTES.GENOMES }, { label: "Priority Pathogens", url: ROUTES.PRIORITY_PATHOGENS }, From d10f2dd402278bcdaf0c58104daa9b7e73bb0645 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:27:12 +1000 Subject: [PATCH 02/18] feat: added plugins rehypeslug and remarkheadings (#331) --- plugins/rehypeSlug.ts | 36 ++++++++++++++++++++++++++++ plugins/remarkHeadings.ts | 50 +++++++++++++++++++++++++++++++++++++++ plugins/utils.ts | 27 +++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 plugins/rehypeSlug.ts create mode 100644 plugins/remarkHeadings.ts create mode 100644 plugins/utils.ts diff --git a/plugins/rehypeSlug.ts b/plugins/rehypeSlug.ts new file mode 100644 index 00000000..a0fa2c3a --- /dev/null +++ b/plugins/rehypeSlug.ts @@ -0,0 +1,36 @@ +import { Plugin } from "unified"; +import { visit } from "unist-util-visit"; +import { generateUniqueId, slugifyHeading } from "./utils"; +import { getHeadingTextValue } from "./remarkHeadings"; + +/** + * Rehype plugin to generate an ID for each heading in MDX content. + * @returns plugin to generate an ID for each heading in MDX content. + */ +export function rehypeSlug(): Plugin { + return (tree) => { + const setOfIds = new Set(); + visit(tree, "element", (node) => { + if (/^h[1-6]$/.test(node.tagName)) { + const headingText = getHeadingTextValue(node.children); + const headingSlug = slugifyHeading(headingText); + const id = generateUniqueId(setOfIds, headingSlug); + // Add the ID to the heading element. + node.properties.id = id; + node.properties.style = "position: relative;"; + // Append AnchorLink to the heading element. + node.children.push({ + attributes: [ + { + name: "anchorLink", + type: "mdxJsxAttribute", + value: id, + }, + ], + name: "AnchorLink", + type: "mdxJsxFlowElement", + }); + } + }); + }; +} diff --git a/plugins/remarkHeadings.ts b/plugins/remarkHeadings.ts new file mode 100644 index 00000000..72662f15 --- /dev/null +++ b/plugins/remarkHeadings.ts @@ -0,0 +1,50 @@ +import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; +import { Heading, PhrasingContent } from "mdast"; +import { Plugin } from "unified"; +import { visit } from "unist-util-visit"; +import { generateUniqueId, slugifyHeading } from "./utils"; + +/** + * Remark plugin to generate an outline from MDX content. + * The outline is a list of headings with their depth, hash, and value. + * @param outline - Outline items. + * @returns plugin to generate an outline from MDX content. + */ +export function remarkHeadings(outline: OutlineItem[]): Plugin { + return (tree) => { + const setOfIds = new Set(); + visit(tree, "heading", (node) => { + const heading = node as Heading; + const { children, depth } = heading; + const value = getHeadingTextValue(children); + const headingSlug = slugifyHeading(value); + const hash = generateUniqueId(setOfIds, headingSlug); + outline.push({ + depth, + hash, + value, + }); + }); + }; +} + +/** + * Returns the value of the heading. + * @param children - Phrasing content. + * @param value - List of heading text values. + * @returns heading text value. + */ +export function getHeadingTextValue( + children: PhrasingContent[], + value: string[] = [] +): string { + for (const child of children) { + if ("value" in child) { + value.push(child.value); + } + if ("children" in child) { + getHeadingTextValue(child.children, value); + } + } + return value.join(""); +} diff --git a/plugins/utils.ts b/plugins/utils.ts new file mode 100644 index 00000000..be3e6939 --- /dev/null +++ b/plugins/utils.ts @@ -0,0 +1,27 @@ +import slugify from "slugify"; + +/** + * Returns node ID, ensuring uniqueness. + * @param setOfIds - Set of IDs. + * @param slug - Slug. + * @returns node ID. + */ +export function generateUniqueId(setOfIds: Set, slug: string): string { + let id = slug; + let i = 1; + while (setOfIds.has(id)) { + id = `${slug}-${i}`; + i++; + } + setOfIds.add(id); + return id; +} + +/** + * Slugify the heading text to generate an ID. + * @param headingText - Heading text. + * @returns heading ID. + */ +export function slugifyHeading(headingText: string): string { + return slugify(headingText, { lower: true, strict: true }); +} From 9b2b4c521a62ccc800ed9d80205988b03beb467a Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:49:54 +1000 Subject: [PATCH 03/18] feat: learn featured analyses stub (#331) --- app/docs/learn/featured-analyses.mdx | 1 + .../LearnContentView/learnContentView.tsx | 9 +++++++ pages/learn/[...slug].tsx | 24 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 app/docs/learn/featured-analyses.mdx create mode 100644 app/views/LearnContentView/learnContentView.tsx create mode 100644 pages/learn/[...slug].tsx diff --git a/app/docs/learn/featured-analyses.mdx b/app/docs/learn/featured-analyses.mdx new file mode 100644 index 00000000..924ac9ad --- /dev/null +++ b/app/docs/learn/featured-analyses.mdx @@ -0,0 +1 @@ +# Featured Analyses diff --git a/app/views/LearnContentView/learnContentView.tsx b/app/views/LearnContentView/learnContentView.tsx new file mode 100644 index 00000000..83af033e --- /dev/null +++ b/app/views/LearnContentView/learnContentView.tsx @@ -0,0 +1,9 @@ +import { Fragment } from "react"; + +export const LearnContentView = (): JSX.Element => { + return ( + +

Learn Content View

+
+ ); +}; diff --git a/pages/learn/[...slug].tsx b/pages/learn/[...slug].tsx new file mode 100644 index 00000000..f9a06b4a --- /dev/null +++ b/pages/learn/[...slug].tsx @@ -0,0 +1,24 @@ +import { GetStaticPaths, GetStaticProps } from "next"; +import { StyledPagesMain } from "../../app/components/Layout/components/Main/main.styles"; +import { LearnContentView } from "../../app/views/LearnContentView/learnContentView"; + +const Page = (): JSX.Element => { + return ; +}; + +export const getStaticProps: GetStaticProps = async () => { + return { + props: {}, + }; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + return { + fallback: false, + paths: [{ params: { slug: ["featured-analyses"] } }], + }; +}; + +export default Page; + +Page.Main = StyledPagesMain; From e05013fb5f2fb31a09a8f750d251b21f8743a4c3 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:09:56 +1000 Subject: [PATCH 04/18] feat: build static paths for the section (#331) --- app/docs/common/files/utils.ts | 57 +++++++++++++++++++ .../common/staticGeneration/staticPaths.ts | 16 ++++++ pages/learn/[...slug].tsx | 8 +-- 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 app/docs/common/files/utils.ts create mode 100644 app/docs/common/staticGeneration/staticPaths.ts diff --git a/app/docs/common/files/utils.ts b/app/docs/common/files/utils.ts new file mode 100644 index 00000000..a34c7c3e --- /dev/null +++ b/app/docs/common/files/utils.ts @@ -0,0 +1,57 @@ +import fs from "fs"; +import * as path from "path"; + +/** + * Returns true if the file is an MDX file. + * @param fileName - File name. + * @returns true if the file is an MDX file. + */ +function isMdxFile(fileName: string): boolean { + return fileName.endsWith(".mdx"); +} + +/** + * Maps each MDX file path to its slug. + * @param docsDirectory - Docs directory. + * @param dirPath - Directory path. + * @param slugByFilePaths - Accumulator: Map of slug by mdx file path. + * @returns returns slug by mdx file path. + */ +export function mapSlugByFilePaths( + docsDirectory: string, + dirPath = docsDirectory, + slugByFilePaths: Map = new Map() +): Map { + const dirents = fs.readdirSync(dirPath, { withFileTypes: true }); + return dirents.reduce((acc, dirent) => { + /* Accumulate the slug for each MDX file. */ + if (dirent.isFile() && isMdxFile(dirent.name)) { + const mdxPath = path.resolve(dirPath, dirent.name); + /* Build the slug from the file relative directory and file name. */ + const mdxRelativePath = path.relative(docsDirectory, mdxPath); + const { dir, name } = path.parse(mdxRelativePath); + let slug = [] as string[]; + if (dir) slug = dir.split(path.sep); + slug.push(name); + acc.set(mdxPath, slug); + } + /* Recurse into subdirectories. */ + if (dirent.isDirectory()) { + mapSlugByFilePaths( + docsDirectory, + path.resolve(dirPath, dirent.name), + acc + ); + } + return acc; + }, slugByFilePaths); +} + +/** + * Resolves the relative path to an absolute path for the docs directory. + * @param relativePath - Relative paths. + * @returns absolute path. + */ +export function resolveRelativePath(relativePath: string[]): string { + return path.join(process.cwd(), "app", "docs", ...relativePath); +} diff --git a/app/docs/common/staticGeneration/staticPaths.ts b/app/docs/common/staticGeneration/staticPaths.ts new file mode 100644 index 00000000..559ee2cb --- /dev/null +++ b/app/docs/common/staticGeneration/staticPaths.ts @@ -0,0 +1,16 @@ +import { GetStaticPathsResult } from "next/types"; +import { mapSlugByFilePaths, resolveRelativePath } from "../files/utils"; + +/** + * Generates static paths for the docs site, for the specified relative paths. + * @param relativePaths - Relative paths. + * @returns static paths. + */ +export function buildStaticPaths( + relativePaths: string[] +): GetStaticPathsResult["paths"] { + const slugByFilePaths = mapSlugByFilePaths( + resolveRelativePath(relativePaths) + ); + return [...slugByFilePaths].map(([, slug]) => ({ params: { slug } })); +} diff --git a/pages/learn/[...slug].tsx b/pages/learn/[...slug].tsx index f9a06b4a..5ece2710 100644 --- a/pages/learn/[...slug].tsx +++ b/pages/learn/[...slug].tsx @@ -1,6 +1,9 @@ import { GetStaticPaths, GetStaticProps } from "next"; import { StyledPagesMain } from "../../app/components/Layout/components/Main/main.styles"; import { LearnContentView } from "../../app/views/LearnContentView/learnContentView"; +import { buildStaticPaths } from "../../app/docs/common/staticGeneration/staticPaths"; + +const SECTION = "learn"; const Page = (): JSX.Element => { return ; @@ -13,10 +16,7 @@ export const getStaticProps: GetStaticProps = async () => { }; export const getStaticPaths: GetStaticPaths = async () => { - return { - fallback: false, - paths: [{ params: { slug: ["featured-analyses"] } }], - }; + return { fallback: false, paths: buildStaticPaths([SECTION]) }; }; export default Page; From cbd508a4517b35693c2b0def5a8a9ea7bb1e63f3 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:38:09 +1000 Subject: [PATCH 05/18] feat: build static props (#331) --- app/docs/common/frontmatter/types.ts | 21 +++++++ app/docs/common/frontmatter/utils.ts | 62 +++++++++++++++++++ .../common/staticGeneration/staticProps.ts | 54 ++++++++++++++++ app/docs/common/staticGeneration/types.ts | 8 +++ app/docs/common/staticGeneration/utils.ts | 17 +++++ app/docs/learn/featured-analyses.mdx | 4 ++ .../LearnContentView/learnContentView.tsx | 5 +- pages/learn/[...slug].tsx | 24 ++++--- 8 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 app/docs/common/frontmatter/types.ts create mode 100644 app/docs/common/frontmatter/utils.ts create mode 100644 app/docs/common/staticGeneration/staticProps.ts create mode 100644 app/docs/common/staticGeneration/types.ts create mode 100644 app/docs/common/staticGeneration/utils.ts diff --git a/app/docs/common/frontmatter/types.ts b/app/docs/common/frontmatter/types.ts new file mode 100644 index 00000000..4cc706ea --- /dev/null +++ b/app/docs/common/frontmatter/types.ts @@ -0,0 +1,21 @@ +import { Breadcrumb } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; +import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; + +export type Frontmatter = FrontmatterBase & + FrontmatterBreadcrumb & + FrontmatterOutline; + +interface FrontmatterBase { + description: string; + hidden?: boolean; + title: string; +} + +interface FrontmatterBreadcrumb { + breadcrumbs?: Breadcrumb[]; +} + +export interface FrontmatterOutline { + enableOutline?: boolean; + outline?: OutlineItem[]; +} diff --git a/app/docs/common/frontmatter/utils.ts b/app/docs/common/frontmatter/utils.ts new file mode 100644 index 00000000..be526c90 --- /dev/null +++ b/app/docs/common/frontmatter/utils.ts @@ -0,0 +1,62 @@ +import fs from "fs"; +import matter from "gray-matter"; +import { mapSlugByFilePaths, resolveRelativePath } from "../files/utils"; +import { Frontmatter } from "./types"; + +/** + * Returns list of paths and frontmatter tuples for the given relative paths. + * @param relativePaths - Relative paths. + * @returns list of paths and frontmatter tuples. + */ +export function getFrontmatterPaths( + relativePaths: string[] +): [string, Frontmatter][] { + const slugByFilePaths = mapSlugByFilePaths( + resolveRelativePath(relativePaths) + ); + return [...getFrontmatterByPaths(slugByFilePaths)]; +} + +/** + * Returns Map of frontmatter by path. + * @param slugByFilePaths - Map slug by file path. + * @returns Map of frontmatter by path. + */ +export function getFrontmatterByPaths( + slugByFilePaths: Map +): Map { + const frontmatterByPath: Map = new Map(); + for (const [filePath, slug] of [...slugByFilePaths]) { + const { data: frontmatter } = getMatter(filePath); + const path = slug.join("/"); + if (path) frontmatterByPath.set(path, frontmatter as Frontmatter); + } + return frontmatterByPath; +} + +/** + * Returns matter object (frontmatter and content) from the given MDX path. + * @param filePath - File path of MD / MDX file. + * @returns matter object. + */ +export function getMatter(filePath: string): matter.GrayMatterFile { + const markdownWithMeta = fs.readFileSync(filePath, "utf-8"); + return matter(markdownWithMeta); +} + +/** + * Returns the frontmatter from the given grey matter file data. + * @param data - Grey matter file data. + * @returns frontmatter. + */ +export function sanitizeFrontmatter( + data: matter.GrayMatterFile["data"] +): Frontmatter | undefined { + if ("title" in data) { + const { enableOutline = true } = data; + return { + enableOutline, + ...data, + } as Frontmatter; + } +} diff --git a/app/docs/common/staticGeneration/staticProps.ts b/app/docs/common/staticGeneration/staticProps.ts new file mode 100644 index 00000000..14c6ea3b --- /dev/null +++ b/app/docs/common/staticGeneration/staticProps.ts @@ -0,0 +1,54 @@ +import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; +import { serialize } from "next-mdx-remote/serialize"; +import { GetStaticPropsResult } from "next/types"; +import remarkGfm from "remark-gfm"; +import { Frontmatter } from "../frontmatter/types"; +import { StaticProps } from "./types"; +import { getMatter, sanitizeFrontmatter } from "../frontmatter/utils"; +import { rehypeSlug } from "../../../../plugins/rehypeSlug"; +import { remarkHeadings } from "../../../../plugins/remarkHeadings"; +import { SerializeOptions } from "next-mdx-remote/dist/types"; +import { resolveRelativePath } from "../files/utils"; + +export async function buildStaticProps( + slug: string[] | undefined, + frontmatterFn = ( + frontmatter: Frontmatter | undefined + ): Frontmatter | undefined => frontmatter, + serializeOptions: SerializeOptions = {} +): Promise | undefined> { + if (!slug) return; + + // Build the file name from the slug. + const fileName = slug.join("/").concat(".mdx"); + + // Extract frontmatter and content from the MDX file. + const { content, data } = getMatter(resolveRelativePath([fileName])); + const frontmatter = frontmatterFn(sanitizeFrontmatter(data)); + + // If the frontmatter is hidden, return. + if (!frontmatter || frontmatter.hidden) return; + + // We expect the frontmatter to have a title. + if (!frontmatter.title) return; + + // Serialize the MDX content. + const outline: OutlineItem[] = []; + const mdxSource = await serialize(content, { + ...serializeOptions, + mdxOptions: { + development: false, + rehypePlugins: [rehypeSlug], + remarkPlugins: [[remarkHeadings, outline], remarkGfm], + }, + scope: { ...serializeOptions.scope, frontmatter }, + }); + + return { + props: { + frontmatter, + mdxSource, + pageTitle: frontmatter.title, + }, + }; +} diff --git a/app/docs/common/staticGeneration/types.ts b/app/docs/common/staticGeneration/types.ts new file mode 100644 index 00000000..fb667877 --- /dev/null +++ b/app/docs/common/staticGeneration/types.ts @@ -0,0 +1,8 @@ +import { Frontmatter } from "../frontmatter/types"; +import { MDXRemoteSerializeResult } from "next-mdx-remote"; + +export interface StaticProps { + frontmatter: Frontmatter; + mdxSource: MDXRemoteSerializeResult; + pageTitle: string; +} diff --git a/app/docs/common/staticGeneration/utils.ts b/app/docs/common/staticGeneration/utils.ts new file mode 100644 index 00000000..91220390 --- /dev/null +++ b/app/docs/common/staticGeneration/utils.ts @@ -0,0 +1,17 @@ +import { GetStaticPropsContext } from "next/types"; + +/** + * Returns the page slug for the given static props context and section. + * @param props - Static props context. + * @param section - Docs section e.g. "learn". + * @returns page slug. + */ +export function buildSlug( + props: GetStaticPropsContext, + section?: string +): string[] | undefined { + const slug = props.params?.slug; + if (!slug || typeof slug === "string") return; + if (section) return [section, ...slug]; + return slug; +} diff --git a/app/docs/learn/featured-analyses.mdx b/app/docs/learn/featured-analyses.mdx index 924ac9ad..560bef47 100644 --- a/app/docs/learn/featured-analyses.mdx +++ b/app/docs/learn/featured-analyses.mdx @@ -1 +1,5 @@ +--- +title: Featured Analyses +--- + # Featured Analyses diff --git a/app/views/LearnContentView/learnContentView.tsx b/app/views/LearnContentView/learnContentView.tsx index 83af033e..32e1dfc9 100644 --- a/app/views/LearnContentView/learnContentView.tsx +++ b/app/views/LearnContentView/learnContentView.tsx @@ -1,9 +1,10 @@ import { Fragment } from "react"; +import { StaticProps } from "../../docs/common/staticGeneration/types"; -export const LearnContentView = (): JSX.Element => { +export const LearnContentView = (props: StaticProps): JSX.Element => { return ( -

Learn Content View

+

{props.pageTitle}

); }; diff --git a/pages/learn/[...slug].tsx b/pages/learn/[...slug].tsx index 5ece2710..23a5fd6f 100644 --- a/pages/learn/[...slug].tsx +++ b/pages/learn/[...slug].tsx @@ -1,18 +1,28 @@ -import { GetStaticPaths, GetStaticProps } from "next"; +import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next"; import { StyledPagesMain } from "../../app/components/Layout/components/Main/main.styles"; import { LearnContentView } from "../../app/views/LearnContentView/learnContentView"; import { buildStaticPaths } from "../../app/docs/common/staticGeneration/staticPaths"; +import { buildStaticProps } from "../../app/docs/common/staticGeneration/staticProps"; +import { buildSlug } from "../../app/docs/common/staticGeneration/utils"; +import { StaticProps } from "../../app/docs/common/staticGeneration/types"; const SECTION = "learn"; -const Page = (): JSX.Element => { - return ; +const Page = (props: StaticProps): JSX.Element => { + return ; }; -export const getStaticProps: GetStaticProps = async () => { - return { - props: {}, - }; +export const getStaticProps: GetStaticProps = async ( + props: GetStaticPropsContext +) => { + // Build the static props for the page. + const staticProps = await buildStaticProps(buildSlug(props, SECTION)); + + // If the static props are not found, return not found. + if (!staticProps) return { notFound: true }; + + // Return the static props. + return staticProps; }; export const getStaticPaths: GetStaticPaths = async () => { From 5d70f6bbec448cf5e16b1bb961bd046b4b351368 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:44:37 +1000 Subject: [PATCH 06/18] feat: added section hero to page (#331) --- app/docs/common/frontmatter/types.ts | 1 + app/docs/learn/featured-analyses.mdx | 6 ++++++ app/views/LearnContentView/learnContentView.tsx | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/docs/common/frontmatter/types.ts b/app/docs/common/frontmatter/types.ts index 4cc706ea..19a75368 100644 --- a/app/docs/common/frontmatter/types.ts +++ b/app/docs/common/frontmatter/types.ts @@ -8,6 +8,7 @@ export type Frontmatter = FrontmatterBase & interface FrontmatterBase { description: string; hidden?: boolean; + subtitle?: string; title: string; } diff --git a/app/docs/learn/featured-analyses.mdx b/app/docs/learn/featured-analyses.mdx index 560bef47..4b30d8ef 100644 --- a/app/docs/learn/featured-analyses.mdx +++ b/app/docs/learn/featured-analyses.mdx @@ -1,4 +1,10 @@ --- +breadcrumbs: + - path: "" + text: "Learn" + - path: "" + text: "Featured Analyses" +description: "" title: Featured Analyses --- diff --git a/app/views/LearnContentView/learnContentView.tsx b/app/views/LearnContentView/learnContentView.tsx index 32e1dfc9..4df3d685 100644 --- a/app/views/LearnContentView/learnContentView.tsx +++ b/app/views/LearnContentView/learnContentView.tsx @@ -1,10 +1,16 @@ import { Fragment } from "react"; import { StaticProps } from "../../docs/common/staticGeneration/types"; +import { SectionHero } from "../../components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero"; export const LearnContentView = (props: StaticProps): JSX.Element => { return ( -

{props.pageTitle}

+ +

{props.frontmatter.description}

); }; From 4151d807e820a9a8399c8924646ade1b458ffab7 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:50:32 +1000 Subject: [PATCH 07/18] feat: added slug to static props (#331) --- app/docs/common/staticGeneration/staticProps.ts | 8 +++----- app/docs/common/staticGeneration/types.ts | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/docs/common/staticGeneration/staticProps.ts b/app/docs/common/staticGeneration/staticProps.ts index 14c6ea3b..de22c719 100644 --- a/app/docs/common/staticGeneration/staticProps.ts +++ b/app/docs/common/staticGeneration/staticProps.ts @@ -44,11 +44,9 @@ export async function buildStaticProps( scope: { ...serializeOptions.scope, frontmatter }, }); + const { title: pageTitle } = frontmatter; + return { - props: { - frontmatter, - mdxSource, - pageTitle: frontmatter.title, - }, + props: { frontmatter, mdxSource, pageTitle, slug }, }; } diff --git a/app/docs/common/staticGeneration/types.ts b/app/docs/common/staticGeneration/types.ts index fb667877..d8f8c8f2 100644 --- a/app/docs/common/staticGeneration/types.ts +++ b/app/docs/common/staticGeneration/types.ts @@ -5,4 +5,5 @@ export interface StaticProps { frontmatter: Frontmatter; mdxSource: MDXRemoteSerializeResult; pageTitle: string; + slug: string[]; } From 97d76c7321fd81ef4220b53f43787c9e886277fb Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:02:56 +1000 Subject: [PATCH 08/18] feat: render page with mdx remote (#331) --- app/docs/common/mdx/constants.ts | 6 ++++++ app/docs/learn/featured-analyses.mdx | 5 ++++- app/views/LearnContentView/learnContentView.tsx | 11 +++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 app/docs/common/mdx/constants.ts diff --git a/app/docs/common/mdx/constants.ts b/app/docs/common/mdx/constants.ts new file mode 100644 index 00000000..6b4e1a48 --- /dev/null +++ b/app/docs/common/mdx/constants.ts @@ -0,0 +1,6 @@ +import { MDXComponents } from "mdx/types"; +import { AnchorLink } from "@databiosphere/findable-ui/lib/components/common/AnchorLink/anchorLink"; + +export const MDX_COMPONENTS: MDXComponents = { + AnchorLink, +}; diff --git a/app/docs/learn/featured-analyses.mdx b/app/docs/learn/featured-analyses.mdx index 4b30d8ef..b186deba 100644 --- a/app/docs/learn/featured-analyses.mdx +++ b/app/docs/learn/featured-analyses.mdx @@ -8,4 +8,7 @@ description: "" title: Featured Analyses --- -# Featured Analyses +## Variant Calling + +- [From data to publication in a browser with BRC-Analytics: Evolutionary dynamics of coding + overlaps in measles virus](/learn/featured-analyses) diff --git a/app/views/LearnContentView/learnContentView.tsx b/app/views/LearnContentView/learnContentView.tsx index 4df3d685..a211c05b 100644 --- a/app/views/LearnContentView/learnContentView.tsx +++ b/app/views/LearnContentView/learnContentView.tsx @@ -1,16 +1,19 @@ import { Fragment } from "react"; import { StaticProps } from "../../docs/common/staticGeneration/types"; import { SectionHero } from "../../components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero"; +import { MDXRemote } from "next-mdx-remote"; +import { MDX_COMPONENTS } from "../../docs/common/mdx/constants"; export const LearnContentView = (props: StaticProps): JSX.Element => { + const { frontmatter, mdxSource } = props; return ( -

{props.frontmatter.description}

+
); }; From e247eda00e075e47bf77c82ba8dbd271d5b1640e Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:41:25 +1000 Subject: [PATCH 09/18] feat: added section styles (#331) --- .../Docs/components/Content/content.styles.ts | 27 +++++++++++++++++++ .../LearnContentView/learnContentView.tsx | 13 ++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 app/components/Docs/components/Content/content.styles.ts diff --git a/app/components/Docs/components/Content/content.styles.ts b/app/components/Docs/components/Content/content.styles.ts new file mode 100644 index 00000000..c8e98ba7 --- /dev/null +++ b/app/components/Docs/components/Content/content.styles.ts @@ -0,0 +1,27 @@ +import { SectionContent } from "../../../content/content.styles"; +import styled from "@emotion/styled"; + +// See https://github.com/emotion-js/emotion/issues/1105. +// See https://github.com/emotion-js/emotion/releases/tag/%40emotion%2Fcache%4011.10.2. +const ignoreSsrWarning = + "/* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */"; +export const StyledSectionContent = styled(SectionContent)` + margin-top: 0; + + > *:first-child:not(style) ${ignoreSsrWarning} { + margin-top: 0; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + &:hover { + a { + opacity: 1; + } + } + } +`; diff --git a/app/views/LearnContentView/learnContentView.tsx b/app/views/LearnContentView/learnContentView.tsx index a211c05b..aa58b934 100644 --- a/app/views/LearnContentView/learnContentView.tsx +++ b/app/views/LearnContentView/learnContentView.tsx @@ -3,6 +3,11 @@ import { StaticProps } from "../../docs/common/staticGeneration/types"; import { SectionHero } from "../../components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero"; import { MDXRemote } from "next-mdx-remote"; import { MDX_COMPONENTS } from "../../docs/common/mdx/constants"; +import { + Section, + SectionLayout, +} from "../../components/content/content.styles"; +import { StyledSectionContent } from "../../components/Docs/components/Content/content.styles"; export const LearnContentView = (props: StaticProps): JSX.Element => { const { frontmatter, mdxSource } = props; @@ -13,7 +18,13 @@ export const LearnContentView = (props: StaticProps): JSX.Element => { head={frontmatter.title} subHead={frontmatter.subtitle} /> - +
+ + + + + +
); }; From 66a6b81094c71a6b0d10039ea63d5aa50acf31d9 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:14:45 +1000 Subject: [PATCH 10/18] feat: add section overview (#331) --- .../SectionOverview/sectionOverview.styles.ts | 47 ++++++++++++++++++ .../SectionOverview/sectionOverview.tsx | 48 +++++++++++++++++++ .../Docs/components/SectionOverview/types.ts | 10 ++++ .../Docs/components/SectionOverview/utils.ts | 13 +++++ .../common/Typography/Heading/heading.tsx | 34 +++++++++++++ app/docs/common/frontmatter/types.ts | 5 ++ app/docs/common/mdx/constants.ts | 2 + app/docs/learn/featured-analyses.mdx | 10 ++-- 8 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 app/components/Docs/components/SectionOverview/sectionOverview.styles.ts create mode 100644 app/components/Docs/components/SectionOverview/sectionOverview.tsx create mode 100644 app/components/Docs/components/SectionOverview/types.ts create mode 100644 app/components/Docs/components/SectionOverview/utils.ts create mode 100644 app/components/Docs/components/common/Typography/Heading/heading.tsx diff --git a/app/components/Docs/components/SectionOverview/sectionOverview.styles.ts b/app/components/Docs/components/SectionOverview/sectionOverview.styles.ts new file mode 100644 index 00000000..af35c9c7 --- /dev/null +++ b/app/components/Docs/components/SectionOverview/sectionOverview.styles.ts @@ -0,0 +1,47 @@ +import styled from "@emotion/styled"; +import { bpUpSm } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; +import { PALETTE } from "@databiosphere/findable-ui/lib/styles/common/constants/palette"; + +export const GroupOverview = styled.div` + .MuiDivider-root, + .MuiTypography-heading { + grid-column: 1 / -1; + } + + .MuiDivider-root { + margin: 32px 0; + } +`; + +export const GroupLinks = styled.div` + margin-top: 8px; + + ${bpUpSm} { + display: grid; + gap: 0 64px; + grid-auto-columns: 1fr; + + ul + ul { + grid-column: 2; + } + } +`; + +export const StyledUnorderedList = styled("ul")` + list-style-position: inside; + padding-left: 0 !important; + + li { + margin: 4px 0; + padding-left: 24px; // required for list-style-position: inside; allows for market to be positioned inside the list item. + text-indent: -15px; // required for list-style-position: inside; centering marker; half of the 24px width and half marker width @ 6px. + + > * { + margin-left: -6px; // required for list-style-position: inside; assists with vertical alignment of list item; difference between indent and padding adjustments and half of the marker width. + } + + &::marker { + color: ${PALETTE.PRIMARY_MAIN}; + } + } +`; diff --git a/app/components/Docs/components/SectionOverview/sectionOverview.tsx b/app/components/Docs/components/SectionOverview/sectionOverview.tsx new file mode 100644 index 00000000..b7c7974c --- /dev/null +++ b/app/components/Docs/components/SectionOverview/sectionOverview.tsx @@ -0,0 +1,48 @@ +import { Link } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link"; +import { Divider } from "@mui/material"; +import { Fragment } from "react"; +import { + GroupOverview, + GroupLinks, + StyledUnorderedList, +} from "./sectionOverview.styles"; +import { splitLinks } from "./utils"; +import { Props } from "./types"; +import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; +import { Heading } from "../common/Typography/Heading/heading"; + +export const SectionOverview = ({ overview }: Props): JSX.Element | null => { + if (!overview) return null; + return ( + + {overview.map(({ label, links }, groupIndex) => { + return ( + links.length > 0 && ( + + {groupIndex > 0 && } + + + {splitLinks(links).map( + (links, linksIndex) => + links.length > 0 && ( + + {links.map((linkProps, listIndex) => ( +
  • + +
  • + ))} +
    + ) + )} +
    +
    + ) + ); + })} +
    + ); +}; diff --git a/app/components/Docs/components/SectionOverview/types.ts b/app/components/Docs/components/SectionOverview/types.ts new file mode 100644 index 00000000..1a9d9334 --- /dev/null +++ b/app/components/Docs/components/SectionOverview/types.ts @@ -0,0 +1,10 @@ +import { LinkProps } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link"; + +export interface Overview { + label: string; + links: LinkProps[]; +} + +export interface Props { + overview?: Overview[]; +} diff --git a/app/components/Docs/components/SectionOverview/utils.ts b/app/components/Docs/components/SectionOverview/utils.ts new file mode 100644 index 00000000..6ec946d5 --- /dev/null +++ b/app/components/Docs/components/SectionOverview/utils.ts @@ -0,0 +1,13 @@ +import { LinkProps } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link"; + +const MAX_ROWS = 3; + +/** + * Splits group overview links into two arrays suitable for a two-column layout. + * @param links - Section overview links. + * @returns section overview links, evenly split into two arrays. + */ +export function splitLinks(links: LinkProps[]): LinkProps[][] { + const sliceIndex = Math.max(MAX_ROWS, Math.ceil(links.length / 2)); + return [links.slice(0, sliceIndex), links.slice(sliceIndex)]; +} diff --git a/app/components/Docs/components/common/Typography/Heading/heading.tsx b/app/components/Docs/components/common/Typography/Heading/heading.tsx new file mode 100644 index 00000000..8b20b4e3 --- /dev/null +++ b/app/components/Docs/components/common/Typography/Heading/heading.tsx @@ -0,0 +1,34 @@ +import { AnchorLink } from "@databiosphere/findable-ui/lib/components/common/AnchorLink/anchorLink"; +import { Typography, TypographyProps } from "@mui/material"; +import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; +import { slugifyHeading } from "../../../../../../../plugins/utils"; + +export interface HeadingProps { + component?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + enableAnchor?: boolean; + headingValue: string; + headingValueSlug?: string; + sx?: TypographyProps["sx"]; + variant?: TypographyProps["variant"]; +} + +export const Heading = ({ + component = "h1", + enableAnchor = true, + headingValue, + headingValueSlug = slugifyHeading(headingValue), + sx = { mb: 2 }, + variant = TYPOGRAPHY_PROPS.VARIANT.HEADING_LARGE, +}: HeadingProps): JSX.Element => { + return ( + + {headingValue} + {enableAnchor && } + + ); +}; diff --git a/app/docs/common/frontmatter/types.ts b/app/docs/common/frontmatter/types.ts index 19a75368..b28f43f9 100644 --- a/app/docs/common/frontmatter/types.ts +++ b/app/docs/common/frontmatter/types.ts @@ -1,5 +1,6 @@ import { Breadcrumb } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; +import { Overview } from "../../../components/Docs/components/SectionOverview/types"; export type Frontmatter = FrontmatterBase & FrontmatterBreadcrumb & @@ -20,3 +21,7 @@ export interface FrontmatterOutline { enableOutline?: boolean; outline?: OutlineItem[]; } + +export interface FrontmatterOverview { + overview?: Overview[]; +} diff --git a/app/docs/common/mdx/constants.ts b/app/docs/common/mdx/constants.ts index 6b4e1a48..a62d6fb5 100644 --- a/app/docs/common/mdx/constants.ts +++ b/app/docs/common/mdx/constants.ts @@ -1,6 +1,8 @@ import { MDXComponents } from "mdx/types"; import { AnchorLink } from "@databiosphere/findable-ui/lib/components/common/AnchorLink/anchorLink"; +import { SectionOverview } from "../../../components/Docs/components/SectionOverview/sectionOverview"; export const MDX_COMPONENTS: MDXComponents = { AnchorLink, + SectionOverview, }; diff --git a/app/docs/learn/featured-analyses.mdx b/app/docs/learn/featured-analyses.mdx index b186deba..e3789d53 100644 --- a/app/docs/learn/featured-analyses.mdx +++ b/app/docs/learn/featured-analyses.mdx @@ -5,10 +5,12 @@ breadcrumbs: - path: "" text: "Featured Analyses" description: "" +overview: + - label: "Variant Calling" + links: + - label: "From data to publication in a browser with BRC-Analytics: Evolutionary dynamics of coding overlaps in measles virus" + url: "" title: Featured Analyses --- -## Variant Calling - -- [From data to publication in a browser with BRC-Analytics: Evolutionary dynamics of coding - overlaps in measles virus](/learn/featured-analyses) + From 1de752589de8bbc0d4c72056375d8f423c38ef4f Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:21:46 +1000 Subject: [PATCH 11/18] feat: added theme options to static props (#331) --- app/docs/common/staticGeneration/staticProps.ts | 10 +++++++++- app/docs/common/staticGeneration/types.ts | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/docs/common/staticGeneration/staticProps.ts b/app/docs/common/staticGeneration/staticProps.ts index de22c719..4e943d11 100644 --- a/app/docs/common/staticGeneration/staticProps.ts +++ b/app/docs/common/staticGeneration/staticProps.ts @@ -47,6 +47,14 @@ export async function buildStaticProps( const { title: pageTitle } = frontmatter; return { - props: { frontmatter, mdxSource, pageTitle, slug }, + props: { + frontmatter, + mdxSource, + pageTitle, + slug, + themeOptions: { + palette: { background: { default: "#FAFBFB" } }, // SMOKE_LIGHTEST + }, + }, }; } diff --git a/app/docs/common/staticGeneration/types.ts b/app/docs/common/staticGeneration/types.ts index d8f8c8f2..b6be571b 100644 --- a/app/docs/common/staticGeneration/types.ts +++ b/app/docs/common/staticGeneration/types.ts @@ -1,3 +1,4 @@ +import { ThemeOptions } from "@mui/material"; import { Frontmatter } from "../frontmatter/types"; import { MDXRemoteSerializeResult } from "next-mdx-remote"; @@ -6,4 +7,5 @@ export interface StaticProps { mdxSource: MDXRemoteSerializeResult; pageTitle: string; slug: string[]; + themeOptions: ThemeOptions; } From 68ece75287826cea52ed434ac89c5b498b99eee9 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:31:35 +1000 Subject: [PATCH 12/18] refactor: unused code (#331) --- app/docs/common/frontmatter/utils.ts | 32 ---------------------------- app/docs/learn/featured-analyses.mdx | 1 + 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/app/docs/common/frontmatter/utils.ts b/app/docs/common/frontmatter/utils.ts index be526c90..33f51f78 100644 --- a/app/docs/common/frontmatter/utils.ts +++ b/app/docs/common/frontmatter/utils.ts @@ -1,39 +1,7 @@ import fs from "fs"; import matter from "gray-matter"; -import { mapSlugByFilePaths, resolveRelativePath } from "../files/utils"; import { Frontmatter } from "./types"; -/** - * Returns list of paths and frontmatter tuples for the given relative paths. - * @param relativePaths - Relative paths. - * @returns list of paths and frontmatter tuples. - */ -export function getFrontmatterPaths( - relativePaths: string[] -): [string, Frontmatter][] { - const slugByFilePaths = mapSlugByFilePaths( - resolveRelativePath(relativePaths) - ); - return [...getFrontmatterByPaths(slugByFilePaths)]; -} - -/** - * Returns Map of frontmatter by path. - * @param slugByFilePaths - Map slug by file path. - * @returns Map of frontmatter by path. - */ -export function getFrontmatterByPaths( - slugByFilePaths: Map -): Map { - const frontmatterByPath: Map = new Map(); - for (const [filePath, slug] of [...slugByFilePaths]) { - const { data: frontmatter } = getMatter(filePath); - const path = slug.join("/"); - if (path) frontmatterByPath.set(path, frontmatter as Frontmatter); - } - return frontmatterByPath; -} - /** * Returns matter object (frontmatter and content) from the given MDX path. * @param filePath - File path of MD / MDX file. diff --git a/app/docs/learn/featured-analyses.mdx b/app/docs/learn/featured-analyses.mdx index e3789d53..fd0a623d 100644 --- a/app/docs/learn/featured-analyses.mdx +++ b/app/docs/learn/featured-analyses.mdx @@ -5,6 +5,7 @@ breadcrumbs: - path: "" text: "Featured Analyses" description: "" +enableOutline: false overview: - label: "Variant Calling" links: From d6037a7273f5505fba8268b71c84bdf7f845d5bd Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:41:00 +1000 Subject: [PATCH 13/18] refactor: split links fn (#331) --- app/components/Docs/components/SectionOverview/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/Docs/components/SectionOverview/utils.ts b/app/components/Docs/components/SectionOverview/utils.ts index 6ec946d5..9c6c2eaf 100644 --- a/app/components/Docs/components/SectionOverview/utils.ts +++ b/app/components/Docs/components/SectionOverview/utils.ts @@ -8,6 +8,7 @@ const MAX_ROWS = 3; * @returns section overview links, evenly split into two arrays. */ export function splitLinks(links: LinkProps[]): LinkProps[][] { - const sliceIndex = Math.max(MAX_ROWS, Math.ceil(links.length / 2)); + if (links.length <= MAX_ROWS) return [links]; + const sliceIndex = Math.ceil(links.length / 2); return [links.slice(0, sliceIndex), links.slice(sliceIndex)]; } From 71fc363f15ec3caee409d6f7dc7f90ca684011d0 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:20:20 +1000 Subject: [PATCH 14/18] chore: update findable-ui to latest v46.0.0 (#331) --- .../SectionOverview/sectionOverview.tsx | 5 +- .../Typography/Heading/heading.styles.ts | 6 + .../common/Typography/Heading/heading.tsx | 36 ++-- app/docs/common/files/utils.ts | 57 ------ app/docs/common/frontmatter/types.ts | 24 +-- app/docs/common/frontmatter/utils.ts | 39 ++-- .../common/staticGeneration/staticPaths.ts | 16 -- .../common/staticGeneration/staticProps.ts | 60 ------ app/docs/common/staticGeneration/types.ts | 14 +- app/docs/common/staticGeneration/utils.ts | 17 -- .../LearnContentView/learnContentView.tsx | 13 +- package-lock.json | 175 +++++++++--------- package.json | 4 +- pages/learn/[...slug].tsx | 32 +++- 14 files changed, 180 insertions(+), 318 deletions(-) create mode 100644 app/components/Docs/components/common/Typography/Heading/heading.styles.ts delete mode 100644 app/docs/common/files/utils.ts delete mode 100644 app/docs/common/staticGeneration/staticPaths.ts delete mode 100644 app/docs/common/staticGeneration/staticProps.ts delete mode 100644 app/docs/common/staticGeneration/utils.ts diff --git a/app/components/Docs/components/SectionOverview/sectionOverview.tsx b/app/components/Docs/components/SectionOverview/sectionOverview.tsx index b7c7974c..3fb6456f 100644 --- a/app/components/Docs/components/SectionOverview/sectionOverview.tsx +++ b/app/components/Docs/components/SectionOverview/sectionOverview.tsx @@ -22,9 +22,10 @@ export const SectionOverview = ({ overview }: Props): JSX.Element | null => { {groupIndex > 0 && } + > + {label} + {splitLinks(links).map( (links, linksIndex) => diff --git a/app/components/Docs/components/common/Typography/Heading/heading.styles.ts b/app/components/Docs/components/common/Typography/Heading/heading.styles.ts new file mode 100644 index 00000000..383f1489 --- /dev/null +++ b/app/components/Docs/components/common/Typography/Heading/heading.styles.ts @@ -0,0 +1,6 @@ +import styled from "@emotion/styled"; +import { Typography } from "@mui/material"; + +export const StyledTypography = styled(Typography)` + position: relative; +` as typeof Typography; diff --git a/app/components/Docs/components/common/Typography/Heading/heading.tsx b/app/components/Docs/components/common/Typography/Heading/heading.tsx index 8b20b4e3..43ff4c9e 100644 --- a/app/components/Docs/components/common/Typography/Heading/heading.tsx +++ b/app/components/Docs/components/common/Typography/Heading/heading.tsx @@ -1,34 +1,28 @@ import { AnchorLink } from "@databiosphere/findable-ui/lib/components/common/AnchorLink/anchorLink"; -import { Typography, TypographyProps } from "@mui/material"; +import { TypographyProps } from "@mui/material"; import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; -import { slugifyHeading } from "../../../../../../../plugins/utils"; +import { slugifyHeading } from "@databiosphere/findable-ui/lib/utils/mdx/plugins/utils"; +import { StyledTypography } from "./heading.styles"; -export interface HeadingProps { - component?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; +export interface HeadingProps extends TypographyProps { enableAnchor?: boolean; - headingValue: string; - headingValueSlug?: string; - sx?: TypographyProps["sx"]; - variant?: TypographyProps["variant"]; } export const Heading = ({ - component = "h1", + children, enableAnchor = true, - headingValue, - headingValueSlug = slugifyHeading(headingValue), - sx = { mb: 2 }, - variant = TYPOGRAPHY_PROPS.VARIANT.HEADING_LARGE, + ...props }: HeadingProps): JSX.Element => { + const id = slugifyHeading(String(children)); return ( - - {headingValue} - {enableAnchor && } - + {children} + {enableAnchor && } + ); }; diff --git a/app/docs/common/files/utils.ts b/app/docs/common/files/utils.ts deleted file mode 100644 index a34c7c3e..00000000 --- a/app/docs/common/files/utils.ts +++ /dev/null @@ -1,57 +0,0 @@ -import fs from "fs"; -import * as path from "path"; - -/** - * Returns true if the file is an MDX file. - * @param fileName - File name. - * @returns true if the file is an MDX file. - */ -function isMdxFile(fileName: string): boolean { - return fileName.endsWith(".mdx"); -} - -/** - * Maps each MDX file path to its slug. - * @param docsDirectory - Docs directory. - * @param dirPath - Directory path. - * @param slugByFilePaths - Accumulator: Map of slug by mdx file path. - * @returns returns slug by mdx file path. - */ -export function mapSlugByFilePaths( - docsDirectory: string, - dirPath = docsDirectory, - slugByFilePaths: Map = new Map() -): Map { - const dirents = fs.readdirSync(dirPath, { withFileTypes: true }); - return dirents.reduce((acc, dirent) => { - /* Accumulate the slug for each MDX file. */ - if (dirent.isFile() && isMdxFile(dirent.name)) { - const mdxPath = path.resolve(dirPath, dirent.name); - /* Build the slug from the file relative directory and file name. */ - const mdxRelativePath = path.relative(docsDirectory, mdxPath); - const { dir, name } = path.parse(mdxRelativePath); - let slug = [] as string[]; - if (dir) slug = dir.split(path.sep); - slug.push(name); - acc.set(mdxPath, slug); - } - /* Recurse into subdirectories. */ - if (dirent.isDirectory()) { - mapSlugByFilePaths( - docsDirectory, - path.resolve(dirPath, dirent.name), - acc - ); - } - return acc; - }, slugByFilePaths); -} - -/** - * Resolves the relative path to an absolute path for the docs directory. - * @param relativePath - Relative paths. - * @returns absolute path. - */ -export function resolveRelativePath(relativePath: string[]): string { - return path.join(process.cwd(), "app", "docs", ...relativePath); -} diff --git a/app/docs/common/frontmatter/types.ts b/app/docs/common/frontmatter/types.ts index b28f43f9..eb07688b 100644 --- a/app/docs/common/frontmatter/types.ts +++ b/app/docs/common/frontmatter/types.ts @@ -1,27 +1,21 @@ import { Breadcrumb } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; -import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; import { Overview } from "../../../components/Docs/components/SectionOverview/types"; +import { FrontmatterProps as BaseFrontmatterProps } from "@databiosphere/findable-ui/lib/utils/mdx/frontmatter/types"; -export type Frontmatter = FrontmatterBase & - FrontmatterBreadcrumb & - FrontmatterOutline; +export type FrontmatterProps = BaseFrontmatterProps< + FrontmatterBreadcrumbProps & + FrontmatterOutlineProps & + FrontmatterOverviewProps +>; -interface FrontmatterBase { - description: string; - hidden?: boolean; - subtitle?: string; - title: string; -} - -interface FrontmatterBreadcrumb { +interface FrontmatterBreadcrumbProps { breadcrumbs?: Breadcrumb[]; } -export interface FrontmatterOutline { +export interface FrontmatterOutlineProps { enableOutline?: boolean; - outline?: OutlineItem[]; } -export interface FrontmatterOverview { +export interface FrontmatterOverviewProps { overview?: Overview[]; } diff --git a/app/docs/common/frontmatter/utils.ts b/app/docs/common/frontmatter/utils.ts index 33f51f78..c8df9069 100644 --- a/app/docs/common/frontmatter/utils.ts +++ b/app/docs/common/frontmatter/utils.ts @@ -1,30 +1,19 @@ -import fs from "fs"; -import matter from "gray-matter"; -import { Frontmatter } from "./types"; +import { FrontmatterProps } from "./types"; /** - * Returns matter object (frontmatter and content) from the given MDX path. - * @param filePath - File path of MD / MDX file. - * @returns matter object. - */ -export function getMatter(filePath: string): matter.GrayMatterFile { - const markdownWithMeta = fs.readFileSync(filePath, "utf-8"); - return matter(markdownWithMeta); -} - -/** - * Returns the frontmatter from the given grey matter file data. - * @param data - Grey matter file data. - * @returns frontmatter. + * Sanitizes the frontmatter by adding default values. + * @param frontmatter - The frontmatter to sanitize. + * @returns The sanitized frontmatter. */ export function sanitizeFrontmatter( - data: matter.GrayMatterFile["data"] -): Frontmatter | undefined { - if ("title" in data) { - const { enableOutline = true } = data; - return { - enableOutline, - ...data, - } as Frontmatter; - } + frontmatter: FrontmatterProps | undefined +): FrontmatterProps | undefined { + if (!frontmatter) return; + + const { enableOutline = true } = frontmatter || {}; + + return { + ...frontmatter, + enableOutline, + }; } diff --git a/app/docs/common/staticGeneration/staticPaths.ts b/app/docs/common/staticGeneration/staticPaths.ts deleted file mode 100644 index 559ee2cb..00000000 --- a/app/docs/common/staticGeneration/staticPaths.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GetStaticPathsResult } from "next/types"; -import { mapSlugByFilePaths, resolveRelativePath } from "../files/utils"; - -/** - * Generates static paths for the docs site, for the specified relative paths. - * @param relativePaths - Relative paths. - * @returns static paths. - */ -export function buildStaticPaths( - relativePaths: string[] -): GetStaticPathsResult["paths"] { - const slugByFilePaths = mapSlugByFilePaths( - resolveRelativePath(relativePaths) - ); - return [...slugByFilePaths].map(([, slug]) => ({ params: { slug } })); -} diff --git a/app/docs/common/staticGeneration/staticProps.ts b/app/docs/common/staticGeneration/staticProps.ts deleted file mode 100644 index 4e943d11..00000000 --- a/app/docs/common/staticGeneration/staticProps.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; -import { serialize } from "next-mdx-remote/serialize"; -import { GetStaticPropsResult } from "next/types"; -import remarkGfm from "remark-gfm"; -import { Frontmatter } from "../frontmatter/types"; -import { StaticProps } from "./types"; -import { getMatter, sanitizeFrontmatter } from "../frontmatter/utils"; -import { rehypeSlug } from "../../../../plugins/rehypeSlug"; -import { remarkHeadings } from "../../../../plugins/remarkHeadings"; -import { SerializeOptions } from "next-mdx-remote/dist/types"; -import { resolveRelativePath } from "../files/utils"; - -export async function buildStaticProps( - slug: string[] | undefined, - frontmatterFn = ( - frontmatter: Frontmatter | undefined - ): Frontmatter | undefined => frontmatter, - serializeOptions: SerializeOptions = {} -): Promise | undefined> { - if (!slug) return; - - // Build the file name from the slug. - const fileName = slug.join("/").concat(".mdx"); - - // Extract frontmatter and content from the MDX file. - const { content, data } = getMatter(resolveRelativePath([fileName])); - const frontmatter = frontmatterFn(sanitizeFrontmatter(data)); - - // If the frontmatter is hidden, return. - if (!frontmatter || frontmatter.hidden) return; - - // We expect the frontmatter to have a title. - if (!frontmatter.title) return; - - // Serialize the MDX content. - const outline: OutlineItem[] = []; - const mdxSource = await serialize(content, { - ...serializeOptions, - mdxOptions: { - development: false, - rehypePlugins: [rehypeSlug], - remarkPlugins: [[remarkHeadings, outline], remarkGfm], - }, - scope: { ...serializeOptions.scope, frontmatter }, - }); - - const { title: pageTitle } = frontmatter; - - return { - props: { - frontmatter, - mdxSource, - pageTitle, - slug, - themeOptions: { - palette: { background: { default: "#FAFBFB" } }, // SMOKE_LIGHTEST - }, - }, - }; -} diff --git a/app/docs/common/staticGeneration/types.ts b/app/docs/common/staticGeneration/types.ts index b6be571b..d4b30e98 100644 --- a/app/docs/common/staticGeneration/types.ts +++ b/app/docs/common/staticGeneration/types.ts @@ -1,11 +1,9 @@ import { ThemeOptions } from "@mui/material"; -import { Frontmatter } from "../frontmatter/types"; -import { MDXRemoteSerializeResult } from "next-mdx-remote"; +import { StaticProps as BaseStaticProps } from "@databiosphere/findable-ui/lib/utils/mdx/staticGeneration/types"; +import { FrontmatterProps } from "../frontmatter/types"; -export interface StaticProps { - frontmatter: Frontmatter; - mdxSource: MDXRemoteSerializeResult; - pageTitle: string; - slug: string[]; - themeOptions: ThemeOptions; +export type StaticProps = BaseStaticProps; + +export interface PageStaticProps { + themeOptions?: ThemeOptions; } diff --git a/app/docs/common/staticGeneration/utils.ts b/app/docs/common/staticGeneration/utils.ts deleted file mode 100644 index 91220390..00000000 --- a/app/docs/common/staticGeneration/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { GetStaticPropsContext } from "next/types"; - -/** - * Returns the page slug for the given static props context and section. - * @param props - Static props context. - * @param section - Docs section e.g. "learn". - * @returns page slug. - */ -export function buildSlug( - props: GetStaticPropsContext, - section?: string -): string[] | undefined { - const slug = props.params?.slug; - if (!slug || typeof slug === "string") return; - if (section) return [section, ...slug]; - return slug; -} diff --git a/app/views/LearnContentView/learnContentView.tsx b/app/views/LearnContentView/learnContentView.tsx index aa58b934..568cf76d 100644 --- a/app/views/LearnContentView/learnContentView.tsx +++ b/app/views/LearnContentView/learnContentView.tsx @@ -9,14 +9,19 @@ import { } from "../../components/content/content.styles"; import { StyledSectionContent } from "../../components/Docs/components/Content/content.styles"; -export const LearnContentView = (props: StaticProps): JSX.Element => { +export const LearnContentView = (props: StaticProps): JSX.Element | null => { const { frontmatter, mdxSource } = props; + + if (!mdxSource) return null; + + const { breadcrumbs, title } = frontmatter || {}; + return (
    diff --git a/package-lock.json b/package-lock.json index dbcc5caa..ab434278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "brc-analytics", "version": "0.16.0", "dependencies": { - "@databiosphere/findable-ui": "^45.1.0", + "@databiosphere/findable-ui": "^46.0.0", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mdx-js/loader": "^3.0.1", @@ -25,7 +25,7 @@ "ky": "^1.7.2", "next": "^14.2.30", "next-compose-plugins": "^2.2.1", - "next-mdx-remote": "^4.2.0", + "next-mdx-remote": "^4.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-window": "1.8.9", @@ -2408,9 +2408,9 @@ } }, "node_modules/@databiosphere/findable-ui": { - "version": "45.1.0", - "resolved": "https://registry.npmjs.org/@databiosphere/findable-ui/-/findable-ui-45.1.0.tgz", - "integrity": "sha512-a2/2SK04IsYJ7W7rqV5S9jhHYrO88pMR2cSBZqpamyR7L0tDA1bnsDC3u1KSFcnRggtqperf0BOoA0aIJj0NVw==", + "version": "46.0.0", + "resolved": "https://registry.npmjs.org/@databiosphere/findable-ui/-/findable-ui-46.0.0.tgz", + "integrity": "sha512-VNwxOKdQX+yAoMRm3RoAks897zpo/2Ys5hnfZL9Zzw10IkbvEbPakNyMqNagDMxoJqbCOhcTT2fVEHdx933Qig==", "engines": { "node": "20.10.0" }, @@ -2424,10 +2424,12 @@ "@tanstack/react-table": "^8.19.2", "@tanstack/react-virtual": "^3.13.12", "copy-to-clipboard": "3.3.1", + "gray-matter": "^4.0.3", "isomorphic-dompurify": "^2.22.0", "ky": "^1.7.2", - "next": "^14.2.30", - "next-auth": "^4.24.7", + "next": "^14.2.32", + "next-auth": "4.24.11", + "next-mdx-remote": "^4.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", @@ -2440,6 +2442,7 @@ "remark-gfm": "^3.0.1", "remark-parse": "^10.0.2", "remark-rehype": "^10.1.0", + "slugify": "^1.6.6", "unified": "^10.1.2", "uuid": "8.3.2", "yup": "^1.6.1" @@ -4450,9 +4453,9 @@ "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==" }, "node_modules/@next/env": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.30.tgz", - "integrity": "sha512-KBiBKrDY6kxTQWGzKjQB7QirL3PiiOkV7KW98leHFjtVRKtft76Ra5qSA/SL75xT44dp6hOcqiiJ6iievLOYug==" + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.33.tgz", + "integrity": "sha512-CgVHNZ1fRIlxkLhIX22flAZI/HmpDaZ8vwyJ/B0SDPTBuLZ1PJ+DWMjCHhqnExfmSQzA/PbZi8OAc7PAq2w9IA==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.2.30", @@ -4492,9 +4495,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.30.tgz", - "integrity": "sha512-EAqfOTb3bTGh9+ewpO/jC59uACadRHM6TSA9DdxJB/6gxOpyV+zrbqeXiFTDy9uV6bmipFDkfpAskeaDcO+7/g==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", "cpu": [ "arm64" ], @@ -4507,9 +4510,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.30.tgz", - "integrity": "sha512-TyO7Wz1IKE2kGv8dwQ0bmPL3s44EKVencOqwIY69myoS3rdpO1NPg5xPM5ymKu7nfX4oYJrpMxv8G9iqLsnL4A==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", "cpu": [ "x64" ], @@ -4522,9 +4525,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.30.tgz", - "integrity": "sha512-I5lg1fgPJ7I5dk6mr3qCH1hJYKJu1FsfKSiTKoYwcuUf53HWTrEkwmMI0t5ojFKeA6Vu+SfT2zVy5NS0QLXV4Q==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", "cpu": [ "arm64" ], @@ -4537,9 +4540,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.30.tgz", - "integrity": "sha512-8GkNA+sLclQyxgzCDs2/2GSwBc92QLMrmYAmoP2xehe5MUKBLB2cgo34Yu242L1siSkwQkiV4YLdCnjwc/Micw==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", "cpu": [ "arm64" ], @@ -4552,9 +4555,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.30.tgz", - "integrity": "sha512-8Ly7okjssLuBoe8qaRCcjGtcMsv79hwzn/63wNeIkzJVFVX06h5S737XNr7DZwlsbTBDOyI6qbL2BJB5n6TV/w==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", "cpu": [ "x64" ], @@ -4567,9 +4570,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.30.tgz", - "integrity": "sha512-dBmV1lLNeX4mR7uI7KNVHsGQU+OgTG5RGFPi3tBJpsKPvOPtg9poyav/BYWrB3GPQL4dW5YGGgalwZ79WukbKQ==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", "cpu": [ "x64" ], @@ -4582,9 +4585,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.30.tgz", - "integrity": "sha512-6MMHi2Qc1Gkq+4YLXAgbYslE1f9zMGBikKMdmQRHXjkGPot1JY3n5/Qrbg40Uvbi8//wYnydPnyvNhI1DMUW1g==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", "cpu": [ "arm64" ], @@ -4597,9 +4600,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.30.tgz", - "integrity": "sha512-pVZMnFok5qEX4RT59mK2hEVtJX+XFfak+/rjHpyFh7juiT52r177bfFKhnlafm0UOSldhXjj32b+LZIOdswGTg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", "cpu": [ "ia32" ], @@ -4612,9 +4615,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.30.tgz", - "integrity": "sha512-4KCo8hMZXMjpTzs3HOqOGYYwAXymXIy7PEPAXNEcEOyKqkjiDlECumrWziy+JEF0Oi4ILHGxzgQ3YiMGG2t/Lg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", "cpu": [ "x64" ], @@ -4724,12 +4727,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", - "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "devOptional": true, "dependencies": { - "playwright": "1.49.1" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -6452,7 +6455,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -9548,7 +9550,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -9759,7 +9760,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, "dependencies": { "is-extendable": "^0.1.0" }, @@ -9992,6 +9992,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -10302,7 +10316,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dev": true, "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", @@ -11145,7 +11158,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13544,7 +13556,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -13712,7 +13723,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -17558,11 +17568,11 @@ "peer": true }, "node_modules/next": { - "version": "14.2.30", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.30.tgz", - "integrity": "sha512-+COdu6HQrHHFQ1S/8BBsCag61jZacmvbuL2avHvQFbWa2Ox7bE+d8FyNgxRLjXQ5wtPyQwEmk85js/AuaG2Sbg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz", + "integrity": "sha512-GiKHLsD00t4ACm1p00VgrI0rUFAC9cRDGReKyERlM57aeEZkOQGcZTpIbsGn0b562FTPJWmYfKwplfO9EaT6ng==", "dependencies": { - "@next/env": "14.2.30", + "@next/env": "14.2.33", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -17577,15 +17587,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.30", - "@next/swc-darwin-x64": "14.2.30", - "@next/swc-linux-arm64-gnu": "14.2.30", - "@next/swc-linux-arm64-musl": "14.2.30", - "@next/swc-linux-x64-gnu": "14.2.30", - "@next/swc-linux-x64-musl": "14.2.30", - "@next/swc-win32-arm64-msvc": "14.2.30", - "@next/swc-win32-ia32-msvc": "14.2.30", - "@next/swc-win32-x64-msvc": "14.2.30" + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -17644,12 +17654,12 @@ "integrity": "sha512-OjJ+fV15FXO2uQXQagLD4C0abYErBjyjE0I0FHpOEIB8upw0hg1ldFP6cqHTJBH1cZqy96OeR3u1dJ+Ez2D4Bg==" }, "node_modules/next-mdx-remote": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.2.0.tgz", - "integrity": "sha512-X5RhD7f7b78pH2abbuusObSGgII5l54OdusS/2iXljN7WN1cel6ToLlZeCZcyxx9cR4wmBGQYGongIttDYNmAA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/next-mdx-remote/-/next-mdx-remote-4.4.1.tgz", + "integrity": "sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==", "dependencies": { - "@mdx-js/mdx": "^2.0.0", - "@mdx-js/react": "^2.0.0", + "@mdx-js/mdx": "^2.2.1", + "@mdx-js/react": "^2.2.1", "vfile": "^5.3.0", "vfile-matter": "^3.0.1" }, @@ -17723,9 +17733,9 @@ } }, "node_modules/next-mdx-remote/node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, "node_modules/next-mdx-remote/node_modules/estree-util-attach-comments": { "version": "2.1.1", @@ -18552,11 +18562,11 @@ } }, "node_modules/next-mdx-remote/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/next-mdx-remote/node_modules/unist-util-is": { @@ -19261,12 +19271,12 @@ } }, "node_modules/playwright": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", - "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "devOptional": true, "dependencies": { - "playwright-core": "1.49.1" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -19279,9 +19289,9 @@ } }, "node_modules/playwright-core": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", - "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "devOptional": true, "bin": { "playwright-core": "cli.js" @@ -20902,7 +20912,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dev": true, "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" @@ -21113,8 +21122,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/stack-utils": { "version": "2.0.6", @@ -21381,7 +21389,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index 7ea45904..fc7a9f76 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "validate-ga2-catalog": "./catalog/ga2/schema/scripts/validate-catalog.sh" }, "dependencies": { - "@databiosphere/findable-ui": "^45.1.0", + "@databiosphere/findable-ui": "^46.0.0", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@mdx-js/loader": "^3.0.1", @@ -48,7 +48,7 @@ "ky": "^1.7.2", "next": "^14.2.30", "next-compose-plugins": "^2.2.1", - "next-mdx-remote": "^4.2.0", + "next-mdx-remote": "^4.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-window": "1.8.9", diff --git a/pages/learn/[...slug].tsx b/pages/learn/[...slug].tsx index 23a5fd6f..8e152ff4 100644 --- a/pages/learn/[...slug].tsx +++ b/pages/learn/[...slug].tsx @@ -1,22 +1,37 @@ import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next"; import { StyledPagesMain } from "../../app/components/Layout/components/Main/main.styles"; import { LearnContentView } from "../../app/views/LearnContentView/learnContentView"; -import { buildStaticPaths } from "../../app/docs/common/staticGeneration/staticPaths"; -import { buildStaticProps } from "../../app/docs/common/staticGeneration/staticProps"; -import { buildSlug } from "../../app/docs/common/staticGeneration/utils"; +import { buildStaticPaths } from "@databiosphere/findable-ui/lib/utils/mdx/staticGeneration/staticPaths"; +import { buildStaticProps } from "@databiosphere/findable-ui/lib/utils/mdx/staticGeneration/staticProps"; import { StaticProps } from "../../app/docs/common/staticGeneration/types"; +import { sanitizeFrontmatter } from "../../app/docs/common/frontmatter/utils"; +import { + buildMDXFilePath, + buildMDXSlug, +} from "@databiosphere/findable-ui/lib/utils/mdx/staticGeneration/utils"; -const SECTION = "learn"; +const APPS_DIR = "app"; +const DOCS_DIR = "docs"; +const LEARN_DIR = "learn"; const Page = (props: StaticProps): JSX.Element => { return ; }; -export const getStaticProps: GetStaticProps = async ( +export const getStaticProps: GetStaticProps = async ( props: GetStaticPropsContext ) => { + // Build the slug. + const slug = buildMDXSlug(props, LEARN_DIR); + // Build the static props for the page. - const staticProps = await buildStaticProps(buildSlug(props, SECTION)); + const staticProps = await buildStaticProps( + buildMDXFilePath([APPS_DIR, DOCS_DIR], slug), + slug, + sanitizeFrontmatter, + undefined, + { themeOptions: { palette: { background: { default: "#FAFBFB" } } } } + ); // If the static props are not found, return not found. if (!staticProps) return { notFound: true }; @@ -26,7 +41,10 @@ export const getStaticProps: GetStaticProps = async ( }; export const getStaticPaths: GetStaticPaths = async () => { - return { fallback: false, paths: buildStaticPaths([SECTION]) }; + return { + fallback: false, + paths: buildStaticPaths([APPS_DIR, DOCS_DIR, LEARN_DIR]), + }; }; export default Page; From 06622b5c186fdee1645f3fe41a9182c95dda7375 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:21:46 +1000 Subject: [PATCH 15/18] feat: content styles updated (#331) --- app/components/Docs/components/Content/content.styles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/Docs/components/Content/content.styles.ts b/app/components/Docs/components/Content/content.styles.ts index c8e98ba7..50a82105 100644 --- a/app/components/Docs/components/Content/content.styles.ts +++ b/app/components/Docs/components/Content/content.styles.ts @@ -8,10 +8,6 @@ const ignoreSsrWarning = export const StyledSectionContent = styled(SectionContent)` margin-top: 0; - > *:first-child:not(style) ${ignoreSsrWarning} { - margin-top: 0; - } - h1, h2, h3, @@ -24,4 +20,8 @@ export const StyledSectionContent = styled(SectionContent)` } } } + + > *:first-child:not(style) ${ignoreSsrWarning} { + margin-top: 0; + } `; From bdf110da7db26fb4758c816f2c8fd3e8ce8374dc Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:52:56 +1000 Subject: [PATCH 16/18] fix: explicitly set mdx dev flag for dev and prod runtime (#331) --- pages/learn/[...slug].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/learn/[...slug].tsx b/pages/learn/[...slug].tsx index 8e152ff4..aa3a2b2d 100644 --- a/pages/learn/[...slug].tsx +++ b/pages/learn/[...slug].tsx @@ -29,7 +29,7 @@ export const getStaticProps: GetStaticProps = async ( buildMDXFilePath([APPS_DIR, DOCS_DIR], slug), slug, sanitizeFrontmatter, - undefined, + { mdxOptions: { development: process.env.NODE_ENV !== "production" } }, { themeOptions: { palette: { background: { default: "#FAFBFB" } } } } ); From 7bbcb029069d1584dcd6cf33fdf34ea85f4cb683 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:04:37 +1000 Subject: [PATCH 17/18] refactor: removed unused plugins (#331) --- plugins/rehypeSlug.ts | 36 ---------------------------- plugins/remarkHeadings.ts | 50 --------------------------------------- plugins/utils.ts | 27 --------------------- 3 files changed, 113 deletions(-) delete mode 100644 plugins/rehypeSlug.ts delete mode 100644 plugins/remarkHeadings.ts delete mode 100644 plugins/utils.ts diff --git a/plugins/rehypeSlug.ts b/plugins/rehypeSlug.ts deleted file mode 100644 index a0fa2c3a..00000000 --- a/plugins/rehypeSlug.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Plugin } from "unified"; -import { visit } from "unist-util-visit"; -import { generateUniqueId, slugifyHeading } from "./utils"; -import { getHeadingTextValue } from "./remarkHeadings"; - -/** - * Rehype plugin to generate an ID for each heading in MDX content. - * @returns plugin to generate an ID for each heading in MDX content. - */ -export function rehypeSlug(): Plugin { - return (tree) => { - const setOfIds = new Set(); - visit(tree, "element", (node) => { - if (/^h[1-6]$/.test(node.tagName)) { - const headingText = getHeadingTextValue(node.children); - const headingSlug = slugifyHeading(headingText); - const id = generateUniqueId(setOfIds, headingSlug); - // Add the ID to the heading element. - node.properties.id = id; - node.properties.style = "position: relative;"; - // Append AnchorLink to the heading element. - node.children.push({ - attributes: [ - { - name: "anchorLink", - type: "mdxJsxAttribute", - value: id, - }, - ], - name: "AnchorLink", - type: "mdxJsxFlowElement", - }); - } - }); - }; -} diff --git a/plugins/remarkHeadings.ts b/plugins/remarkHeadings.ts deleted file mode 100644 index 72662f15..00000000 --- a/plugins/remarkHeadings.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { OutlineItem } from "@databiosphere/findable-ui/lib/components/Layout/components/Outline/types"; -import { Heading, PhrasingContent } from "mdast"; -import { Plugin } from "unified"; -import { visit } from "unist-util-visit"; -import { generateUniqueId, slugifyHeading } from "./utils"; - -/** - * Remark plugin to generate an outline from MDX content. - * The outline is a list of headings with their depth, hash, and value. - * @param outline - Outline items. - * @returns plugin to generate an outline from MDX content. - */ -export function remarkHeadings(outline: OutlineItem[]): Plugin { - return (tree) => { - const setOfIds = new Set(); - visit(tree, "heading", (node) => { - const heading = node as Heading; - const { children, depth } = heading; - const value = getHeadingTextValue(children); - const headingSlug = slugifyHeading(value); - const hash = generateUniqueId(setOfIds, headingSlug); - outline.push({ - depth, - hash, - value, - }); - }); - }; -} - -/** - * Returns the value of the heading. - * @param children - Phrasing content. - * @param value - List of heading text values. - * @returns heading text value. - */ -export function getHeadingTextValue( - children: PhrasingContent[], - value: string[] = [] -): string { - for (const child of children) { - if ("value" in child) { - value.push(child.value); - } - if ("children" in child) { - getHeadingTextValue(child.children, value); - } - } - return value.join(""); -} diff --git a/plugins/utils.ts b/plugins/utils.ts deleted file mode 100644 index be3e6939..00000000 --- a/plugins/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -import slugify from "slugify"; - -/** - * Returns node ID, ensuring uniqueness. - * @param setOfIds - Set of IDs. - * @param slug - Slug. - * @returns node ID. - */ -export function generateUniqueId(setOfIds: Set, slug: string): string { - let id = slug; - let i = 1; - while (setOfIds.has(id)) { - id = `${slug}-${i}`; - i++; - } - setOfIds.add(id); - return id; -} - -/** - * Slugify the heading text to generate an ID. - * @param headingText - Heading text. - * @returns heading ID. - */ -export function slugifyHeading(headingText: string): string { - return slugify(headingText, { lower: true, strict: true }); -} From 2c29404e9f394bd69410f1588a9cbf0663ba972e Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:06:07 +1000 Subject: [PATCH 18/18] feat: hide learn link in header (#331) --- site-config/brc-analytics/local/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site-config/brc-analytics/local/config.ts b/site-config/brc-analytics/local/config.ts index f3538561..1e279cc2 100644 --- a/site-config/brc-analytics/local/config.ts +++ b/site-config/brc-analytics/local/config.ts @@ -89,7 +89,7 @@ export function makeConfig( undefined, [ { label: "About", url: ROUTES.ABOUT }, - { label: "Learn", url: ROUTES.LEARN }, + // { label: "Learn", url: ROUTES.LEARN }, { label: "Organisms", url: ROUTES.ORGANISMS }, { label: "Assemblies", url: ROUTES.GENOMES }, { label: "Priority Pathogens", url: ROUTES.PRIORITY_PATHOGENS },