import type { Component, Setter } from 'solid-js' import type { ExtraSearchResult } from '../../data/search' import type { LocNav } from '../../ui/utils-ui' import { A } from '@solidjs/router' import { Logger } from 'besonders-logger' import { startCase } from 'lodash-es' import { createEffect, For, Match, on, onCleanup, Show, Switch } from 'solid-js' import { Dynamic } from 'solid-js/web' import { useSearchContext } from '../../data/search' import { useFocus } from '../../ui/reactive' import { focusViewOnBlock, makeNote3Url, replaceUriParts, useLocationNavigate } from '../../ui/utils-ui' import { Iconify, ShortID } from '../mini-components' const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line unused-imports/no-unused-vars export const HeaderSearchBar: Component<{ open: boolean setOpen: Setter // onEnterPressed: () => void firstResult: any }> = (props) => { // let setOpenInternal // if (open === undefined) { // ;[open, setOpenInternal] = createSignal(false) // const setOpenOriginal = setOpen // setOpen = (state: boolean) => { // setOpenInternal(state) // setOpen(state) // } // } const { locnav, location, navigate } = useLocationNavigate() const [search, setSearch] = useSearchContext() const focus = useFocus() createEffect(on(() => search(), (search) => { if (search && !props.open) { props.setOpen(true) inputRef?.focus() } })) // Auto-navigate for search results (except block-not-found which is feedback-only) createEffect(on(() => props.firstResult, (firstResult) => { if (!firstResult || !search()) return if (firstResult.type !== 'block-not-found') { close() openExtraSearchResult(firstResult, { location, navigate }) setSearch(null) } })) const close = () => { props.setOpen(false) } // Global keylistener function onEnterPressed() { const firstResult = props.firstResult if (firstResult) { close() openExtraSearchResult(firstResult, { location, navigate }) // setSearch(null) } } const handleKeyDown = (event: KeyboardEvent) => { DEBUG(`[handleKeyDown]`, event) if (event.ctrlKey && event.code === 'Slash') { // HACK: code=='Slash' so that Shift+/ will still match... if (props.open) { if (event.shiftKey) { // Ctrl+Shift+/ when search is open if (location.pathname.startsWith('/block/')) { // Navigate to global view while preserving search params navigate(replaceUriParts(location, { pathname: '/timeline' })) } else { // Already in global view, close search close() } } else { // Ctrl+/ when search is open: close search close() } } else { if (location.pathname.startsWith('settings')) { focusViewOnBlock({ id: null }, locnav) } else if (event.shiftKey && focus()) { focusViewOnBlock({ id: null }, locnav) } props.setOpen(true) inputRef?.focus() // TODO: when header button click? } } else if (props.open) { if (event.key === 'Escape') { close() } } } document.addEventListener('keydown', handleKeyDown, true) onCleanup(() => { document.removeEventListener('keydown', handleKeyDown, true) }) let inputRef // , resultsContainerRef // const handleArrowKeys = (event: KeyboardEvent) => { // DEBUG(`[handleArrowKeys]`, event) // if (['ArrowUp', 'ArrowDown'].includes(event.key)) { // let target = event.target as HTMLElement // if (target.getRootNode() instanceof ShadowRoot) { // // Get the host element of the shadow root // target = (target.getRootNode() as ShadowRoot).host as HTMLElement // } // const current = target.closest('sl-input, div[data-block-id]') as HTMLElement // DEBUG(`[handleArrowKeys] current`, current) // if (!current) return // let newTarget = (event.key === 'ArrowUp' ? current.previousElementSibling : current.nextElementSibling) as HTMLElement // if (!newTarget && current.parentElement == resultsContainerRef && event.key === 'ArrowUp') { // newTarget = inputRef // } // if (newTarget == resultsContainerRef) { // newTarget = newTarget.firstElementChild as HTMLElement // } // DEBUG(`[handleArrowKeys] newTarget`, newTarget) // if (newTarget) { // newTarget.focus() // event.preventDefault() // } // } // } // const currentDS = useCurrentThread() // const [results] = createResource(() => ({ search: search(), currentDS }), performSearch) // createEffect(() => { // if (open()) { // DEBUG(`Focussing search field:`, inputRef) // inputRef?.focus() // } // }) // const openResult = (result: Result) => { // close() // if (result.type === 'block') zoomToBlock({ id: result.id }, location, navigate) // else if (result.type === 'preview') navigate(makeNote3Url({ pub: result.id, block: result.focus, previewPub: true, relative: true })) // else navigate(`/settings/${result.type}s/${result.id}`) // } return ( setSearch(e.target.value)} onsl-clear={close} border='2 solid white' clearable={true} type='search' autocomplete='nope' // FIXME https://stackoverflow.com/a/30873633/1633985 & https://stackoverflow.com/questions/25823448/ng-form-and-autocomplete-off/39689037#39689037 rounded tabindex='0' onKeyDown={({ key }) => key === 'Enter' && onEnterPressed()} > ) } // export const OverlaySearchBar: Component<{ // open: Accessor // setOpen: Setter // }> = ( // { open, setOpen }, // ) => { // const location = useLocation() // const navigate = useNavigate() // // let setOpenInternal // // if (open === undefined) { // // ;[open, setOpenInternal] = createSignal(false) // // const setOpenOriginal = setOpen // // setOpen = (state: boolean) => { // // setOpenInternal(state) // // setOpen(state) // // } // // } // const [search, setSearch] = useSearch() // const close = () => { // setOpen(false) // setTimeout(() => setSearch(''), 300) // prevent glitch during fadeout // } // const handleKeyDown = (event: KeyboardEvent) => { // DEBUG(`[handleKeyDown]`, event) // if (event.ctrlKey && event.key === '/') { // if (open()) close() // else setOpen(true) // } else if (open()) { // if (event.key === 'Escape') { // close() // } // } // } // document.addEventListener('keydown', handleKeyDown, true) // onCleanup(() => { // document.removeEventListener('keydown', handleKeyDown, true) // }) // const handleClickToClose = (event: MouseEvent) => { // DEBUG(`[handleClickToClose]`, event) // // FIXME: if I drag from the input to outside, this is triggered // if ((event.target as HTMLElement).dataset.clickaway !== undefined) { // close() // } // } // let inputRef, resultsContainerRef // const handleArrowKeys = (event: KeyboardEvent) => { // DEBUG(`[handleArrowKeys]`, event) // if (['ArrowUp', 'ArrowDown'].includes(event.key)) { // let target = event.target as HTMLElement // if (target.getRootNode() instanceof ShadowRoot) { // // Get the host element of the shadow root // target = (target.getRootNode() as ShadowRoot).host as HTMLElement // } // const current = target.closest('sl-input, div[data-block-id]') as HTMLElement // DEBUG(`[handleArrowKeys] current`, current) // if (!current) return // let newTarget = (event.key === 'ArrowUp' ? current.previousElementSibling : current.nextElementSibling) as HTMLElement // if (!newTarget && current.parentElement == resultsContainerRef && event.key === 'ArrowUp') { // newTarget = inputRef // } // if (newTarget == resultsContainerRef) { // newTarget = newTarget.firstElementChild as HTMLElement // } // DEBUG(`[handleArrowKeys] newTarget`, newTarget) // if (newTarget) { // newTarget.focus() // event.preventDefault() // } // } // } // const currentDS = useCurrentDS() // const [results] = createResource(() => ({ search: search(), currentDS }), useExtraSearchResults) // createEffect(() => { // if (open()) { // DEBUG(`Focussing search field:`, inputRef) // inputRef?.focus() // } // }) // // const openResult = (result: ExtraSearchResult) => { // // close() // // if (result.type === 'block') zoomToBlock({ id: result.id }, location, navigate) // // else if (result.type === 'preview') navigate(makeNote3Url({ pub: result.id, block: result.focus, previewPub: true, relative: true })) // // else navigate(`/settings/${result.type}s/${result.id}`) // // } // return ( // // //
//
// setSearch(e.target.value)} // border='2 solid white' // rounded // tabindex='0' // onKeyDown={({ key }) => key === 'Enter' && openResult(results()?.[0])} // /> //
// }> // //
//
//
// //
// ) // } export const ExtraSearchResults: Component<{ results: readonly ExtraSearchResult[] close: () => void }> = (props) => { VERBOSE(`.created`, props) const { locnav, location, navigate } = useLocationNavigate() const openResult = (result: ExtraSearchResult) => { props.close() openExtraSearchResult(result, locnav) } return (
{/*
{JSON.stringify(props.results,undefined,4)}
*/} {(result, _index) => ( key === 'Enter' && openResult(result)} onclick={() => openResult(result)} class='block cursor-pointer rounded p-2 text-xs' tabindex='0' bg='gray-900 hover:bluegray-700 focus:bluegray-800' flex='1' border='1 solid white focus:blue' > {/*
{JSON.stringify(applog,undefined,4)}
*/} Block:   {/* @ts-expect-error TS inference doesn't fully work in solid */} {contentVlToPlaintext(result.content)} {/* TODO proper block rendering postponed because of search re-think ideas */} Block:  
⚠ {result.message}
{startCase(result.type)} :
)}
) } export function openExtraSearchResult(result: ExtraSearchResult, { location, navigate }: LocNav) { if (result.type === 'block') { focusViewOnBlock({ id: result.id }, { location, navigate }) } else if (result.type === 'preview') { navigate(makeNote3Url({ pub: result.id, block: result.focus, previewPub: true, relative: true })) } else if (result.type === 'block-not-found') { // Block not found - don't navigate, just close search } else { navigate(`/settings/${result.type}s/${result.id}`) } }