@myst-theme/site 0.13.3 → 0.13.5

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.13.3",
3
+ "version": "0.13.5",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -21,25 +21,22 @@
21
21
  "dependencies": {
22
22
  "@headlessui/react": "^1.7.15",
23
23
  "@heroicons/react": "^2.0.18",
24
- "@myst-theme/common": "^0.13.3",
25
- "@myst-theme/diagrams": "^0.13.3",
26
- "@myst-theme/frontmatter": "^0.13.3",
27
- "@myst-theme/jupyter": "^0.13.3",
28
- "@myst-theme/providers": "^0.13.3",
29
- "@myst-theme/search": "^0.13.3",
24
+ "@myst-theme/common": "^0.13.5",
25
+ "@myst-theme/diagrams": "^0.13.5",
26
+ "@myst-theme/frontmatter": "^0.13.5",
27
+ "@myst-theme/jupyter": "^0.13.5",
28
+ "@myst-theme/providers": "^0.13.5",
29
+ "@myst-theme/search": "^0.13.5",
30
30
  "@radix-ui/react-collapsible": "^1.0.3",
31
31
  "@radix-ui/react-dialog": "^1.0.3",
32
- "@radix-ui/react-radio-group": "^1.2.0",
33
- "@radix-ui/react-roving-focus": "^1.1.0",
34
- "@radix-ui/react-slot": "^1.1.0",
35
32
  "@radix-ui/react-visually-hidden": "^1.1.0",
36
33
  "classnames": "^2.3.2",
37
34
  "lodash.throttle": "^4.1.1",
38
35
  "myst-common": "^1.7.3",
39
36
  "myst-config": "^1.7.3",
40
- "myst-demo": "^0.13.3",
37
+ "myst-demo": "^0.13.5",
41
38
  "myst-spec-ext": "^1.7.3",
42
- "myst-to-react": "^0.13.3",
39
+ "myst-to-react": "^0.13.5",
43
40
  "nbtx": "^0.2.3",
44
41
  "node-cache": "^5.1.2",
45
42
  "node-fetch": "^2.6.11",
@@ -3,14 +3,14 @@ import classNames from 'classnames';
3
3
  import { HashLink } from 'myst-to-react';
4
4
  import { useState } from 'react';
5
5
 
6
- const HIDE_OVER_N_REFERENCES = 5;
7
-
8
6
  export function Bibliography({
9
7
  containerClassName,
10
8
  innerClassName,
9
+ hideLongBibliography = 15,
11
10
  }: {
12
11
  containerClassName?: string;
13
12
  innerClassName?: string;
13
+ hideLongBibliography?: false | number;
14
14
  }) {
15
15
  const references = useReferences();
16
16
  const grid = useGridSystemProvider();
@@ -18,14 +18,14 @@ export function Bibliography({
18
18
  const filtered = order?.filter((l) => l);
19
19
  const [hidden, setHidden] = useState(true);
20
20
  if (!filtered || !data || filtered.length === 0) return null;
21
- const refs = hidden ? filtered.slice(0, HIDE_OVER_N_REFERENCES) : filtered;
21
+ const refs = hidden && hideLongBibliography ? filtered.slice(0, hideLongBibliography) : filtered;
22
22
  return (
23
23
  <section
24
24
  id="references"
25
25
  className={classNames(grid, 'subgrid-gap col-screen', containerClassName)}
26
26
  >
27
27
  <div className={innerClassName}>
28
- {filtered.length > HIDE_OVER_N_REFERENCES && (
28
+ {!!hideLongBibliography && filtered.length > hideLongBibliography && (
29
29
  <button
30
30
  onClick={() => setHidden(!hidden)}
31
31
  className="float-right p-1 px-2 text-xs border rounded hover:border-blue-500 dark:hover:border-blue-400"
@@ -56,7 +56,7 @@ export function Bibliography({
56
56
  />
57
57
  );
58
58
  })}
59
- {filtered.length > HIDE_OVER_N_REFERENCES && (
59
+ {!!hideLongBibliography && filtered.length > hideLongBibliography && (
60
60
  <li className="text-center list-none">
61
61
  <button
62
62
  onClick={() => setHidden(!hidden)}
@@ -1,4 +1,4 @@
1
- import { MyST } from 'myst-to-react';
1
+ import { Details, MyST } from 'myst-to-react';
2
2
  import { SourceFileKind } from 'myst-spec-ext';
3
3
  import type { GenericParent } from 'myst-common';
4
4
  import classNames from 'classnames';
@@ -22,17 +22,18 @@ export function Block({
22
22
  className?: string;
23
23
  }) {
24
24
  const grid = useGridSystemProvider();
25
- const subGrid = `${grid} subgrid-gap col-screen`;
25
+ const subGrid = node.visibility === 'hide' ? '' : `${grid} subgrid-gap col-screen`;
26
26
  const dataClassName = typeof node.data?.class === 'string' ? node.data?.class : undefined;
27
27
  // Hide the subgrid if either the dataClass or the className exists and includes `col-`
28
28
  const noSubGrid =
29
29
  (dataClassName && dataClassName.includes('col-')) || (className && className.includes('col-'));
30
- return (
30
+ const block = (
31
31
  <div
32
32
  key={`block-${id}`}
33
33
  id={id}
34
34
  className={classNames('relative group/block', className, dataClassName, {
35
35
  [subGrid]: !noSubGrid,
36
+ hidden: node.visibility === 'remove',
36
37
  })}
37
38
  >
38
39
  {pageKind === SourceFileKind.Notebook && isACodeCell(node) && (
@@ -53,6 +54,10 @@ export function Block({
53
54
  <MyST ast={node.children} />
54
55
  </div>
55
56
  );
57
+ if (node.visibility === 'hide') {
58
+ return <Details title="Notebook Cell">{block}</Details>;
59
+ }
60
+ return block;
56
61
  }
57
62
 
58
63
  export function ContentBlocks({
@@ -12,6 +12,7 @@ import type { RefObject } from 'react';
12
12
  import { DocumentChartBarIcon } from '@heroicons/react/24/outline';
13
13
  import { ChevronRightIcon } from '@heroicons/react/24/solid';
14
14
  import * as Collapsible from '@radix-ui/react-collapsible';
15
+ import { slugToUrl } from 'myst-common';
15
16
 
16
17
  const SELECTOR = [1, 2, 3, 4].map((n) => `main h${n}`).join(', ');
17
18
 
@@ -351,6 +352,7 @@ function useMarginOccluder() {
351
352
 
352
353
  export const DocumentOutline = ({
353
354
  outlineRef,
355
+ title = 'Contents',
354
356
  top = 0,
355
357
  className,
356
358
  selector = SELECTOR,
@@ -359,6 +361,7 @@ export const DocumentOutline = ({
359
361
  isMargin,
360
362
  }: {
361
363
  outlineRef?: React.RefObject<HTMLElement>;
364
+ title?: React.ReactNode;
362
365
  top?: number;
363
366
  height?: number;
364
367
  className?: string;
@@ -405,7 +408,7 @@ export const DocumentOutline = ({
405
408
  }}
406
409
  >
407
410
  <div className="flex flex-row gap-2 mb-4 text-sm leading-6 uppercase rounded-lg text-slate-900 dark:text-slate-100">
408
- In this article
411
+ {title}
409
412
  <Collapsible.Trigger asChild>
410
413
  <button className="self-center flex-none rounded-md group hover:bg-slate-300/30 focus:outline outline-blue-200 outline-2">
411
414
  <ChevronRightIcon
@@ -443,7 +446,7 @@ export function SupportingDocuments() {
443
446
  return (
444
447
  <li key={p.slug}>
445
448
  <NavLink
446
- to={withBaseurl(`/${p.slug}#main`, baseurl)}
449
+ to={withBaseurl(`/${slugToUrl(p.slug)}#main`, baseurl)}
447
450
  prefetch="intent"
448
451
  className={({ isActive }) =>
449
452
  classNames('no-underline flex self-center hover:text-blue-700', {
@@ -174,7 +174,7 @@ export const PrimarySidebar = ({
174
174
  },
175
175
  )}
176
176
  >
177
- <div className="flex-grow py-6 overflow-y-auto">
177
+ <div className="flex-grow py-6 overflow-y-auto primary-scrollbar">
178
178
  {nav && (
179
179
  <nav
180
180
  aria-label="Navigation"
@@ -1,4 +1,12 @@
1
- import { useEffect, useState, useMemo, useCallback, useRef, forwardRef } from 'react';
1
+ import {
2
+ createElement,
3
+ useEffect,
4
+ useState,
5
+ useMemo,
6
+ useCallback,
7
+ useRef,
8
+ forwardRef,
9
+ } from 'react';
2
10
  import type { KeyboardEventHandler, Dispatch, SetStateAction, FormEvent, MouseEvent } from 'react';
3
11
  import { useFetcher } from '@remix-run/react';
4
12
  import {
@@ -42,10 +50,21 @@ function matchAll(text: string, pattern: RegExp) {
42
50
  * Highlight a text string with an array of match words
43
51
  *
44
52
  * @param text - text to highlight
45
- * @param result - search result to use for highlighting
46
- * @param limit - limit to the number of tokens after first match
53
+ * @param matches - regular expression patterns to match against
54
+ * @param limit - limit to the number of characters after first match
55
+ * @param classname - CSS classname to use
47
56
  */
48
- function MarkedText({ text, matches, limit }: { text: string; matches: string[]; limit?: number }) {
57
+ function MarkedText({
58
+ text,
59
+ matches,
60
+ limit,
61
+ className,
62
+ }: {
63
+ text: string;
64
+ matches: string[];
65
+ limit?: number;
66
+ className?: string;
67
+ }) {
49
68
  // Split by delimeter, but _keep_ delimeter!
50
69
  const splits = matchAll(text, SPACE_OR_PUNCTUATION);
51
70
  const tokens: string[] = [];
@@ -79,23 +98,38 @@ function MarkedText({ text, matches, limit }: { text: string; matches: string[];
79
98
  lastIndex = tokens.length;
80
99
  } else {
81
100
  firstIndex = tokens.findIndex((token) => pattern.test(token));
82
- lastIndex = firstIndex + limit;
101
+ let numChars = 0;
102
+ for (
103
+ lastIndex = firstIndex + 1;
104
+ lastIndex < tokens.length - 1 && numChars + tokens[lastIndex].length <= limit;
105
+ lastIndex++
106
+ ) {
107
+ numChars += tokens[lastIndex].length;
108
+ }
83
109
  }
84
110
 
85
111
  if (tokens.length === 0) {
86
- return <>{...tokens}</>;
112
+ return <span className={className}>{...tokens}</span>;
87
113
  } else {
88
114
  const firstRenderer = renderToken(tokens[firstIndex]);
89
115
  const remainingTokens = tokens.slice(firstIndex + 1, lastIndex);
90
116
  const remainingRenderers = remainingTokens.map((token) => renderToken(token));
91
117
 
92
118
  return (
93
- <>
94
- {hasLimit && '... '}
119
+ <span
120
+ className={classNames(
121
+ className,
122
+ {
123
+ "before:content-['..._']": hasLimit,
124
+ "after:content-['_...']": hasLimit,
125
+ },
126
+ 'truncate',
127
+ 'w-full',
128
+ )}
129
+ >
95
130
  {firstRenderer}
96
131
  {...remainingRenderers}
97
- {hasLimit && ' ...'}
98
- </>
132
+ </span>
99
133
  );
100
134
  }
101
135
  }
@@ -178,8 +212,10 @@ function SearchShortcut() {
178
212
  function SearchResultItem({
179
213
  result,
180
214
  closeSearch,
215
+ charLimit,
181
216
  }: {
182
217
  result: RankedSearchResult;
218
+ charLimit?: number;
183
219
  closeSearch?: () => void;
184
220
  }) {
185
221
  const { hierarchy, type, url, queries } = result;
@@ -187,14 +223,13 @@ function SearchResultItem({
187
223
  const Link = useLinkProvider();
188
224
 
189
225
  // Render the icon
190
- const iconRenderer =
191
- type === 'lvl1' ? (
192
- <DocumentIcon className="inline-block w-6 mx-2" />
193
- ) : type === 'content' ? (
194
- <Bars3BottomLeftIcon className="inline-block w-6 mx-2" />
195
- ) : (
196
- <HashtagIcon className="inline-block w-6 mx-2" />
197
- );
226
+ const iconProps = useMemo(() => {
227
+ return { className: 'inline-block w-6 mx-2 shrink-0' };
228
+ }, []);
229
+ const iconRenderer = createElement(
230
+ type === 'lvl1' ? DocumentIcon : type === 'content' ? Bars3BottomLeftIcon : HashtagIcon,
231
+ iconProps,
232
+ );
198
233
 
199
234
  // Generic "this document matched"
200
235
  const title = result.type === 'content' ? result['content'] : hierarchy[type as HeadingLevel]!;
@@ -202,7 +237,12 @@ function SearchResultItem({
202
237
 
203
238
  // Render the title, i.e. content or heading
204
239
  const titleRenderer = (
205
- <MarkedText text={title} matches={matches} limit={type === 'content' ? 16 : undefined} />
240
+ <MarkedText
241
+ text={title}
242
+ matches={matches}
243
+ limit={type === 'content' ? charLimit : undefined}
244
+ className="text-sm"
245
+ />
206
246
  );
207
247
 
208
248
  // Render the subtitle i.e. file name
@@ -211,7 +251,7 @@ function SearchResultItem({
211
251
  subtitleRenderer = undefined;
212
252
  } else {
213
253
  const subtitle = result.hierarchy.lvl1!;
214
- subtitleRenderer = <MarkedText text={subtitle} matches={matches} />;
254
+ subtitleRenderer = <MarkedText text={subtitle} matches={matches} className="text-xs" />;
215
255
  }
216
256
 
217
257
  const enterIconRenderer = (
@@ -227,9 +267,9 @@ function SearchResultItem({
227
267
  >
228
268
  <div className="flex flex-row h-11">
229
269
  {iconRenderer}
230
- <div className="flex flex-col justify-center grow">
231
- <span className="text-sm">{titleRenderer}</span>
232
- {subtitleRenderer && <span className="text-xs">{subtitleRenderer}</span>}
270
+ <div className="flex flex-col justify-center truncate grow">
271
+ {titleRenderer}
272
+ {subtitleRenderer}
233
273
  </div>
234
274
  {enterIconRenderer}
235
275
  </div>
@@ -242,6 +282,7 @@ interface SearchResultsProps {
242
282
  searchListID: string;
243
283
  searchLabelID: string;
244
284
  selectedIndex: number;
285
+ charLimit?: number;
245
286
  onHoverSelect: (index: number) => void;
246
287
  className?: string;
247
288
  closeSearch?: () => void;
@@ -251,6 +292,7 @@ function SearchResults({
251
292
  searchResults,
252
293
  searchListID,
253
294
  searchLabelID,
295
+ charLimit,
254
296
  className,
255
297
  selectedIndex,
256
298
  onHoverSelect,
@@ -325,7 +367,7 @@ function SearchResults({
325
367
  // Trigger selection on movement, so that scrolling doesn't trigger handler
326
368
  onMouseMove={handleMouseMove}
327
369
  >
328
- <SearchResultItem result={result} closeSearch={closeSearch} />
370
+ <SearchResultItem result={result} closeSearch={closeSearch} charLimit={charLimit} />
329
371
  </li>
330
372
  ))}
331
373
  </ul>
@@ -541,11 +583,12 @@ const SearchPlaceholderButton = forwardRef<
541
583
 
542
584
  export interface SearchProps {
543
585
  debounceTime?: number;
586
+ charLimit?: number;
544
587
  }
545
588
  /**
546
589
  * Component that implements a basic search interface
547
590
  */
548
- export function Search({ debounceTime = 500 }: SearchProps) {
591
+ export function Search({ debounceTime = 500, charLimit = 64 }: SearchProps) {
549
592
  const [open, setOpen] = useState(false);
550
593
  const [searchResults, setSearchResults] = useState<RankedSearchResult[] | undefined>();
551
594
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -624,6 +667,7 @@ export function Search({ debounceTime = 500 }: SearchProps) {
624
667
  selectedIndex={selectedIndex}
625
668
  onHoverSelect={setSelectedIndex}
626
669
  closeSearch={triggerClose}
670
+ charLimit={charLimit}
627
671
  />
628
672
  )}
629
673
  </Dialog.Content>
@@ -44,6 +44,9 @@ export const ArticlePage = React.memo(function ({
44
44
  const keywords = article.frontmatter?.keywords ?? [];
45
45
  const parts = extractKnownParts(tree, article.frontmatter?.parts);
46
46
 
47
+ const { thebe } = manifest as any;
48
+ const { location } = article;
49
+
47
50
  return (
48
51
  <ReferencesProvider
49
52
  references={{ ...article.references, article: article.mdast }}
@@ -55,6 +58,8 @@ export const ArticlePage = React.memo(function ({
55
58
  <FrontmatterBlock
56
59
  kind={article.kind}
57
60
  frontmatter={{ ...article.frontmatter, downloads }}
61
+ thebe={thebe}
62
+ location={location}
58
63
  className="mb-8 pt-9"
59
64
  />
60
65
  )}