1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-18 20:59:44 +02:00

fix: Save description when clicking outside

This commit is contained in:
Maksim Eltyshev 2024-10-31 14:56:11 +01:00
parent f75b0237d3
commit d9e8c24c3f
5 changed files with 106 additions and 12 deletions

View file

@ -1,8 +1,11 @@
import React, { useCallback, useImperativeHandle, useMemo, useState } from 'react'; import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form } from 'semantic-ui-react'; import { Button, Form } from 'semantic-ui-react';
import SimpleMDE from 'react-simplemde-editor'; import SimpleMDE from 'react-simplemde-editor';
import { useClickAwayListener } from '../../lib/hooks';
import { useNestedRef } from '../../hooks';
import styles from './DescriptionEdit.module.scss'; import styles from './DescriptionEdit.module.scss';
@ -11,6 +14,10 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
const [isOpened, setIsOpened] = useState(false); const [isOpened, setIsOpened] = useState(false);
const [value, setValue] = useState(null); const [value, setValue] = useState(null);
const editorWrapperRef = useRef(null);
const codemirrorRef = useRef(null);
const [buttonRef, handleButtonRef] = useNestedRef();
const open = useCallback(() => { const open = useCallback(() => {
setIsOpened(true); setIsOpened(true);
setValue(defaultValue || ''); setValue(defaultValue || '');
@ -55,6 +62,28 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
close(); close();
}, [close]); }, [close]);
const handleAwayClick = useCallback(() => {
if (!isOpened) {
return;
}
close();
}, [isOpened, close]);
const handleClickAwayCancel = useCallback(() => {
codemirrorRef.current.focus();
}, []);
const clickAwayProps = useClickAwayListener(
[editorWrapperRef, buttonRef],
handleAwayClick,
handleClickAwayCancel,
);
const handleGetCodemirrorInstance = useCallback((codemirror) => {
codemirrorRef.current = codemirror;
}, []);
const mdEditorOptions = useMemo( const mdEditorOptions = useMemo(
() => ({ () => ({
autoDownloadFontAwesome: false, autoDownloadFontAwesome: false,
@ -92,16 +121,20 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<SimpleMDE {/* eslint-disable-next-line react/jsx-props-no-spreading */}
value={value} <div {...clickAwayProps} ref={editorWrapperRef}>
options={mdEditorOptions} <SimpleMDE
placeholder={t('common.enterDescription')} value={value}
className={styles.field} options={mdEditorOptions}
onKeyDown={handleFieldKeyDown} placeholder={t('common.enterDescription')}
onChange={setValue} className={styles.field}
/> getCodemirrorInstance={handleGetCodemirrorInstance}
onKeyDown={handleFieldKeyDown}
onChange={setValue}
/>
</div>
<div className={styles.controls}> <div className={styles.controls}>
<Button positive content={t('action.save')} /> <Button positive ref={handleButtonRef} content={t('action.save')} />
</div> </div>
</Form> </Form>
); );

View file

@ -1,7 +1,8 @@
import useNestedRef from './use-nested-ref';
import useField from './use-field'; import useField from './use-field';
import useForm from './use-form'; import useForm from './use-form';
import useSteps from './use-steps'; import useSteps from './use-steps';
import useModal from './use-modal'; import useModal from './use-modal';
import useClosableForm from './use-closable-form'; import useClosableForm from './use-closable-form';
export { useField, useForm, useSteps, useModal, useClosableForm }; export { useNestedRef, useField, useForm, useSteps, useModal, useClosableForm };

View file

@ -0,0 +1,14 @@
import { useCallback, useRef } from 'react';
export default (nestedRefName = 'ref') => {
const ref = useRef(null);
const handleRef = useCallback(
(element) => {
ref.current = element?.[nestedRefName].current;
},
[nestedRefName],
);
return [ref, handleRef];
};

View file

@ -2,5 +2,6 @@ import usePrevious from './use-previous';
import useToggle from './use-toggle'; import useToggle from './use-toggle';
import useForceUpdate from './use-force-update'; import useForceUpdate from './use-force-update';
import useDidUpdate from './use-did-update'; import useDidUpdate from './use-did-update';
import useClickAwayListener from './use-click-away-listener';
export { usePrevious, useToggle, useForceUpdate, useDidUpdate }; export { usePrevious, useToggle, useForceUpdate, useDidUpdate, useClickAwayListener };

View file

@ -0,0 +1,45 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
export default (elementRefs, onAwayClick, onCancel) => {
const pressedElement = useRef(null);
const handlePress = useCallback((event) => {
pressedElement.current = event.target;
}, []);
useEffect(() => {
const handleEvent = (event) => {
const element = elementRefs.find(({ current }) => current?.contains(event.target))?.current;
if (element) {
if (!pressedElement.current || pressedElement.current !== element) {
onCancel();
}
} else if (pressedElement.current) {
onCancel();
} else {
onAwayClick();
}
pressedElement.current = null;
};
document.addEventListener('mouseup', handleEvent, true);
document.addEventListener('touchend', handleEvent, true);
return () => {
document.removeEventListener('mouseup', handleEvent, true);
document.removeEventListener('touchend', handleEvent, true);
};
}, [onAwayClick, onCancel]); // eslint-disable-line react-hooks/exhaustive-deps
const props = useMemo(
() => ({
onMouseDown: handlePress,
onTouchStart: handlePress,
}),
[handlePress],
);
return props;
};