user suggestions on forward
This commit is contained in:
parent
016e357ebd
commit
a5d1d8dc0f
|
@ -134,6 +134,10 @@ export default {
|
||||||
cursor: users.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
cursor: users.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
||||||
users
|
users
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
searchUsers: async (parent, { name }, { models }) => {
|
||||||
|
return await models.$queryRaw`
|
||||||
|
SELECT * FROM users where id > 615 AND SIMILARITY(name, ${name}) > .1 ORDER BY SIMILARITY(name, ${name}) DESC LIMIT 5`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default gql`
|
||||||
users: [User!]
|
users: [User!]
|
||||||
nameAvailable(name: String!): Boolean!
|
nameAvailable(name: String!): Boolean!
|
||||||
topUsers(cursor: String, within: String!, userType: String!): TopUsers
|
topUsers(cursor: String, within: String!, userType: String!): TopUsers
|
||||||
|
searchUsers(name: String!): [User!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users {
|
type Users {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import AccordianItem from './accordian-item'
|
import AccordianItem from './accordian-item'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { Input } from './form'
|
import { Input, InputUserSuggest } from './form'
|
||||||
import { InputGroup } from 'react-bootstrap'
|
import { InputGroup } from 'react-bootstrap'
|
||||||
import { BOOST_MIN } from '../lib/constants'
|
import { BOOST_MIN } from '../lib/constants'
|
||||||
import { NAME_QUERY } from '../fragments/users'
|
import { NAME_QUERY } from '../fragments/users'
|
||||||
|
@ -69,7 +69,7 @@ export default function AdvPostForm ({ edit }) {
|
||||||
hint={<span className='text-muted'>ranks posts higher temporarily based on the amount</span>}
|
hint={<span className='text-muted'>ranks posts higher temporarily based on the amount</span>}
|
||||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||||
/>
|
/>
|
||||||
<Input
|
<InputUserSuggest
|
||||||
label={<>forward sats to</>}
|
label={<>forward sats to</>}
|
||||||
name='forward'
|
name='forward'
|
||||||
hint={<span className='text-muted'>100% of sats will be sent to this user</span>}
|
hint={<span className='text-muted'>100% of sats will be sent to this user</span>}
|
||||||
|
|
|
@ -41,6 +41,7 @@ export function DiscussionForm ({
|
||||||
initial={{
|
initial={{
|
||||||
title: item?.title || '',
|
title: item?.title || '',
|
||||||
text: item?.text || '',
|
text: item?.text || '',
|
||||||
|
suggest: '',
|
||||||
...AdvPostInitial({ forward: item?.fwdUser?.name })
|
...AdvPostInitial({ forward: item?.fwdUser?.name })
|
||||||
}}
|
}}
|
||||||
schema={DiscussionSchema}
|
schema={DiscussionSchema}
|
||||||
|
|
|
@ -6,13 +6,15 @@ import { Formik, Form as FormikForm, useFormikContext, useField, FieldArray } fr
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import copy from 'clipboard-copy'
|
import copy from 'clipboard-copy'
|
||||||
import Thumb from '../svgs/thumb-up-fill.svg'
|
import Thumb from '../svgs/thumb-up-fill.svg'
|
||||||
import { Col, Nav } from 'react-bootstrap'
|
import { Col, Dropdown, Nav } from 'react-bootstrap'
|
||||||
import Markdown from '../svgs/markdown-line.svg'
|
import Markdown from '../svgs/markdown-line.svg'
|
||||||
import styles from './form.module.css'
|
import styles from './form.module.css'
|
||||||
import Text from '../components/text'
|
import Text from '../components/text'
|
||||||
import AddIcon from '../svgs/add-fill.svg'
|
import AddIcon from '../svgs/add-fill.svg'
|
||||||
import { mdHas } from '../lib/md'
|
import { mdHas } from '../lib/md'
|
||||||
import CloseIcon from '../svgs/close-line.svg'
|
import CloseIcon from '../svgs/close-line.svg'
|
||||||
|
import { useLazyQuery } from '@apollo/client'
|
||||||
|
import { USER_SEARCH } from '../fragments/users'
|
||||||
|
|
||||||
export function SubmitButton ({
|
export function SubmitButton ({
|
||||||
children, variant, value, onClick, ...props
|
children, variant, value, onClick, ...props
|
||||||
|
@ -130,7 +132,7 @@ function FormGroup ({ className, label, children }) {
|
||||||
|
|
||||||
function InputInner ({
|
function InputInner ({
|
||||||
prepend, append, hint, showValid, onChange, overrideValue,
|
prepend, append, hint, showValid, onChange, overrideValue,
|
||||||
innerRef, storageKeyPrefix, noForm, clear, ...props
|
innerRef, storageKeyPrefix, noForm, clear, onKeyDown, ...props
|
||||||
}) {
|
}) {
|
||||||
const [field, meta, helpers] = noForm ? [{}, {}, {}] : useField(props)
|
const [field, meta, helpers] = noForm ? [{}, {}, {}] : useField(props)
|
||||||
const formik = noForm ? null : useFormikContext()
|
const formik = noForm ? null : useFormikContext()
|
||||||
|
@ -168,6 +170,7 @@ function InputInner ({
|
||||||
if (e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
|
if (e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
|
||||||
formik?.submitForm()
|
formik?.submitForm()
|
||||||
}
|
}
|
||||||
|
if (onKeyDown) onKeyDown(e)
|
||||||
}}
|
}}
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
{...field} {...props}
|
{...field} {...props}
|
||||||
|
@ -215,6 +218,76 @@ function InputInner ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function InputUserSuggest ({ label, groupClassName, ...props }) {
|
||||||
|
const [getSuggestions] = useLazyQuery(USER_SEARCH, {
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
onCompleted: data => {
|
||||||
|
setSuggestions({ array: data.searchUsers, index: 0 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const INITIAL_SUGGESTIONS = { array: [], index: 0 }
|
||||||
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS)
|
||||||
|
const [ovalue, setOValue] = useState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup label={label} className={groupClassName}>
|
||||||
|
<InputInner
|
||||||
|
{...props}
|
||||||
|
autoComplete='off'
|
||||||
|
onChange={(_, e) => getSuggestions({ variables: { name: e.target.value } })}
|
||||||
|
overrideValue={ovalue}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault()
|
||||||
|
setSuggestions(
|
||||||
|
{
|
||||||
|
...suggestions,
|
||||||
|
index: Math.max(suggestions.index - 1, 0)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault()
|
||||||
|
setSuggestions(
|
||||||
|
{
|
||||||
|
...suggestions,
|
||||||
|
index: Math.min(suggestions.index + 1, suggestions.array.length - 1)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'Enter':
|
||||||
|
e.preventDefault()
|
||||||
|
setOValue(suggestions.array[suggestions.index].name)
|
||||||
|
setSuggestions(INITIAL_SUGGESTIONS)
|
||||||
|
break
|
||||||
|
case 'Escape':
|
||||||
|
e.preventDefault()
|
||||||
|
setSuggestions(INITIAL_SUGGESTIONS)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Dropdown show={suggestions.array.length > 0}>
|
||||||
|
<Dropdown.Menu className={styles.suggestionsMenu}>
|
||||||
|
{suggestions.array.map((v, i) =>
|
||||||
|
<Dropdown.Item
|
||||||
|
key={v.name}
|
||||||
|
active={suggestions.index === i}
|
||||||
|
onClick={() => {
|
||||||
|
setOValue(v.name)
|
||||||
|
setSuggestions(INITIAL_SUGGESTIONS)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{v.name}
|
||||||
|
</Dropdown.Item>)}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
</FormGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function Input ({ label, groupClassName, ...props }) {
|
export function Input ({ label, groupClassName, ...props }) {
|
||||||
return (
|
return (
|
||||||
<FormGroup label={label} className={groupClassName}>
|
<FormGroup label={label} className={groupClassName}>
|
||||||
|
|
|
@ -22,4 +22,10 @@
|
||||||
|
|
||||||
.clearButton.isInvalid {
|
.clearButton.isInvalid {
|
||||||
border-color: #c03221;
|
border-color: #c03221;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://github.com/react-bootstrap/react-bootstrap/issues/5475 */
|
||||||
|
.suggestionsMenu {
|
||||||
|
opacity: 1 !important;
|
||||||
|
pointer-events: unset !important;
|
||||||
}
|
}
|
|
@ -50,7 +50,8 @@ export default function Search ({ sub }) {
|
||||||
required
|
required
|
||||||
autoFocus={showSearch && !atBottom}
|
autoFocus={showSearch && !atBottom}
|
||||||
groupClassName='mr-3 mb-0 flex-grow-1'
|
groupClassName='mr-3 mb-0 flex-grow-1'
|
||||||
className='w-100'
|
className='flex-grow-1'
|
||||||
|
clear
|
||||||
onChange={async (formik, e) => {
|
onChange={async (formik, e) => {
|
||||||
setSearching(true)
|
setSearching(true)
|
||||||
setQ(e.target.value?.trim())
|
setQ(e.target.value?.trim())
|
||||||
|
|
|
@ -89,6 +89,14 @@ gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const USER_SEARCH =
|
||||||
|
gql`
|
||||||
|
query searchUsers($name: String!) {
|
||||||
|
searchUsers(name: $name) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
export const USER_FIELDS = gql`
|
export const USER_FIELDS = gql`
|
||||||
${ITEM_FIELDS}
|
${ITEM_FIELDS}
|
||||||
fragment UserFields on User {
|
fragment UserFields on User {
|
||||||
|
|
|
@ -87,4 +87,4 @@
|
||||||
"eslint-plugin-compat": "^3.9.0",
|
"eslint-plugin-compat": "^3.9.0",
|
||||||
"standard": "^16.0.3"
|
"standard": "^16.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
CREATE EXTENSION pg_trgm;
|
Loading…
Reference in New Issue