Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
label: 'Labs',
className: 'navbar__item--desktop-only',
},
{
type: 'custom-searchBar',
position: 'right',
},

{
href: 'https://threefold.io',
label: 'ThreeFold.io',
Expand All @@ -151,6 +148,10 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
},
],
},
// Enable search functionality
search: {
// This will be handled by our custom SearchBar component
},
footer: {
style: 'dark',
links: [
Expand Down
2 changes: 1 addition & 1 deletion farmers/docs/3node_buying/order_a_node.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ sidebar_position: 1

The ThreeFold V4 nodes are available now for order directly on our official partners' websites.

- [HostService](https://hostservice.nl/en/) (Europe, Asia, Africa, Australia)
- [YourData Network](https://yourdata.network/) (Europe, Asia, Africa, Australia)
- [Duck Farm Data](https://duckfarmdata.com) (America)

> Make sure to input the correct [farm ID as explained here](./create_a_farm)
96 changes: 50 additions & 46 deletions labs/docs/documentation/farmers/3node_building/6_boot_3node.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,31 @@ Ignore the TFTP Server settings.

**TFTP server setup on a debian machine such as Ubuntu or Raspberry Pi**

> apt-get update
>
> apt-get install tftpd-hpa
>
> cd /srv/tftp/
>
> wget http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/netboot.tar.gz
>
> wget http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/pxelinux.0
>
> wget https://bootstrap.grid.tf/krn/prod/YOUR_FARM_ID --no-check-certificate
>
> mv YOUR_FARM_ID ipxe-prod.lkrn
>
> tar -xvzf netboot.tar.gz
>
> rm version.info netboot.tar.gz
>
> rm pxelinux.cfg/default
>
> chmod 777 /srv/tftp/pxelinux.cfg (optional if next step fails)
>
> echo 'default ipxe-prod.lkrn' >> pxelinux.cfg/default
```
apt-get update

apt-get install tftpd-hpa

cd /srv/tftp/

wget http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/netboot.tar.gz

wget http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/pxelinux.0

wget https://bootstrap.grid.tf/krn/prod/YOUR_FARM_ID --no-check-certificate

mv YOUR_FARM_ID ipxe-prod.lkrn

tar -xvzf netboot.tar.gz

rm version.info netboot.tar.gz

rm pxelinux.cfg/default

chmod 777 /srv/tftp/pxelinux.cfg (optional if next step fails)

echo 'default ipxe-prod.lkrn' >> pxelinux.cfg/default
```


**TFTP Server on a OPNsense router**
Expand All @@ -102,29 +104,31 @@ The first step is to download the TFTP server plugin. Go to system>firmware>Stat

Turn on ssh for your router. In OPNsense it is System>Settings>Administration. Then check the Enable, root login, and password login. Hop over to Putty and connect to your router, normally 192.168.1.1. Login as root and input your password. Hit 8 to enter the shell.

In OPNsense the tftp directory is /usr/local/tftp

> cd /usr/local
>
> mkdir tftp
>
> cd ./tftp
>
> fetch http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/netboot.tar.gz
>
> fetch http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/pxelinux.0
>
> fetch https://bootstrap.grid.tf/krn/prod/YOUR_FARM_ID
>
> mv YOUR_FARM_ID ipxe-prod.lkrn
>
> tar -xvzf netboot.tar.gz
>
> rm version.info netboot.tar.gz
>
> rm pxelinux.cfg/default
>
> echo 'default ipxe-prod.lkrn' >> pxelinux.cfg/default
In OPNsense, the tftp directory is `/usr/local/tftp`.

```
cd /usr/local

mkdir tftp

cd ./tftp

fetch http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/netboot.tar.gz

fetch http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/pxelinux.0

fetch https://bootstrap.grid.tf/krn/prod/YOUR_FARM_ID

mv YOUR_FARM_ID ipxe-prod.lkrn

tar -xvzf netboot.tar.gz

rm version.info netboot.tar.gz

rm pxelinux.cfg/default

echo 'default ipxe-prod.lkrn' >> pxelinux.cfg/default
```

You can get out of shell by entering exit or just closing the window.

Expand Down
202 changes: 54 additions & 148 deletions src/components/SearchBar/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import React, { useState, useEffect, useRef, useCallback, useId } from 'react';
import { useHistory } from '@docusaurus/router';
import { translate } from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
Expand All @@ -9,14 +9,16 @@ const SearchBar = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [miniSearch, setMiniSearch] = useState(null);
const searchRef = useRef(null);
const resultsRef = useRef(null);
const modalRef = useRef(null);
const history = useHistory();
const { siteConfig } = useDocusaurusContext();

// Generate unique ID for this component instance
const baseId = useId();
const searchInputId = `${baseId}-search-input`;

// Initialize MiniSearch with the search index
useEffect(() => {
Expand Down Expand Up @@ -85,37 +87,16 @@ const SearchBar = () => {
history.push(result.url);
setQuery('');
setIsOpen(false);
setIsModalOpen(false);
searchRef.current?.blur();
};

// Handle mobile search icon click
const handleMobileSearchClick = () => {
setIsModalOpen(true);
// Focus on the modal input after a brief delay
setTimeout(() => {
const modalInput = modalRef.current?.querySelector('.search-input');
modalInput?.focus();
}, 100);
};

// Handle modal close
const handleModalClose = () => {
setIsModalOpen(false);
setQuery('');
setResults([]);
setIsOpen(false);
};
// Removed mobile-specific handler - using single responsive component

// Handle keyboard navigation
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
if (isModalOpen) {
handleModalClose();
} else {
setIsOpen(false);
searchRef.current?.blur();
}
setIsOpen(false);
searchRef.current?.blur();
}
};

Expand All @@ -133,134 +114,59 @@ const SearchBar = () => {
}, []);

return (
<>
{/* Desktop Search Bar */}
<div className="search-bar-container search-bar-desktop" ref={searchRef}>
<div className="search-input-wrapper">
<input
type="text"
id="search-input-desktop"
name="search"
className="search-input"
placeholder={translate({
id: 'theme.SearchBar.label',
message: 'Search documentation...',
description: 'The ARIA label and placeholder for search button'
})}
value={query}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={() => query && setIsOpen(true)}
disabled={isLoading}
/>
<div className="search-icon">
{isLoading ? (
<div className="search-loading">⏳</div>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
)}
</div>
<div className="search-bar-container" ref={searchRef}>
<div className="search-input-wrapper">
<input
type="text"
id={searchInputId}
name="search"
className="search-input"
placeholder={translate({
id: 'theme.SearchBar.label',
message: 'Search',
description: 'The ARIA label and placeholder for search button'
})}
value={query}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={() => query && setIsOpen(true)}
disabled={isLoading}
/>
<div className="search-icon">
{isLoading ? (
<div className="search-loading">⏳</div>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
)}
</div>

{isOpen && (
<div className="search-results" ref={resultsRef}>
{results.length > 0 ? (
<div className="search-results-list">
{results.map((result, index) => (
<div
key={result.id}
className="search-result-item"
onClick={() => handleResultClick(result)}
>
<div className="search-result-title">{result.title}</div>
<div className="search-result-category">{result.category}</div>
<div className="search-result-url">{result.url}</div>
</div>
))}
</div>
) : query.trim() && !isLoading ? (
<div className="search-no-results">
No results found for "{query}"
</div>
) : null}
</div>
)}
</div>

{/* Mobile Search Icon */}
<button
className="search-bar-mobile"
onClick={handleMobileSearchClick}
aria-label="Search"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
</button>

{/* Mobile Search Modal */}
{isModalOpen && (
<div className="search-modal-overlay" onClick={handleModalClose}>
<div className="search-modal" ref={modalRef} onClick={(e) => e.stopPropagation()}>
<div className="search-modal-header">
<div className="search-input-wrapper">
<input
type="text"
id="search-input-mobile"
name="search"
className="search-input"
placeholder="Search documentation..."
value={query}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
autoFocus
/>
<div className="search-icon">
{isLoading ? (
<div className="search-loading">⏳</div>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
)}
{isOpen && (
<div className="search-results" ref={resultsRef}>
{results.length > 0 ? (
<div className="search-results-list">
{results.map((result, index) => (
<div
key={result.id}
className="search-result-item"
onClick={() => handleResultClick(result)}
>
<div className="search-result-title">{result.title}</div>
<div className="search-result-category">{result.category}</div>
<div className="search-result-url">{result.url}</div>
</div>
</div>
<button className="search-modal-close" onClick={handleModalClose}>
</button>
))}
</div>

{results.length > 0 ? (
<div className="search-results search-results-modal">
<div className="search-results-list">
{results.map((result, index) => (
<div
key={result.id}
className="search-result-item"
onClick={() => handleResultClick(result)}
>
<div className="search-result-title">{result.title}</div>
<div className="search-result-category">{result.category}</div>
<div className="search-result-url">{result.url}</div>
</div>
))}
</div>
</div>
) : query.trim() && !isLoading ? (
<div className="search-no-results">
No results found for "{query}"
</div>
) : (
<div className="search-modal-placeholder">
Start typing to search documentation...
</div>
)}
</div>
) : query.trim() && !isLoading ? (
<div className="search-no-results">
No results found for "{query}"
</div>
) : null}
</div>
)}
</>
</div>
);
};

Expand Down
Loading