feat: Territory autocomplete (#2124)
* feat: Territory autocomplete Closes #992. * refactor: refactor UserSuggest and TerritorySuggest components * style: lint * refactor: unify user and territory autocomplete logic * simplify a bit and fix unrelated onSelect re-query * fix skipping empty string on forward draft population --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
		
							parent
							
								
									d4e3853f27
								
							
						
					
					
						commit
						0edf68cab9
					
				@ -35,6 +35,27 @@ export async function getSub (parent, { name }, { models, me }) {
 | 
			
		||||
export default {
 | 
			
		||||
  Query: {
 | 
			
		||||
    sub: getSub,
 | 
			
		||||
    subSuggestions: async (parent, { q, limit = 5 }, { models }) => {
 | 
			
		||||
      let subs = []
 | 
			
		||||
      if (q) {
 | 
			
		||||
        subs = await models.$queryRaw`
 | 
			
		||||
          SELECT name
 | 
			
		||||
          FROM "Sub"
 | 
			
		||||
          WHERE status = 'ACTIVE'
 | 
			
		||||
          AND SIMILARITY(name, ${q}) > 0.1
 | 
			
		||||
          ORDER BY SIMILARITY(name, ${q}) DESC
 | 
			
		||||
          LIMIT ${limit}`
 | 
			
		||||
      } else {
 | 
			
		||||
        subs = await models.$queryRaw`
 | 
			
		||||
          SELECT name
 | 
			
		||||
          FROM "Sub"
 | 
			
		||||
          WHERE status = 'ACTIVE'
 | 
			
		||||
          ORDER BY name ASC
 | 
			
		||||
          LIMIT ${limit}`
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return subs
 | 
			
		||||
    },
 | 
			
		||||
    subs: async (parent, args, { models, me }) => {
 | 
			
		||||
      if (me) {
 | 
			
		||||
        const currentUser = await models.user.findUnique({ where: { id: me.id } })
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ export default gql`
 | 
			
		||||
    subs: [Sub!]!
 | 
			
		||||
    topSubs(cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
 | 
			
		||||
    userSubs(name: String!, cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
 | 
			
		||||
    subSuggestions(q: String!, limit: Limit): [Sub!]!
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type Subs {
 | 
			
		||||
 | 
			
		||||
@ -198,7 +198,7 @@ export default function AdvPostForm ({ children, item, sub, storageKeyPrefix })
 | 
			
		||||
      for (let i = 0; i < MAX_FORWARDS; i++) {
 | 
			
		||||
        ['nym', 'pct'].forEach(key => {
 | 
			
		||||
          const value = window.localStorage.getItem(`${storageKeyPrefix}-forward[${i}].${key}`)
 | 
			
		||||
          if (value) {
 | 
			
		||||
          if (value !== undefined && value !== null) {
 | 
			
		||||
            formik?.setFieldValue(`forward[${i}].${key}`, value)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import AddIcon from '@/svgs/add-fill.svg'
 | 
			
		||||
import CloseIcon from '@/svgs/close-line.svg'
 | 
			
		||||
import { gql, useLazyQuery } from '@apollo/client'
 | 
			
		||||
import { USER_SUGGESTIONS } from '@/fragments/users'
 | 
			
		||||
import { SUB_SUGGESTIONS } from '@/fragments/subs'
 | 
			
		||||
import TextareaAutosize from 'react-textarea-autosize'
 | 
			
		||||
import { useToast } from './toast'
 | 
			
		||||
import { numWithUnits } from '@/lib/format'
 | 
			
		||||
@ -139,6 +140,94 @@ function setNativeValue (textarea, value) {
 | 
			
		||||
  textarea.dispatchEvent(new Event('input', { bubbles: true, value }))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function useEntityAutocomplete ({
 | 
			
		||||
  prefix,
 | 
			
		||||
  meta,
 | 
			
		||||
  helpers,
 | 
			
		||||
  innerRef,
 | 
			
		||||
  setSelectionRange,
 | 
			
		||||
  SuggestComponent
 | 
			
		||||
}) {
 | 
			
		||||
  const [entityData, setEntityData] = useState()
 | 
			
		||||
 | 
			
		||||
  const handleSelect = useCallback((name) => {
 | 
			
		||||
    if (entityData?.start === undefined || entityData?.end === undefined) return
 | 
			
		||||
    const { start, end } = entityData
 | 
			
		||||
    setEntityData(undefined)
 | 
			
		||||
    const first = `${meta?.value.substring(0, start)}${prefix}${name}`
 | 
			
		||||
    const second = meta?.value.substring(end)
 | 
			
		||||
    const updatedValue = `${first}${second}`
 | 
			
		||||
    helpers.setValue(updatedValue)
 | 
			
		||||
    setSelectionRange({ start: first.length, end: first.length })
 | 
			
		||||
    innerRef.current.focus()
 | 
			
		||||
  }, [entityData, meta?.value, helpers, prefix, setSelectionRange, innerRef])
 | 
			
		||||
 | 
			
		||||
  const handleTextChange = useCallback((e) => {
 | 
			
		||||
    const { value, selectionStart } = e.target
 | 
			
		||||
    if (!value || selectionStart === undefined) {
 | 
			
		||||
      setEntityData(undefined)
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let priorSpace = -1
 | 
			
		||||
    for (let i = selectionStart - 1; i >= 0; i--) {
 | 
			
		||||
      if (/[^\w@~]/.test(value[i])) {
 | 
			
		||||
        priorSpace = i
 | 
			
		||||
        break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let nextSpace = value.length
 | 
			
		||||
    for (let i = selectionStart; i <= value.length; i++) {
 | 
			
		||||
      if (/[^\w]/.test(value[i])) {
 | 
			
		||||
        nextSpace = i
 | 
			
		||||
        break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const currentSegment = value.substring(priorSpace + 1, nextSpace)
 | 
			
		||||
    const regexPattern = new RegExp(`^\\${prefix}\\w*$`)
 | 
			
		||||
 | 
			
		||||
    if (regexPattern.test(currentSegment)) {
 | 
			
		||||
      const { top, left } = textAreaCaret(e.target, e.target.selectionStart)
 | 
			
		||||
      setEntityData({
 | 
			
		||||
        query: currentSegment,
 | 
			
		||||
        start: priorSpace + 1,
 | 
			
		||||
        end: nextSpace,
 | 
			
		||||
        style: {
 | 
			
		||||
          position: 'absolute',
 | 
			
		||||
          top: `${top + Number(window.getComputedStyle(e.target).lineHeight.replace('px', ''))}px`,
 | 
			
		||||
          left: `${left}px`
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setEntityData(undefined)
 | 
			
		||||
    return false
 | 
			
		||||
  }, [prefix])
 | 
			
		||||
 | 
			
		||||
  // Return a function that takes a render prop instead of directly returning the component
 | 
			
		||||
  return {
 | 
			
		||||
    entityData,
 | 
			
		||||
    handleSelect,
 | 
			
		||||
    handleTextChange,
 | 
			
		||||
    renderSuggest: (renderProps) => {
 | 
			
		||||
      if (!entityData) return null
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <SuggestComponent
 | 
			
		||||
          query={entityData?.query}
 | 
			
		||||
          onSelect={handleSelect}
 | 
			
		||||
          dropdownStyle={entityData?.style}
 | 
			
		||||
        >
 | 
			
		||||
          {renderProps}
 | 
			
		||||
        </SuggestComponent>
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKeyDown, innerRef, ...props }) {
 | 
			
		||||
  const [tab, setTab] = useState('write')
 | 
			
		||||
  const [, meta, helpers] = useField(props)
 | 
			
		||||
@ -198,18 +287,23 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKe
 | 
			
		||||
    }
 | 
			
		||||
  }, [innerRef, selectionRange.start, selectionRange.end])
 | 
			
		||||
 | 
			
		||||
  const [mention, setMention] = useState()
 | 
			
		||||
  const insertMention = useCallback((name) => {
 | 
			
		||||
    if (mention?.start === undefined || mention?.end === undefined) return
 | 
			
		||||
    const { start, end } = mention
 | 
			
		||||
    setMention(undefined)
 | 
			
		||||
    const first = `${meta?.value.substring(0, start)}@${name}`
 | 
			
		||||
    const second = meta?.value.substring(end)
 | 
			
		||||
    const updatedValue = `${first}${second}`
 | 
			
		||||
    helpers.setValue(updatedValue)
 | 
			
		||||
    setSelectionRange({ start: first.length, end: first.length })
 | 
			
		||||
    innerRef.current.focus()
 | 
			
		||||
  }, [mention, meta?.value, helpers?.setValue])
 | 
			
		||||
  const userAutocomplete = useEntityAutocomplete({
 | 
			
		||||
    prefix: '@',
 | 
			
		||||
    meta,
 | 
			
		||||
    helpers,
 | 
			
		||||
    innerRef,
 | 
			
		||||
    setSelectionRange,
 | 
			
		||||
    SuggestComponent: UserSuggest
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const territoryAutocomplete = useEntityAutocomplete({
 | 
			
		||||
    prefix: '~',
 | 
			
		||||
    meta,
 | 
			
		||||
    helpers,
 | 
			
		||||
    innerRef,
 | 
			
		||||
    setSelectionRange,
 | 
			
		||||
    SuggestComponent: TerritorySuggest
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const uploadFeesUpdate = useDebounceCallback(
 | 
			
		||||
    (text) => {
 | 
			
		||||
@ -219,50 +313,16 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKe
 | 
			
		||||
 | 
			
		||||
  const onChangeInner = useCallback((formik, e) => {
 | 
			
		||||
    if (onChange) onChange(formik, e)
 | 
			
		||||
    // check for mention editing
 | 
			
		||||
    const { value, selectionStart } = e.target
 | 
			
		||||
    uploadFeesUpdate(value)
 | 
			
		||||
    // check for mentions and territory suggestions
 | 
			
		||||
    uploadFeesUpdate(e.target.value)
 | 
			
		||||
 | 
			
		||||
    if (!value || selectionStart === undefined) {
 | 
			
		||||
      setMention(undefined)
 | 
			
		||||
      return
 | 
			
		||||
    // Try to match user mentions first, then territories
 | 
			
		||||
    if (!userAutocomplete.handleTextChange(e)) {
 | 
			
		||||
      territoryAutocomplete.handleTextChange(e)
 | 
			
		||||
    }
 | 
			
		||||
  }, [onChange, uploadFeesUpdate, userAutocomplete, territoryAutocomplete])
 | 
			
		||||
 | 
			
		||||
    let priorSpace = -1
 | 
			
		||||
    for (let i = selectionStart - 1; i >= 0; i--) {
 | 
			
		||||
      if (/[^\w@]/.test(value[i])) {
 | 
			
		||||
        priorSpace = i
 | 
			
		||||
        break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    let nextSpace = value.length
 | 
			
		||||
    for (let i = selectionStart; i <= value.length; i++) {
 | 
			
		||||
      if (/[^\w]/.test(value[i])) {
 | 
			
		||||
        nextSpace = i
 | 
			
		||||
        break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const currentSegment = value.substring(priorSpace + 1, nextSpace)
 | 
			
		||||
 | 
			
		||||
    // set the query to the current character segment and note where it appears
 | 
			
		||||
    if (/^@\w*$/.test(currentSegment)) {
 | 
			
		||||
      const { top, left } = textAreaCaret(e.target, e.target.selectionStart)
 | 
			
		||||
      setMention({
 | 
			
		||||
        query: currentSegment,
 | 
			
		||||
        start: priorSpace + 1,
 | 
			
		||||
        end: nextSpace,
 | 
			
		||||
        style: {
 | 
			
		||||
          position: 'absolute',
 | 
			
		||||
          top: `${top + Number(window.getComputedStyle(e.target).lineHeight.replace('px', ''))}px`,
 | 
			
		||||
          left: `${left}px`
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      setMention(undefined)
 | 
			
		||||
    }
 | 
			
		||||
  }, [onChange, setMention, uploadFeesUpdate])
 | 
			
		||||
 | 
			
		||||
  const onKeyDownInner = useCallback((userSuggestOnKeyDown) => {
 | 
			
		||||
  const onKeyDownInner = useCallback((userSuggestOnKeyDown, territorySuggestOnKeyDown) => {
 | 
			
		||||
    return (e) => {
 | 
			
		||||
      const metaOrCtrl = e.metaKey || e.ctrlKey
 | 
			
		||||
      if (metaOrCtrl) {
 | 
			
		||||
@ -293,12 +353,16 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKe
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!metaOrCtrl) {
 | 
			
		||||
        userSuggestOnKeyDown(e)
 | 
			
		||||
        if (userAutocomplete.entityData) {
 | 
			
		||||
          userSuggestOnKeyDown(e)
 | 
			
		||||
        } else if (territoryAutocomplete.entityData) {
 | 
			
		||||
          territorySuggestOnKeyDown(e)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (onKeyDown) onKeyDown(e)
 | 
			
		||||
    }
 | 
			
		||||
  }, [innerRef, helpers?.setValue, setSelectionRange, onKeyDown])
 | 
			
		||||
  }, [innerRef, helpers?.setValue, setSelectionRange, onKeyDown, userAutocomplete.entityData, territoryAutocomplete.entityData])
 | 
			
		||||
 | 
			
		||||
  const onPaste = useCallback((event) => {
 | 
			
		||||
    const items = event.clipboardData.items
 | 
			
		||||
@ -409,22 +473,32 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKe
 | 
			
		||||
        </Nav>
 | 
			
		||||
        <div className={`position-relative ${tab === 'write' ? '' : 'd-none'}`}>
 | 
			
		||||
          <UserSuggest
 | 
			
		||||
            query={mention?.query}
 | 
			
		||||
            onSelect={insertMention}
 | 
			
		||||
            dropdownStyle={mention?.style}
 | 
			
		||||
          >{({ onKeyDown: userSuggestOnKeyDown, resetSuggestions }) => (
 | 
			
		||||
            <InputInner
 | 
			
		||||
              innerRef={innerRef}
 | 
			
		||||
              {...props}
 | 
			
		||||
              onChange={onChangeInner}
 | 
			
		||||
              onKeyDown={onKeyDownInner(userSuggestOnKeyDown)}
 | 
			
		||||
              onBlur={() => setTimeout(resetSuggestions, 500)}
 | 
			
		||||
              onDragEnter={onDragEnter}
 | 
			
		||||
              onDragLeave={onDragLeave}
 | 
			
		||||
              onDrop={onDrop}
 | 
			
		||||
              onPaste={onPaste}
 | 
			
		||||
              className={dragStyle === 'over' ? styles.dragOver : ''}
 | 
			
		||||
            />)}
 | 
			
		||||
            query={userAutocomplete.entityData?.query}
 | 
			
		||||
            onSelect={userAutocomplete.handleSelect}
 | 
			
		||||
            dropdownStyle={userAutocomplete.entityData?.style}
 | 
			
		||||
          >{({ onKeyDown: userSuggestOnKeyDown, resetSuggestions: resetUserSuggestions }) => (
 | 
			
		||||
            <TerritorySuggest
 | 
			
		||||
              query={territoryAutocomplete.entityData?.query}
 | 
			
		||||
              onSelect={territoryAutocomplete.handleSelect}
 | 
			
		||||
              dropdownStyle={territoryAutocomplete.entityData?.style}
 | 
			
		||||
            >{({ onKeyDown: territorySuggestOnKeyDown, resetSuggestions: resetTerritorySuggestions }) => (
 | 
			
		||||
              <InputInner
 | 
			
		||||
                innerRef={innerRef}
 | 
			
		||||
                {...props}
 | 
			
		||||
                onChange={onChangeInner}
 | 
			
		||||
                onKeyDown={onKeyDownInner(userSuggestOnKeyDown, territorySuggestOnKeyDown)}
 | 
			
		||||
                onBlur={() => {
 | 
			
		||||
                  setTimeout(resetUserSuggestions, 500)
 | 
			
		||||
                  setTimeout(resetTerritorySuggestions, 500)
 | 
			
		||||
                }}
 | 
			
		||||
                onDragEnter={onDragEnter}
 | 
			
		||||
                onDragLeave={onDragLeave}
 | 
			
		||||
                onDrop={onDrop}
 | 
			
		||||
                onPaste={onPaste}
 | 
			
		||||
                className={dragStyle === 'over' ? styles.dragOver : ''}
 | 
			
		||||
              />)}
 | 
			
		||||
            </TerritorySuggest>
 | 
			
		||||
          )}
 | 
			
		||||
          </UserSuggest>
 | 
			
		||||
        </div>
 | 
			
		||||
        {tab !== 'write' &&
 | 
			
		||||
@ -617,34 +691,34 @@ function InputInner ({
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const INITIAL_SUGGESTIONS = { array: [], index: 0 }
 | 
			
		||||
export function UserSuggest ({
 | 
			
		||||
  query, onSelect, dropdownStyle, children,
 | 
			
		||||
  transformUser = user => user, selectWithTab = true, filterUsers = () => true
 | 
			
		||||
 | 
			
		||||
export function BaseSuggest ({
 | 
			
		||||
  query, onSelect, dropdownStyle,
 | 
			
		||||
  transformItem = item => item, selectWithTab = true, filterItems = () => true,
 | 
			
		||||
  getSuggestionsQuery, queryName, itemsField,
 | 
			
		||||
  children
 | 
			
		||||
}) {
 | 
			
		||||
  const [getSuggestions] = useLazyQuery(USER_SUGGESTIONS, {
 | 
			
		||||
  const [getSuggestions] = useLazyQuery(getSuggestionsQuery, {
 | 
			
		||||
    onCompleted: data => {
 | 
			
		||||
      query !== undefined && setSuggestions({
 | 
			
		||||
        array: data.userSuggestions
 | 
			
		||||
          .filter((...args) => filterUsers(query, ...args))
 | 
			
		||||
          .map(transformUser),
 | 
			
		||||
        array: data[itemsField]
 | 
			
		||||
          .filter((...args) => filterItems(query, ...args))
 | 
			
		||||
          .map(transformItem),
 | 
			
		||||
        index: 0
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS)
 | 
			
		||||
  const resetSuggestions = useCallback(() => setSuggestions(INITIAL_SUGGESTIONS), [])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (query !== undefined) {
 | 
			
		||||
      // remove both the leading @ and any @domain after nym
 | 
			
		||||
      const q = query?.replace(/^[@ ]+|[ ]+$/g, '').replace(/@[^\s]*$/, '')
 | 
			
		||||
      // remove the leading character and any trailing spaces
 | 
			
		||||
      const q = query?.replace(/^[@ ~]+|[ ]+$/g, '').replace(/@[^\s]*$/, '').replace(/~[^\s]*$/, '')
 | 
			
		||||
      getSuggestions({ variables: { q, limit: 5 } })
 | 
			
		||||
    } else {
 | 
			
		||||
      resetSuggestions()
 | 
			
		||||
    }
 | 
			
		||||
  }, [query, resetSuggestions, getSuggestions])
 | 
			
		||||
 | 
			
		||||
  const onKeyDown = useCallback(e => {
 | 
			
		||||
    switch (e.code) {
 | 
			
		||||
      case 'ArrowUp':
 | 
			
		||||
@ -689,7 +763,6 @@ export function UserSuggest ({
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
  }, [onSelect, resetSuggestions, suggestions])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {children?.({ onKeyDown, resetSuggestions })}
 | 
			
		||||
@ -712,17 +785,17 @@ export function UserSuggest ({
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function InputUserSuggest ({
 | 
			
		||||
  label, groupClassName, transformUser, filterUsers,
 | 
			
		||||
  selectWithTab, onChange, transformQuery, ...props
 | 
			
		||||
function BaseInputSuggest ({
 | 
			
		||||
  label, groupClassName, transformItem, filterItems,
 | 
			
		||||
  selectWithTab, onChange, transformQuery, SuggestComponent, prefixRegex, ...props
 | 
			
		||||
}) {
 | 
			
		||||
  const [ovalue, setOValue] = useState()
 | 
			
		||||
  const [query, setQuery] = useState()
 | 
			
		||||
  return (
 | 
			
		||||
    <FormGroup label={label} className={groupClassName}>
 | 
			
		||||
      <UserSuggest
 | 
			
		||||
        transformUser={transformUser}
 | 
			
		||||
        filterUsers={filterUsers}
 | 
			
		||||
      <SuggestComponent
 | 
			
		||||
        transformItem={transformItem}
 | 
			
		||||
        filterItems={filterItems}
 | 
			
		||||
        selectWithTab={selectWithTab}
 | 
			
		||||
        onSelect={(v) => {
 | 
			
		||||
          // HACK ... ovalue does not trigger onChange
 | 
			
		||||
@ -737,19 +810,85 @@ export function InputUserSuggest ({
 | 
			
		||||
            autoComplete='off'
 | 
			
		||||
            onChange={(formik, e) => {
 | 
			
		||||
              onChange && onChange(formik, e)
 | 
			
		||||
              if (e.target.value === ovalue) {
 | 
			
		||||
                // we don't need to set the ovalue or query if the value is the same
 | 
			
		||||
                return
 | 
			
		||||
              }
 | 
			
		||||
              setOValue(e.target.value)
 | 
			
		||||
              setQuery(e.target.value.replace(/^[@ ]+|[ ]+$/g, ''))
 | 
			
		||||
              setQuery(e.target.value.replace(prefixRegex, ''))
 | 
			
		||||
            }}
 | 
			
		||||
            overrideValue={ovalue}
 | 
			
		||||
            onKeyDown={onKeyDown}
 | 
			
		||||
            onBlur={() => setTimeout(resetSuggestions, 500)}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      </UserSuggest>
 | 
			
		||||
      </SuggestComponent>
 | 
			
		||||
    </FormGroup>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function InputUserSuggest ({
 | 
			
		||||
  transformUser, filterUsers, ...props
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <BaseInputSuggest
 | 
			
		||||
      transformItem={transformUser}
 | 
			
		||||
      filterItems={filterUsers}
 | 
			
		||||
      SuggestComponent={UserSuggest}
 | 
			
		||||
      prefixRegex={/^[@ ]+|[ ]+$/g}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function InputTerritorySuggest ({
 | 
			
		||||
  transformSub, filterSubs, ...props
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <BaseInputSuggest
 | 
			
		||||
      transformItem={transformSub}
 | 
			
		||||
      filterItems={filterSubs}
 | 
			
		||||
      SuggestComponent={TerritorySuggest}
 | 
			
		||||
      prefixRegex={/^[~ ]+|[ ]+$/g}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function UserSuggest ({
 | 
			
		||||
  transformUser = user => user, filterUsers = () => true,
 | 
			
		||||
  children, ...props
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <BaseSuggest
 | 
			
		||||
      transformItem={transformUser}
 | 
			
		||||
      filterItems={filterUsers}
 | 
			
		||||
      getSuggestionsQuery={USER_SUGGESTIONS}
 | 
			
		||||
      itemsField='userSuggestions'
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {children}
 | 
			
		||||
    </BaseSuggest>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TerritorySuggest ({
 | 
			
		||||
  transformSub = sub => sub, filterSubs = () => true,
 | 
			
		||||
  children, ...props
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <BaseSuggest
 | 
			
		||||
      transformItem={transformSub}
 | 
			
		||||
      filterItems={filterSubs}
 | 
			
		||||
      getSuggestionsQuery={SUB_SUGGESTIONS}
 | 
			
		||||
      itemsField='subSuggestions'
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      {children}
 | 
			
		||||
    </BaseSuggest>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Input ({ label, groupClassName, under, ...props }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <FormGroup label={label} className={groupClassName}>
 | 
			
		||||
 | 
			
		||||
@ -122,6 +122,14 @@ export const SUB_SEARCH = gql`
 | 
			
		||||
  }
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
export const SUB_SUGGESTIONS = gql`
 | 
			
		||||
  query subSuggestions($q: String!, $limit: Limit) {
 | 
			
		||||
    subSuggestions(q: $q, limit: $limit) {
 | 
			
		||||
      name
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
export const TOP_SUBS = gql`
 | 
			
		||||
  ${SUB_FULL_FIELDS}
 | 
			
		||||
  query TopSubs($cursor: String, $when: String, $from: String, $to: String, $by: String, ) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user