mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added format buttons
This commit is contained in:
parent
3fdbf6208b
commit
5cf3d22466
9 changed files with 158 additions and 15 deletions
|
@ -1,4 +1,12 @@
|
|||
import React, { FC, KeyboardEventHandler, memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import React, {
|
||||
FC,
|
||||
KeyboardEventHandler,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Textarea } from '~/components/input/Textarea';
|
||||
import styles from './styles.module.scss';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
|
@ -19,8 +27,9 @@ import { getFileType } from '~/utils/uploader';
|
|||
import { useRandomPhrase } from '~/constants/phrases';
|
||||
import { ERROR_LITERAL } from '~/constants/errors';
|
||||
import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches';
|
||||
import { CommentFormAttachButtons } from '~/components/comment/CommentFormButtons';
|
||||
import { CommentFormAttachButtons } from '~/components/comment/CommentFormAttachButtons';
|
||||
import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone';
|
||||
import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons';
|
||||
|
||||
const mapStateToProps = (state: IState) => ({
|
||||
node: selectNode(state),
|
||||
|
@ -51,12 +60,11 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
uploadUploadFiles,
|
||||
nodeCancelCommentEdit,
|
||||
}) => {
|
||||
const [textarea, setTextarea] = useState<HTMLTextAreaElement>();
|
||||
const comment = useMemo(() => comment_data[id], [comment_data, id]);
|
||||
|
||||
const onUpload = useCallback(
|
||||
(files: File[]) => {
|
||||
console.log(files);
|
||||
|
||||
const items: IFileWithUUID[] = files.map(
|
||||
(file: File): IFileWithUUID => ({
|
||||
file,
|
||||
|
@ -182,6 +190,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
disabled={is_sending_comment}
|
||||
placeholder={placeholder}
|
||||
minRows={2}
|
||||
setRef={setTextarea}
|
||||
/>
|
||||
|
||||
{comment.error && (
|
||||
|
@ -203,6 +212,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
|||
|
||||
<Group horizontal className={styles.buttons}>
|
||||
<CommentFormAttachButtons onUpload={onUpload} />
|
||||
<CommentFormFormatButtons element={textarea} handler={onInput} />
|
||||
|
||||
<Filler />
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
background: transparentize(black, 0.8);
|
||||
padding: $gap / 2;
|
||||
border-radius: 0 0 $radius $radius;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.uploads {
|
||||
|
|
59
src/components/comment/CommentFormFormatButtons/index.tsx
Normal file
59
src/components/comment/CommentFormFormatButtons/index.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import { ButtonGroup } from '~/components/input/ButtonGroup';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { useFormatWrapper } from '~/utils/hooks/useFormatWrapper';
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
interface IProps {
|
||||
element: HTMLTextAreaElement;
|
||||
handler: (val: string) => void;
|
||||
}
|
||||
|
||||
const CommentFormFormatButtons: FC<IProps> = ({ element, handler }) => {
|
||||
const wrap = useCallback(
|
||||
(prefix = '', suffix = '') => useFormatWrapper(element, handler, prefix, suffix),
|
||||
[element, handler]
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonGroup className={styles.wrap}>
|
||||
<Button
|
||||
onClick={wrap('**', '**')}
|
||||
iconLeft="bold"
|
||||
size="small"
|
||||
color="gray"
|
||||
iconOnly
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={wrap('*', '*')}
|
||||
iconLeft="italic"
|
||||
size="small"
|
||||
color="gray"
|
||||
iconOnly
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={wrap('## ', '')}
|
||||
iconLeft="title"
|
||||
size="small"
|
||||
color="gray"
|
||||
iconOnly
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={wrap('[ссылка](', ')')}
|
||||
iconLeft="link"
|
||||
size="small"
|
||||
color="gray"
|
||||
iconOnly
|
||||
type="button"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export { CommentFormFormatButtons };
|
|
@ -0,0 +1,8 @@
|
|||
@import "~/styles/variables.scss";
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import React, { HTMLAttributes } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type IProps = HTMLAttributes<HTMLDivElement> & {};
|
||||
|
||||
export const ButtonGroup = ({ children }: IProps) => <div className={styles.wrap}>{children}</div>;
|
||||
export const ButtonGroup = ({ children, className }: IProps) => (
|
||||
<div className={classNames(styles.wrap, className)}>{children}</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {
|
||||
ChangeEvent,
|
||||
LegacyRef,
|
||||
DetailedHTMLProps,
|
||||
memo,
|
||||
TextareaHTMLAttributes,
|
||||
useCallback,
|
||||
|
@ -14,7 +14,10 @@ import autosize from 'autosize';
|
|||
import styles from '~/styles/common/inputs.module.scss';
|
||||
import { Icon } from '../Icon';
|
||||
|
||||
type IProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||
type IProps = DetailedHTMLProps<
|
||||
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||
HTMLTextAreaElement
|
||||
> & {
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
|
@ -26,6 +29,7 @@ type IProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|||
status?: 'error' | 'success' | '';
|
||||
title?: string;
|
||||
seamless?: boolean;
|
||||
setRef?: (r: HTMLTextAreaElement) => void;
|
||||
};
|
||||
|
||||
const Textarea = memo<IProps>(
|
||||
|
@ -40,12 +44,12 @@ const Textarea = memo<IProps>(
|
|||
status = '',
|
||||
seamless,
|
||||
value,
|
||||
setRef,
|
||||
...props
|
||||
}) => {
|
||||
const [rows, setRows] = useState(minRows || 1);
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
const textarea: LegacyRef<HTMLTextAreaElement> = useRef(null);
|
||||
const ref = useRef<HTMLTextAreaElement>();
|
||||
|
||||
const onInput = useCallback(
|
||||
({ target }: ChangeEvent<HTMLTextAreaElement>) => handler(target.value),
|
||||
|
@ -56,12 +60,13 @@ const Textarea = memo<IProps>(
|
|||
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!textarea.current) return;
|
||||
if (!ref.current) return;
|
||||
|
||||
autosize(textarea.current);
|
||||
autosize(ref.current);
|
||||
setRef(ref.current);
|
||||
|
||||
return () => autosize.destroy(textarea.current);
|
||||
}, [textarea.current]);
|
||||
return () => autosize.destroy(ref.current);
|
||||
}, [ref.current]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -80,7 +85,7 @@ const Textarea = memo<IProps>(
|
|||
placeholder={placeholder}
|
||||
className={classNames(styles.textarea, className)}
|
||||
onChange={onInput}
|
||||
ref={textarea}
|
||||
ref={ref}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
style={{
|
||||
|
|
|
@ -212,6 +212,26 @@ const Sprites: FC<{}> = () => (
|
|||
<path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z" />
|
||||
</g>
|
||||
|
||||
<g id="bold" stroke="none">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" />
|
||||
</g>
|
||||
|
||||
<g id="italic" stroke="none">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" />
|
||||
</g>
|
||||
|
||||
<g id="link" stroke="none">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" />
|
||||
</g>
|
||||
|
||||
<g id="title" stroke="none">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M5 4v3h5.5v12h3V7H19V4z" />
|
||||
</g>
|
||||
|
||||
<g id="sad" stroke="none">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
|
|
39
src/utils/hooks/useFormatWrapper.ts
Normal file
39
src/utils/hooks/useFormatWrapper.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { MouseEventHandler, useCallback } from 'react';
|
||||
|
||||
export const useFormatWrapper = (
|
||||
target: HTMLTextAreaElement,
|
||||
onChange: (val: string) => void,
|
||||
prefix = '',
|
||||
suffix = ''
|
||||
) => {
|
||||
return useCallback<MouseEventHandler>(
|
||||
event => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!target) return;
|
||||
|
||||
const start = target.selectionStart;
|
||||
const end = target.selectionEnd;
|
||||
const selection = target.value.substring(start, end);
|
||||
|
||||
const replacement = prefix + selection + suffix;
|
||||
|
||||
onChange(
|
||||
target.value.substring(0, start) +
|
||||
replacement +
|
||||
target.value.substring(end, target.value.length)
|
||||
);
|
||||
|
||||
target.focus();
|
||||
|
||||
setTimeout(() => {
|
||||
if (start === end) {
|
||||
target.selectionEnd = end + prefix.length;
|
||||
} else {
|
||||
target.selectionEnd = end + prefix.length + suffix.length;
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
[target, onChange, prefix, suffix]
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue