fix? markdown input rerendering more than needed
This commit is contained in:
parent
8ace053be5
commit
a7e016e9ba
@ -91,6 +91,7 @@ export function InputSkeleton ({ label, hint }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_MENTION_INDICES = { start: -1, end: -1 }
|
||||||
export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setHasImgLink, onKeyDown, innerRef, ...props }) {
|
export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setHasImgLink, onKeyDown, innerRef, ...props }) {
|
||||||
const [tab, setTab] = useState('write')
|
const [tab, setTab] = useState('write')
|
||||||
const [, meta, helpers] = useField(props)
|
const [, meta, helpers] = useField(props)
|
||||||
@ -122,7 +123,7 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setH
|
|||||||
}, [innerRef, selectionRange.start, selectionRange.end])
|
}, [innerRef, selectionRange.start, selectionRange.end])
|
||||||
|
|
||||||
const [mentionQuery, setMentionQuery] = useState()
|
const [mentionQuery, setMentionQuery] = useState()
|
||||||
const [mentionIndices, setMentionIndices] = useState({ start: -1, end: -1 })
|
const [mentionIndices, setMentionIndices] = useState(DEFAULT_MENTION_INDICES)
|
||||||
const [userSuggestDropdownStyle, setUserSuggestDropdownStyle] = useState({})
|
const [userSuggestDropdownStyle, setUserSuggestDropdownStyle] = useState({})
|
||||||
const insertMention = useCallback((name) => {
|
const insertMention = useCallback((name) => {
|
||||||
const { start, end } = mentionIndices
|
const { start, end } = mentionIndices
|
||||||
@ -133,33 +134,9 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setH
|
|||||||
helpers.setValue(updatedValue)
|
helpers.setValue(updatedValue)
|
||||||
setSelectionRange({ start: first.length, end: first.length })
|
setSelectionRange({ start: first.length, end: first.length })
|
||||||
innerRef.current.focus()
|
innerRef.current.focus()
|
||||||
}, [mentionIndices, innerRef, helpers])
|
}, [mentionIndices, innerRef, helpers?.setValue])
|
||||||
|
|
||||||
return (
|
const onChangeInner = useCallback((formik, e) => {
|
||||||
<FormGroup label={label} className={groupClassName}>
|
|
||||||
<div className={`${styles.markdownInput} ${tab === 'write' ? styles.noTopLeftRadius : ''}`}>
|
|
||||||
<Nav variant='tabs' defaultActiveKey='write' activeKey={tab} onSelect={tab => setTab(tab)}>
|
|
||||||
<Nav.Item>
|
|
||||||
<Nav.Link eventKey='write'>write</Nav.Link>
|
|
||||||
</Nav.Item>
|
|
||||||
<Nav.Item>
|
|
||||||
<Nav.Link eventKey='preview' disabled={!meta.value}>preview</Nav.Link>
|
|
||||||
</Nav.Item>
|
|
||||||
<a
|
|
||||||
className='ms-auto text-muted d-flex align-items-center'
|
|
||||||
href='https://guides.github.com/features/mastering-markdown/' target='_blank' rel='noreferrer'
|
|
||||||
>
|
|
||||||
<Markdown width={18} height={18} />
|
|
||||||
</a>
|
|
||||||
</Nav>
|
|
||||||
<div className={`position-relative ${tab === 'write' ? '' : 'd-none'}`}>
|
|
||||||
<UserSuggest
|
|
||||||
query={mentionQuery}
|
|
||||||
onSelect={insertMention}
|
|
||||||
dropdownStyle={userSuggestDropdownStyle}
|
|
||||||
>{({ onKeyDown: userSuggestOnKeyDown }) => (
|
|
||||||
<InputInner
|
|
||||||
{...props} onChange={(formik, e) => {
|
|
||||||
if (onChange) onChange(formik, e)
|
if (onChange) onChange(formik, e)
|
||||||
if (setHasImgLink) {
|
if (setHasImgLink) {
|
||||||
setHasImgLink(mdHas(e.target.value, ['link', 'image']))
|
setHasImgLink(mdHas(e.target.value, ['link', 'image']))
|
||||||
@ -186,20 +163,20 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setH
|
|||||||
if (/^@[\w_]*$/.test(currentSegment)) {
|
if (/^@[\w_]*$/.test(currentSegment)) {
|
||||||
setMentionQuery(currentSegment)
|
setMentionQuery(currentSegment)
|
||||||
setMentionIndices({ start: priorSpace + 1, end: nextSpace })
|
setMentionIndices({ start: priorSpace + 1, end: nextSpace })
|
||||||
} else {
|
|
||||||
setMentionQuery(undefined)
|
|
||||||
setMentionIndices({ start: -1, end: -1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const { top, left } = textAreaCaret(e.target, e.target.selectionStart)
|
const { top, left } = textAreaCaret(e.target, e.target.selectionStart)
|
||||||
setUserSuggestDropdownStyle({
|
setUserSuggestDropdownStyle({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: `${top + Number(window.getComputedStyle(e.target).lineHeight.replace('px', ''))}px`,
|
top: `${top + Number(window.getComputedStyle(e.target).lineHeight.replace('px', ''))}px`,
|
||||||
left: `${left}px`
|
left: `${left}px`
|
||||||
})
|
})
|
||||||
}}
|
} else {
|
||||||
innerRef={innerRef}
|
setMentionQuery(undefined)
|
||||||
onKeyDown={(e) => {
|
setMentionIndices(DEFAULT_MENTION_INDICES)
|
||||||
|
}
|
||||||
|
}, [onChange, setHasImgLink, setMentionQuery, setMentionIndices, setUserSuggestDropdownStyle])
|
||||||
|
|
||||||
|
const onKeyDownInner = useCallback((userSuggestOnKeyDown) => {
|
||||||
|
return (e) => {
|
||||||
const metaOrCtrl = e.metaKey || e.ctrlKey
|
const metaOrCtrl = e.metaKey || e.ctrlKey
|
||||||
if (metaOrCtrl) {
|
if (metaOrCtrl) {
|
||||||
if (e.key === 'k') {
|
if (e.key === 'k') {
|
||||||
@ -228,7 +205,37 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, setH
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onKeyDown) onKeyDown(e)
|
if (onKeyDown) onKeyDown(e)
|
||||||
}}
|
}
|
||||||
|
}, [innerRef, helpers?.setValue, setSelectionRange, onKeyDown])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormGroup label={label} className={groupClassName}>
|
||||||
|
<div className={`${styles.markdownInput} ${tab === 'write' ? styles.noTopLeftRadius : ''}`}>
|
||||||
|
<Nav variant='tabs' defaultActiveKey='write' activeKey={tab} onSelect={tab => setTab(tab)}>
|
||||||
|
<Nav.Item>
|
||||||
|
<Nav.Link eventKey='write'>write</Nav.Link>
|
||||||
|
</Nav.Item>
|
||||||
|
<Nav.Item>
|
||||||
|
<Nav.Link eventKey='preview' disabled={!meta.value}>preview</Nav.Link>
|
||||||
|
</Nav.Item>
|
||||||
|
<a
|
||||||
|
className='ms-auto text-muted d-flex align-items-center'
|
||||||
|
href='https://guides.github.com/features/mastering-markdown/' target='_blank' rel='noreferrer'
|
||||||
|
>
|
||||||
|
<Markdown width={18} height={18} />
|
||||||
|
</a>
|
||||||
|
</Nav>
|
||||||
|
<div className={`position-relative ${tab === 'write' ? '' : 'd-none'}`}>
|
||||||
|
<UserSuggest
|
||||||
|
query={mentionQuery}
|
||||||
|
onSelect={insertMention}
|
||||||
|
dropdownStyle={userSuggestDropdownStyle}
|
||||||
|
>{({ onKeyDown: userSuggestOnKeyDown }) => (
|
||||||
|
<InputInner
|
||||||
|
innerRef={innerRef}
|
||||||
|
{...props}
|
||||||
|
onChange={onChangeInner}
|
||||||
|
onKeyDown={onKeyDownInner(userSuggestOnKeyDown)}
|
||||||
/>)}
|
/>)}
|
||||||
</UserSuggest>
|
</UserSuggest>
|
||||||
</div>
|
</div>
|
||||||
@ -300,6 +307,32 @@ function InputInner ({
|
|||||||
|
|
||||||
const storageKey = storageKeyPrefix ? storageKeyPrefix + '-' + props.name : undefined
|
const storageKey = storageKeyPrefix ? storageKeyPrefix + '-' + props.name : undefined
|
||||||
|
|
||||||
|
const onKeyDownInner = useCallback((e) => {
|
||||||
|
const metaOrCtrl = e.metaKey || e.ctrlKey
|
||||||
|
if (metaOrCtrl) {
|
||||||
|
if (e.key === 'Enter') formik?.submitForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onKeyDown) onKeyDown(e)
|
||||||
|
}, [formik?.submitForm, onKeyDown])
|
||||||
|
|
||||||
|
const onChangeInner = useCallback((e) => {
|
||||||
|
field?.onChange(e)
|
||||||
|
|
||||||
|
if (storageKey) {
|
||||||
|
window.localStorage.setItem(storageKey, e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
onChange(formik, e)
|
||||||
|
}
|
||||||
|
}, [field?.onChange, storageKey, onChange])
|
||||||
|
|
||||||
|
const onBlurInner = useCallback((e) => {
|
||||||
|
field?.onBlur?.(e)
|
||||||
|
onBlur && onBlur(e)
|
||||||
|
}, [field?.onBlur, onBlur])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (overrideValue) {
|
if (overrideValue) {
|
||||||
helpers.setValue(overrideValue)
|
helpers.setValue(overrideValue)
|
||||||
@ -331,31 +364,12 @@ function InputInner ({
|
|||||||
<InputGroup hasValidation className={inputGroupClassName}>
|
<InputGroup hasValidation className={inputGroupClassName}>
|
||||||
{prepend}
|
{prepend}
|
||||||
<BootstrapForm.Control
|
<BootstrapForm.Control
|
||||||
onKeyDown={(e) => {
|
|
||||||
const metaOrCtrl = e.metaKey || e.ctrlKey
|
|
||||||
if (metaOrCtrl) {
|
|
||||||
if (e.key === 'Enter') formik?.submitForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onKeyDown) onKeyDown(e)
|
|
||||||
}}
|
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
{...field} {...props}
|
{...field}
|
||||||
onChange={(e) => {
|
{...props}
|
||||||
field.onChange(e)
|
onKeyDown={onKeyDownInner}
|
||||||
|
onChange={onChangeInner}
|
||||||
if (storageKey) {
|
onBlur={onBlurInner}
|
||||||
window.localStorage.setItem(storageKey, e.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onChange) {
|
|
||||||
onChange(formik, e)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onBlur={(e) => {
|
|
||||||
field.onBlur?.(e)
|
|
||||||
onBlur && onBlur(e)
|
|
||||||
}}
|
|
||||||
isInvalid={invalid}
|
isInvalid={invalid}
|
||||||
isValid={showValid && meta.initialValue !== meta.value && meta.touched && !meta.error}
|
isValid={showValid && meta.initialValue !== meta.value && meta.touched && !meta.error}
|
||||||
/>
|
/>
|
||||||
@ -393,6 +407,7 @@ function InputInner ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INITIAL_SUGGESTIONS = { array: [], index: 0 }
|
||||||
export function UserSuggest ({ query, onSelect, dropdownStyle, children }) {
|
export function UserSuggest ({ query, onSelect, dropdownStyle, children }) {
|
||||||
const [getUsers] = useLazyQuery(TOP_USERS, {
|
const [getUsers] = useLazyQuery(TOP_USERS, {
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
@ -405,7 +420,6 @@ export function UserSuggest ({ query, onSelect, dropdownStyle, children }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const INITIAL_SUGGESTIONS = { array: [], index: 0 }
|
|
||||||
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS)
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS)
|
||||||
const resetSuggestions = useCallback(() => setSuggestions(INITIAL_SUGGESTIONS), [])
|
const resetSuggestions = useCallback(() => setSuggestions(INITIAL_SUGGESTIONS), [])
|
||||||
|
|
||||||
@ -602,7 +616,7 @@ export function Form ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
function clearLocalStorage (values) {
|
const clearLocalStorage = useCallback((values) => {
|
||||||
Object.keys(values).forEach(v => {
|
Object.keys(values).forEach(v => {
|
||||||
window.localStorage.removeItem(storageKeyPrefix + '-' + v)
|
window.localStorage.removeItem(storageKeyPrefix + '-' + v)
|
||||||
if (Array.isArray(values[v])) {
|
if (Array.isArray(values[v])) {
|
||||||
@ -615,7 +629,7 @@ export function Form ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}, [storageKeyPrefix])
|
||||||
|
|
||||||
// if `invoiceable` is set,
|
// if `invoiceable` is set,
|
||||||
// support for payment per invoice if they are lurking or don't have enough balance
|
// support for payment per invoice if they are lurking or don't have enough balance
|
||||||
@ -627,14 +641,7 @@ export function Form ({
|
|||||||
onSubmit = useInvoiceable(onSubmit, { callback: clearLocalStorage, ...options })
|
onSubmit = useInvoiceable(onSubmit, { callback: clearLocalStorage, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const onSubmitInner = useCallback(async (values, ...args) => {
|
||||||
<Formik
|
|
||||||
initialValues={initial}
|
|
||||||
validateOnChange={validateOnChange}
|
|
||||||
validationSchema={schema}
|
|
||||||
initialTouched={validateImmediately && initial}
|
|
||||||
validateOnBlur={false}
|
|
||||||
onSubmit={async (values, ...args) => {
|
|
||||||
try {
|
try {
|
||||||
if (onSubmit) {
|
if (onSubmit) {
|
||||||
const options = await onSubmit(values, ...args)
|
const options = await onSubmit(values, ...args)
|
||||||
@ -645,7 +652,16 @@ export function Form ({
|
|||||||
console.log(err)
|
console.log(err)
|
||||||
toaster.danger(err.message || err.toString?.())
|
toaster.danger(err.message || err.toString?.())
|
||||||
}
|
}
|
||||||
}}
|
}, [onSubmit, toaster, clearLocalStorage, storageKeyPrefix])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={initial}
|
||||||
|
validateOnChange={validateOnChange}
|
||||||
|
validationSchema={schema}
|
||||||
|
initialTouched={validateImmediately && initial}
|
||||||
|
validateOnBlur={false}
|
||||||
|
onSubmit={onSubmitInner}
|
||||||
innerRef={innerRef}
|
innerRef={innerRef}
|
||||||
>
|
>
|
||||||
<FormikForm {...props} noValidate>
|
<FormikForm {...props} noValidate>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user