From 9be9470b13482623b47b17749a4bf8f0b55ac213 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Wed, 16 Oct 2024 09:59:13 -0600 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8F=97=20Support=20parts=20mdast=20tr?= =?UTF-8?q?ees=20in=20page=20frontmatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/itchy-doors-leave.md | 12 +++++ packages/common/src/types.ts | 3 +- packages/common/src/utils.ts | 47 ++++++++++--------- packages/frontmatter/src/FrontmatterBlock.tsx | 2 +- packages/myst-demo/src/index.tsx | 3 +- packages/myst-to-react/src/crossReference.tsx | 19 ++++++-- packages/providers/src/references.tsx | 6 +-- packages/site/src/pages/Article.tsx | 2 +- themes/article/app/components/Article.tsx | 2 +- themes/book/app/components/ArticlePage.tsx | 2 +- 10 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 .changeset/itchy-doors-leave.md diff --git a/.changeset/itchy-doors-leave.md b/.changeset/itchy-doors-leave.md new file mode 100644 index 000000000..72137166c --- /dev/null +++ b/.changeset/itchy-doors-leave.md @@ -0,0 +1,12 @@ +--- +'myst-to-react': patch +'@myst-theme/frontmatter': patch +'myst-demo': patch +'@myst-theme/providers': patch +'@myst-theme/common': patch +'@myst-theme/article': patch +'@myst-theme/site': patch +'@myst-theme/book': patch +--- + +Support parts mdast trees in page frontmatter diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index ff6fa68ec..2b38c48df 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -45,7 +45,8 @@ export type FooterLinks = { }; }; -type PageFrontmatterWithDownloads = Omit & { +type PageFrontmatterWithDownloads = Omit & { + parts?: Record; downloads?: SiteAction[]; exports?: SiteExport[]; }; diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index f9994d377..5f56e3516 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -188,28 +188,31 @@ export function updatePageStaticLinksInplace(data: PageLoader, updateUrl: Update return { ...exp, url: updateUrl(exp.url) }; }); } - // Fix all of the images to point to the CDN - const images = selectAll('image', data.mdast) as Image[]; - images.forEach((node) => { - node.url = updateUrl(node.url); - if (node.urlOptimized) { - node.urlOptimized = updateUrl(node.urlOptimized); - } - }); - const links = selectAll('link,linkBlock,card', data.mdast) as Link[]; - const staticLinks = links.filter((node) => node.static); - staticLinks.forEach((node) => { - // These are static links to thinks like PDFs or other referenced files - node.url = updateUrl(node.url); - }); - const outputs = selectAll('output', data.mdast) as Output[]; - outputs.forEach((node) => { - if (!node.data) return; - walkOutputs(node.data, (obj) => { - // The path will be defined from output of myst - // Here we are re-assigning it to the current domain - if (!obj.path) return; - obj.path = updateUrl(obj.path); + const allMdastTrees = [data.mdast, ...Object.values(data.frontmatter?.parts ?? {})]; + allMdastTrees.forEach((tree) => { + // Fix all of the images to point to the CDN + const images = selectAll('image', tree) as Image[]; + images.forEach((node) => { + node.url = updateUrl(node.url); + if (node.urlOptimized) { + node.urlOptimized = updateUrl(node.urlOptimized); + } + }); + const links = selectAll('link,linkBlock,card', tree) as Link[]; + const staticLinks = links.filter((node) => node.static); + staticLinks.forEach((node) => { + // These are static links to thinks like PDFs or other referenced files + node.url = updateUrl(node.url); + }); + const outputs = selectAll('output', tree) as Output[]; + outputs.forEach((node) => { + if (!node.data) return; + walkOutputs(node.data, (obj) => { + // The path will be defined from output of myst + // Here we are re-assigning it to the current domain + if (!obj.path) return; + obj.path = updateUrl(obj.path); + }); }); }); return data; diff --git a/packages/frontmatter/src/FrontmatterBlock.tsx b/packages/frontmatter/src/FrontmatterBlock.tsx index d311cf643..36c66ad22 100644 --- a/packages/frontmatter/src/FrontmatterBlock.tsx +++ b/packages/frontmatter/src/FrontmatterBlock.tsx @@ -194,7 +194,7 @@ export function FrontmatterBlock({ hideExports, className, }: { - frontmatter: PageFrontmatter; + frontmatter: Omit; kind?: SourceFileKind; authorStyle?: 'block' | 'list'; hideBadges?: boolean; diff --git a/packages/myst-demo/src/index.tsx b/packages/myst-demo/src/index.tsx index 3fbcebcbc..6e7ef4f56 100644 --- a/packages/myst-demo/src/index.tsx +++ b/packages/myst-demo/src/index.tsx @@ -386,6 +386,7 @@ export function MySTRenderer({ ); const mdastStage = astStage === 'pre' ? mdastPre : mdastPost; + const { downloads, exports, parts, ...reducedFrontmatter } = frontmatter; return (
{previewType === 'DEMO' && ( <> - + {TitleBlock && } diff --git a/packages/myst-to-react/src/crossReference.tsx b/packages/myst-to-react/src/crossReference.tsx index cbddf7f0c..07fe07fd2 100644 --- a/packages/myst-to-react/src/crossReference.tsx +++ b/packages/myst-to-react/src/crossReference.tsx @@ -7,11 +7,13 @@ import { XRefProvider, useXRefState, type NodeRenderer, + useFrontmatter, } from '@myst-theme/providers'; import { InlineError } from './inlineError.js'; import { default as useSWR } from 'swr'; import { HoverPopover } from './components/index.js'; import { MyST } from './MyST.js'; +import type { GenericNode } from 'myst-common'; import { selectMdastNodes } from 'myst-common'; import { scrollToElement } from './hashLink.js'; @@ -100,11 +102,20 @@ export function useFetchMdast({ function useSelectNodes({ load, identifier }: { load?: boolean; identifier: string }) { const references = useReferences(); + const frontmatter = useFrontmatter(); const { remote, url, remoteBaseUrl, dataUrl } = useXRefState(); if (!load) return; const { data, error } = useFetchMdast({ remote, url, remoteBaseUrl, dataUrl }); - const mdast = data?.mdast ?? references?.article; - const { nodes, htmlId } = selectMdastNodes(mdast, identifier, 3); + const mdast = data ? data.mdast : references?.article; + const parts = data ? data.frontmatter?.parts : frontmatter?.parts; + let nodes: GenericNode[] = []; + let htmlId: string | undefined; + [mdast, ...Object.values(parts ?? {})].forEach((tree) => { + if (!tree || nodes.length > 0) return; + const selected = selectMdastNodes(tree, identifier, 3); + nodes = selected.nodes; + htmlId = selected.htmlId; + }); return { htmlId, nodes, loading: remote && !data, error: remote && error }; } @@ -130,8 +141,8 @@ export function CrossReferenceHover({ const parent = useXRefState(); const remoteBaseUrl = remoteBaseUrlIn ?? parent.remoteBaseUrl; const remote = !!remoteBaseUrl || parent.remote || remoteIn; - const url = parent.remote ? (urlIn ?? parent.url) : urlIn; - const dataUrl = parent.remote ? (dataUrlIn ?? parent.dataUrl) : dataUrlIn; + const url = parent.remote ? urlIn ?? parent.url : urlIn; + const dataUrl = parent.remote ? dataUrlIn ?? parent.dataUrl : dataUrlIn; const external = !!remoteBaseUrl || (url?.startsWith('http') ?? false); const scroll: React.MouseEventHandler = (e) => { e.preventDefault(); diff --git a/packages/providers/src/references.tsx b/packages/providers/src/references.tsx index 3397d73b2..392ce4881 100644 --- a/packages/providers/src/references.tsx +++ b/packages/providers/src/references.tsx @@ -1,9 +1,9 @@ import React, { useContext } from 'react'; import type { References } from 'myst-common'; -import type { PageFrontmatter } from 'myst-frontmatter'; +import type { PageLoader } from '@myst-theme/common'; const ReferencesContext = React.createContext<{ - frontmatter?: PageFrontmatter; + frontmatter?: PageLoader['frontmatter']; references?: References; }>({}); @@ -12,7 +12,7 @@ export function ReferencesProvider({ frontmatter, children, }: { - frontmatter?: PageFrontmatter; + frontmatter?: PageLoader['frontmatter']; references?: References; children: React.ReactNode; }) { diff --git a/packages/site/src/pages/Article.tsx b/packages/site/src/pages/Article.tsx index 50e17e417..717b9c687 100644 --- a/packages/site/src/pages/Article.tsx +++ b/packages/site/src/pages/Article.tsx @@ -42,7 +42,7 @@ export const ArticlePage = React.memo(function ({ const downloads = combineDownloads(manifest?.downloads, article.frontmatter); const tree = copyNode(article.mdast); const keywords = article.frontmatter?.keywords ?? []; - const parts = extractKnownParts(tree); + const parts = { ...extractKnownParts(tree), ...article.frontmatter?.parts }; return ( Date: Thu, 17 Oct 2024 13:00:38 -0600 Subject: [PATCH 2/3] Handle frontmatter parts as mdast/frontmatter objects --- packages/common/src/utils.ts | 12 ++++++------ packages/myst-to-react/src/crossReference.tsx | 12 ++++++------ packages/site/src/pages/Article.tsx | 9 ++++++++- packages/site/src/pages/ErrorUnhandled.tsx | 2 +- themes/article/app/components/Article.tsx | 9 ++++++++- themes/book/app/components/ArticlePage.tsx | 9 ++++++++- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 5f56e3516..c2bf336c4 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -188,23 +188,23 @@ export function updatePageStaticLinksInplace(data: PageLoader, updateUrl: Update return { ...exp, url: updateUrl(exp.url) }; }); } - const allMdastTrees = [data.mdast, ...Object.values(data.frontmatter?.parts ?? {})]; - allMdastTrees.forEach((tree) => { + const allMdastTrees = [data, ...Object.values(data.frontmatter?.parts ?? {})]; + allMdastTrees.forEach(({ mdast }) => { // Fix all of the images to point to the CDN - const images = selectAll('image', tree) as Image[]; + const images = selectAll('image', mdast) as Image[]; images.forEach((node) => { node.url = updateUrl(node.url); if (node.urlOptimized) { node.urlOptimized = updateUrl(node.urlOptimized); } }); - const links = selectAll('link,linkBlock,card', tree) as Link[]; - const staticLinks = links.filter((node) => node.static); + const links = selectAll('link,linkBlock,card', mdast) as Link[]; + const staticLinks = links?.filter((node) => node.static); staticLinks.forEach((node) => { // These are static links to thinks like PDFs or other referenced files node.url = updateUrl(node.url); }); - const outputs = selectAll('output', tree) as Output[]; + const outputs = selectAll('output', mdast) as Output[]; outputs.forEach((node) => { if (!node.data) return; walkOutputs(node.data, (obj) => { diff --git a/packages/myst-to-react/src/crossReference.tsx b/packages/myst-to-react/src/crossReference.tsx index 07fe07fd2..a7a8c1602 100644 --- a/packages/myst-to-react/src/crossReference.tsx +++ b/packages/myst-to-react/src/crossReference.tsx @@ -13,7 +13,7 @@ import { InlineError } from './inlineError.js'; import { default as useSWR } from 'swr'; import { HoverPopover } from './components/index.js'; import { MyST } from './MyST.js'; -import type { GenericNode } from 'myst-common'; +import type { GenericNode, GenericParent } from 'myst-common'; import { selectMdastNodes } from 'myst-common'; import { scrollToElement } from './hashLink.js'; @@ -106,11 +106,11 @@ function useSelectNodes({ load, identifier }: { load?: boolean; identifier: stri const { remote, url, remoteBaseUrl, dataUrl } = useXRefState(); if (!load) return; const { data, error } = useFetchMdast({ remote, url, remoteBaseUrl, dataUrl }); - const mdast = data ? data.mdast : references?.article; - const parts = data ? data.frontmatter?.parts : frontmatter?.parts; + const mdast = data ? (data.mdast as GenericParent) : references?.article; + const parts = data ? (data.frontmatter?.parts as { mdast: GenericParent }) : frontmatter?.parts; let nodes: GenericNode[] = []; let htmlId: string | undefined; - [mdast, ...Object.values(parts ?? {})].forEach((tree) => { + [{ mdast }, ...Object.values(parts ?? {})].forEach(({ mdast: tree }) => { if (!tree || nodes.length > 0) return; const selected = selectMdastNodes(tree, identifier, 3); nodes = selected.nodes; @@ -141,8 +141,8 @@ export function CrossReferenceHover({ const parent = useXRefState(); const remoteBaseUrl = remoteBaseUrlIn ?? parent.remoteBaseUrl; const remote = !!remoteBaseUrl || parent.remote || remoteIn; - const url = parent.remote ? urlIn ?? parent.url : urlIn; - const dataUrl = parent.remote ? dataUrlIn ?? parent.dataUrl : dataUrlIn; + const url = parent.remote ? (urlIn ?? parent.url) : urlIn; + const dataUrl = parent.remote ? (dataUrlIn ?? parent.dataUrl) : dataUrlIn; const external = !!remoteBaseUrl || (url?.startsWith('http') ?? false); const scroll: React.MouseEventHandler = (e) => { e.preventDefault(); diff --git a/packages/site/src/pages/Article.tsx b/packages/site/src/pages/Article.tsx index 717b9c687..577c92831 100644 --- a/packages/site/src/pages/Article.tsx +++ b/packages/site/src/pages/Article.tsx @@ -42,7 +42,14 @@ export const ArticlePage = React.memo(function ({ const downloads = combineDownloads(manifest?.downloads, article.frontmatter); const tree = copyNode(article.mdast); const keywords = article.frontmatter?.keywords ?? []; - const parts = { ...extractKnownParts(tree), ...article.frontmatter?.parts }; + const parts = { + ...extractKnownParts(tree), + ...Object.fromEntries( + Object.entries(article.frontmatter?.parts ?? {}).map(([k, v]) => { + return [k, v.mdast]; + }), + ), + }; return (

Unexpected Error Occurred

Status: {error.status}

-

{error.data.message}

+

{error.data?.message ?? ''}

); } diff --git a/themes/article/app/components/Article.tsx b/themes/article/app/components/Article.tsx index 7d32c2626..ba85d61f5 100644 --- a/themes/article/app/components/Article.tsx +++ b/themes/article/app/components/Article.tsx @@ -34,7 +34,14 @@ export function Article({ }) { const keywords = article.frontmatter?.keywords ?? []; const tree = copyNode(article.mdast); - const parts = { ...extractKnownParts(tree), ...article.frontmatter?.parts }; + const parts = { + ...extractKnownParts(tree), + ...Object.fromEntries( + Object.entries(article.frontmatter?.parts ?? {}).map(([k, v]) => { + return [k, v.mdast]; + }), + ), + }; const { title, subtitle } = article.frontmatter; const compute = useComputeOptions(); const top = useThemeTop(); diff --git a/themes/book/app/components/ArticlePage.tsx b/themes/book/app/components/ArticlePage.tsx index f7b75658d..a262c6892 100644 --- a/themes/book/app/components/ArticlePage.tsx +++ b/themes/book/app/components/ArticlePage.tsx @@ -74,7 +74,14 @@ export const ArticlePage = React.memo(function ({ const downloads = combineDownloads(manifest?.downloads, article.frontmatter); const tree = copyNode(article.mdast); const keywords = article.frontmatter?.keywords ?? []; - const parts = { ...extractKnownParts(tree), ...article.frontmatter?.parts }; + const parts = { + ...extractKnownParts(tree), + ...Object.fromEntries( + Object.entries(article.frontmatter?.parts ?? {}).map(([k, v]) => { + return [k, v.mdast]; + }), + ), + }; const isOutlineMargin = useMediaQuery('(min-width: 1024px)'); return ( Date: Thu, 17 Oct 2024 14:49:46 -0600 Subject: [PATCH 3/3] Update knownParts function --- packages/common/src/types.ts | 2 +- packages/site/src/pages/Article.tsx | 9 +-------- packages/site/src/utils.ts | 12 ++++++++++-- themes/article/app/components/Article.tsx | 9 +-------- themes/book/app/components/ArticlePage.tsx | 9 +-------- 5 files changed, 14 insertions(+), 27 deletions(-) diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 2b38c48df..bb235b60c 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -46,7 +46,7 @@ export type FooterLinks = { }; type PageFrontmatterWithDownloads = Omit & { - parts?: Record; + parts?: Record; downloads?: SiteAction[]; exports?: SiteExport[]; }; diff --git a/packages/site/src/pages/Article.tsx b/packages/site/src/pages/Article.tsx index 577c92831..af0319469 100644 --- a/packages/site/src/pages/Article.tsx +++ b/packages/site/src/pages/Article.tsx @@ -42,14 +42,7 @@ export const ArticlePage = React.memo(function ({ const downloads = combineDownloads(manifest?.downloads, article.frontmatter); const tree = copyNode(article.mdast); const keywords = article.frontmatter?.keywords ?? []; - const parts = { - ...extractKnownParts(tree), - ...Object.fromEntries( - Object.entries(article.frontmatter?.parts ?? {}).map(([k, v]) => { - return [k, v.mdast]; - }), - ), - }; + const parts = extractKnownParts(tree, article.frontmatter?.parts); return ( , +): KnownParts { const abstract = extractPart(tree, 'abstract'); const summary = extractPart(tree, 'summary', { requireExplicitPart: true }); const keypoints = extractPart(tree, ['keypoints'], { requireExplicitPart: true }); const data_availability = extractPart(tree, ['data_availability', 'data availability']); const acknowledgments = extractPart(tree, ['acknowledgments', 'acknowledgements']); - return { abstract, summary, keypoints, data_availability, acknowledgments }; + const otherParts = Object.fromEntries( + Object.entries(parts ?? {}).map(([k, v]) => { + return [k, v.mdast]; + }), + ); + return { abstract, summary, keypoints, data_availability, acknowledgments, ...otherParts }; } /** diff --git a/themes/article/app/components/Article.tsx b/themes/article/app/components/Article.tsx index ba85d61f5..b4c2eeb14 100644 --- a/themes/article/app/components/Article.tsx +++ b/themes/article/app/components/Article.tsx @@ -34,14 +34,7 @@ export function Article({ }) { const keywords = article.frontmatter?.keywords ?? []; const tree = copyNode(article.mdast); - const parts = { - ...extractKnownParts(tree), - ...Object.fromEntries( - Object.entries(article.frontmatter?.parts ?? {}).map(([k, v]) => { - return [k, v.mdast]; - }), - ), - }; + const parts = extractKnownParts(tree, article.frontmatter?.parts); const { title, subtitle } = article.frontmatter; const compute = useComputeOptions(); const top = useThemeTop(); diff --git a/themes/book/app/components/ArticlePage.tsx b/themes/book/app/components/ArticlePage.tsx index a262c6892..2700b956f 100644 --- a/themes/book/app/components/ArticlePage.tsx +++ b/themes/book/app/components/ArticlePage.tsx @@ -74,14 +74,7 @@ export const ArticlePage = React.memo(function ({ const downloads = combineDownloads(manifest?.downloads, article.frontmatter); const tree = copyNode(article.mdast); const keywords = article.frontmatter?.keywords ?? []; - const parts = { - ...extractKnownParts(tree), - ...Object.fromEntries( - Object.entries(article.frontmatter?.parts ?? {}).map(([k, v]) => { - return [k, v.mdast]; - }), - ), - }; + const parts = extractKnownParts(tree, article.frontmatter?.parts); const isOutlineMargin = useMediaQuery('(min-width: 1024px)'); return (