@myst-theme/site 0.9.6 → 0.9.8

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.9.6",
3
+ "version": "0.9.8",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -17,19 +17,19 @@
17
17
  "dependencies": {
18
18
  "@headlessui/react": "^1.7.15",
19
19
  "@heroicons/react": "^2.0.18",
20
- "@myst-theme/common": "^0.9.6",
21
- "@myst-theme/diagrams": "^0.9.6",
22
- "@myst-theme/frontmatter": "^0.9.6",
23
- "@myst-theme/jupyter": "^0.9.6",
24
- "@myst-theme/providers": "^0.9.6",
20
+ "@myst-theme/common": "^0.9.8",
21
+ "@myst-theme/diagrams": "^0.9.8",
22
+ "@myst-theme/frontmatter": "^0.9.8",
23
+ "@myst-theme/jupyter": "^0.9.8",
24
+ "@myst-theme/providers": "^0.9.8",
25
25
  "@radix-ui/react-collapsible": "^1.0.3",
26
26
  "classnames": "^2.3.2",
27
27
  "lodash.throttle": "^4.1.1",
28
28
  "myst-common": "^1.4.4",
29
29
  "myst-config": "^1.4.4",
30
- "myst-demo": "^0.9.6",
30
+ "myst-demo": "^0.9.8",
31
31
  "myst-spec-ext": "^1.4.4",
32
- "myst-to-react": "^0.9.6",
32
+ "myst-to-react": "^0.9.8",
33
33
  "nbtx": "^0.2.3",
34
34
  "node-cache": "^5.1.2",
35
35
  "node-fetch": "^2.6.11",
@@ -1,41 +1,21 @@
1
1
  import type { GenericParent } from 'myst-common';
2
2
  import { ContentBlocks } from './ContentBlocks.js';
3
- import classNames from 'classnames';
4
3
  import { HashLink } from 'myst-to-react';
5
- import { type KnownParts } from '../utils.js';
6
-
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
4
 
27
5
  export function Abstract({
28
6
  content,
29
7
  title = 'Abstract',
30
8
  id = 'abstract',
9
+ className,
31
10
  }: {
32
11
  title?: string;
33
12
  id?: string;
34
13
  content?: GenericParent;
14
+ className?: string;
35
15
  }) {
36
16
  if (!content) return null;
37
17
  return (
38
- <>
18
+ <div className={className}>
39
19
  <h2 id={id} className="mb-3 text-base font-semibold group">
40
20
  {title}
41
21
  <HashLink id={id} title={`Link to ${title}`} hover className="ml-2" />
@@ -43,32 +23,6 @@ export function Abstract({
43
23
  <div className="px-6 py-1 mb-3 rounded-sm bg-slate-50 dark:bg-slate-800">
44
24
  <ContentBlocks mdast={content} className="col-body" />
45
25
  </div>
46
- </>
47
- );
48
- }
49
-
50
- export function Keywords({
51
- keywords,
52
- hideKeywords,
53
- }: {
54
- keywords?: string[];
55
- hideKeywords?: boolean;
56
- }) {
57
- if (hideKeywords || !keywords || keywords.length === 0) return null;
58
- return (
59
- <div className="mb-10 group">
60
- <span className="mr-2 font-semibold">Keywords:</span>
61
- {keywords.map((k, i) => (
62
- <span
63
- key={k}
64
- className={classNames({
65
- "after:content-[','] after:mr-1": i < keywords.length - 1,
66
- })}
67
- >
68
- {k}
69
- </span>
70
- ))}
71
- <HashLink id="keywords" title="Link to Keywords" hover className="ml-2" />
72
26
  </div>
73
27
  );
74
28
  }
@@ -1,47 +1,49 @@
1
- import type { GenericNode, GenericParent } from 'myst-common';
1
+ import type { GenericParent } from 'myst-common';
2
2
  import { HashLink, MyST } from 'myst-to-react';
3
- import type { KnownParts } from '../utils.js';
3
+ import { getChildren, type KnownParts } from '../utils.js';
4
+ import classNames from 'classnames';
4
5
 
5
- export function BackmatterParts({ parts }: { parts: KnownParts }) {
6
+ export function BackmatterParts({
7
+ parts,
8
+ containerClassName,
9
+ innerClassName,
10
+ }: {
11
+ parts: KnownParts;
12
+ containerClassName?: string;
13
+ innerClassName?: string;
14
+ }) {
6
15
  return (
7
- <>
8
- <Backmatter title="Acknowledgments" id="acknowledgments" content={parts.acknowledgments} />
16
+ <div className={containerClassName}>
17
+ <Backmatter
18
+ className={innerClassName}
19
+ title="Acknowledgments"
20
+ id="acknowledgments"
21
+ content={parts.acknowledgments}
22
+ />
9
23
  <Backmatter
24
+ className={innerClassName}
10
25
  title="Data Availability"
11
26
  id="data-availability"
12
27
  content={parts.data_availability}
13
28
  />
14
- </>
29
+ </div>
15
30
  );
16
31
  }
17
32
 
18
- /**
19
- * This returns the contents of a part that we want to render (not the root or block, which are already wrapped)
20
- * This also fixes a bug that the key is not defined on a block.
21
- */
22
- function getChildren(content?: GenericParent): GenericNode | GenericNode[] {
23
- if (
24
- content?.type === 'root' &&
25
- content.children?.length === 1 &&
26
- content.children[0].type === 'block'
27
- ) {
28
- return content.children[0].children as GenericNode[];
29
- }
30
- return content as GenericNode;
31
- }
32
-
33
33
  export function Backmatter({
34
34
  title,
35
35
  id,
36
36
  content,
37
+ className,
37
38
  }: {
38
39
  title: string;
39
40
  id: string;
40
41
  content?: GenericParent;
42
+ className?: string;
41
43
  }) {
42
44
  if (!content) return null;
43
45
  return (
44
- <div className="flex flex-col w-full md:flex-row group/backmatter">
46
+ <div className={classNames('flex flex-col w-full md:flex-row group/backmatter', className)}>
45
47
  <h2
46
48
  id={id}
47
49
  className="mt-5 text-base font-semibold group md:w-[200px] self-start md:flex-none opacity-90 group-hover/backmatter:opacity-100"
@@ -5,7 +5,13 @@ import { useState } from 'react';
5
5
 
6
6
  const HIDE_OVER_N_REFERENCES = 5;
7
7
 
8
- export function Bibliography() {
8
+ export function Bibliography({
9
+ containerClassName,
10
+ innerClassName,
11
+ }: {
12
+ containerClassName?: string;
13
+ innerClassName?: string;
14
+ }) {
9
15
  const references = useReferences();
10
16
  const grid = useGridSystemProvider();
11
17
  const { order, data } = references?.cite ?? {};
@@ -14,8 +20,11 @@ export function Bibliography() {
14
20
  if (!filtered || !data || filtered.length === 0) return null;
15
21
  const refs = hidden ? filtered.slice(0, HIDE_OVER_N_REFERENCES) : filtered;
16
22
  return (
17
- <section id="references" className={classNames(grid, 'subgrid-gap col-screen')}>
18
- <div>
23
+ <section
24
+ id="references"
25
+ className={classNames(grid, 'subgrid-gap col-screen', containerClassName)}
26
+ >
27
+ <div className={innerClassName}>
19
28
  {filtered.length > HIDE_OVER_N_REFERENCES && (
20
29
  <button
21
30
  onClick={() => setHidden(!hidden)}
@@ -29,7 +38,12 @@ export function Bibliography() {
29
38
  <HashLink id="references" title="Link to References" hover className="ml-2" />
30
39
  </header>
31
40
  </div>
32
- <div className="pl-3 mb-8 text-xs text-stone-500 dark:text-stone-300">
41
+ <div
42
+ className={classNames(
43
+ 'pl-3 mb-8 text-xs text-stone-500 dark:text-stone-300',
44
+ innerClassName,
45
+ )}
46
+ >
33
47
  <ol>
34
48
  {refs.map((label) => {
35
49
  const { html } = data[label];
@@ -3,18 +3,14 @@ import { SourceFileKind } from 'myst-spec-ext';
3
3
  import type { GenericParent } from 'myst-common';
4
4
  import classNames from 'classnames';
5
5
  import {
6
- executableNodesFromBlock,
7
6
  NotebookClearCell,
8
7
  NotebookRunCell,
9
8
  NotebookRunCellSpinnerOnly,
10
9
  } from '@myst-theme/jupyter';
11
10
  import { useGridSystemProvider } from '@myst-theme/providers';
11
+ import { isACodeCell } from '../utils.js';
12
12
 
13
- function isACodeCell(node: GenericParent) {
14
- return !!executableNodesFromBlock(node);
15
- }
16
-
17
- function Block({
13
+ export function Block({
18
14
  id,
19
15
  pageKind,
20
16
  node,
@@ -15,7 +15,7 @@ const HIGHLIGHT_CLASS = 'highlight';
15
15
 
16
16
  const onClient = typeof document !== 'undefined';
17
17
 
18
- type Heading = {
18
+ export type Heading = {
19
19
  element: HTMLHeadingElement;
20
20
  id: string;
21
21
  title: string;
@@ -105,7 +105,7 @@ function getHeaders(selector: string): HTMLHeadingElement[] {
105
105
  return headers as HTMLHeadingElement[];
106
106
  }
107
107
 
108
- function useHeaders(selector: string, maxdepth: number) {
108
+ export function useHeaders(selector: string, maxdepth: number) {
109
109
  if (!onClient) return { activeId: '', headings: [] };
110
110
  const onScreen = useRef<Set<HTMLHeadingElement>>(new Set());
111
111
  const [activeId, setActiveId] = useState<string>();
@@ -3,7 +3,7 @@ import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
3
3
  import type { FooterLinks, NavigationLink } from '@myst-theme/common';
4
4
  import { useLinkProvider, useBaseurl, withBaseurl } from '@myst-theme/providers';
5
5
 
6
- const FooterLink = ({
6
+ export const FooterLink = ({
7
7
  title,
8
8
  short_title,
9
9
  url,
@@ -5,21 +5,35 @@ import type { FootnoteDefinition, FootnoteReference } from 'myst-spec-ext';
5
5
  import { HashLink, MyST } from 'myst-to-react';
6
6
  import { selectAll } from 'unist-util-select';
7
7
 
8
- export function Footnotes() {
8
+ export function Footnotes({
9
+ containerClassName,
10
+ innerClassName,
11
+ }: {
12
+ containerClassName?: string;
13
+ innerClassName?: string;
14
+ }) {
9
15
  const references = useReferences();
10
16
  const grid = useGridSystemProvider();
11
17
  const defs = selectAll('footnoteDefinition', references?.article) as FootnoteDefinition[];
12
18
  const refs = selectAll('footnoteReference', references?.article) as FootnoteReference[];
13
19
  if (defs.length === 0) return null;
14
20
  return (
15
- <section id="footnotes" className={classNames(grid, 'subgrid-gap col-screen')}>
16
- <div>
21
+ <section
22
+ id="footnotes"
23
+ className={classNames(grid, 'subgrid-gap col-screen', containerClassName)}
24
+ >
25
+ <div className={innerClassName}>
17
26
  <header className="text-lg font-semibold text-stone-900 dark:text-white group">
18
27
  Footnotes
19
28
  <HashLink id="footnotes" title="Link to Footnotes" hover className="ml-2" />
20
29
  </header>
21
30
  </div>
22
- <div className="pl-3 mb-8 text-xs text-stone-500 dark:text-stone-300">
31
+ <div
32
+ className={classNames(
33
+ 'pl-3 mb-8 text-xs text-stone-500 dark:text-stone-300',
34
+ innerClassName,
35
+ )}
36
+ >
23
37
  <ol>
24
38
  {defs.map((fn) => {
25
39
  return (
@@ -0,0 +1,37 @@
1
+ import { type KnownParts } from '../utils.js';
2
+ import { Abstract } from './Abstract.js';
3
+ import { Keywords } from './Keywords.js';
4
+
5
+ export function FrontmatterParts({
6
+ parts,
7
+ keywords,
8
+ hideKeywords,
9
+ containerClassName,
10
+ innerClassName,
11
+ }: {
12
+ parts: KnownParts;
13
+ keywords?: string[];
14
+ hideKeywords?: boolean;
15
+ containerClassName?: string;
16
+ innerClassName?: string;
17
+ }) {
18
+ if (!parts.abstract && !parts.keypoints && !parts.summary) return null;
19
+ return (
20
+ <div className={containerClassName}>
21
+ <Abstract className={innerClassName} content={parts.abstract} />
22
+ <Abstract
23
+ className={innerClassName}
24
+ content={parts.keypoints}
25
+ title="Key Points"
26
+ id="keypoints"
27
+ />
28
+ <Abstract
29
+ className={innerClassName}
30
+ content={parts.summary}
31
+ title="Plain Language Summary"
32
+ id="summary"
33
+ />
34
+ <Keywords className={innerClassName} keywords={keywords} hideKeywords={hideKeywords} />
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1,30 @@
1
+ import classNames from 'classnames';
2
+ import { HashLink } from 'myst-to-react';
3
+
4
+ export function Keywords({
5
+ keywords,
6
+ hideKeywords,
7
+ className,
8
+ }: {
9
+ keywords?: string[];
10
+ hideKeywords?: boolean;
11
+ className?: string;
12
+ }) {
13
+ if (hideKeywords || !keywords || keywords.length === 0) return null;
14
+ return (
15
+ <div className={classNames('mb-10 group', className)}>
16
+ <span className="mr-2 font-semibold">Keywords:</span>
17
+ {keywords.map((k, i) => (
18
+ <span
19
+ key={k}
20
+ className={classNames({
21
+ "after:content-[','] after:mr-1": i < keywords.length - 1,
22
+ })}
23
+ >
24
+ {k}
25
+ </span>
26
+ ))}
27
+ <HashLink id="keywords" title="Link to Keywords" hover className="ml-2" />
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,48 @@
1
+ import { Fragment } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Menu, Transition } from '@headlessui/react';
4
+ import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
5
+ import type { SiteManifest } from 'myst-config';
6
+
7
+ export function ActionMenu({ actions }: { actions?: SiteManifest['actions'] }) {
8
+ if (!actions || actions.length === 0) return null;
9
+ return (
10
+ <Menu as="div" className="relative">
11
+ <div>
12
+ <Menu.Button className="flex text-sm bg-transparent rounded-full focus:outline-none">
13
+ <span className="sr-only">Open Menu</span>
14
+ <div className="flex items-center text-stone-200 hover:text-white">
15
+ <EllipsisVerticalIcon width="2rem" height="2rem" className="p-1" />
16
+ </div>
17
+ </Menu.Button>
18
+ </div>
19
+ <Transition
20
+ as={Fragment}
21
+ enter="transition ease-out duration-100"
22
+ enterFrom="transform opacity-0 scale-95"
23
+ enterTo="transform opacity-100 scale-100"
24
+ leave="transition ease-in duration-75"
25
+ leaveFrom="transform opacity-100 scale-100"
26
+ leaveTo="transform opacity-0 scale-95"
27
+ >
28
+ <Menu.Items className="absolute right-0 w-48 py-1 mt-2 origin-top-right bg-white rounded-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
29
+ {actions?.map((action) => (
30
+ <Menu.Item key={action.url}>
31
+ {({ active }) => (
32
+ <a
33
+ href={action.url}
34
+ className={classNames(
35
+ active ? 'bg-gray-100' : '',
36
+ 'block px-4 py-2 text-sm text-gray-700',
37
+ )}
38
+ >
39
+ {action.title}
40
+ </a>
41
+ )}
42
+ </Menu.Item>
43
+ ))}
44
+ </Menu.Items>
45
+ </Transition>
46
+ </Menu>
47
+ );
48
+ }
@@ -0,0 +1,55 @@
1
+ import classNames from 'classnames';
2
+ import { useBaseurl, useLinkProvider, withBaseurl } from '@myst-theme/providers';
3
+
4
+ export function HomeLink({
5
+ logo,
6
+ logoDark,
7
+ logoText,
8
+ name,
9
+ }: {
10
+ logo?: string;
11
+ logoDark?: string;
12
+ logoText?: string;
13
+ name?: string;
14
+ }) {
15
+ const Link = useLinkProvider();
16
+ const baseurl = useBaseurl();
17
+ const nothingSet = !logo && !logoText;
18
+ return (
19
+ <Link
20
+ className="flex items-center ml-3 dark:text-white w-fit md:ml-5 xl:ml-7"
21
+ to={withBaseurl('/', baseurl)}
22
+ prefetch="intent"
23
+ >
24
+ {logo && (
25
+ <div
26
+ className={classNames('p-1 mr-3', {
27
+ 'dark:bg-white dark:rounded': !logoDark,
28
+ })}
29
+ >
30
+ <img
31
+ src={logo}
32
+ className={classNames('h-9', { 'dark:hidden': !!logoDark })}
33
+ alt={logoText || name}
34
+ height="2.25rem"
35
+ ></img>
36
+ {logoDark && (
37
+ <img
38
+ src={logoDark}
39
+ className="hidden h-9 dark:block"
40
+ alt={logoText || name}
41
+ height="2.25rem"
42
+ ></img>
43
+ )}
44
+ </div>
45
+ )}
46
+ <span
47
+ className={classNames('text-md sm:text-xl tracking-tight sm:mr-5', {
48
+ 'sr-only': !(logoText || nothingSet),
49
+ })}
50
+ >
51
+ {logoText || 'Made with MyST'}
52
+ </span>
53
+ </Link>
54
+ );
55
+ }
@@ -5,7 +5,7 @@ import classNames from 'classnames';
5
5
  /**
6
6
  * Show a loading progess bad if the load takes more than 150ms
7
7
  */
8
- function useLoading() {
8
+ export function useLoading() {
9
9
  const transitionState = useNavigation().state;
10
10
  const ref = useMemo<{ start?: NodeJS.Timeout; finish?: NodeJS.Timeout }>(() => ({}), []);
11
11
  const [showLoading, setShowLoading] = useState(false);
@@ -1,22 +1,18 @@
1
1
  import { Fragment } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { Menu, Transition } from '@headlessui/react';
4
- import {
5
- EllipsisVerticalIcon,
6
- ChevronDownIcon,
7
- Bars3Icon as MenuIcon,
8
- } from '@heroicons/react/24/solid';
4
+ import { ChevronDownIcon, Bars3Icon as MenuIcon } from '@heroicons/react/24/solid';
9
5
  import type { SiteManifest, SiteNavItem } from 'myst-config';
10
6
  import { ThemeButton } from './ThemeButton.js';
11
7
  import {
12
- useBaseurl,
13
8
  useLinkProvider,
14
9
  useNavLinkProvider,
15
10
  useNavOpen,
16
11
  useSiteManifest,
17
- withBaseurl,
18
12
  } from '@myst-theme/providers';
19
13
  import { LoadingBar } from './Loading.js';
14
+ import { HomeLink } from './HomeLink.js';
15
+ import { ActionMenu } from './ActionMenu.js';
20
16
 
21
17
  export const DEFAULT_NAV_HEIGHT = 60;
22
18
 
@@ -57,7 +53,7 @@ function ExternalOrInternalLink({
57
53
  );
58
54
  }
59
55
 
60
- function NavItem({ item }: { item: SiteNavItem }) {
56
+ export function NavItem({ item }: { item: SiteNavItem }) {
61
57
  const NavLink = useNavLinkProvider();
62
58
  if (!('children' in item)) {
63
59
  return (
@@ -136,7 +132,7 @@ function NavItem({ item }: { item: SiteNavItem }) {
136
132
  );
137
133
  }
138
134
 
139
- function NavItems({ nav }: { nav?: SiteManifest['nav'] }) {
135
+ export function NavItems({ nav }: { nav?: SiteManifest['nav'] }) {
140
136
  if (!nav) return null;
141
137
  return (
142
138
  <div className="flex-grow hidden text-md lg:block">
@@ -147,102 +143,6 @@ function NavItems({ nav }: { nav?: SiteManifest['nav'] }) {
147
143
  );
148
144
  }
149
145
 
150
- function ActionMenu({ actions }: { actions?: SiteManifest['actions'] }) {
151
- if (!actions || actions.length === 0) return null;
152
- return (
153
- <Menu as="div" className="relative">
154
- <div>
155
- <Menu.Button className="flex text-sm bg-transparent rounded-full focus:outline-none">
156
- <span className="sr-only">Open Menu</span>
157
- <div className="flex items-center text-stone-200 hover:text-white">
158
- <EllipsisVerticalIcon width="2rem" height="2rem" className="p-1" />
159
- </div>
160
- </Menu.Button>
161
- </div>
162
- <Transition
163
- as={Fragment}
164
- enter="transition ease-out duration-100"
165
- enterFrom="transform opacity-0 scale-95"
166
- enterTo="transform opacity-100 scale-100"
167
- leave="transition ease-in duration-75"
168
- leaveFrom="transform opacity-100 scale-100"
169
- leaveTo="transform opacity-0 scale-95"
170
- >
171
- <Menu.Items className="absolute right-0 w-48 py-1 mt-2 origin-top-right bg-white rounded-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
172
- {actions?.map((action) => (
173
- <Menu.Item key={action.url}>
174
- {({ active }) => (
175
- <a
176
- href={action.url}
177
- className={classNames(
178
- active ? 'bg-gray-100' : '',
179
- 'block px-4 py-2 text-sm text-gray-700',
180
- )}
181
- >
182
- {action.title}
183
- </a>
184
- )}
185
- </Menu.Item>
186
- ))}
187
- </Menu.Items>
188
- </Transition>
189
- </Menu>
190
- );
191
- }
192
-
193
- function HomeLink({
194
- logo,
195
- logoDark,
196
- logoText,
197
- name,
198
- }: {
199
- logo?: string;
200
- logoDark?: string;
201
- logoText?: string;
202
- name?: string;
203
- }) {
204
- const Link = useLinkProvider();
205
- const baseurl = useBaseurl();
206
- const nothingSet = !logo && !logoText;
207
- return (
208
- <Link
209
- className="flex items-center ml-3 dark:text-white w-fit md:ml-5 xl:ml-7"
210
- to={withBaseurl('/', baseurl)}
211
- prefetch="intent"
212
- >
213
- {logo && (
214
- <div
215
- className={classNames('p-1 mr-3', {
216
- 'dark:bg-white dark:rounded': !logoDark,
217
- })}
218
- >
219
- <img
220
- src={logo}
221
- className={classNames('h-9', { 'dark:hidden': !!logoDark })}
222
- alt={logoText || name}
223
- height="2.25rem"
224
- ></img>
225
- {logoDark && (
226
- <img
227
- src={logoDark}
228
- className="hidden h-9 dark:block"
229
- alt={logoText || name}
230
- height="2.25rem"
231
- ></img>
232
- )}
233
- </div>
234
- )}
235
- <span
236
- className={classNames('text-md sm:text-xl tracking-tight sm:mr-5', {
237
- 'sr-only': !(logoText || nothingSet),
238
- })}
239
- >
240
- {logoText || 'Made with MyST'}
241
- </span>
242
- </Link>
243
- );
244
- }
245
-
246
146
  export function TopNav() {
247
147
  const [open, setOpen] = useNavOpen();
248
148
  const config = useSiteManifest();
@@ -1,5 +1,7 @@
1
1
  export { ThemeButton } from './ThemeButton.js';
2
- export { TopNav, DEFAULT_NAV_HEIGHT } from './TopNav.js';
2
+ export { TopNav, NavItems, NavItem, DEFAULT_NAV_HEIGHT } from './TopNav.js';
3
3
  export { Navigation } from './Navigation.js';
4
4
  export { TableOfContents, InlineTableOfContents, useTocHeight } from './TableOfContents.js';
5
5
  export { LoadingBar } from './Loading.js';
6
+ export { ActionMenu } from './ActionMenu.js';
7
+ export { HomeLink } from './HomeLink.js';
@@ -1,4 +1,4 @@
1
- import { useCallback } from 'react';
1
+ import React, { useCallback } from 'react';
2
2
 
3
3
  function makeSkipClickHandler(hash: string) {
4
4
  return (e: React.UIEvent<HTMLElement, Event>) => {
@@ -10,6 +10,10 @@ function makeSkipClickHandler(hash: string) {
10
10
  };
11
11
  }
12
12
 
13
+ /**
14
+ * @deprecated use `SkipTo` instead with a list of targets
15
+ *
16
+ */
13
17
  export function SkipToArticle({
14
18
  frontmatter = true,
15
19
  article = true,
@@ -48,3 +52,26 @@ export function SkipToArticle({
48
52
  </div>
49
53
  );
50
54
  }
55
+
56
+ /**
57
+ * Add a skip navigation unit with links based on a list of targets
58
+ */
59
+ export const SkipTo = React.memo(({ targets }: { targets: { id: string; title: string }[] }) => {
60
+ return (
61
+ <div
62
+ className="fixed top-1 left-1 h-[0px] w-[0px] focus-within:z-40 focus-within:h-auto focus-within:w-auto bg-white overflow-hidden focus-within:p-2 focus-within:ring-1"
63
+ aria-label="skip to content options"
64
+ >
65
+ {targets.map(({ id, title }) => (
66
+ <a
67
+ key={id}
68
+ href={`#${id}`}
69
+ className="block px-2 py-1 text-black underline"
70
+ onClick={makeSkipClickHandler(id)}
71
+ >
72
+ {title}
73
+ </a>
74
+ ))}
75
+ </div>
76
+ );
77
+ });
@@ -1,13 +1,20 @@
1
- export { ContentBlocks } from './ContentBlocks.js';
2
- export { DocumentOutline, useOutlineHeight, SupportingDocuments } from './DocumentOutline.js';
3
- export { FooterLinksBlock } from './FooterLinksBlock.js';
1
+ export { ContentBlocks, Block } from './ContentBlocks.js';
2
+ export {
3
+ DocumentOutline,
4
+ useOutlineHeight,
5
+ SupportingDocuments,
6
+ useHeaders,
7
+ } from './DocumentOutline.js';
8
+ export { FooterLinksBlock, FooterLink } from './FooterLinksBlock.js';
4
9
  export { ContentReload } from './ContentReload.js';
5
10
  export { Bibliography } from './Bibliography.js';
6
11
  export { Footnotes } from './Footnotes.js';
7
12
  export { ArticleHeader } from './Headers.js';
8
- export { FrontmatterParts, Abstract, Keywords } from './Abstract.js';
13
+ export { Abstract } from './Abstract.js';
14
+ export { Keywords } from './Keywords.js';
15
+ export { FrontmatterParts } from './FrontmatterParts.js';
9
16
  export { BackmatterParts, Backmatter } from './Backmatter.js';
10
17
  export { ExternalOrInternalLink } from './ExternalOrInternalLink.js';
11
18
  export * from './Navigation/index.js';
12
19
  export { renderers } from './renderers.js';
13
- export * from './SkipToArticle.js';
20
+ export { SkipToArticle, SkipTo } from './SkipToArticle.js';
@@ -21,25 +21,7 @@ import {
21
21
  useComputeOptions,
22
22
  } from '@myst-theme/jupyter';
23
23
  import { FrontmatterBlock } from '@myst-theme/frontmatter';
24
- import { extractKnownParts } from '../utils.js';
25
- import type { SiteAction } from 'myst-config';
26
-
27
- /**
28
- * Combines the project downloads and the export options
29
- */
30
- function combineDownloads(
31
- siteDownloads: SiteAction[] | undefined,
32
- pageFrontmatter: PageLoader['frontmatter'],
33
- ) {
34
- if (pageFrontmatter.downloads) {
35
- return pageFrontmatter.downloads;
36
- }
37
- // No downloads on the page, combine the exports if they exist
38
- if (siteDownloads) {
39
- return [...(pageFrontmatter.exports ?? []), ...siteDownloads];
40
- }
41
- return pageFrontmatter.exports;
42
- }
24
+ import { combineDownloads, extractKnownParts } from '../utils.js';
43
25
 
44
26
  /**
45
27
  * @deprecated This component is not maintained, in favor of theme-specific ArticlePages
package/src/seo/meta.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type { HtmlMetaDescriptor, V2_MetaDescriptor } from '@remix-run/react';
2
2
 
3
- type SocialSite = {
3
+ export type SocialSite = {
4
4
  title: string;
5
5
  description?: string;
6
6
  twitter?: string;
7
7
  };
8
8
 
9
- type SocialArticle = {
9
+ export type SocialArticle = {
10
10
  origin: string;
11
11
  url: string;
12
12
  // TODO: canonical
package/src/utils.ts CHANGED
@@ -1,5 +1,8 @@
1
- import type { GenericParent } from 'myst-common';
1
+ import type { GenericNode, GenericParent } from 'myst-common';
2
2
  import { extractPart } from 'myst-common';
3
+ import type { PageLoader } from '@myst-theme/common';
4
+ import type { SiteAction } from 'myst-config';
5
+ import { executableNodesFromBlock } from '@myst-theme/jupyter';
3
6
 
4
7
  export function getDomainFromRequest(request: Request) {
5
8
  const url = new URL(request.url);
@@ -23,3 +26,39 @@ export function extractKnownParts(tree: GenericParent): KnownParts {
23
26
  const acknowledgments = extractPart(tree, ['acknowledgments', 'acknowledgements']);
24
27
  return { abstract, summary, keypoints, data_availability, acknowledgments };
25
28
  }
29
+
30
+ /**
31
+ * Combines the project downloads and the export options
32
+ */
33
+ export function combineDownloads(
34
+ siteDownloads: SiteAction[] | undefined,
35
+ pageFrontmatter: PageLoader['frontmatter'],
36
+ ) {
37
+ if (pageFrontmatter.downloads) {
38
+ return pageFrontmatter.downloads;
39
+ }
40
+ // No downloads on the page, combine the exports if they exist
41
+ if (siteDownloads) {
42
+ return [...(pageFrontmatter.exports ?? []), ...siteDownloads];
43
+ }
44
+ return pageFrontmatter.exports;
45
+ }
46
+
47
+ /**
48
+ * This returns the contents of a part that we want to render (not the root or block, which are already wrapped)
49
+ * This also fixes a bug that the key is not defined on a block.
50
+ */
51
+ export function getChildren(content?: GenericParent): GenericNode | GenericNode[] {
52
+ if (
53
+ content?.type === 'root' &&
54
+ content.children?.length === 1 &&
55
+ content.children[0].type === 'block'
56
+ ) {
57
+ return content.children[0].children as GenericNode[];
58
+ }
59
+ return content as GenericNode;
60
+ }
61
+
62
+ export function isACodeCell(node: GenericParent) {
63
+ return !!executableNodesFromBlock(node);
64
+ }