@seqera/docusaurus-theme-seqera 1.0.30-next.99 → 1.0.31-next.102

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.
@@ -3,7 +3,7 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  justify-content: flex-start;
6
- align-items: flex-start;
6
+ align-items: stretch;
7
7
  padding: 1rem;
8
8
  font-size: 1rem;
9
9
  line-height: 1.6;
@@ -18,8 +18,7 @@ h6 {
18
18
  border-bottom: 4px solid transparent;
19
19
  }
20
20
  .navbar__link--active {
21
- /* border-bottom: 4px solid var(--color-nextflow-600); */
22
- border-bottom: 4px solid var(--color-blu-600);
21
+ border-bottom: 4px solid var(--color-nextflow-500);
23
22
  padding-bottom: 0.1rem;
24
23
  }
25
24
 
@@ -256,6 +255,7 @@ main prose styles
256
255
 
257
256
  /* Tables */
258
257
  table {
258
+ display: table;
259
259
  width: 100%;
260
260
  margin-top: 2rem;
261
261
  margin-bottom: 2rem;
@@ -343,7 +343,7 @@ main prose styles
343
343
  /* Dark mode overrides */
344
344
  [data-theme='dark'] .prose__wrapper {
345
345
  code:not(pre code) {
346
- border-color: var(--color-gray-200);
346
+ border-color: var(--color-gray-700);
347
347
  }
348
348
 
349
349
  .admonition code:not(pre code) {
@@ -7,11 +7,11 @@ export default function BlogLayout(props) {
7
7
  const hasSidebar = sidebar && sidebar.items.length > 0;
8
8
  return (
9
9
  <Layout {...layoutProps}>
10
- <div className="container">
10
+ <div className="container container--fluid margin-vert--lg">
11
11
  <div className="row">
12
12
  <BlogSidebar sidebar={sidebar} />
13
13
  <main
14
- className={clsx('w-full prose__wrapper col max-w-3xl mx-auto', {
14
+ className={clsx('w-full prose__wrapper col px-4 md:px-8', {
15
15
  'col--7': hasSidebar,
16
16
  'col--9 col--offset-1': !hasSidebar,
17
17
  })}>
@@ -1,4 +1,9 @@
1
1
  import React from 'react';
2
+ import clsx from 'clsx';
2
3
  export default function BlogPostItemContainer({children, className}) {
3
- return <article className={className}>{children}</article>;
4
+ return (
5
+ <article className={clsx('max-w-3xl mx-auto', className)}>
6
+ {children}
7
+ </article>
8
+ );
4
9
  }
@@ -28,7 +28,7 @@ function BlogSidebarDesktop({sidebar}) {
28
28
  const pathMatch = location.pathname.match(
29
29
  /\/changelog\/(?:tags\/)?([^\/]+)(?:\/v[\d.]+.*)?/,
30
30
  );
31
- const product = pathMatch ? pathMatch[1] : null;
31
+ const product = pathMatch?.[1] ?? null;
32
32
  // Map product names to their correct documentation paths
33
33
  const getProductPath = (product) => {
34
34
  if (!product || product === 'tags' || product === 'page') return '/';
@@ -39,9 +39,13 @@ function BlogSidebarDesktop({sidebar}) {
39
39
  return mapping[product] || `/${product}`;
40
40
  };
41
41
  // Filter the sidebar for just this product
42
- const filteredItems = product
43
- ? items.filter((item) => item.permalink.includes(`/changelog/${product}/`))
44
- : items;
42
+ const productPath = getProductPath(product);
43
+ const filteredItems =
44
+ product && productPath !== '/'
45
+ ? items.filter((item) =>
46
+ item.permalink.includes(`/changelog/${product}/`),
47
+ )
48
+ : items;
45
49
  return (
46
50
  <aside
47
51
  className={`${styles.sidebarWrapper} col col--3 border-r border-black/20 dark:border-white/20`}>
@@ -9,6 +9,7 @@ import {
9
9
  splitNavbarItems,
10
10
  useNavbarMobileSidebar,
11
11
  } from '@docusaurus/theme-common/internal';
12
+ import {useLocation} from '@docusaurus/router';
12
13
  import NavbarItem from '@theme/NavbarItem';
13
14
  import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';
14
15
  import SearchBar from '@theme/SearchBar';
@@ -66,6 +67,7 @@ function NavbarContentLayout({left, right, isMenuOpen}) {
66
67
  }
67
68
  export default function NavbarContent() {
68
69
  const mobileSidebar = useNavbarMobileSidebar();
70
+ const {pathname} = useLocation();
69
71
  const items = useNavbarItems();
70
72
  const [leftItems, rightItems] = splitNavbarItems(items);
71
73
  const searchBarItem = items.find((item) => item.type === 'search');
@@ -110,13 +112,19 @@ export default function NavbarContent() {
110
112
  </div>
111
113
  <div className="mr-2">
112
114
  <a
113
- className="navbar__link ml-8 font-normal"
115
+ className={clsx(
116
+ 'navbar__link ml-8 font-normal',
117
+ pathname.startsWith('/platform-api/') && 'navbar__link--active',
118
+ )}
114
119
  href="/platform-api/"
115
120
  aria-label="Platform API">
116
121
  Platform API
117
122
  </a>
118
123
  <a
119
- className="navbar__link ml-8 font-normal"
124
+ className={clsx(
125
+ 'navbar__link ml-8 font-normal',
126
+ pathname.startsWith('/changelog/') && 'navbar__link--active',
127
+ )}
120
128
  href="/changelog/"
121
129
  aria-label="Changelog">
122
130
  Changelog
@@ -50,28 +50,20 @@ export default function NavbarMobilePrimaryMenu() {
50
50
  Resources
51
51
  </h3>
52
52
 
53
- <div className="mb-3">
54
- <li className="mb-3">
55
- <Link
56
- className="menu__link"
57
- to={'/changelog'}
58
- aria-label="Changelog">
59
- Changelog
60
- </Link>
61
- </li>
53
+ <li className="mb-3">
62
54
  <Link
63
55
  className="menu__link"
64
- to={'/platform-cloud/seqera-ai'}
65
- aria-label="Seqera AI">
66
- Seqera AI
56
+ to={'/platform-api/'}
57
+ aria-label="Platform API">
58
+ Platform API
67
59
  </Link>
68
- </div>
60
+ </li>
69
61
  <li className="mb-3">
70
62
  <Link
71
63
  className="menu__link"
72
- to={'/platform-cloud/seqera-mcp'}
73
- aria-label="Seqera MCP">
74
- Seqera MCP
64
+ to={'/changelog/'}
65
+ aria-label="Changelog">
66
+ Changelog
75
67
  </Link>
76
68
  </li>
77
69
  </div>
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
+ import clsx from 'clsx';
2
3
  import Link from '@docusaurus/Link';
3
4
  import useBaseUrl from '@docusaurus/useBaseUrl';
4
5
  import isInternalUrl from '@docusaurus/isInternalUrl';
5
6
  import {isRegexpStringMatch} from '@docusaurus/theme-common';
7
+ import {useLocation} from '@docusaurus/router';
6
8
  import IconExternalLink from '@theme/Icon/ExternalLink';
7
9
  export default function NavbarNavLink({
8
10
  activeBasePath,
@@ -21,6 +23,7 @@ export default function NavbarNavLink({
21
23
  const activeBaseUrl = useBaseUrl(activeBasePath);
22
24
  const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
23
25
  const isExternalLink = label && href && !isInternalUrl(href);
26
+ const {pathname} = useLocation();
24
27
  // Link content is set through html XOR label
25
28
  const linkContentProps = html
26
29
  ? {dangerouslySetInnerHTML: {__html: html}}
@@ -37,10 +40,14 @@ export default function NavbarNavLink({
37
40
  ),
38
41
  };
39
42
  if (href) {
43
+ const isActiveHref = !isExternalLink && pathname.startsWith(href);
40
44
  return (
41
45
  <Link
42
46
  href={prependBaseUrlToHref ? normalizedHref : href}
43
47
  {...props}
48
+ {...(isActiveHref && {
49
+ className: clsx(props.className, props.activeClassName),
50
+ })}
44
51
  {...linkContentProps}
45
52
  />
46
53
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seqera/docusaurus-theme-seqera",
3
- "version": "1.0.30-next.99",
3
+ "version": "1.0.31-next.102",
4
4
  "description": "Seqera docs theme for Docusaurus",
5
5
  "author": "Seqera docs team <education@seqera.io>",
6
6
  "license": "Apache-2.0",
@@ -3,7 +3,7 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  justify-content: flex-start;
6
- align-items: flex-start;
6
+ align-items: stretch;
7
7
  padding: 1rem;
8
8
  font-size: 1rem;
9
9
  line-height: 1.6;
@@ -18,8 +18,7 @@ h6 {
18
18
  border-bottom: 4px solid transparent;
19
19
  }
20
20
  .navbar__link--active {
21
- /* border-bottom: 4px solid var(--color-nextflow-600); */
22
- border-bottom: 4px solid var(--color-blu-600);
21
+ border-bottom: 4px solid var(--color-nextflow-500);
23
22
  padding-bottom: 0.1rem;
24
23
  }
25
24
 
@@ -256,6 +255,7 @@ main prose styles
256
255
 
257
256
  /* Tables */
258
257
  table {
258
+ display: table;
259
259
  width: 100%;
260
260
  margin-top: 2rem;
261
261
  margin-bottom: 2rem;
@@ -343,7 +343,7 @@ main prose styles
343
343
  /* Dark mode overrides */
344
344
  [data-theme='dark'] .prose__wrapper {
345
345
  code:not(pre code) {
346
- border-color: var(--color-gray-200);
346
+ border-color: var(--color-gray-700);
347
347
  }
348
348
 
349
349
  .admonition code:not(pre code) {
@@ -13,11 +13,11 @@ export default function BlogLayout(props: Props): ReactNode {
13
13
 
14
14
  return (
15
15
  <Layout {...layoutProps}>
16
- <div className="container">
16
+ <div className="container container--fluid margin-vert--lg">
17
17
  <div className="row">
18
18
  <BlogSidebar sidebar={sidebar} />
19
19
  <main
20
- className={clsx('w-full prose__wrapper col max-w-3xl mx-auto', {
20
+ className={clsx('w-full prose__wrapper col px-4 md:px-8', {
21
21
  'col--7': hasSidebar,
22
22
  'col--9 col--offset-1': !hasSidebar,
23
23
  })}>
@@ -1,11 +1,12 @@
1
1
 
2
2
 
3
3
  import React, {type ReactNode} from 'react';
4
+ import clsx from 'clsx';
4
5
  import type {Props} from '@theme/BlogPostItem/Container';
5
6
 
6
7
  export default function BlogPostItemContainer({
7
8
  children,
8
9
  className,
9
10
  }: Props): ReactNode {
10
- return <article className={className}>{children}</article>;
11
+ return <article className={clsx('max-w-3xl mx-auto', className)}>{children}</article>;
11
12
  }
@@ -31,7 +31,7 @@ function BlogSidebarDesktop({sidebar}: Props) {
31
31
  // Figure out if we're looking at a product tag or changelog entry
32
32
  const location = useLocation();
33
33
  const pathMatch = location.pathname.match(/\/changelog\/(?:tags\/)?([^\/]+)(?:\/v[\d.]+.*)?/);
34
- const product = pathMatch ? pathMatch[1] : null;
34
+ const product = pathMatch?.[1] ?? null;
35
35
 
36
36
  // Map product names to their correct documentation paths
37
37
  const getProductPath = (product: string | null): string => {
@@ -44,8 +44,9 @@ function BlogSidebarDesktop({sidebar}: Props) {
44
44
  };
45
45
 
46
46
  // Filter the sidebar for just this product
47
- const filteredItems = product
48
- ? items.filter(item => item.permalink.includes(`/changelog/${product}/`))
47
+ const productPath = getProductPath(product);
48
+ const filteredItems = product && productPath !== '/'
49
+ ? items.filter(item => item.permalink.includes(`/changelog/${product}/`))
49
50
  : items;
50
51
 
51
52
 
@@ -9,6 +9,7 @@ import {
9
9
  splitNavbarItems,
10
10
  useNavbarMobileSidebar,
11
11
  } from '@docusaurus/theme-common/internal';
12
+ import {useLocation} from '@docusaurus/router';
12
13
  import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem';
13
14
  import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';
14
15
  import SearchBar from '@theme/SearchBar';
@@ -79,6 +80,7 @@ function NavbarContentLayout({
79
80
 
80
81
  export default function NavbarContent(): ReactNode {
81
82
  const mobileSidebar = useNavbarMobileSidebar();
83
+ const {pathname} = useLocation();
82
84
 
83
85
  const items = useNavbarItems();
84
86
  const [leftItems, rightItems] = splitNavbarItems(items);
@@ -126,13 +128,13 @@ export default function NavbarContent(): ReactNode {
126
128
  </div>
127
129
  <div className="mr-2">
128
130
  <a
129
- className="navbar__link ml-8 font-normal"
131
+ className={clsx('navbar__link ml-8 font-normal', pathname.startsWith('/platform-api/') && 'navbar__link--active')}
130
132
  href="/platform-api/"
131
133
  aria-label="Platform API">
132
134
  Platform API
133
135
  </a>
134
136
  <a
135
- className="navbar__link ml-8 font-normal"
137
+ className={clsx('navbar__link ml-8 font-normal', pathname.startsWith('/changelog/') && 'navbar__link--active')}
136
138
  href="/changelog/"
137
139
  aria-label="Changelog">
138
140
  Changelog
@@ -54,28 +54,20 @@ export default function NavbarMobilePrimaryMenu(): ReactNode {
54
54
  Resources
55
55
  </h3>
56
56
 
57
- <div className="mb-3">
58
- <li className="mb-3">
59
- <Link
60
- className="menu__link"
61
- to={'/changelog'}
62
- aria-label="Changelog">
63
- Changelog
64
- </Link>
65
- </li>
57
+ <li className="mb-3">
66
58
  <Link
67
59
  className="menu__link"
68
- to={'/platform-cloud/seqera-ai'}
69
- aria-label="Seqera AI">
70
- Seqera AI
60
+ to={'/platform-api/'}
61
+ aria-label="Platform API">
62
+ Platform API
71
63
  </Link>
72
- </div>
64
+ </li>
73
65
  <li className="mb-3">
74
66
  <Link
75
67
  className="menu__link"
76
- to={'/platform-cloud/seqera-mcp'}
77
- aria-label="Seqera MCP">
78
- Seqera MCP
68
+ to={'/changelog/'}
69
+ aria-label="Changelog">
70
+ Changelog
79
71
  </Link>
80
72
  </li>
81
73
  </div>
@@ -1,10 +1,12 @@
1
1
 
2
2
 
3
3
  import React, {type ReactNode} from 'react';
4
+ import clsx from 'clsx';
4
5
  import Link from '@docusaurus/Link';
5
6
  import useBaseUrl from '@docusaurus/useBaseUrl';
6
7
  import isInternalUrl from '@docusaurus/isInternalUrl';
7
8
  import {isRegexpStringMatch} from '@docusaurus/theme-common';
9
+ import {useLocation} from '@docusaurus/router';
8
10
  import IconExternalLink from '@theme/Icon/ExternalLink';
9
11
  import type {Props} from '@theme/NavbarItem/NavbarNavLink';
10
12
 
@@ -25,6 +27,7 @@ export default function NavbarNavLink({
25
27
  const activeBaseUrl = useBaseUrl(activeBasePath);
26
28
  const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
27
29
  const isExternalLink = label && href && !isInternalUrl(href);
30
+ const {pathname} = useLocation();
28
31
 
29
32
  // Link content is set through html XOR label
30
33
  const linkContentProps = html
@@ -43,10 +46,14 @@ export default function NavbarNavLink({
43
46
  };
44
47
 
45
48
  if (href) {
49
+ const isActiveHref = !isExternalLink && pathname.startsWith(href);
46
50
  return (
47
51
  <Link
48
52
  href={prependBaseUrlToHref ? normalizedHref : href}
49
53
  {...props}
54
+ {...(isActiveHref && {
55
+ className: clsx(props.className, props.activeClassName),
56
+ })}
50
57
  {...linkContentProps}
51
58
  />
52
59
  );
@@ -1,12 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * Swizzled from docusaurus-theme-search-typesense SearchBar.
8
- * Adds full hierarchy breadcrumbs and product labels to the path shown
9
- * under each modal result. Product routes are configured via
10
- * themeConfig.typesense.productRoutes.
11
- */
12
- export default function SearchBar(): JSX.Element;
@@ -1,239 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * Swizzled from docusaurus-theme-search-typesense SearchBar.
8
- * Adds full hierarchy breadcrumbs and product labels to the path shown
9
- * under each modal result. Product routes are configured via
10
- * themeConfig.typesense.productRoutes.
11
- */
12
- import React, {useState, useRef, useCallback, useMemo} from 'react';
13
- // @ts-ignore
14
- import {createPortal} from 'react-dom';
15
- // @ts-ignore
16
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
17
- // @ts-ignore
18
- import {useHistory} from '@docusaurus/router';
19
- // @ts-ignore
20
- import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
21
- // @ts-ignore
22
- import Link from '@docusaurus/Link';
23
- // @ts-ignore
24
- import {isRegexpStringMatch} from '@docusaurus/theme-common';
25
- import {
26
- DocSearchButton,
27
- useDocSearchKeyboardEvents,
28
- } from 'typesense-docsearch-react';
29
- import {useTypesenseContextualFilters} from 'docusaurus-theme-search-typesense/client';
30
- // @ts-ignore
31
- import Translate from '@docusaurus/Translate';
32
- // @ts-ignore
33
- import translations from '@theme/SearchTranslations';
34
- import {DocsPreferredVersionContextProvider} from '@docusaurus/plugin-content-docs/lib/client/index.js';
35
- let DocSearchModal = null;
36
- function Hit({hit, children}) {
37
- return <Link to={hit.url}>{children}</Link>;
38
- }
39
- function ResultsFooter({state, onClose}) {
40
- const {
41
- siteConfig: {baseUrl},
42
- } = useDocusaurusContext();
43
- return (
44
- <Link
45
- to={`${baseUrl}search?q=${encodeURIComponent(state.query)}`}
46
- onClick={onClose}>
47
- <Translate
48
- id="theme.SearchBar.seeAll"
49
- values={{count: state.context.nbHits}}>
50
- {'See all {count} results'}
51
- </Translate>
52
- </Link>
53
- );
54
- }
55
- /**
56
- * Resolve a product label from a URL pathname using the configured productRoutes.
57
- */
58
- function getProductLabel(pathname, productRoutes) {
59
- const match = productRoutes.find(([prefix]) => pathname.startsWith(prefix));
60
- return match ? match[1] : null;
61
- }
62
- /**
63
- * Build a breadcrumb string from all ancestor hierarchy levels of a hit.
64
- * For lvl2+ and content hits this replaces the single-level hierarchy.lvl1
65
- * path with the full parent chain (e.g. "Nextflow › Getting Started › Installation").
66
- *
67
- * When productRoutes are provided, the first level (lvl0, typically "Documentation")
68
- * is replaced with the product name derived from the hit URL.
69
- */
70
- function buildBreadcrumb(item, productRoutes) {
71
- const {type} = item;
72
- const maxLevel =
73
- type === 'content' ? 7 : parseInt(type.replace('lvl', ''), 10);
74
- const parts = [];
75
- for (let i = 0; i < maxLevel; i++) {
76
- const val = item[`hierarchy.lvl${i}`];
77
- if (val) parts.push(val);
78
- }
79
- // Replace lvl0 (typically "Documentation") with the product label
80
- if (parts.length > 0 && productRoutes.length > 0) {
81
- try {
82
- const pathname = new URL(item.url).pathname;
83
- const product = getProductLabel(pathname, productRoutes);
84
- if (product) {
85
- parts[0] = product;
86
- }
87
- } catch {
88
- // URL parsing failed — keep the original lvl0 value
89
- }
90
- }
91
- return parts.length > 1 ? parts.join(' › ') : null;
92
- }
93
- function DocSearch({
94
- contextualSearch,
95
- externalUrlRegex,
96
- productRoutes = [],
97
- ...props
98
- }) {
99
- const contextualSearchFacetFilters = useTypesenseContextualFilters();
100
- const configFacetFilters = props.typesenseSearchParameters?.filter_by ?? '';
101
- const facetFilters = contextualSearch
102
- ? [contextualSearchFacetFilters, configFacetFilters]
103
- .filter((e) => e)
104
- .join(' && ')
105
- : configFacetFilters;
106
- const typesenseSearchParameters = {
107
- filter_by: facetFilters,
108
- ...props.typesenseSearchParameters,
109
- };
110
- const {withBaseUrl} = useBaseUrlUtils();
111
- const history = useHistory();
112
- const searchContainer = useRef(null);
113
- const searchButtonRef = useRef(null);
114
- const [isOpen, setIsOpen] = useState(false);
115
- const [initialQuery, setInitialQuery] = useState(undefined);
116
- const importDocSearchModalIfNeeded = useCallback(() => {
117
- if (DocSearchModal) {
118
- return Promise.resolve();
119
- }
120
- return Promise.all([
121
- // @ts-ignore
122
- import('typesense-docsearch-react/modal'),
123
- // @ts-ignore
124
- import('typesense-docsearch-react/style'),
125
- // @ts-ignore
126
- import('./styles.css'),
127
- ]).then(([{DocSearchModal: Modal}]) => {
128
- DocSearchModal = Modal;
129
- });
130
- }, []);
131
- const onOpen = useCallback(() => {
132
- importDocSearchModalIfNeeded().then(() => {
133
- searchContainer.current = document.createElement('div');
134
- document.body.insertBefore(
135
- searchContainer.current,
136
- document.body.firstChild,
137
- );
138
- setIsOpen(true);
139
- });
140
- }, [importDocSearchModalIfNeeded]);
141
- const onClose = useCallback(() => {
142
- setIsOpen(false);
143
- searchContainer.current?.remove();
144
- }, []);
145
- const onInput = useCallback(
146
- (event) => {
147
- importDocSearchModalIfNeeded().then(() => {
148
- setIsOpen(true);
149
- setInitialQuery(event.key);
150
- });
151
- },
152
- [importDocSearchModalIfNeeded],
153
- );
154
- const navigator = useRef({
155
- navigate({itemUrl}) {
156
- if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
157
- window.location.href = itemUrl;
158
- } else {
159
- history.push(itemUrl);
160
- }
161
- },
162
- }).current;
163
- const transformItems = useRef((items) =>
164
- items.map((item) => {
165
- // Transform absolute URL to relative.
166
- const withRelativeUrl = isRegexpStringMatch(externalUrlRegex, item.url)
167
- ? item
168
- : {
169
- ...item,
170
- url: withBaseUrl(
171
- `${new URL(item.url).pathname}${new URL(item.url).hash}`,
172
- ),
173
- };
174
- // Overwrite hierarchy.lvl1 with the full ancestor breadcrumb so the
175
- // DocSearch-Hit-path slot shows the complete path for lvl2+ results.
176
- const breadcrumb = buildBreadcrumb(withRelativeUrl, productRoutes);
177
- if (!breadcrumb) return withRelativeUrl;
178
- return {
179
- ...withRelativeUrl,
180
- 'hierarchy.lvl1': breadcrumb,
181
- };
182
- }),
183
- ).current;
184
- const resultsFooterComponent = useMemo(
185
- () =>
186
- // eslint-disable-next-line react/no-unstable-nested-components
187
- (footerProps) =>
188
- <ResultsFooter {...footerProps} onClose={onClose} />,
189
- [onClose],
190
- );
191
- useDocSearchKeyboardEvents({
192
- isOpen,
193
- onOpen,
194
- onClose,
195
- onInput,
196
- searchButtonRef,
197
- });
198
- return (
199
- <>
200
- <DocSearchButton
201
- onTouchStart={importDocSearchModalIfNeeded}
202
- onFocus={importDocSearchModalIfNeeded}
203
- onMouseOver={importDocSearchModalIfNeeded}
204
- onClick={onOpen}
205
- ref={searchButtonRef}
206
- translations={translations.button}
207
- />
208
- {isOpen &&
209
- DocSearchModal &&
210
- searchContainer.current &&
211
- createPortal(
212
- <DocSearchModal
213
- onClose={onClose}
214
- initialScrollY={window.scrollY}
215
- initialQuery={initialQuery}
216
- navigator={navigator}
217
- transformItems={transformItems}
218
- hitComponent={Hit}
219
- {...(props.searchPagePath && {resultsFooterComponent})}
220
- {...props}
221
- typesenseSearchParameters={typesenseSearchParameters}
222
- typesenseServerConfig={props.typesenseServerConfig}
223
- typesenseCollectionName={props.typesenseCollectionName}
224
- placeholder={translations.placeholder}
225
- translations={translations.modal}
226
- />,
227
- searchContainer.current,
228
- )}
229
- </>
230
- );
231
- }
232
- export default function SearchBar() {
233
- const {siteConfig} = useDocusaurusContext();
234
- return (
235
- <DocsPreferredVersionContextProvider>
236
- <DocSearch {...siteConfig.themeConfig.typesense} />
237
- </DocsPreferredVersionContextProvider>
238
- );
239
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
- :root {
9
- --docsearch-primary-color: var(--ifm-color-primary);
10
- --docsearch-text-color: var(--ifm-font-color-base);
11
- }
12
-
13
- .DocSearch-Button {
14
- margin: 0;
15
- transition: all var(--ifm-transition-fast)
16
- var(--ifm-transition-timing-default);
17
- }
18
-
19
- .DocSearch-Container {
20
- z-index: calc(var(--ifm-z-index-fixed) + 1);
21
- }
@@ -1,314 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * Swizzled from docusaurus-theme-search-typesense SearchBar.
8
- * Adds full hierarchy breadcrumbs and product labels to the path shown
9
- * under each modal result. Product routes are configured via
10
- * themeConfig.typesense.productRoutes.
11
- */
12
-
13
- import React, {useState, useRef, useCallback, useMemo} from 'react';
14
- // @ts-ignore
15
- import {createPortal} from 'react-dom';
16
- // @ts-ignore
17
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
18
- // @ts-ignore
19
- import {useHistory} from '@docusaurus/router';
20
- // @ts-ignore
21
- import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
22
- // @ts-ignore
23
- import Link from '@docusaurus/Link';
24
- // @ts-ignore
25
- import {isRegexpStringMatch} from '@docusaurus/theme-common';
26
- import {
27
- DocSearchButton,
28
- useDocSearchKeyboardEvents,
29
- } from 'typesense-docsearch-react';
30
- import {useTypesenseContextualFilters} from 'docusaurus-theme-search-typesense/client';
31
- // @ts-ignore
32
- import Translate from '@docusaurus/Translate';
33
- // @ts-ignore
34
- import translations from '@theme/SearchTranslations';
35
- import {DocsPreferredVersionContextProvider} from '@docusaurus/plugin-content-docs/lib/client/index.js';
36
-
37
- import type {
38
- DocSearchModal as DocSearchModalType,
39
- DocSearchModalProps,
40
- } from 'typesense-docsearch-react';
41
- import type {
42
- InternalDocSearchHit,
43
- StoredDocSearchHit,
44
- } from 'typesense-docsearch-react/dist/esm/types';
45
-
46
- type DocSearchProps = Omit<
47
- DocSearchModalProps,
48
- 'onClose' | 'initialScrollY'
49
- > & {
50
- contextualSearch?: string;
51
- externalUrlRegex?: string;
52
- searchPagePath: boolean | string;
53
- productRoutes?: [string, string, string | null, string | null][];
54
- };
55
-
56
- let DocSearchModal: typeof DocSearchModalType | null = null;
57
-
58
- function Hit({
59
- hit,
60
- children,
61
- }: {
62
- hit: InternalDocSearchHit | StoredDocSearchHit;
63
- children: React.ReactNode;
64
- }) {
65
- return <Link to={hit.url}>{children}</Link>;
66
- }
67
-
68
- type ResultsFooterProps = {
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- state: any;
71
- onClose: () => void;
72
- };
73
-
74
- function ResultsFooter({state, onClose}: ResultsFooterProps) {
75
- const {
76
- siteConfig: {baseUrl},
77
- } = useDocusaurusContext();
78
-
79
- return (
80
- <Link
81
- to={`${baseUrl}search?q=${encodeURIComponent(state.query)}`}
82
- onClick={onClose}>
83
- <Translate
84
- id="theme.SearchBar.seeAll"
85
- values={{count: state.context.nbHits}}>
86
- {'See all {count} results'}
87
- </Translate>
88
- </Link>
89
- );
90
- }
91
-
92
- /**
93
- * Resolve a product label from a URL pathname using the configured productRoutes.
94
- */
95
- function getProductLabel(
96
- pathname: string,
97
- productRoutes: [string, string, string | null, string | null][],
98
- ): string | null {
99
- const match = productRoutes.find(([prefix]) =>
100
- pathname.startsWith(prefix),
101
- );
102
- return match ? match[1] : null;
103
- }
104
-
105
- /**
106
- * Build a breadcrumb string from all ancestor hierarchy levels of a hit.
107
- * For lvl2+ and content hits this replaces the single-level hierarchy.lvl1
108
- * path with the full parent chain (e.g. "Nextflow › Getting Started › Installation").
109
- *
110
- * When productRoutes are provided, the first level (lvl0, typically "Documentation")
111
- * is replaced with the product name derived from the hit URL.
112
- */
113
- function buildBreadcrumb(
114
- item: InternalDocSearchHit | StoredDocSearchHit,
115
- productRoutes: [string, string, string | null, string | null][],
116
- ): string | null {
117
- const {type} = item;
118
- const maxLevel =
119
- type === 'content' ? 7 : parseInt(type.replace('lvl', ''), 10);
120
-
121
- const parts: string[] = [];
122
- for (let i = 0; i < maxLevel; i++) {
123
- const val = (item as unknown as Record<string, string | null>)[
124
- `hierarchy.lvl${i}`
125
- ];
126
- if (val) parts.push(val);
127
- }
128
-
129
- // Replace lvl0 (typically "Documentation") with the product label
130
- if (parts.length > 0 && productRoutes.length > 0) {
131
- try {
132
- const pathname = new URL(item.url).pathname;
133
- const product = getProductLabel(pathname, productRoutes);
134
- if (product) {
135
- parts[0] = product;
136
- }
137
- } catch {
138
- // URL parsing failed — keep the original lvl0 value
139
- }
140
- }
141
-
142
- return parts.length > 1 ? parts.join(' › ') : null;
143
- }
144
-
145
- function DocSearch({
146
- contextualSearch,
147
- externalUrlRegex,
148
- productRoutes = [],
149
- ...props
150
- }: DocSearchProps) {
151
- const contextualSearchFacetFilters =
152
- useTypesenseContextualFilters() as string;
153
- const configFacetFilters: string =
154
- props.typesenseSearchParameters?.filter_by ?? '';
155
- const facetFilters = contextualSearch
156
- ? [contextualSearchFacetFilters, configFacetFilters]
157
- .filter((e) => e)
158
- .join(' && ')
159
- : configFacetFilters;
160
-
161
- const typesenseSearchParameters = {
162
- filter_by: facetFilters,
163
- ...props.typesenseSearchParameters,
164
- };
165
-
166
- const {withBaseUrl} = useBaseUrlUtils();
167
- const history = useHistory();
168
- const searchContainer = useRef<HTMLDivElement | null>(null);
169
- const searchButtonRef = useRef<HTMLButtonElement>(null);
170
- const [isOpen, setIsOpen] = useState(false);
171
- const [initialQuery, setInitialQuery] = useState<string | undefined>(
172
- undefined,
173
- );
174
-
175
- const importDocSearchModalIfNeeded = useCallback(() => {
176
- if (DocSearchModal) {
177
- return Promise.resolve();
178
- }
179
- return Promise.all([
180
- // @ts-ignore
181
- import('typesense-docsearch-react/modal') as Promise<
182
- typeof import('typesense-docsearch-react')
183
- >,
184
- // @ts-ignore
185
- import('typesense-docsearch-react/style'),
186
- // @ts-ignore
187
- import('./styles.css'),
188
- ]).then(([{DocSearchModal: Modal}]) => {
189
- DocSearchModal = Modal;
190
- });
191
- }, []);
192
-
193
- const onOpen = useCallback(() => {
194
- importDocSearchModalIfNeeded().then(() => {
195
- searchContainer.current = document.createElement('div');
196
- document.body.insertBefore(
197
- searchContainer.current,
198
- document.body.firstChild,
199
- );
200
- setIsOpen(true);
201
- });
202
- }, [importDocSearchModalIfNeeded]);
203
-
204
- const onClose = useCallback(() => {
205
- setIsOpen(false);
206
- searchContainer.current?.remove();
207
- }, []);
208
-
209
- const onInput = useCallback(
210
- (event: KeyboardEvent) => {
211
- importDocSearchModalIfNeeded().then(() => {
212
- setIsOpen(true);
213
- setInitialQuery(event.key);
214
- });
215
- },
216
- [importDocSearchModalIfNeeded],
217
- );
218
-
219
- const navigator = useRef({
220
- navigate({itemUrl}: {itemUrl?: string}) {
221
- if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
222
- window.location.href = itemUrl!;
223
- } else {
224
- history.push(itemUrl!);
225
- }
226
- },
227
- }).current;
228
-
229
- const transformItems = useRef<DocSearchModalProps['transformItems']>(
230
- (items) =>
231
- items.map((item) => {
232
- // Transform absolute URL to relative.
233
- const withRelativeUrl = isRegexpStringMatch(externalUrlRegex, item.url)
234
- ? item
235
- : {
236
- ...item,
237
- url: withBaseUrl(
238
- `${new URL(item.url).pathname}${new URL(item.url).hash}`,
239
- ),
240
- };
241
-
242
- // Overwrite hierarchy.lvl1 with the full ancestor breadcrumb so the
243
- // DocSearch-Hit-path slot shows the complete path for lvl2+ results.
244
- const breadcrumb = buildBreadcrumb(withRelativeUrl, productRoutes);
245
- if (!breadcrumb) return withRelativeUrl;
246
-
247
- return {
248
- ...withRelativeUrl,
249
- 'hierarchy.lvl1': breadcrumb,
250
- };
251
- }),
252
- ).current;
253
-
254
- const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
255
- useMemo(
256
- () =>
257
- // eslint-disable-next-line react/no-unstable-nested-components
258
- (footerProps: Omit<ResultsFooterProps, 'onClose'>): JSX.Element => (
259
- <ResultsFooter {...footerProps} onClose={onClose} />
260
- ),
261
- [onClose],
262
- );
263
-
264
- useDocSearchKeyboardEvents({
265
- isOpen,
266
- onOpen,
267
- onClose,
268
- onInput,
269
- searchButtonRef,
270
- });
271
-
272
- return (
273
- <>
274
- <DocSearchButton
275
- onTouchStart={importDocSearchModalIfNeeded}
276
- onFocus={importDocSearchModalIfNeeded}
277
- onMouseOver={importDocSearchModalIfNeeded}
278
- onClick={onOpen}
279
- ref={searchButtonRef}
280
- translations={translations.button}
281
- />
282
- {isOpen &&
283
- DocSearchModal &&
284
- searchContainer.current &&
285
- createPortal(
286
- <DocSearchModal
287
- onClose={onClose}
288
- initialScrollY={window.scrollY}
289
- initialQuery={initialQuery}
290
- navigator={navigator}
291
- transformItems={transformItems}
292
- hitComponent={Hit}
293
- {...(props.searchPagePath && {resultsFooterComponent})}
294
- {...props}
295
- typesenseSearchParameters={typesenseSearchParameters}
296
- typesenseServerConfig={props.typesenseServerConfig}
297
- typesenseCollectionName={props.typesenseCollectionName}
298
- placeholder={translations.placeholder}
299
- translations={translations.modal}
300
- />,
301
- searchContainer.current,
302
- )}
303
- </>
304
- );
305
- }
306
-
307
- export default function SearchBar(): JSX.Element {
308
- const {siteConfig} = useDocusaurusContext();
309
- return (
310
- <DocsPreferredVersionContextProvider>
311
- <DocSearch {...(siteConfig.themeConfig.typesense as DocSearchProps)} />
312
- </DocsPreferredVersionContextProvider>
313
- );
314
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
- :root {
9
- --docsearch-primary-color: var(--ifm-color-primary);
10
- --docsearch-text-color: var(--ifm-font-color-base);
11
- }
12
-
13
- .DocSearch-Button {
14
- margin: 0;
15
- transition: all var(--ifm-transition-fast)
16
- var(--ifm-transition-timing-default);
17
- }
18
-
19
- .DocSearch-Container {
20
- z-index: calc(var(--ifm-z-index-fixed) + 1);
21
- }