import { useState, useCallback, useRef, createContext, useContext, useEffect } from 'react'
import { createPortal } from 'react-dom'
import Link from 'next/link'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { DocSearchModal } from '@docsearch/react'
import clsx from 'clsx'
import { useActionKey } from '~/hooks/useActionKey'
// [Joshen] We're currently using this over Algolia search for the time being
// Refer to the comment in Navigation/AlgoliaSearch for more information about this
const INDEX_NAME = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME
const API_KEY = process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY
const APP_ID = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID
const SearchContext = createContext(null)
export function SearchProvider({ children }: any) {
const router = useRouter()
const [isOpen, setIsOpen] = useState(false)
const [initialQuery, setInitialQuery] = useState(null)
const onOpen = useCallback(() => {
setIsOpen(true)
}, [setIsOpen])
const onClose = useCallback(() => {
setIsOpen(false)
}, [setIsOpen])
const onInput = useCallback(
(e) => {
setIsOpen(true)
setInitialQuery(e.key)
},
[setIsOpen, setInitialQuery]
)
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
// @ts-ignore
onInput,
})
return (
<>
{children}
{isOpen &&
createPortal(
{
return items.map((item, index) => {
// We transform the absolute URL into a relative URL to
// leverage Next's preloading.
const a = document.createElement('a')
a.href = item.url
const hash = a.hash === '#content-wrapper' ? '' : a.hash
return {
...item,
url: `${a.pathname}${hash}`,
__is_result: () => true,
__is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0,
__is_child: () =>
item.type !== 'lvl1' &&
items.length > 1 &&
items[0].type === 'lvl1' &&
index !== 0,
__is_first: () => index === 1,
__is_last: () => index === items.length - 1 && index !== 0,
}
})
}}
/>,
document.body
)}
>
)
}
// @ts-ignore
function Hit({ hit, children }) {
return (
{children}
)
}
// @ts-ignore
export function SearchButton({ children, ...props }) {
let searchButtonRef = useRef()
let actionKey = useActionKey()
// @ts-ignore
let { onOpen, onInput } = useContext(SearchContext)
useEffect(() => {
// @ts-ignore
function onKeyDown(event) {
if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) {
if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) {
onInput(event)
}
}
}
window.addEventListener('keydown', onKeyDown)
return () => {
window.removeEventListener('keydown', onKeyDown)
}
}, [onInput, searchButtonRef])
return (
// @ts-ignore
)
}
// @ts-ignore
function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) {
useEffect(() => {
// @ts-ignore
function onKeyDown(event) {
function open() {
// We check that no other DocSearch modal is showing before opening
// another one.
if (!document.body.classList.contains('DocSearch--active')) {
onOpen()
}
}
if (
(event.keyCode === 27 && isOpen) ||
(event.key === 'k' && (event.metaKey || event.ctrlKey)) ||
(!isEditingContent(event) && event.key === '/' && !isOpen)
) {
event.preventDefault()
if (isOpen) {
onClose()
} else if (!document.body.classList.contains('DocSearch--active')) {
open()
}
}
}
window.addEventListener('keydown', onKeyDown)
return () => {
window.removeEventListener('keydown', onKeyDown)
}
}, [isOpen, onOpen, onClose])
}
// @ts-ignore
function isEditingContent(event) {
let element = event.target
let tagName = element.tagName
return (
element.isContentEditable ||
tagName === 'INPUT' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
)
}