@myst-theme/site 0.5.10 → 0.5.12
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 +14 -13
- package/src/components/Abstract.tsx +35 -7
- package/src/components/Backmatter.tsx +43 -0
- package/src/components/Bibliography.tsx +6 -2
- package/src/components/ContentBlocks.tsx +11 -3
- package/src/components/Navigation/TableOfContents.tsx +5 -106
- package/src/components/Navigation/TableOfContentsItems.tsx +162 -0
- package/src/components/Navigation/TopNav.tsx +3 -8
- package/src/components/index.ts +2 -1
- package/src/pages/Article.tsx +10 -10
- package/src/utils.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myst-theme/site",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
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/
|
|
21
|
-
"@myst-theme/
|
|
22
|
-
"@myst-theme/
|
|
23
|
-
"@myst-theme/
|
|
24
|
-
"@myst-theme/providers": "^0.5.
|
|
20
|
+
"@myst-theme/common": "^0.5.12",
|
|
21
|
+
"@myst-theme/diagrams": "^0.5.12",
|
|
22
|
+
"@myst-theme/frontmatter": "^0.5.12",
|
|
23
|
+
"@myst-theme/jupyter": "^0.5.12",
|
|
24
|
+
"@myst-theme/providers": "^0.5.12",
|
|
25
|
+
"@radix-ui/react-collapsible": "^1.0.3",
|
|
25
26
|
"classnames": "^2.3.2",
|
|
26
27
|
"lodash.throttle": "^4.1.1",
|
|
27
|
-
"myst-common": "^1.1.
|
|
28
|
-
"myst-
|
|
29
|
-
"myst-
|
|
30
|
-
"myst-
|
|
31
|
-
"myst-to-react": "^0.5.
|
|
28
|
+
"myst-common": "^1.1.12",
|
|
29
|
+
"myst-config": "^1.1.12",
|
|
30
|
+
"myst-demo": "^0.5.12",
|
|
31
|
+
"myst-spec-ext": "^1.1.12",
|
|
32
|
+
"myst-to-react": "^0.5.12",
|
|
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
|
|
7
|
-
|
|
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=
|
|
11
|
-
|
|
12
|
-
<HashLink id=
|
|
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">
|
|
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
|
|
82
|
-
|
|
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 {
|
|
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
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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, baseurl?: string): string[] {
|
|
39
|
+
return headings
|
|
40
|
+
.map((heading) => {
|
|
41
|
+
if (withBaseurl(heading.path, baseurl) === pathname) return [heading.id];
|
|
42
|
+
const open = childrenOpen(heading.children, pathname, baseurl);
|
|
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 baseurl = useBaseurl();
|
|
107
|
+
const startOpen = childrenOpen([heading], pathname, baseurl).includes(heading.id);
|
|
108
|
+
const nav = useNavigation();
|
|
109
|
+
const [open, setOpen] = React.useState(startOpen);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (nav.state === 'idle') setOpen(startOpen);
|
|
112
|
+
}, [nav.state]);
|
|
113
|
+
const exact = pathname === withBaseurl(heading.path, baseurl);
|
|
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 {
|
|
248
|
-
|
|
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} />
|
package/src/components/index.ts
CHANGED
|
@@ -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';
|
package/src/pages/Article.tsx
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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(
|
|
37
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
+
}
|