diff --git a/app/[locale]/[...slug]/page.tsx b/app/[locale]/[...slug]/page.tsx index 28020fde0ff..8f7229b3039 100644 --- a/app/[locale]/[...slug]/page.tsx +++ b/app/[locale]/[...slug]/page.tsx @@ -7,6 +7,7 @@ import mdComponents from "@/components/MdComponents" import { dataLoader } from "@/lib/utils/data/dataLoader" import { dateToString } from "@/lib/utils/date" +import { getLayoutFromSlug } from "@/lib/utils/layout" import { getPostSlugs } from "@/lib/utils/md" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -19,18 +20,6 @@ import { getMdMetadata } from "@/lib/md/metadata" const loadData = dataLoader([["gfissues", fetchGFIs]]) -function getLayoutFromSlug(slug: string) { - if (slug.includes("developers/docs")) { - return "docs" - } - - if (slug.includes("developers/tutorials")) { - return "tutorial" - } - - return "static" -} - export default async function Page({ params, }: { @@ -69,7 +58,8 @@ export default async function Page({ slug, // TODO: Address component typing error here (flip `FC` types to prop object types) // @ts-expect-error Incompatible component function signatures - components: { ...mdComponents, ...componentsMapping }, + baseComponents: mdComponents, + componentsMapping, scope: { gfissues, }, diff --git a/public/content/ai-agents/index.md b/public/content/ai-agents/index.md index eb8e8a4100b..be8f143a0d8 100644 --- a/public/content/ai-agents/index.md +++ b/public/content/ai-agents/index.md @@ -39,7 +39,7 @@ In contrast, Ethereum's decentralized ecosystem offers several key advantages: These factors transform AI agents from simple bots into dynamic, self-improving systems that offer significant value across multiple sectors: - + diff --git a/public/content/payments/index.md b/public/content/payments/index.md index 88d93f2af25..72569c2908c 100644 --- a/public/content/payments/index.md +++ b/public/content/payments/index.md @@ -27,7 +27,7 @@ This isn't a far-off dream – it's happening today on Ethereum. While tradition For millions of people working abroad, sending money back home is a regular necessity. Traditional remittance services often come with high fees and slow processing times. Ethereum offers a compelling alternative. - + diff --git a/public/content/prediction-markets/index.md b/public/content/prediction-markets/index.md index d1d0d79c49d..bfd97221f07 100644 --- a/public/content/prediction-markets/index.md +++ b/public/content/prediction-markets/index.md @@ -30,7 +30,7 @@ In theory, because bettors stand to profit from being correct, prediction market Unlike traditional forecasting, blockchain-based prediction markets are: - + diff --git a/src/components/ActionCard.tsx b/src/components/ActionCard.tsx index 9491c2d15e6..c6c8e4d3cef 100644 --- a/src/components/ActionCard.tsx +++ b/src/components/ActionCard.tsx @@ -41,7 +41,7 @@ const ActionCard = ({ return (
diff --git a/src/components/Codeblock.tsx b/src/components/Codeblock.tsx index f4c6989977a..dc91bdea881 100644 --- a/src/components/Codeblock.tsx +++ b/src/components/Codeblock.tsx @@ -41,10 +41,6 @@ const TopBarItem = ({ const codeTheme = { light: { - plain: { - backgroundColor: "#f7f7f7", // background-highlight (gray-50) - color: "#6C24DF", // primary (purple-600) - }, styles: [ { style: { color: "#6c6783" }, @@ -113,10 +109,6 @@ const codeTheme = { }, dark: { // Pulled from `defaultProps.theme` for potential customization - plain: { - backgroundColor: "#121212", // background-highlight (gray-900) - color: "#B38DF0", // primary (purple-400) - }, styles: [ { style: { color: "#6c6783" }, @@ -253,7 +245,7 @@ const Codeblock = ({ /* Context: https://github.com/ethereum/ethereum-org-website/issues/6202 */
) => ( (
) +const Pre = (props: React.HTMLAttributes) => { + const match = props.className?.match(/(language-\S+)/) + const codeLanguage = match ? match[0] : "plain-text" + return +} + export const docsComponents = { h1: H1, h2: H2, h3: H3, h4: H4, - pre: Codeblock, + pre: Pre, ...mdxTableComponents, ButtonLink, Card, @@ -88,7 +94,7 @@ export const docsComponents = { GlossaryTooltip, InfoBanner, YouTube, -} +} as MDXRemoteProps["components"] type DocsLayoutProps = Pick< MdPageContent, diff --git a/src/layouts/Tutorial.tsx b/src/layouts/Tutorial.tsx index f7cbcc5c667..7cb4e7e9147 100644 --- a/src/layouts/Tutorial.tsx +++ b/src/layouts/Tutorial.tsx @@ -1,3 +1,4 @@ +import { MDXRemoteProps } from "next-mdx-remote" import type { HTMLAttributes } from "react" import type { ChildOnlyProp } from "@/lib/types" @@ -28,17 +29,11 @@ import YouTube from "@/components/YouTube" import { getEditPath } from "@/lib/utils/editPath" const Heading1 = (props: HTMLAttributes) => ( - + ) const Heading2 = (props: HTMLAttributes) => ( - + ) const Heading3 = (props: HTMLAttributes) => ( @@ -66,6 +61,12 @@ const KBD = (props: HTMLAttributes) => ( /> ) +const Pre = (props: React.HTMLAttributes) => { + const match = props.className?.match(/(language-\S+)/) + const codeLanguage = match ? match[0] : "plain-text" + return +} + export const tutorialsComponents = { a: TooltipLink, h1: Heading1, @@ -74,7 +75,7 @@ export const tutorialsComponents = { h4: Heading4, p: Paragraph, kbd: KBD, - pre: Codeblock, + pre: Pre, ...mdxTableComponents, ButtonLink, CallToContribute, @@ -83,7 +84,8 @@ export const tutorialsComponents = { EnvWarningBanner, InfoBanner, YouTube, -} +} as MDXRemoteProps["components"] + type TutorialLayoutProps = ChildOnlyProp & Pick< MdPageContent, diff --git a/src/layouts/index.ts b/src/layouts/index.ts index 58315d9cc7d..ed4ab2d45c7 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -1,3 +1,7 @@ +import { MDXRemoteProps } from "next-mdx-remote" + +import { Layout } from "@/lib/types" + import { docsComponents, DocsLayout } from "./Docs" import * as mdLayouts from "./md" import { staticComponents, StaticLayout } from "./Static" @@ -20,13 +24,13 @@ export const layoutMapping = { tutorial: TutorialLayout, } -export const componentsMapping = { - ...staticComponents, - ...mdLayouts.useCasesComponents, - ...mdLayouts.stakingComponents, - ...mdLayouts.roadmapComponents, - ...mdLayouts.upgradeComponents, - ...mdLayouts.translatathonComponents, - ...docsComponents, - ...tutorialsComponents, -} as const +export const componentsMapping: Record = { + static: staticComponents, + "use-cases": mdLayouts.useCasesComponents, + staking: mdLayouts.stakingComponents, + roadmap: mdLayouts.roadmapComponents, + upgrade: mdLayouts.upgradeComponents, + translatathon: mdLayouts.translatathonComponents, + docs: docsComponents, + tutorial: tutorialsComponents, +} diff --git a/src/layouts/md/Roadmap.tsx b/src/layouts/md/Roadmap.tsx index 42a99514679..fac90dcac5b 100644 --- a/src/layouts/md/Roadmap.tsx +++ b/src/layouts/md/Roadmap.tsx @@ -11,13 +11,8 @@ import { ContentLayout } from "../ContentLayout" import { useTranslation } from "@/hooks/useTranslation" import RoadmapHubHeroImage from "@/public/images/heroes/roadmap-hub-hero.jpg" -const CardGrid = (props: ChildOnlyProp) => ( -
-) - // Roadmap layout components export const roadmapComponents = { - CardGrid, RoadmapActionCard, RoadmapImageContent, } diff --git a/src/layouts/md/UseCases.tsx b/src/layouts/md/UseCases.tsx index 3d89264affd..3702fdf35ef 100644 --- a/src/layouts/md/UseCases.tsx +++ b/src/layouts/md/UseCases.tsx @@ -18,8 +18,16 @@ import { ContentLayout } from "../ContentLayout" import { useTranslation } from "@/hooks/useTranslation" +const CardGrid = (props: ChildOnlyProp) => ( +
+) + // UseCases layout components export const useCasesComponents = { + CardGrid, AiAgentProductLists, BuildYourOwnAIAgent, PredictionMarketLists, diff --git a/src/lib/md/compile.ts b/src/lib/md/compile.ts index 6aa64f5ea74..bdfda42dbc4 100644 --- a/src/lib/md/compile.ts +++ b/src/lib/md/compile.ts @@ -9,7 +9,7 @@ import remarkGfm from "remark-gfm" import remarkHeadingId from "remark-heading-id" import { CONTENT_DIR, CONTENT_PATH } from "../constants" -import { Frontmatter, TocNodeType } from "../types" +import { Frontmatter, Layout, TocNodeType } from "../types" import rehypeImg from "@/lib/md/rehypeImg" import remarkInferToc from "@/lib/md/remarkInferToc" @@ -80,3 +80,15 @@ export const compile = async ({ tocNodeItems, } } + +export const extractLayoutFromMarkdown = async ( + markdown: string +): Promise => { + const source = preprocessMarkdown(markdown) + + const { frontmatter } = await compileMDX({ + source, + options: { parseFrontmatter: true }, + }) + return frontmatter.template +} diff --git a/src/lib/md/data.ts b/src/lib/md/data.ts index 0a6db9a3df6..74e4093ca58 100644 --- a/src/lib/md/data.ts +++ b/src/lib/md/data.ts @@ -1,6 +1,7 @@ import { MDXRemoteProps } from "next-mdx-remote" import readingTime, { ReadTimeResults } from "reading-time" +import type { Layout } from "@/lib/types" import { CommitHistory, FileContributor, @@ -12,7 +13,9 @@ import { import { getMarkdownFileContributorInfo } from "@/lib/utils/contributors" import { getLocaleTimestamp } from "@/lib/utils/time" -import { compile } from "./compile" +import { getLayoutFromSlug } from "../utils/layout" + +import { compile, extractLayoutFromMarkdown } from "./compile" import { importMd } from "./import" const commitHistoryCache: CommitHistory = {} @@ -20,7 +23,9 @@ const commitHistoryCache: CommitHistory = {} interface GetPageDataParams { locale: string slug: string - components: MDXRemoteProps["components"] + baseComponents: MDXRemoteProps["components"] + componentsMapping: Record + layout?: Layout scope?: Record } @@ -37,13 +42,26 @@ interface PageData { export async function getPageData({ locale, slug, - components, + baseComponents, + componentsMapping, + layout: layoutFromProps, scope, }: GetPageDataParams): Promise { const slugArray = slug.split("/") // Import and compile markdown const { markdown, isTranslated } = await importMd(locale, slug) + // Determine layout first to finalize list of components + const layout = + layoutFromProps || + (await extractLayoutFromMarkdown(markdown)) || + getLayoutFromSlug(slug) + + const components: MDXRemoteProps["components"] = { + ...baseComponents, + ...(layout ? componentsMapping[layout] : {}), + } + const { content, frontmatter, tocNodeItems } = await compile({ markdown, slugArray, diff --git a/src/lib/utils/layout.ts b/src/lib/utils/layout.ts new file mode 100644 index 00000000000..718a2ae1e65 --- /dev/null +++ b/src/lib/utils/layout.ts @@ -0,0 +1,5 @@ +export const getLayoutFromSlug = (slug: string) => { + if (slug.includes("developers/docs")) return "docs" + if (slug.includes("developers/tutorials")) return "tutorial" + return "static" +}