Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📇 Search improvements #122

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
80 changes: 63 additions & 17 deletions app/classes/search-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React, { useState } from 'react'
import Client from '@searchkit/instantsearch-client'
import Searchkit from 'searchkit'
import searchkit_options from '~/data/searchkit.json'
Expand All @@ -8,6 +9,7 @@ import {
Index,
Pagination,
HitsPerPage,
Configure,
} from 'react-instantsearch'
import {
Error,
Expand All @@ -22,17 +24,36 @@ import { Carousel } from '~/components/Carousel'
import { NoResults } from '~/components/NoResults'
import { AAPBResults } from '~/components/AAPBResults'
import { Refinements } from '~/components/Refinements'
import Help from '~/components/Help'
import { SearchProps } from '~/routes/search'
import { Router, stateToRoute, routeToState } from '~/components/Router'
import { Tabs, Tab } from '@mui/material'

import { useLoaderData, useRouteError } from '@remix-run/react'

const sk = new Searchkit(searchkit_options)

export const searchClient = Client(sk)
export const searchClient = Client(sk, {
getQuery: (query, search_attributes) => {
console.log('search query', query, search_attributes)
return [
{
simple_query_string: {
query,
},
},
]
},
})

export const Search = ({ serverUrl, aapb_host }: SearchProps) => {
export const Search = () => {
const { serverUrl, aapb_host }: SearchProps = useLoaderData()
const [activeTab, setActiveTab] = useState(0)
let timerId: NodeJS.Timeout
let timeout: number = 350

let timeout: number = 300
const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setActiveTab(newValue)
}
return (
<InstantSearch
searchClient={searchClient}
Expand All @@ -41,25 +62,46 @@ export const Search = ({ serverUrl, aapb_host }: SearchProps) => {
stateMapping: { stateToRoute, routeToState },
}}
insights={false}
future={{
preserveSharedStateOnUnmount: true,
}}
>
<ScrollTo className="max-w-6xl p-4 flex gap-4 m-auto">
<SearchBox
queryHook={(query, refine) => {
// console.log('searchbox', query)
// debounce the search input box
clearTimeout(timerId)
timerId = setTimeout(() => refine(query), timeout)
}}
className="search-box"
/>
<Error />
<div className="search-results">
<SearchBox
autoFocus
placeholder={
activeTab === 0
? 'Search GBH Open Vault'
: activeTab === 1
? 'Search GBH Series'
: activeTab === 2
? 'Search American Archive'
: ''
}
// queryHook={(query, search) => {
// // console.log('searchbox', search)
// // debounce the search input box

// clearTimeout(timerId)
// timerId = setTimeout(() => search(query), timeout)
// }}
className="search-box"
/>
<Error />
<EmptyQueryBoundary fallback={<EmptyQueryMessage />}>
<AAPBResults aapb_host={aapb_host} />
<LoadingIndicator />
</EmptyQueryBoundary>

<Tabs value={activeTab} onChange={handleTabChange}>
<Tab label="Open Vault" />
<Tab label="GBH Series" />
<Tab label="American Archive" />
<Tab label="Help" />
</Tabs>
{activeTab === 0 && (
<Index indexName="wagtail__wagtailcore_page">
<NoResultsBoundary fallback={<NoResults />}>
<h2>Open Vault results</h2>
<Refinements />
<Hits hitComponent={Hit} />
<Pagination />
Expand All @@ -74,13 +116,17 @@ export const Search = ({ serverUrl, aapb_host }: SearchProps) => {
/>
</NoResultsBoundary>
</Index>
)}
{activeTab === 1 && (
<Index indexName="gbh-series">
<NoResultsBoundary fallback={null}>
<h3>GBH Series results</h3>
<Carousel aapb_host={aapb_host} />
</NoResultsBoundary>
</Index>
</EmptyQueryBoundary>
)}
{activeTab === 2 && <AAPBResults aapb_host={aapb_host} />}
{activeTab === 3 && <Help />}
</div>
</ScrollTo>
</InstantSearch>
Expand Down
62 changes: 59 additions & 3 deletions app/classes/search-utils.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,65 @@
import { useSearchBox, Pagination } from 'react-instantsearch'
import { useInstantSearch } from 'react-instantsearch-core'

export const EmptyQueryMessage = () => <>
<h2>Search Open Vault</h2>
</>
import sampleSize from 'lodash/sampleSize'

const default_suggestions = [
'Julia Child',
'Louis Lyons',
'Boston',
'Arthur',
'NOVA',
'Civil Rights',
'Vietnam',
'WGBH',
'Cooking',
'Music',
]

export const EmptyQueryMessage = () => {
const { refine } = useSearchBox()

function setQuery(newQuery) {
refine(newQuery)
}
return (
<>
<p>Search articles, titles, and GBH Series on Open Vault</p>
<Suggestions queries={sampleSize(default_suggestions, 4)} />
<p>See the help section for search tips and advanced syntax.</p>
</>
)
}

export const Suggestions = ({ queries, ...props }) => {
const { refine } = useSearchBox(props)

return (
<>
<h4>Suggestions</h4>
<ul>
{queries.map(query => (
<li>{SearchLink(query)}</li>
))}
</ul>
</>
)
}

export const SearchLink = query => {
const { refine } = useSearchBox()

return (
<a
onClick={event => {
event.preventDefault()
refine(query)
}}
>
{query}
</a>
)
}

export function Error() {
const { error } = useInstantSearch({ catchError: true })
Expand Down
12 changes: 9 additions & 3 deletions app/components/AAPBResults.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useState, useEffect, useCallback } from 'react'
import { useInstantSearch } from 'react-instantsearch'
import pkg from 'lodash'
const { debounce } = pkg

import debounce from 'lodash/debounce'
import { Spinner } from './Spinner'

const gbh_query =
'+AND+(contributing_organizations:%20WGBH(MA)%20OR%20producing_organizations:%20WGBH%20Educational%20Foundation)&f[access_types][]=online'

export const AAPBResults = ({ aapb_host }) => {
const { indexUiState } = useInstantSearch()

Expand All @@ -30,7 +33,10 @@ export const AAPBResults = ({ aapb_host }) => {
}, [fetchResults, indexUiState])

return (
<a href={`${aapb_host}/catalog?q=${indexUiState.query}`} target="_blank">
<a
href={`${aapb_host}/catalog?q=${indexUiState.query}${gbh_query}`}
target="_blank"
>
<span className="ais-RefinementList-count">
{result_count || <Spinner />}
</span>
Expand Down
136 changes: 136 additions & 0 deletions app/components/Help.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* Help page for search */
import { SearchLink } from '~/classes/search-utils'
export default () => (
<>
<p>
Open Vault is a search engine for the GBH Media Library and Archives. You
can search for exhibits, collections, and series from GBH. You can also
search for records on{' '}
<a href="https://americanarchive.org/" target="_blank">
AmericanArchive.org
</a>
</p>
<h1>Search tabs</h1>
<p>The search results are divided into tabs:</p>
<ul>
<li>
<strong>Open Vault</strong> - search results from the GBH Media Library
and Archives
</li>
<li>
<strong>GBH Series</strong> - Program names and series produced by GBH
</li>
<li>
<strong>American Archive</strong> - Search records from over 200 media
organizations hosted by the{' '}
<a href="https://americanarchive.org/" target="_blank">
American Archive of Public Broadcasting
</a>
</li>
</ul>
<h1>Advanced search</h1>
<p>You can use advanced search operators to refine your search query.</p>
<h3>Phrases " "</h3>
If you want to search for an exact phrase, you can use double quotes.
<ul>
<li>{SearchLink('"Julia Child"')}</li>
<li>{SearchLink('"Louis Lyons"')}</li>
<li>{SearchLink('"Civil War"')}</li>
</ul>
<h3>Similarity ~</h3>
Including a tilde <code>~</code> after a word (without a number) will match
similar words.
<ul>
<li>
{SearchLink('Archive')} (without a tilde) will match <em>archive</em>,
and <em>archives</em>
</li>
<li>
{SearchLink('Archive~')} (with a tilde ~) includes <em>archived</em>,{' '}
<em>archiving</em>, <em>archival</em>, and similarly spelled words like{' '}
<em>architecture</em> and <em>achievement</em>
</li>
</ul>
The "fuzzyness" factor can be adjusted by adding a number after the tilde.
<ul>
<li>
{SearchLink('America~0')} will match <em>America</em> and{' '}
<em>American</em>
</li>
<li>
{SearchLink('America~1')} will include <em>Americanized</em> and{' '}
<em>Americanizing</em>
</li>
<li>
{SearchLink('America~2')} will include <em>African American</em> and
even unrelated words like <em>medical</em>
</li>
</ul>
When searching a phrase with quotes, including a tilde <code>~</code>{' '}
followed by a number will match phrases separated by that number of words.
<ul>
<li>
{SearchLink('"Vietnam war"~3')} will match <em>Vietnam war</em> and{' '}
<em>war in Vietnam</em>
</li>
</ul>
<h3>Wildcard *</h3>
You can use an asterisk <code>*</code> to search for records that contain a
term with a wildcard.
<ul>
<li>
{SearchLink('tech*')} matches <em>tech</em> and <em>technology</em>
</li>
<li>
{SearchLink('front*')} matches <em>front</em> and <em>Frontline</em>
</li>
</ul>
<h3>Booleans</h3>
Use <code>+</code>, <code>-</code>, and <code>|</code> operators to include
or exclude specific terms.
<h4>
<code>+</code> (plus)
</h4>
Use <code>+</code> to include only records that contain that term.
<ul>
<li>
{SearchLink('+Boston +busing')} matches records that contain both{' '}
<em>Boston</em> and <em>busing</em>
</li>
</ul>
<h4>
<code>-</code> (minus)
</h4>
Use <code>-</code> to exclude records that contain that term.
<ul>
<li>
{SearchLink('music +-folk')} matches records that contain <em>music</em>{' '}
and do not contain <em>folk</em>
</li>
</ul>
<h4>
<code>|</code> (pipe)
</h4>
Use <code>|</code> between terms to match either term.
<ul>
<li>
{SearchLink('television | radio')} matches records that contain either{' '}
<em>television</em> or <em>radio</em>
</li>
</ul>
<h3>Groups ( )</h3>
Use parentheses to group terms.
<ul>
<li>
{SearchLink('(tech | technology) +Frontline')} matches records that
contain either <em>tech</em> or <em>technology</em> and includes{' '}
<em>Frontline</em>
</li>
<li>
{SearchLink('("Julia Child" | "Joyce Chen") +radio')} matches records
that contain either exact phrase <em>"Julia Child"</em> or{' '}
<em>"Joyce Chen"</em> and also includes <em>radio</em>
</li>
</ul>
</>
)
4 changes: 2 additions & 2 deletions app/data/searchkit.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
],
"highlight_attributes": ["title"],
"snippet_attributes": [
"exhibits_exhibitpage__body_edgengrams",
"ov_collections_collection__introduction_edgengrams"
"exhibits_exhibitpage__body_edgengrams:40",
"ov_collections_collection__introduction_edgengrams:40"
],
"facet_attributes": [
{ "attribute": "content_type", "field": "content_type", "type": "string" }
Expand Down
10 changes: 9 additions & 1 deletion app/routes/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Search } from '~/classes/search-ui'
import 'instantsearch.css/themes/algolia-min.css'
import '~/styles/search.css'
import { Meta } from '~/classes/meta'
import { useNavigation } from '@remix-run/react'

export const meta: MetaFunction = ({ location }) => {
const query = new URLSearchParams(location.search).get('q')
Expand Down Expand Up @@ -48,7 +49,14 @@ export type SearchProps = {

export default function SearchPage() {
const { serverUrl, aapb_host }: SearchProps = useLoaderData()
return <Search serverUrl={serverUrl} aapb_host={aapb_host} />
return (
<>
<div className="page-body-container">
<h1>Search Open Vault</h1>
<Search serverUrl={serverUrl} aapb_host={aapb_host} />
</div>
</>
)
}

export function ErrorBoundary() {
Expand Down
Loading