mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +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 { Textarea } from '~/components/input/Textarea';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from '~/components/containers/Filler';
|
||||||
|
@ -19,8 +27,9 @@ import { getFileType } from '~/utils/uploader';
|
||||||
import { useRandomPhrase } from '~/constants/phrases';
|
import { useRandomPhrase } from '~/constants/phrases';
|
||||||
import { ERROR_LITERAL } from '~/constants/errors';
|
import { ERROR_LITERAL } from '~/constants/errors';
|
||||||
import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches';
|
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 { CommentFormDropzone } from '~/components/comment/CommentFormDropzone';
|
||||||
|
import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons';
|
||||||
|
|
||||||
const mapStateToProps = (state: IState) => ({
|
const mapStateToProps = (state: IState) => ({
|
||||||
node: selectNode(state),
|
node: selectNode(state),
|
||||||
|
@ -51,12 +60,11 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
||||||
uploadUploadFiles,
|
uploadUploadFiles,
|
||||||
nodeCancelCommentEdit,
|
nodeCancelCommentEdit,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [textarea, setTextarea] = useState<HTMLTextAreaElement>();
|
||||||
const comment = useMemo(() => comment_data[id], [comment_data, id]);
|
const comment = useMemo(() => comment_data[id], [comment_data, id]);
|
||||||
|
|
||||||
const onUpload = useCallback(
|
const onUpload = useCallback(
|
||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
console.log(files);
|
|
||||||
|
|
||||||
const items: IFileWithUUID[] = files.map(
|
const items: IFileWithUUID[] = files.map(
|
||||||
(file: File): IFileWithUUID => ({
|
(file: File): IFileWithUUID => ({
|
||||||
file,
|
file,
|
||||||
|
@ -182,6 +190,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
||||||
disabled={is_sending_comment}
|
disabled={is_sending_comment}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
minRows={2}
|
minRows={2}
|
||||||
|
setRef={setTextarea}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{comment.error && (
|
{comment.error && (
|
||||||
|
@ -203,6 +212,7 @@ const CommentFormUnconnected: FC<IProps> = memo(
|
||||||
|
|
||||||
<Group horizontal className={styles.buttons}>
|
<Group horizontal className={styles.buttons}>
|
||||||
<CommentFormAttachButtons onUpload={onUpload} />
|
<CommentFormAttachButtons onUpload={onUpload} />
|
||||||
|
<CommentFormFormatButtons element={textarea} handler={onInput} />
|
||||||
|
|
||||||
<Filler />
|
<Filler />
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
background: transparentize(black, 0.8);
|
background: transparentize(black, 0.8);
|
||||||
padding: $gap / 2;
|
padding: $gap / 2;
|
||||||
border-radius: 0 0 $radius $radius;
|
border-radius: 0 0 $radius $radius;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploads {
|
.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 React, { HTMLAttributes } from 'react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & {};
|
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, {
|
import React, {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
LegacyRef,
|
DetailedHTMLProps,
|
||||||
memo,
|
memo,
|
||||||
TextareaHTMLAttributes,
|
TextareaHTMLAttributes,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
@ -14,7 +14,10 @@ import autosize from 'autosize';
|
||||||
import styles from '~/styles/common/inputs.module.scss';
|
import styles from '~/styles/common/inputs.module.scss';
|
||||||
import { Icon } from '../Icon';
|
import { Icon } from '../Icon';
|
||||||
|
|
||||||
type IProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
type IProps = DetailedHTMLProps<
|
||||||
|
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||||
|
HTMLTextAreaElement
|
||||||
|
> & {
|
||||||
value: string;
|
value: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
|
@ -26,6 +29,7 @@ type IProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||||
status?: 'error' | 'success' | '';
|
status?: 'error' | 'success' | '';
|
||||||
title?: string;
|
title?: string;
|
||||||
seamless?: boolean;
|
seamless?: boolean;
|
||||||
|
setRef?: (r: HTMLTextAreaElement) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Textarea = memo<IProps>(
|
const Textarea = memo<IProps>(
|
||||||
|
@ -40,12 +44,12 @@ const Textarea = memo<IProps>(
|
||||||
status = '',
|
status = '',
|
||||||
seamless,
|
seamless,
|
||||||
value,
|
value,
|
||||||
|
setRef,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [rows, setRows] = useState(minRows || 1);
|
const [rows, setRows] = useState(minRows || 1);
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
|
const ref = useRef<HTMLTextAreaElement>();
|
||||||
const textarea: LegacyRef<HTMLTextAreaElement> = useRef(null);
|
|
||||||
|
|
||||||
const onInput = useCallback(
|
const onInput = useCallback(
|
||||||
({ target }: ChangeEvent<HTMLTextAreaElement>) => handler(target.value),
|
({ target }: ChangeEvent<HTMLTextAreaElement>) => handler(target.value),
|
||||||
|
@ -56,12 +60,13 @@ const Textarea = memo<IProps>(
|
||||||
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
const onBlur = useCallback(() => setFocused(false), [setFocused]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!textarea.current) return;
|
if (!ref.current) return;
|
||||||
|
|
||||||
autosize(textarea.current);
|
autosize(ref.current);
|
||||||
|
setRef(ref.current);
|
||||||
|
|
||||||
return () => autosize.destroy(textarea.current);
|
return () => autosize.destroy(ref.current);
|
||||||
}, [textarea.current]);
|
}, [ref.current]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -80,7 +85,7 @@ const Textarea = memo<IProps>(
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={classNames(styles.textarea, className)}
|
className={classNames(styles.textarea, className)}
|
||||||
onChange={onInput}
|
onChange={onInput}
|
||||||
ref={textarea}
|
ref={ref}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
style={{
|
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" />
|
<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>
|
||||||
|
|
||||||
|
<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">
|
<g id="sad" stroke="none">
|
||||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
<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