mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
added tag deletion interface
This commit is contained in:
parent
0a75feef8d
commit
04a7b28a53
16 changed files with 195 additions and 39 deletions
|
@ -27,7 +27,7 @@ const LabTags: FC<IProps> = ({ tags, isLoading }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.tags}>
|
<div className={styles.tags}>
|
||||||
{tags.slice(0, 10).map(tag => (
|
{tags.slice(0, 10).map(tag => (
|
||||||
<Tag tag={tag} key={tag.id} />
|
<Tag tag={tag} key={tag.ID} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { NodeAuthorBlock } from '~/components/node/NodeAuthorBlock';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
|
canEdit: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
commentsOrder: 'ASC' | 'DESC';
|
commentsOrder: 'ASC' | 'DESC';
|
||||||
comments: IComment[];
|
comments: IComment[];
|
||||||
|
@ -26,6 +27,7 @@ interface IProps {
|
||||||
|
|
||||||
const NodeBottomBlock: FC<IProps> = ({
|
const NodeBottomBlock: FC<IProps> = ({
|
||||||
node,
|
node,
|
||||||
|
canEdit,
|
||||||
isLoading,
|
isLoading,
|
||||||
isLoadingComments,
|
isLoadingComments,
|
||||||
comments,
|
comments,
|
||||||
|
@ -66,7 +68,7 @@ const NodeBottomBlock: FC<IProps> = ({
|
||||||
<NodeAuthorBlock node={node} />
|
<NodeAuthorBlock node={node} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.left_item}>
|
<div className={styles.left_item}>
|
||||||
<NodeTagsBlock node={node} isLoading={isLoading} />
|
<NodeTagsBlock node={node} canEdit={canEdit} isLoading={isLoading} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.left_item}>
|
<div className={styles.left_item}>
|
||||||
<NodeRelatedBlock isLoading={isLoading} node={node} related={related} />
|
<NodeRelatedBlock isLoading={isLoading} node={node} related={related} />
|
||||||
|
|
|
@ -3,16 +3,27 @@ import { ITag } from '~/redux/types';
|
||||||
import { Tags } from '~/components/tags/Tags';
|
import { Tags } from '~/components/tags/Tags';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
is_deletable?: boolean;
|
||||||
is_editable?: boolean;
|
is_editable?: boolean;
|
||||||
tags: ITag[];
|
tags: ITag[];
|
||||||
onChange?: (tags: string[]) => void;
|
onChange?: (tags: string[]) => void;
|
||||||
onTagClick?: (tag: Partial<ITag>) => void;
|
onTagClick?: (tag: Partial<ITag>) => void;
|
||||||
|
onTagDelete?: (id: ITag['ID']) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeTags: FC<IProps> = memo(({ is_editable, tags, onChange, onTagClick }) => {
|
const NodeTags: FC<IProps> = memo(
|
||||||
|
({ is_editable, is_deletable, tags, onChange, onTagClick, onTagDelete }) => {
|
||||||
return (
|
return (
|
||||||
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} onTagClick={onTagClick} />
|
<Tags
|
||||||
|
tags={tags}
|
||||||
|
is_editable={is_editable}
|
||||||
|
onTagsChange={onChange}
|
||||||
|
onTagClick={onTagClick}
|
||||||
|
onTagDelete={onTagDelete}
|
||||||
|
is_deletable={is_deletable}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export { NodeTags };
|
export { NodeTags };
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
import { INode, ITag } from '~/redux/types';
|
import { INode, ITag } from '~/redux/types';
|
||||||
import { URLS } from '~/constants/urls';
|
import { URLS } from '~/constants/urls';
|
||||||
import { nodeUpdateTags } from '~/redux/node/actions';
|
import { nodeDeleteTag, nodeUpdateTags } from '~/redux/node/actions';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { NodeTags } from '~/components/node/NodeTags';
|
import { NodeTags } from '~/components/node/NodeTags';
|
||||||
|
@ -9,10 +9,11 @@ import { useUser } from '~/utils/hooks/user/userUser';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
|
canEdit: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
const NodeTagsBlock: FC<IProps> = ({ node, canEdit, isLoading }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { is_user } = useUser();
|
const { is_user } = useUser();
|
||||||
|
@ -35,6 +36,13 @@ const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
||||||
[history, node]
|
[history, node]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onTagDelete = useCallback(
|
||||||
|
(tagId: ITag['ID']) => {
|
||||||
|
dispatch(nodeDeleteTag(node.id, tagId));
|
||||||
|
},
|
||||||
|
[dispatch, node.id]
|
||||||
|
);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -42,9 +50,11 @@ const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
||||||
return (
|
return (
|
||||||
<NodeTags
|
<NodeTags
|
||||||
is_editable={is_user}
|
is_editable={is_user}
|
||||||
|
is_deletable={canEdit}
|
||||||
tags={node.tags}
|
tags={node.tags}
|
||||||
onChange={onTagsChange}
|
onChange={onTagsChange}
|
||||||
onTagClick={onTagClick}
|
onTagClick={onTagClick}
|
||||||
|
onTagDelete={onTagDelete}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,26 +12,46 @@ interface IProps {
|
||||||
tag: Partial<ITag>;
|
tag: Partial<ITag>;
|
||||||
size?: 'normal' | 'big';
|
size?: 'normal' | 'big';
|
||||||
|
|
||||||
|
is_deletable?: boolean;
|
||||||
is_hoverable?: boolean;
|
is_hoverable?: boolean;
|
||||||
is_editing?: boolean;
|
is_editing?: boolean;
|
||||||
|
|
||||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||||
onClick?: (tag: Partial<ITag>) => void;
|
onClick?: (tag: Partial<ITag>) => void;
|
||||||
|
onDelete?: (id: ITag['ID']) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tag: FC<IProps> = ({ tag, is_hoverable, is_editing, size = 'normal', onBlur, onClick }) => {
|
const Tag: FC<IProps> = ({
|
||||||
|
tag,
|
||||||
|
is_deletable,
|
||||||
|
is_hoverable,
|
||||||
|
is_editing,
|
||||||
|
size = 'normal',
|
||||||
|
onClick,
|
||||||
|
onDelete,
|
||||||
|
}) => {
|
||||||
const onClickHandler = useCallback(() => {
|
const onClickHandler = useCallback(() => {
|
||||||
if (!onClick) return;
|
if (!onClick) return;
|
||||||
onClick(tag);
|
onClick(tag);
|
||||||
}, [tag, onClick]);
|
}, [tag, onClick]);
|
||||||
|
|
||||||
|
const onDeleteHandler = useCallback(() => {
|
||||||
|
if (!onDelete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDelete(tag.ID!);
|
||||||
|
}, [onDelete, tag]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TagWrapper
|
<TagWrapper
|
||||||
feature={getTagFeature(tag)}
|
feature={getTagFeature(tag)}
|
||||||
size={size}
|
size={size}
|
||||||
|
is_deletable={is_deletable}
|
||||||
is_hoverable={is_hoverable}
|
is_hoverable={is_hoverable}
|
||||||
is_editing={is_editing}
|
is_editing={is_editing}
|
||||||
onClick={onClick && onClickHandler}
|
onClick={onClick && onClickHandler}
|
||||||
|
onDelete={onDeleteHandler}
|
||||||
title={tag.title}
|
title={tag.title}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
import { Manager, Popper, Reference } from 'react-popper';
|
||||||
|
import { Icon } from '~/components/input/Icon';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
feature?: string;
|
feature?: string;
|
||||||
size?: string;
|
size?: string;
|
||||||
|
is_deletable?: boolean;
|
||||||
is_hoverable?: boolean;
|
is_hoverable?: boolean;
|
||||||
is_editing?: boolean;
|
is_editing?: boolean;
|
||||||
has_input?: boolean;
|
has_input?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,25 +20,39 @@ const TagWrapper: FC<IProps> = ({
|
||||||
children,
|
children,
|
||||||
feature,
|
feature,
|
||||||
size,
|
size,
|
||||||
|
is_deletable,
|
||||||
is_hoverable,
|
is_hoverable,
|
||||||
is_editing,
|
is_editing,
|
||||||
has_input,
|
has_input,
|
||||||
onClick,
|
onClick,
|
||||||
|
onDelete,
|
||||||
title = '',
|
title = '',
|
||||||
}) => (
|
}) => {
|
||||||
|
const deletable = is_deletable && !is_editing && !has_input;
|
||||||
|
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.tag, feature, size, {
|
className={classNames(styles.tag, feature, size, {
|
||||||
is_hoverable,
|
is_hoverable,
|
||||||
is_editing,
|
is_editing,
|
||||||
|
deletable,
|
||||||
input: has_input,
|
input: has_input,
|
||||||
clickable: onClick,
|
clickable: onClick,
|
||||||
})}
|
})}
|
||||||
onClick={onClick}
|
|
||||||
>
|
>
|
||||||
|
<div className={styles.content} onClick={onClick}>
|
||||||
<div className={styles.hole} />
|
<div className={styles.hole} />
|
||||||
<div className={styles.title}>{title}</div>
|
<div className={styles.title}>{title}</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
|
{deletable && (
|
||||||
|
<button type="button" className={styles.delete} onClick={onDelete}>
|
||||||
|
<Icon icon="close" size={20} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { TagWrapper };
|
export { TagWrapper };
|
||||||
|
|
|
@ -5,21 +5,21 @@ $big: 1.2;
|
||||||
.tag {
|
.tag {
|
||||||
@include outer_shadow();
|
@include outer_shadow();
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
height: $tag_height;
|
height: $tag_height;
|
||||||
background: $tag_bg;
|
background: $tag_bg;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: stretch;
|
|
||||||
border-radius: ($tag_height / 2) 3px 3px ($tag_height / 2);
|
border-radius: ($tag_height / 2) 3px 3px ($tag_height / 2);
|
||||||
font: $font_14_semibold;
|
font: $font_14_semibold;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
padding: 0 8px 0 0;
|
padding: 0 8px 0 0;
|
||||||
//margin: 0 $gap $gap 0;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 12;
|
z-index: 12;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.big) {
|
&:global(.big) {
|
||||||
height: $tag_height * $big;
|
height: $tag_height * $big;
|
||||||
font: $font_16_semibold;
|
font: $font_16_semibold;
|
||||||
|
@ -98,6 +98,19 @@ $big: 1.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: stretch;
|
||||||
|
width: 100%;
|
||||||
|
transition: transform 250ms;
|
||||||
|
|
||||||
|
:hover:global(.deletable) > & {
|
||||||
|
transform: translate(-32px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hole {
|
.hole {
|
||||||
width: $tag_height;
|
width: $tag_height;
|
||||||
height: $tag_height;
|
height: $tag_height;
|
||||||
|
@ -123,3 +136,28 @@ $big: 1.2;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.delete {
|
||||||
|
@include inner_shadow;
|
||||||
|
|
||||||
|
width: 32px;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 24;
|
||||||
|
background: $red;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 0 -5px;
|
||||||
|
border-radius: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: -32px;
|
||||||
|
top: 0;
|
||||||
|
transition: transform 250ms;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
:hover > & {
|
||||||
|
transform: translate(-32px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,12 +8,22 @@ import { separateTags } from '~/utils/tag';
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & {
|
type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
tags: Partial<ITag>[];
|
tags: Partial<ITag>[];
|
||||||
|
is_deletable?: boolean;
|
||||||
is_editable?: boolean;
|
is_editable?: boolean;
|
||||||
onTagsChange?: (tags: string[]) => void;
|
onTagsChange?: (tags: string[]) => void;
|
||||||
onTagClick?: (tag: Partial<ITag>) => void;
|
onTagClick?: (tag: Partial<ITag>) => void;
|
||||||
|
onTagDelete?: (id: ITag['ID']) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, onTagClick, ...props }) => {
|
export const Tags: FC<IProps> = ({
|
||||||
|
tags,
|
||||||
|
is_deletable,
|
||||||
|
is_editable,
|
||||||
|
onTagsChange,
|
||||||
|
onTagClick,
|
||||||
|
onTagDelete,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const [data, setData] = useState<string[]>([]);
|
const [data, setData] = useState<string[]>([]);
|
||||||
|
|
||||||
const [catTags, ordinaryTags] = useMemo(() => separateTags(tags), [tags]);
|
const [catTags, ordinaryTags] = useMemo(() => separateTags(tags), [tags]);
|
||||||
|
@ -56,11 +66,23 @@ export const Tags: FC<IProps> = ({ tags, is_editable, onTagsChange, onTagClick,
|
||||||
return (
|
return (
|
||||||
<TagField {...props}>
|
<TagField {...props}>
|
||||||
{catTags.map(tag => (
|
{catTags.map(tag => (
|
||||||
<Tag key={tag.title} tag={tag} onClick={onTagClick} />
|
<Tag
|
||||||
|
key={tag.title}
|
||||||
|
tag={tag}
|
||||||
|
onClick={onTagClick}
|
||||||
|
is_deletable={is_deletable}
|
||||||
|
onDelete={onTagDelete}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{ordinaryTags.map(tag => (
|
{ordinaryTags.map(tag => (
|
||||||
<Tag key={tag.title} tag={tag} onClick={onTagClick} />
|
<Tag
|
||||||
|
key={tag.title}
|
||||||
|
tag={tag}
|
||||||
|
onClick={onTagClick}
|
||||||
|
is_deletable={is_deletable}
|
||||||
|
onDelete={onTagDelete}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{data.map(title => (
|
{data.map(title => (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IComment, INode } from '~/redux/types';
|
import { IComment, INode, ITag } from '~/redux/types';
|
||||||
import { ISocialProvider } from '~/redux/auth/types';
|
import { ISocialProvider } from '~/redux/auth/types';
|
||||||
|
|
||||||
export const API = {
|
export const API = {
|
||||||
|
@ -30,6 +30,7 @@ export const API = {
|
||||||
COMMENT: (id: INode['id']) => `/node/${id}/comment`,
|
COMMENT: (id: INode['id']) => `/node/${id}/comment`,
|
||||||
RELATED: (id: INode['id']) => `/node/${id}/related`,
|
RELATED: (id: INode['id']) => `/node/${id}/related`,
|
||||||
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
|
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
|
||||||
|
DELETE_TAG: (id: INode['id'], tagId: ITag['ID']) => `/node/${id}/tags/${tagId}`,
|
||||||
POST_LIKE: (id: INode['id']) => `/node/${id}/like`,
|
POST_LIKE: (id: INode['id']) => `/node/${id}/like`,
|
||||||
POST_HEROIC: (id: INode['id']) => `/node/${id}/heroic`,
|
POST_HEROIC: (id: INode['id']) => `/node/${id}/heroic`,
|
||||||
POST_LOCK: (id: INode['id']) => `/node/${id}/lock`,
|
POST_LOCK: (id: INode['id']) => `/node/${id}/lock`,
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { useLoadNode } from '~/utils/hooks/node/useLoadNode';
|
||||||
import { URLS } from '~/constants/urls';
|
import { URLS } from '~/constants/urls';
|
||||||
import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog';
|
import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog';
|
||||||
import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
|
import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
|
||||||
|
import { canEditNode } from '~/utils/node';
|
||||||
|
import { useNodePermissions } from '~/utils/hooks/node/useNodePermissions';
|
||||||
|
|
||||||
type IProps = RouteComponentProps<{ id: string }> & {};
|
type IProps = RouteComponentProps<{ id: string }> & {};
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ const NodeLayout: FC<IProps> = memo(
|
||||||
useOnNodeSeen(current);
|
useOnNodeSeen(current);
|
||||||
|
|
||||||
const { head, block } = useNodeBlocks(current, is_loading);
|
const { head, block } = useNodeBlocks(current, is_loading);
|
||||||
|
const [canEdit] = useNodePermissions(current);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
|
@ -54,13 +57,14 @@ const NodeLayout: FC<IProps> = memo(
|
||||||
<NodePanel node={current} isLoading={is_loading} />
|
<NodePanel node={current} isLoading={is_loading} />
|
||||||
|
|
||||||
<NodeBottomBlock
|
<NodeBottomBlock
|
||||||
|
canEdit={canEdit}
|
||||||
node={current}
|
node={current}
|
||||||
isLoadingComments={is_loading_comments}
|
|
||||||
comments={comments}
|
comments={comments}
|
||||||
isLoading={is_loading}
|
|
||||||
commentsCount={comment_count}
|
commentsCount={comment_count}
|
||||||
commentsOrder="DESC"
|
commentsOrder="DESC"
|
||||||
related={related}
|
related={related}
|
||||||
|
isLoadingComments={is_loading_comments}
|
||||||
|
isLoading={is_loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
|
@ -86,6 +86,12 @@ export const nodeUpdateTags = (id: INode['id'], tags: string[]) => ({
|
||||||
tags,
|
tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const nodeDeleteTag = (id: INode['id'], tagId: ITag['ID']) => ({
|
||||||
|
type: NODE_ACTIONS.DELETE_TAG,
|
||||||
|
id: id!,
|
||||||
|
tagId,
|
||||||
|
});
|
||||||
|
|
||||||
export const nodeSetTags = (tags: ITag[]) => ({
|
export const nodeSetTags = (tags: ITag[]) => ({
|
||||||
type: NODE_ACTIONS.SET_TAGS,
|
type: NODE_ACTIONS.SET_TAGS,
|
||||||
tags,
|
tags,
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { IComment, INode } from '../types';
|
||||||
import { API } from '~/constants/api';
|
import { API } from '~/constants/api';
|
||||||
import { COMMENTS_DISPLAY } from './constants';
|
import { COMMENTS_DISPLAY } from './constants';
|
||||||
import {
|
import {
|
||||||
|
ApiDeleteNodeTagsRequest,
|
||||||
|
ApiDeleteNodeTagsResult,
|
||||||
ApiGetNodeRelatedRequest,
|
ApiGetNodeRelatedRequest,
|
||||||
ApiGetNodeRelatedResult,
|
ApiGetNodeRelatedResult,
|
||||||
ApiGetNodeRequest,
|
ApiGetNodeRequest,
|
||||||
|
@ -101,6 +103,9 @@ export const apiPostNodeTags = ({ id, tags }: ApiPostNodeTagsRequest) =>
|
||||||
.post<ApiPostNodeTagsResult>(API.NODE.UPDATE_TAGS(id), { tags })
|
.post<ApiPostNodeTagsResult>(API.NODE.UPDATE_TAGS(id), { tags })
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
|
export const apiDeleteNodeTag = ({ id, tagId }: ApiDeleteNodeTagsRequest) =>
|
||||||
|
api.delete<ApiDeleteNodeTagsResult>(API.NODE.DELETE_TAG(id, tagId)).then(cleanResult);
|
||||||
|
|
||||||
export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) =>
|
export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) =>
|
||||||
api.post<ApiPostNodeLikeResult>(API.NODE.POST_LIKE(id)).then(cleanResult);
|
api.post<ApiPostNodeLikeResult>(API.NODE.POST_LIKE(id)).then(cleanResult);
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ export const NODE_ACTIONS = {
|
||||||
SET_RELATED: `${prefix}SET_RELATED`,
|
SET_RELATED: `${prefix}SET_RELATED`,
|
||||||
|
|
||||||
UPDATE_TAGS: `${prefix}UPDATE_TAGS`,
|
UPDATE_TAGS: `${prefix}UPDATE_TAGS`,
|
||||||
|
DELETE_TAG: `${prefix}DELETE_TAG`,
|
||||||
SET_TAGS: `${prefix}SET_TAGS`,
|
SET_TAGS: `${prefix}SET_TAGS`,
|
||||||
SET_COVER_IMAGE: `${prefix}SET_COVER_IMAGE`,
|
SET_COVER_IMAGE: `${prefix}SET_COVER_IMAGE`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
import {
|
||||||
nodeCreate,
|
nodeCreate,
|
||||||
|
nodeDeleteTag,
|
||||||
nodeEdit,
|
nodeEdit,
|
||||||
nodeGotoNode,
|
nodeGotoNode,
|
||||||
nodeLike,
|
nodeLike,
|
||||||
|
@ -30,6 +31,7 @@ import {
|
||||||
nodeUpdateTags,
|
nodeUpdateTags,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import {
|
import {
|
||||||
|
apiDeleteNodeTag,
|
||||||
apiGetNode,
|
apiGetNode,
|
||||||
apiGetNodeComments,
|
apiGetNodeComments,
|
||||||
apiGetNodeRelated,
|
apiGetNodeRelated,
|
||||||
|
@ -246,6 +248,13 @@ function* onUpdateTags({ id, tags }: ReturnType<typeof nodeUpdateTags>) {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* onDeleteTag({ id, tagId }: ReturnType<typeof nodeDeleteTag>) {
|
||||||
|
try {
|
||||||
|
const { tags }: Unwrap<typeof apiDeleteNodeTag> = yield call(apiDeleteNodeTag, { id, tagId });
|
||||||
|
yield put(nodeSetTags(tags));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
function* onCreateSaga({ node_type: type, isLab }: ReturnType<typeof nodeCreate>) {
|
function* onCreateSaga({ node_type: type, isLab }: ReturnType<typeof nodeCreate>) {
|
||||||
if (!type || !has(type, NODE_EDITOR_DIALOGS)) return;
|
if (!type || !has(type, NODE_EDITOR_DIALOGS)) return;
|
||||||
|
|
||||||
|
@ -385,6 +394,7 @@ export default function* nodeSaga() {
|
||||||
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
yield takeLatest(NODE_ACTIONS.LOAD_NODE, onNodeLoad);
|
||||||
yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment);
|
yield takeLatest(NODE_ACTIONS.POST_COMMENT, onPostComment);
|
||||||
yield takeLatest(NODE_ACTIONS.UPDATE_TAGS, onUpdateTags);
|
yield takeLatest(NODE_ACTIONS.UPDATE_TAGS, onUpdateTags);
|
||||||
|
yield takeLatest(NODE_ACTIONS.DELETE_TAG, onDeleteTag);
|
||||||
yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga);
|
yield takeLatest(NODE_ACTIONS.CREATE, onCreateSaga);
|
||||||
yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga);
|
yield takeLatest(NODE_ACTIONS.EDIT, onEditSaga);
|
||||||
yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga);
|
yield takeLatest(NODE_ACTIONS.LIKE, onLikeSaga);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IComment, INode } from '~/redux/types';
|
import { IComment, INode, ITag } from '~/redux/types';
|
||||||
import { INodeState } from '~/redux/node/reducer';
|
import { INodeState } from '~/redux/node/reducer';
|
||||||
|
|
||||||
export interface IEditorComponentProps {}
|
export interface IEditorComponentProps {}
|
||||||
|
@ -56,6 +56,14 @@ export type ApiPostNodeTagsResult = {
|
||||||
node: INode;
|
node: INode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiDeleteNodeTagsRequest = {
|
||||||
|
id: INode['id'];
|
||||||
|
tagId: ITag['ID'];
|
||||||
|
};
|
||||||
|
export type ApiDeleteNodeTagsResult = {
|
||||||
|
tags: ITag[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiPostNodeLikeRequest = { id: INode['id'] };
|
export type ApiPostNodeLikeRequest = { id: INode['id'] };
|
||||||
export type ApiPostNodeLikeResult = { is_liked: boolean };
|
export type ApiPostNodeLikeResult = { is_liked: boolean };
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { CallEffect } from 'redux-saga/effects';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
export interface ITag {
|
export interface ITag {
|
||||||
id: number;
|
ID: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
data: Record<string, string>;
|
data: Record<string, string>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue