@myst-theme/site 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myst-theme/site",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -17,18 +17,19 @@
17
17
  "dependencies": {
18
18
  "@headlessui/react": "^1.7.15",
19
19
  "@heroicons/react": "^2.0.18",
20
- "@myst-theme/diagrams": "^0.5.9",
21
- "@myst-theme/frontmatter": "^0.5.9",
22
- "@myst-theme/jupyter": "^0.5.9",
23
- "@myst-theme/common": "^0.5.9",
24
- "@myst-theme/providers": "^0.5.9",
20
+ "@myst-theme/common": "^0.5.11",
21
+ "@myst-theme/diagrams": "^0.5.11",
22
+ "@myst-theme/frontmatter": "^0.5.11",
23
+ "@myst-theme/jupyter": "^0.5.11",
24
+ "@myst-theme/providers": "^0.5.11",
25
+ "@radix-ui/react-collapsible": "^1.0.3",
25
26
  "classnames": "^2.3.2",
26
27
  "lodash.throttle": "^4.1.1",
27
28
  "myst-common": "^1.1.8",
28
- "myst-spec-ext": "^1.1.8",
29
29
  "myst-config": "^1.1.8",
30
- "myst-demo": "^0.5.9",
31
- "myst-to-react": "^0.5.9",
30
+ "myst-demo": "^0.5.11",
31
+ "myst-spec-ext": "^1.1.8",
32
+ "myst-to-react": "^0.5.11",
32
33
  "nbtx": "^0.2.3",
33
34
  "node-cache": "^5.1.2",
34
35
  "node-fetch": "^2.6.11",
@@ -36,10 +37,10 @@
36
37
  "unist-util-select": "^4.0.1"
37
38
  },
38
39
  "peerDependencies": {
40
+ "@remix-run/node": "^1.17 || ^2.0",
41
+ "@remix-run/react": "^1.17 || ^2.0",
39
42
  "@types/react": "^16.8 || ^17.0 || ^18.0",
40
43
  "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
41
- "@remix-run/react": "^1.17 || ^2.0",
42
- "@remix-run/node": "^1.17 || ^2.0",
43
44
  "react": "^16.8 || ^17.0 || ^18.0",
44
45
  "react-dom": "^16.8 || ^17.0 || ^18.0"
45
46
  },
@@ -2,14 +2,43 @@ import type { GenericParent } from 'myst-common';
2
2
  import { ContentBlocks } from './ContentBlocks.js';
3
3
  import classNames from 'classnames';
4
4
  import { HashLink } from 'myst-to-react';
5
+ import { type KnownParts } from '../utils.js';
5
6
 
6
- export function Abstract({ content }: { content: GenericParent }) {
7
- if (!content) return <div className="hidden" aria-label="this article has no abstract" />;
7
+ export function FrontmatterParts({
8
+ parts,
9
+ keywords,
10
+ hideKeywords,
11
+ }: {
12
+ parts: KnownParts;
13
+ keywords?: string[];
14
+ hideKeywords?: boolean;
15
+ }) {
16
+ if (!parts.abstract && !parts.keypoints && !parts.summary) return null;
17
+ return (
18
+ <>
19
+ <Abstract content={parts.abstract} />
20
+ <Abstract content={parts.keypoints} title="Key Points" id="keypoints" />
21
+ <Abstract content={parts.summary} title="Plain Language Summary" id="summary" />
22
+ <Keywords keywords={keywords} hideKeywords={hideKeywords} />
23
+ </>
24
+ );
25
+ }
26
+
27
+ export function Abstract({
28
+ content,
29
+ title = 'Abstract',
30
+ id = 'abstract',
31
+ }: {
32
+ title?: string;
33
+ id?: string;
34
+ content?: GenericParent;
35
+ }) {
36
+ if (!content) return null;
8
37
  return (
9
38
  <>
10
- <h2 id="abstract" className="mb-3 text-base font-semibold group">
11
- Abstract
12
- <HashLink id="abstract" title="Link to Abstract" hover className="ml-2" />
39
+ <h2 id={id} className="mb-3 text-base font-semibold group">
40
+ {title}
41
+ <HashLink id={id} title={`Link to ${title}`} hover className="ml-2" />
13
42
  </h2>
14
43
  <div className="px-6 py-1 mb-3 rounded-sm bg-slate-50 dark:bg-slate-800">
15
44
  <ContentBlocks mdast={content} className="col-body" />
@@ -25,8 +54,7 @@ export function Keywords({
25
54
  keywords?: string[];
26
55
  hideKeywords?: boolean;
27
56
  }) {
28
- if (hideKeywords || !keywords || keywords.length === 0)
29
- return <div className="hidden" aria-label="this article has no keywords" />;
57
+ if (hideKeywords || !keywords || keywords.length === 0) return null;
30
58
  return (
31
59
  <div className="mb-10 group">
32
60
  <span className="mr-2 font-semibold">Keywords:</span>
@@ -0,0 +1,43 @@
1
+ import type { GenericParent } from 'myst-common';
2
+ import { ContentBlocks } from './ContentBlocks.js';
3
+ import { HashLink } from 'myst-to-react';
4
+ import type { KnownParts } from '../utils.js';
5
+
6
+ export function BackmatterParts({ parts }: { parts: KnownParts }) {
7
+ return (
8
+ <>
9
+ <Backmatter
10
+ title="Data Availability"
11
+ id="data-availability"
12
+ content={parts.data_availability}
13
+ />
14
+ <Backmatter title="Acknowledgments" id="acknowledgments" content={parts.acknowledgments} />
15
+ </>
16
+ );
17
+ }
18
+
19
+ export function Backmatter({
20
+ title,
21
+ id,
22
+ content,
23
+ }: {
24
+ title: string;
25
+ id: string;
26
+ content?: GenericParent;
27
+ }) {
28
+ if (!content) return null;
29
+ return (
30
+ <div className="flex flex-col w-full md:flex-row">
31
+ <h2
32
+ id={id}
33
+ className="mt-5 text-base font-semibold group md:w-[200px] self-start md:flex-none opacity-90"
34
+ >
35
+ {title}
36
+ <HashLink id={id} title={`Link to ${title}`} hover className="ml-2" />
37
+ </h2>
38
+ <div className="grow">
39
+ <ContentBlocks mdast={content} className="opacity-80 col-screen" />
40
+ </div>
41
+ </div>
42
+ );
43
+ }
@@ -1,5 +1,6 @@
1
1
  import { useGridSystemProvider, useReferences } from '@myst-theme/providers';
2
2
  import classNames from 'classnames';
3
+ import { HashLink } from 'myst-to-react';
3
4
  import { useState } from 'react';
4
5
 
5
6
  const HIDE_OVER_N_REFERENCES = 5;
@@ -13,7 +14,7 @@ export function Bibliography() {
13
14
  if (!filtered || !data || filtered.length === 0) return null;
14
15
  const refs = hidden ? filtered.slice(0, HIDE_OVER_N_REFERENCES) : filtered;
15
16
  return (
16
- <section className={classNames(grid, 'subgrid-gap col-screen')}>
17
+ <section id="references" className={classNames(grid, 'subgrid-gap col-screen')}>
17
18
  <div>
18
19
  {filtered.length > HIDE_OVER_N_REFERENCES && (
19
20
  <button
@@ -23,7 +24,10 @@ export function Bibliography() {
23
24
  {hidden ? 'Show All' : 'Collapse'}
24
25
  </button>
25
26
  )}
26
- <header className="text-lg font-semibold text-stone-900 dark:text-white">References</header>
27
+ <header className="text-lg font-semibold text-stone-900 dark:text-white group">
28
+ References
29
+ <HashLink id="references" title="Link to References" hover className="ml-2" />
30
+ </header>
27
31
  </div>
28
32
  <div className="pl-3 mb-8 text-xs text-stone-500 dark:text-stone-300">
29
33
  <ol>
@@ -78,9 +78,17 @@ export function ContentBlocks({
78
78
  const blocks = mdast.children as GenericParent[];
79
79
  return (
80
80
  <>
81
- {blocks.map((node) => (
82
- <Block key={node.key} id={node.key} pageKind={pageKind} node={node} className={className} />
83
- ))}
81
+ {blocks
82
+ .filter((node) => node.visibility !== 'remove')
83
+ .map((node) => (
84
+ <Block
85
+ key={node.key}
86
+ id={node.key}
87
+ pageKind={pageKind}
88
+ node={node}
89
+ className={className}
90
+ />
91
+ ))}
84
92
  </>
85
93
  );
86
94
  }
@@ -1,115 +1,14 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import classNames from 'classnames';
3
- import { useLocation, useNavigation } from '@remix-run/react';
4
- import type { SiteManifest } from 'myst-config';
3
+ import { useNavigation } from '@remix-run/react';
5
4
  import {
6
- useNavLinkProvider,
7
5
  useNavOpen,
8
6
  useSiteManifest,
9
- useBaseurl,
10
- withBaseurl,
11
7
  useGridSystemProvider,
12
8
  useThemeTop,
13
9
  } from '@myst-theme/providers';
14
- import { getProjectHeadings, type Heading } from '@myst-theme/common';
15
-
16
- type Props = {
17
- folder?: string;
18
- headings: Heading[];
19
- sections?: ManifestProject[];
20
- };
21
-
22
- type ManifestProject = Required<SiteManifest>['projects'][0];
23
-
24
- const HeadingLink = ({
25
- path,
26
- isIndex,
27
- title,
28
- children,
29
- }: {
30
- path: string;
31
- isIndex?: boolean;
32
- title?: string;
33
- children: React.ReactNode;
34
- }) => {
35
- const { pathname } = useLocation();
36
- const NavLink = useNavLinkProvider();
37
- const exact = pathname === path;
38
- const baseurl = useBaseurl();
39
- const [, setOpen] = useNavOpen();
40
- return (
41
- <NavLink
42
- prefetch="intent"
43
- title={title}
44
- className={({ isActive }: { isActive: boolean }) =>
45
- classNames('block break-words', {
46
- 'mb-8 lg:mb-3 font-semibold': isIndex,
47
- 'text-slate-900 dark:text-slate-200': isIndex && !exact,
48
- 'text-blue-500 dark:text-blue-400': isIndex && exact,
49
- 'border-l pl-4 text-blue-500 border-current dark:text-blue-400': !isIndex && isActive,
50
- 'font-semibold': isActive,
51
- 'border-l pl-4 border-transparent hover:border-slate-400 dark:hover:border-slate-500 text-slate-700 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300':
52
- !isIndex && !isActive,
53
- })
54
- }
55
- to={withBaseurl(path, baseurl)}
56
- suppressHydrationWarning // The pathname is not defined on the server always.
57
- onClick={() => {
58
- // Close the nav panel if it is open
59
- setOpen(false);
60
- }}
61
- >
62
- {children}
63
- </NavLink>
64
- );
65
- };
66
-
67
- const HEADING_CLASSES = 'text-slate-900 leading-5 dark:text-slate-100';
68
-
69
- const Headings = ({ folder, headings, sections }: Props) => {
70
- const secs = sections || [];
71
- return (
72
- <ul className="space-y-6 lg:space-y-2">
73
- {secs.map((sec) => {
74
- if (sec.slug === folder) {
75
- return headings.map((heading, index) => (
76
- <li
77
- key={heading.slug || index}
78
- className={classNames('', {
79
- [HEADING_CLASSES]: heading.level === 'index',
80
- 'font-semibold': heading.level === 'index',
81
- // 'pl-4': heading.level === 2,
82
- // 'pl-6': heading.level === 3,
83
- // 'pl-8': heading.level === 4,
84
- // 'pl-10': heading.level === 5,
85
- // 'pl-12': heading.level === 6,
86
- })}
87
- >
88
- {heading.path ? (
89
- <HeadingLink
90
- title={heading.title}
91
- path={heading.path}
92
- isIndex={heading.level === 'index'}
93
- >
94
- {heading.short_title || heading.title}
95
- </HeadingLink>
96
- ) : (
97
- <h5 className="mb-3 font-semibold break-words lg:mt-8 dark:text-white">
98
- {heading.short_title || heading.title}
99
- </h5>
100
- )}
101
- </li>
102
- ));
103
- }
104
- return (
105
- <li key={sec.slug} className={classNames('p-1 my-2 lg:hidden', HEADING_CLASSES)}>
106
- <HeadingLink path={`/${sec.slug}`}>{sec.short_title || sec.title}</HeadingLink>
107
- </li>
108
- );
109
- })}
110
- </ul>
111
- );
112
- };
10
+ import { getProjectHeadings } from '@myst-theme/common';
11
+ import { Toc } from './TableOfContentsItems.js';
113
12
 
114
13
  export function useTocHeight<T extends HTMLElement = HTMLElement>(top = 0, inset = 0) {
115
14
  const container = useRef<T>(null);
@@ -189,7 +88,7 @@ export const TableOfContents = ({
189
88
  aria-label="Table of Contents"
190
89
  className="flex-grow overflow-y-auto transition-opacity mt-6 pb-3 ml-3 xl:ml-0 mr-3 max-w-[350px]"
191
90
  >
192
- <Headings folder={projectSlug} headings={headings} sections={config?.projects} />
91
+ <Toc headings={headings} />
193
92
  </nav>
194
93
  {footer && (
195
94
  <div
@@ -221,7 +120,7 @@ export const InlineTableOfContents = ({
221
120
  if (!headings) return null;
222
121
  return (
223
122
  <nav aria-label="Table of Contents" className={className} ref={tocRef}>
224
- <Headings folder={projectSlug} headings={headings} sections={config?.projects} />
123
+ <Toc headings={headings} />
225
124
  </nav>
226
125
  );
227
126
  };
@@ -0,0 +1,162 @@
1
+ import React, { useEffect } from 'react';
2
+ import classNames from 'classnames';
3
+ import * as Collapsible from '@radix-ui/react-collapsible';
4
+ import type { Heading } from '@myst-theme/common';
5
+ import { useBaseurl, useNavLinkProvider, useNavOpen, withBaseurl } from '@myst-theme/providers';
6
+ import { useLocation, useNavigation } from '@remix-run/react';
7
+ import { ChevronRightIcon } from '@heroicons/react/24/solid';
8
+
9
+ type NestedHeading = Heading & { id: string; children: NestedHeading[] };
10
+
11
+ function nestToc(toc: Heading[]): NestedHeading[] {
12
+ const items: NestedHeading[] = [];
13
+ const stack: (Omit<NestedHeading, 'level'> & { level: number })[] = [];
14
+
15
+ toc.forEach((tocItem, id) => {
16
+ const item = tocItem as NestedHeading;
17
+ item.children = [];
18
+ item.id = String(id);
19
+ if (item.level === 'index') {
20
+ while (stack.length) stack.pop();
21
+ items.push(item);
22
+ return;
23
+ }
24
+ while (stack.length && stack[stack.length - 1].level >= item.level) {
25
+ stack.pop();
26
+ }
27
+ const top = stack[stack.length - 1];
28
+ if (top) {
29
+ top.children.push(item);
30
+ } else {
31
+ items.push(item);
32
+ }
33
+ stack.push(item as any);
34
+ });
35
+ return items;
36
+ }
37
+
38
+ function childrenOpen(headings: NestedHeading[], pathname: string): string[] {
39
+ return headings
40
+ .map((heading) => {
41
+ if (heading.path === pathname) return [heading.id];
42
+ const open = childrenOpen(heading.children, pathname);
43
+ if (open.length === 0) return [];
44
+ return [heading.id, ...open];
45
+ })
46
+ .flat();
47
+ }
48
+
49
+ export const Toc = ({ headings }: { headings: Heading[] }) => {
50
+ const nested = nestToc(headings);
51
+ return (
52
+ <div className="w-full px-1 dark:text-white">
53
+ {nested.map((item) => (
54
+ <NestedToc heading={item} key={item.id} />
55
+ ))}
56
+ </div>
57
+ );
58
+ };
59
+
60
+ function LinkItem({
61
+ className,
62
+ heading,
63
+ onClick,
64
+ }: {
65
+ className?: string;
66
+ heading: NestedHeading;
67
+ onClick?: () => void;
68
+ }) {
69
+ const NavLink = useNavLinkProvider();
70
+ const baseurl = useBaseurl();
71
+ const [, setOpen] = useNavOpen();
72
+ if (!heading.path) {
73
+ return (
74
+ <div
75
+ title={heading.title}
76
+ className={classNames('block break-words rounded', className)}
77
+ onClick={() => {
78
+ onClick?.();
79
+ }}
80
+ >
81
+ {heading.short_title || heading.title}
82
+ </div>
83
+ );
84
+ }
85
+ return (
86
+ <NavLink
87
+ prefetch="intent"
88
+ title={heading.title}
89
+ className={classNames(
90
+ 'block break-words focus:outline outline-blue-200 outline-2 rounded',
91
+ className,
92
+ )}
93
+ to={withBaseurl(heading.path, baseurl)}
94
+ onClick={() => {
95
+ onClick?.();
96
+ setOpen(false);
97
+ }}
98
+ >
99
+ {heading.short_title || heading.title}
100
+ </NavLink>
101
+ );
102
+ }
103
+
104
+ const NestedToc = ({ heading }: { heading: NestedHeading }) => {
105
+ const { pathname } = useLocation();
106
+ const startOpen = childrenOpen([heading], pathname).includes(heading.id);
107
+ const nav = useNavigation();
108
+ nav.state;
109
+ const [open, setOpen] = React.useState(startOpen);
110
+ useEffect(() => {
111
+ if (nav.state === 'idle') setOpen(startOpen);
112
+ }, [nav.state]);
113
+ const exact = pathname === heading.path;
114
+ if (!heading.children || heading.children.length === 0) {
115
+ return (
116
+ <LinkItem
117
+ className={classNames('p-2 my-1 rounded-lg', {
118
+ 'bg-blue-300/30': exact,
119
+ 'hover:bg-slate-300/30': !exact,
120
+ 'font-bold': heading.level === 'index',
121
+ })}
122
+ heading={heading}
123
+ />
124
+ );
125
+ }
126
+ return (
127
+ <Collapsible.Root className="w-full" open={open} onOpenChange={setOpen}>
128
+ <div
129
+ className={classNames(
130
+ 'flex flex-row w-full gap-2 px-2 my-1 text-left rounded-lg outline-none',
131
+ {
132
+ 'bg-blue-300/30': exact,
133
+ 'hover:bg-slate-300/30': !exact,
134
+ },
135
+ )}
136
+ >
137
+ <LinkItem
138
+ className={classNames('py-2 grow', {
139
+ 'font-semibold text-blue-800 dark:text-blue-200': startOpen,
140
+ 'cursor-pointer': !heading.path,
141
+ })}
142
+ heading={heading}
143
+ onClick={() => setOpen(heading.path ? true : !open)}
144
+ />
145
+ <Collapsible.Trigger asChild>
146
+ <button className="self-center flex-none rounded-md group hover:bg-slate-300/30 focus:outline outline-blue-200 outline-2">
147
+ <ChevronRightIcon
148
+ className="transition-transform duration-300 group-data-[state=open]:rotate-90 text-text-slate-700 dark:text-slate-100"
149
+ height="1.5rem"
150
+ width="1.5rem"
151
+ />
152
+ </button>
153
+ </Collapsible.Trigger>
154
+ </div>
155
+ <Collapsible.Content className="pl-3 pr-[2px] collapsible-content">
156
+ {heading.children.map((item) => (
157
+ <NestedToc heading={item} key={item.id} />
158
+ ))}
159
+ </Collapsible.Content>
160
+ </Collapsible.Root>
161
+ );
162
+ };
@@ -244,8 +244,8 @@ function HomeLink({
244
244
  export function TopNav() {
245
245
  const [open, setOpen] = useNavOpen();
246
246
  const config = useSiteManifest();
247
- const { logo, logo_dark, logo_text, logoText, actions, title, nav } =
248
- config ?? ({} as SiteManifest);
247
+ const { title, nav, actions } = config ?? {};
248
+ const { logo, logo_dark, logo_text } = config?.options ?? {};
249
249
  return (
250
250
  <div className="bg-white/80 backdrop-blur dark:bg-stone-900/80 shadow dark:shadow-stone-700 p-3 md:px-8 fixed w-screen top-0 z-30 h-[60px]">
251
251
  <nav className="flex items-center justify-between flex-wrap max-w-[1440px] mx-auto">
@@ -261,12 +261,7 @@ export function TopNav() {
261
261
  <span className="sr-only">Open Menu</span>
262
262
  </button>
263
263
  </div>
264
- <HomeLink
265
- name={title}
266
- logo={logo}
267
- logoDark={logo_dark}
268
- logoText={logo_text || logoText}
269
- />
264
+ <HomeLink name={title} logo={logo} logoDark={logo_dark} logoText={logo_text} />
270
265
  </div>
271
266
  <div className="flex items-center flex-grow w-auto">
272
267
  <NavItems nav={nav} />
@@ -4,7 +4,8 @@ export { FooterLinksBlock } from './FooterLinksBlock.js';
4
4
  export { ContentReload } from './ContentReload.js';
5
5
  export { Bibliography } from './Bibliography.js';
6
6
  export { ArticleHeader } from './Headers.js';
7
- export { Abstract, Keywords } from './Abstract.js';
7
+ export { FrontmatterParts, Abstract, Keywords } from './Abstract.js';
8
+ export { BackmatterParts, Backmatter } from './Backmatter.js';
8
9
  export { ExternalOrInternalLink } from './ExternalOrInternalLink.js';
9
10
  export * from './Navigation/index.js';
10
11
  export { renderers } from './renderers.js';
@@ -1,16 +1,16 @@
1
1
  import React from 'react';
2
2
  import { ReferencesProvider } from '@myst-theme/providers';
3
3
  import {
4
- Abstract,
5
4
  Bibliography,
6
5
  ContentBlocks,
7
6
  FooterLinksBlock,
8
- Keywords,
7
+ FrontmatterParts,
8
+ BackmatterParts,
9
9
  } from '../components/index.js';
10
10
  import { ErrorDocumentNotFound } from './ErrorDocumentNotFound.js';
11
11
  import { ErrorProjectNotFound } from './ErrorProjectNotFound.js';
12
12
  import type { PageLoader } from '@myst-theme/common';
13
- import { copyNode, extractPart, type GenericParent } from 'myst-common';
13
+ import { copyNode, type GenericParent } from 'myst-common';
14
14
  import { SourceFileKind } from 'myst-spec-ext';
15
15
  import {
16
16
  ExecuteScopeProvider,
@@ -21,24 +21,24 @@ import {
21
21
  ErrorTray,
22
22
  } from '@myst-theme/jupyter';
23
23
  import { FrontmatterBlock } from '@myst-theme/frontmatter';
24
+ import { extractKnownParts } from '../utils.js';
24
25
 
25
26
  export const ArticlePage = React.memo(function ({
26
27
  article,
27
28
  hide_all_footer_links,
28
- showAbstract,
29
29
  hideKeywords,
30
30
  }: {
31
31
  article: PageLoader;
32
32
  hide_all_footer_links?: boolean;
33
- showAbstract?: boolean;
34
33
  hideKeywords?: boolean;
35
34
  }) {
36
- const canCompute = useCanCompute(article);
37
- const { hide_title_block, hide_footer_links } = (article.frontmatter as any)?.design ?? {};
35
+ const canCompute = useCanCompute();
36
+
37
+ const { hide_title_block, hide_footer_links } = (article.frontmatter as any)?.options ?? {};
38
38
 
39
39
  const tree = copyNode(article.mdast);
40
40
  const keywords = article.frontmatter?.keywords ?? [];
41
- const abstract = showAbstract ? extractPart(tree, 'abstract') : undefined;
41
+ const parts = extractKnownParts(tree);
42
42
 
43
43
  return (
44
44
  <ReferencesProvider
@@ -57,9 +57,9 @@ export const ArticlePage = React.memo(function ({
57
57
  {canCompute && article.kind === SourceFileKind.Notebook && <NotebookToolbar showLaunch />}
58
58
  <ErrorTray pageSlug={article.slug} />
59
59
  <div id="skip-to-article" />
60
- <Abstract content={abstract as GenericParent} />
61
- {abstract && <Keywords keywords={keywords} hideKeywords={hideKeywords} />}
60
+ <FrontmatterParts parts={parts} keywords={keywords} hideKeywords={hideKeywords} />
62
61
  <ContentBlocks pageKind={article.kind} mdast={tree as GenericParent} />
62
+ <BackmatterParts parts={parts} />
63
63
  <Bibliography />
64
64
  <ConnectionStatusTray />
65
65
  {!hide_footer_links && !hide_all_footer_links && (
package/src/utils.ts CHANGED
@@ -1,5 +1,25 @@
1
+ import type { GenericParent } from 'myst-common';
2
+ import { extractPart } from 'myst-common';
3
+
1
4
  export function getDomainFromRequest(request: Request) {
2
5
  const url = new URL(request.url);
3
6
  const domain = `${url.protocol}//${url.hostname}${url.port ? `:${url.port}` : ''}`;
4
7
  return domain;
5
8
  }
9
+
10
+ export type KnownParts = {
11
+ abstract?: GenericParent;
12
+ summary?: GenericParent;
13
+ keypoints?: GenericParent;
14
+ data_availability?: GenericParent;
15
+ acknowledgments?: GenericParent;
16
+ };
17
+
18
+ export function extractKnownParts(tree: GenericParent): KnownParts {
19
+ const abstract = extractPart(tree, 'abstract');
20
+ const summary = extractPart(tree, 'summary');
21
+ const keypoints = extractPart(tree, 'keypoints');
22
+ const data_availability = extractPart(tree, 'data_availability');
23
+ const acknowledgments = extractPart(tree, 'acknowledgments');
24
+ return { abstract, summary, keypoints, data_availability, acknowledgments };
25
+ }