search filters
This commit is contained in:
parent
6225b9e7aa
commit
e4d150413b
|
@ -8,7 +8,8 @@ import invite from './invite'
|
||||||
import sub from './sub'
|
import sub from './sub'
|
||||||
import upload from './upload'
|
import upload from './upload'
|
||||||
import growth from './growth'
|
import growth from './growth'
|
||||||
|
import search from './search'
|
||||||
import { GraphQLJSONObject } from 'graphql-type-json'
|
import { GraphQLJSONObject } from 'graphql-type-json'
|
||||||
|
|
||||||
export default [user, item, message, wallet, lnurl, notifications, invite, sub,
|
export default [user, item, message, wallet, lnurl, notifications, invite, sub,
|
||||||
upload, growth, { JSONObject: GraphQLJSONObject }]
|
upload, growth, search, { JSONObject: GraphQLJSONObject }]
|
||||||
|
|
|
@ -435,123 +435,6 @@ export default {
|
||||||
comments: async (parent, { id, sort }, { me, models }) => {
|
comments: async (parent, { id, sort }, { me, models }) => {
|
||||||
return comments(me, models, id, sort)
|
return comments(me, models, id, sort)
|
||||||
},
|
},
|
||||||
search: async (parent, { q: query, sub, cursor }, { me, models, search }) => {
|
|
||||||
const decodedCursor = decodeCursor(cursor)
|
|
||||||
let sitems
|
|
||||||
|
|
||||||
try {
|
|
||||||
sitems = await search.search({
|
|
||||||
index: 'item',
|
|
||||||
size: LIMIT,
|
|
||||||
from: decodedCursor.offset,
|
|
||||||
body: {
|
|
||||||
query: {
|
|
||||||
bool: {
|
|
||||||
must: [
|
|
||||||
sub
|
|
||||||
? { match: { 'sub.name': sub } }
|
|
||||||
: { bool: { must_not: { exists: { field: 'sub.name' } } } },
|
|
||||||
me
|
|
||||||
? {
|
|
||||||
bool: {
|
|
||||||
should: [
|
|
||||||
{ match: { status: 'ACTIVE' } },
|
|
||||||
{ match: { status: 'NOSATS' } },
|
|
||||||
{ match: { userId: me.id } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
bool: {
|
|
||||||
should: [
|
|
||||||
{ match: { status: 'ACTIVE' } },
|
|
||||||
{ match: { status: 'NOSATS' } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
bool: {
|
|
||||||
should: [
|
|
||||||
{
|
|
||||||
// all terms are matched in fields
|
|
||||||
multi_match: {
|
|
||||||
query,
|
|
||||||
type: 'most_fields',
|
|
||||||
fields: ['title^20', 'text'],
|
|
||||||
minimum_should_match: '100%',
|
|
||||||
boost: 400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// all terms are matched in fields
|
|
||||||
multi_match: {
|
|
||||||
query,
|
|
||||||
type: 'most_fields',
|
|
||||||
fields: ['title^20', 'text'],
|
|
||||||
fuzziness: 'AUTO',
|
|
||||||
prefix_length: 3,
|
|
||||||
minimum_should_match: '100%',
|
|
||||||
boost: 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// only some terms must match
|
|
||||||
multi_match: {
|
|
||||||
query,
|
|
||||||
type: 'most_fields',
|
|
||||||
fields: ['title^20', 'text'],
|
|
||||||
fuzziness: 'AUTO',
|
|
||||||
prefix_length: 3,
|
|
||||||
minimum_should_match: '60%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: add wildcard matches for
|
|
||||||
// user.name and url
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
filter: {
|
|
||||||
range: {
|
|
||||||
createdAt: {
|
|
||||||
lte: decodedCursor.time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
highlight: {
|
|
||||||
fields: {
|
|
||||||
title: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] },
|
|
||||||
text: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
return {
|
|
||||||
cursor: null,
|
|
||||||
items: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return highlights
|
|
||||||
const items = sitems.body.hits.hits.map(async e => {
|
|
||||||
// this is super inefficient but will suffice until we do something more generic
|
|
||||||
const item = await getItem(parent, { id: e._source.id }, { me, models })
|
|
||||||
|
|
||||||
item.searchTitle = (e.highlight.title && e.highlight.title[0]) || item.title
|
|
||||||
item.searchText = (e.highlight.text && e.highlight.text[0]) || item.text
|
|
||||||
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
|
||||||
items
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auctionPosition: async (parent, { id, sub, bid }, { models, me }) => {
|
auctionPosition: async (parent, { id, sub, bid }, { models, me }) => {
|
||||||
const createdAt = id ? (await getItem(parent, { id }, { models, me })).createdAt : new Date()
|
const createdAt = id ? (await getItem(parent, { id }, { models, me })).createdAt : new Date()
|
||||||
let where
|
let where
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
|
import { getItem } from './item'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Query: {
|
||||||
|
search: async (parent, { q: query, sub, cursor, sort, what, when }, { me, models, search }) => {
|
||||||
|
const decodedCursor = decodeCursor(cursor)
|
||||||
|
let sitems
|
||||||
|
|
||||||
|
const sortArr = []
|
||||||
|
switch (sort) {
|
||||||
|
case 'recent':
|
||||||
|
sortArr.push({ createdAt: 'desc' })
|
||||||
|
break
|
||||||
|
case 'comments':
|
||||||
|
sortArr.push({ ncomments: 'desc' })
|
||||||
|
break
|
||||||
|
case 'sats':
|
||||||
|
sortArr.push({ sats: 'desc' })
|
||||||
|
break
|
||||||
|
case 'votes':
|
||||||
|
sortArr.push({ upvotes: 'desc' })
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sortArr.push('_score')
|
||||||
|
|
||||||
|
const whatArr = []
|
||||||
|
switch (what) {
|
||||||
|
case 'posts':
|
||||||
|
whatArr.push({ bool: { must_not: { exists: { field: 'parentId' } } } })
|
||||||
|
break
|
||||||
|
case 'comments':
|
||||||
|
whatArr.push({ bool: { must: { exists: { field: 'parentId' } } } })
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let whenGte
|
||||||
|
switch (when) {
|
||||||
|
case 'day':
|
||||||
|
whenGte = 'now-1d'
|
||||||
|
break
|
||||||
|
case 'week':
|
||||||
|
whenGte = 'now-7d'
|
||||||
|
break
|
||||||
|
case 'month':
|
||||||
|
whenGte = 'now-30d'
|
||||||
|
break
|
||||||
|
case 'year':
|
||||||
|
whenGte = 'now-365d'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
sitems = await search.search({
|
||||||
|
index: 'item',
|
||||||
|
size: LIMIT,
|
||||||
|
from: decodedCursor.offset,
|
||||||
|
body: {
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
must: [
|
||||||
|
...whatArr,
|
||||||
|
sub
|
||||||
|
? { match: { 'sub.name': sub } }
|
||||||
|
: { bool: { must_not: { exists: { field: 'sub.name' } } } },
|
||||||
|
me
|
||||||
|
? {
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ match: { status: 'ACTIVE' } },
|
||||||
|
{ match: { status: 'NOSATS' } },
|
||||||
|
{ match: { userId: me.id } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ match: { status: 'ACTIVE' } },
|
||||||
|
{ match: { status: 'NOSATS' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{
|
||||||
|
// all terms are matched in fields
|
||||||
|
multi_match: {
|
||||||
|
query,
|
||||||
|
type: 'most_fields',
|
||||||
|
fields: ['title^20', 'text'],
|
||||||
|
minimum_should_match: '100%',
|
||||||
|
boost: 400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// all terms are matched in fields
|
||||||
|
multi_match: {
|
||||||
|
query,
|
||||||
|
type: 'most_fields',
|
||||||
|
fields: ['title^20', 'text'],
|
||||||
|
fuzziness: 'AUTO',
|
||||||
|
prefix_length: 3,
|
||||||
|
minimum_should_match: '100%',
|
||||||
|
boost: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// only some terms must match unless we're sorting
|
||||||
|
multi_match: {
|
||||||
|
query,
|
||||||
|
type: 'most_fields',
|
||||||
|
fields: ['title^20', 'text'],
|
||||||
|
fuzziness: 'AUTO',
|
||||||
|
prefix_length: 3,
|
||||||
|
minimum_should_match: sortArr.length > 1 ? '100%' : '60%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: add wildcard matches for
|
||||||
|
// user.name and url
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filter: {
|
||||||
|
range: {
|
||||||
|
createdAt: {
|
||||||
|
lte: decodedCursor.time,
|
||||||
|
gte: whenGte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sort: sortArr,
|
||||||
|
highlight: {
|
||||||
|
fields: {
|
||||||
|
title: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] },
|
||||||
|
text: { number_of_fragments: 0, pre_tags: [':high['], post_tags: [']'] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return {
|
||||||
|
cursor: null,
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return highlights
|
||||||
|
const items = sitems.body.hits.hits.map(async e => {
|
||||||
|
// this is super inefficient but will suffice until we do something more generic
|
||||||
|
const item = await getItem(parent, { id: e._source.id }, { me, models })
|
||||||
|
|
||||||
|
item.searchTitle = (e.highlight.title && e.highlight.title[0]) || item.title
|
||||||
|
item.searchText = (e.highlight.text && e.highlight.text[0]) || item.text
|
||||||
|
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
cursor: items.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
||||||
|
items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ export default gql`
|
||||||
pageTitle(url: String!): String
|
pageTitle(url: String!): String
|
||||||
dupes(url: String!): [Item!]
|
dupes(url: String!): [Item!]
|
||||||
allItems(cursor: String): Items
|
allItems(cursor: String): Items
|
||||||
search(q: String, sub: String, cursor: String): Items
|
search(q: String, sub: String, cursor: String, what: String, sort: String, when: String): Items
|
||||||
auctionPosition(sub: String, id: ID, bid: Int!): Int!
|
auctionPosition(sub: String, id: ID, bid: Int!): Int!
|
||||||
itemRepetition(parentId: ID): Int!
|
itemRepetition(parentId: ID): Int!
|
||||||
outlawedItems(cursor: String): Items
|
outlawedItems(cursor: String): Items
|
||||||
|
|
|
@ -193,11 +193,14 @@ function InputInner ({
|
||||||
{(clear && field.value) &&
|
{(clear && field.value) &&
|
||||||
<Button
|
<Button
|
||||||
variant={null}
|
variant={null}
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
helpers.setValue('')
|
helpers.setValue('')
|
||||||
if (storageKey) {
|
if (storageKey) {
|
||||||
localStorage.removeItem(storageKey)
|
localStorage.removeItem(storageKey)
|
||||||
}
|
}
|
||||||
|
if (onChange) {
|
||||||
|
onChange(formik, { target: { value: '' } })
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className={`${styles.clearButton} ${invalid ? styles.isInvalid : ''}`}
|
className={`${styles.clearButton} ${invalid ? styles.isInvalid : ''}`}
|
||||||
><CloseIcon className='fill-grey' height={20} width={20} />
|
><CloseIcon className='fill-grey' height={20} width={20} />
|
||||||
|
@ -430,12 +433,24 @@ export function SyncForm ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Select ({ label, items, groupClassName, ...props }) {
|
export function Select ({ label, items, groupClassName, onChange, noForm, ...props }) {
|
||||||
const [field] = useField(props)
|
const [field] = useField(props)
|
||||||
|
const formik = noForm ? null : useFormikContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup label={label} className={groupClassName}>
|
<FormGroup label={label} className={groupClassName}>
|
||||||
<BootstrapForm.Control as='select' custom {...field} {...props}>
|
<BootstrapForm.Control
|
||||||
|
as='select'
|
||||||
|
{...field} {...props}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(e)
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
onChange(formik, e)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
custom
|
||||||
|
>
|
||||||
{items.map(item => <option key={item}>{item}</option>)}
|
{items.map(item => <option key={item}>{item}</option>)}
|
||||||
</BootstrapForm.Control>
|
</BootstrapForm.Control>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
|
@ -3,13 +3,13 @@ import styles from './search.module.css'
|
||||||
import SearchIcon from '../svgs/search-line.svg'
|
import SearchIcon from '../svgs/search-line.svg'
|
||||||
import CloseIcon from '../svgs/close-line.svg'
|
import CloseIcon from '../svgs/close-line.svg'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Form, Input, SubmitButton } from './form'
|
import { Form, Input, Select, SubmitButton } from './form'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
export default function Search ({ sub }) {
|
export default function Search ({ sub }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [searching, setSearching] = useState(router.query.q)
|
const [searching, setSearching] = useState(router.query.q)
|
||||||
const [q, setQ] = useState(router.query.q)
|
const [q, setQ] = useState(router.query.q || '')
|
||||||
const [atBottom, setAtBottom] = useState()
|
const [atBottom, setAtBottom] = useState()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -23,65 +23,105 @@ export default function Search ({ sub }) {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const search = async values => {
|
||||||
|
let prefix = ''
|
||||||
|
if (sub) {
|
||||||
|
prefix = `/~${sub}`
|
||||||
|
}
|
||||||
|
if (values.q?.trim() !== '') {
|
||||||
|
if (values.what === '') delete values.what
|
||||||
|
if (values.sort === '') delete values.sort
|
||||||
|
if (values.when === '') delete values.when
|
||||||
|
await router.push({
|
||||||
|
pathname: prefix + '/search',
|
||||||
|
query: values
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const showSearch = atBottom || searching || router.query.q
|
const showSearch = atBottom || searching || router.query.q
|
||||||
|
const filter = router.query.q && !sub
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${styles.searchSection} ${showSearch ? styles.solid : styles.hidden}`}>
|
<div className={`${styles.searchSection} ${showSearch ? styles.solid : styles.hidden}`}>
|
||||||
<Container className={`px-sm-0 ${styles.searchContainer}`}>
|
<Container className={`px-sm-0 ${styles.searchContainer} ${filter ? styles.leaveRoom : ''}`}>
|
||||||
{showSearch
|
{showSearch
|
||||||
? (
|
? (
|
||||||
<Form
|
<Form
|
||||||
|
className={styles.formActive}
|
||||||
initial={{
|
initial={{
|
||||||
q: router.query.q || ''
|
q: router.query.q || '',
|
||||||
}}
|
what: router.query.what || '',
|
||||||
className={`w-auto ${styles.active}`}
|
sort: router.query.sort || '',
|
||||||
onSubmit={async ({ q }) => {
|
when: router.query.when || ''
|
||||||
if (q.trim() !== '') {
|
|
||||||
let prefix = ''
|
|
||||||
if (sub) {
|
|
||||||
prefix = `/~${sub}`
|
|
||||||
}
|
|
||||||
router.push(prefix + `/search?q=${encodeURIComponent(q)}`)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
onSubmit={search}
|
||||||
>
|
>
|
||||||
<Input
|
{filter &&
|
||||||
name='q'
|
<div className='text-muted font-weight-bold my-3 d-flex align-items-center'>
|
||||||
required
|
<Select
|
||||||
autoFocus={showSearch && !atBottom}
|
groupClassName='mr-2 mb-0'
|
||||||
groupClassName='mr-3 mb-0 flex-grow-1'
|
onChange={(formik, e) => search({ ...formik?.values, what: e.target.value })}
|
||||||
className='flex-grow-1'
|
name='what'
|
||||||
clear
|
size='sm'
|
||||||
onChange={async (formik, e) => {
|
items={['all', 'posts', 'comments']}
|
||||||
setSearching(true)
|
/>
|
||||||
setQ(e.target.value?.trim())
|
by
|
||||||
}}
|
<Select
|
||||||
/>
|
groupClassName='mx-2 mb-0'
|
||||||
{q || atBottom || router.query.q
|
onChange={(formik, e) => search({ ...formik?.values, sort: e.target.value })}
|
||||||
? (
|
name='sort'
|
||||||
<SubmitButton variant='primary' className={styles.search}>
|
size='sm'
|
||||||
<SearchIcon width={22} height={22} />
|
items={['match', 'recent', 'comments', 'sats', 'votes']}
|
||||||
</SubmitButton>
|
/>
|
||||||
)
|
for
|
||||||
: (
|
<Select
|
||||||
<Button
|
groupClassName='mb-0 ml-2'
|
||||||
className={styles.search} onClick={() => {
|
onChange={(formik, e) => search({ ...formik?.values, when: e.target.value })}
|
||||||
setSearching(false)
|
name='when'
|
||||||
}}
|
size='sm'
|
||||||
>
|
items={['forever', 'day', 'week', 'month', 'year']}
|
||||||
<CloseIcon width={26} height={26} />
|
/>
|
||||||
</Button>)}
|
</div>}
|
||||||
|
<div className={`${styles.active}`}>
|
||||||
|
<Input
|
||||||
|
name='q'
|
||||||
|
required
|
||||||
|
autoFocus={showSearch && !atBottom}
|
||||||
|
groupClassName='mr-3 mb-0 flex-grow-1'
|
||||||
|
className='flex-grow-1'
|
||||||
|
clear
|
||||||
|
onChange={async (formik, e) => {
|
||||||
|
setSearching(true)
|
||||||
|
setQ(e.target.value?.trim())
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{q || atBottom || router.query.q
|
||||||
|
? (
|
||||||
|
<SubmitButton variant='primary' className={styles.search}>
|
||||||
|
<SearchIcon width={22} height={22} />
|
||||||
|
</SubmitButton>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Button
|
||||||
|
className={styles.search} onClick={() => {
|
||||||
|
setSearching(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon width={26} height={26} />
|
||||||
|
</Button>)}
|
||||||
|
</div>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<Button className={`${styles.search} ${styles.active}`} onClick={() => setSearching(true)}>
|
<Button className={`${styles.search} ${styles.formActive}`} onClick={() => setSearching(true)}>
|
||||||
<SearchIcon width={22} height={22} />
|
<SearchIcon width={22} height={22} />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.searchPadding} />
|
<div className={`${styles.searchPadding} ${filter ? styles.leaveRoom : ''}`} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 88px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchContainer {
|
.searchContainer {
|
||||||
|
@ -11,6 +10,10 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaveRoom {
|
||||||
|
height: 130px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.searchSection.solid {
|
.searchSection.solid {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
background: var(--theme-body);
|
background: var(--theme-body);
|
||||||
|
@ -30,18 +33,20 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
left: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.formActive {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
bottom: 18px;
|
bottom: 18px;
|
||||||
right: 18px;
|
right: 18px;
|
||||||
|
left: 18px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
form.active {
|
form>.active {
|
||||||
left: 18px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
pointer-events: auto;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,8 +175,8 @@ export const ITEM_WITH_COMMENTS = gql`
|
||||||
|
|
||||||
export const ITEM_SEARCH = gql`
|
export const ITEM_SEARCH = gql`
|
||||||
${ITEM_FIELDS}
|
${ITEM_FIELDS}
|
||||||
query Search($q: String, $cursor: String) {
|
query Search($q: String, $cursor: String, $sort: String, $what: String, $when: String) {
|
||||||
search(q: $q, cursor: $cursor) {
|
search(q: $q, cursor: $cursor, sort: $sort, what: $what, when: $when) {
|
||||||
cursor
|
cursor
|
||||||
items {
|
items {
|
||||||
...ItemFields
|
...ItemFields
|
||||||
|
|
|
@ -92,7 +92,7 @@ export default function getApolloClient () {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
keyArgs: ['q'],
|
keyArgs: ['q', 'sub', 'sort', 'what', 'when'],
|
||||||
merge (existing, incoming) {
|
merge (existing, incoming) {
|
||||||
if (isFirstPage(incoming.cursor, existing?.items)) {
|
if (isFirstPage(incoming.cursor, existing?.items)) {
|
||||||
return incoming
|
return incoming
|
||||||
|
|
|
@ -15,7 +15,8 @@ export default function Index ({ data: { search: { items, cursor } } }) {
|
||||||
<SeoSearch />
|
<SeoSearch />
|
||||||
{router.query?.q &&
|
{router.query?.q &&
|
||||||
<SearchItems
|
<SearchItems
|
||||||
items={items} cursor={cursor} variables={{ q: router.query?.q }}
|
items={items} cursor={cursor}
|
||||||
|
variables={{ q: router.query?.q, sort: router.query?.sort, what: router.query?.what, when: router.query?.when }}
|
||||||
/>}
|
/>}
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -282,7 +282,6 @@ footer {
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
|
|
||||||
input,
|
input,
|
||||||
select,
|
|
||||||
textarea,
|
textarea,
|
||||||
.form-control,
|
.form-control,
|
||||||
.form-control:focus,
|
.form-control:focus,
|
||||||
|
|
Loading…
Reference in New Issue