mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
node edit and like buttons + actions
This commit is contained in:
parent
f4c337b255
commit
249a9b368c
7 changed files with 117 additions and 28 deletions
|
@ -7,9 +7,14 @@ import { NodePanelInner } from '~/components/node/NodePanelInner';
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
layout: {};
|
layout: {};
|
||||||
|
|
||||||
|
can_edit: boolean;
|
||||||
|
can_like: boolean;
|
||||||
|
onEdit: () => void;
|
||||||
|
onLike: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanel: FC<IProps> = ({ node, layout }) => {
|
const NodePanel: FC<IProps> = ({ node, layout, can_edit, can_like, onEdit, onLike }) => {
|
||||||
const [stack, setStack] = useState(false);
|
const [stack, setStack] = useState(false);
|
||||||
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
@ -37,9 +42,25 @@ const NodePanel: FC<IProps> = ({ node, layout }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.place} ref={ref}>
|
<div className={styles.place} ref={ref}>
|
||||||
{stack ? (
|
{stack ? (
|
||||||
createPortal(<NodePanelInner node={node} stack />, document.body)
|
createPortal(
|
||||||
|
<NodePanelInner
|
||||||
|
node={node}
|
||||||
|
stack
|
||||||
|
onEdit={onEdit}
|
||||||
|
onLike={onLike}
|
||||||
|
can_edit={can_edit}
|
||||||
|
can_like={can_like}
|
||||||
|
/>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<NodePanelInner node={node} />
|
<NodePanelInner
|
||||||
|
node={node}
|
||||||
|
onEdit={onEdit}
|
||||||
|
onLike={onLike}
|
||||||
|
can_edit={can_edit}
|
||||||
|
can_like={can_like}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,9 +9,21 @@ import classNames from 'classnames';
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
stack?: boolean;
|
stack?: boolean;
|
||||||
|
|
||||||
|
can_edit: boolean;
|
||||||
|
can_like: boolean;
|
||||||
|
onEdit: () => void;
|
||||||
|
onLike: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanelInner: FC<IProps> = ({ node: { title, user }, stack }) => {
|
const NodePanelInner: FC<IProps> = ({
|
||||||
|
node: { title, user },
|
||||||
|
stack,
|
||||||
|
can_edit,
|
||||||
|
can_like,
|
||||||
|
onEdit,
|
||||||
|
onLike,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.wrap, { stack })}>
|
<div className={classNames(styles.wrap, { stack })}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
|
@ -23,11 +35,16 @@ const NodePanelInner: FC<IProps> = ({ node: { title, user }, stack }) => {
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<Icon icon="edit" size={24} />
|
{can_edit && (
|
||||||
|
<div>
|
||||||
<div className={styles.sep} />
|
<Icon icon="edit" size={24} onClick={onEdit} />
|
||||||
|
</div>
|
||||||
<Icon icon="heart" size={24} />
|
)}
|
||||||
|
{can_like && (
|
||||||
|
<div>
|
||||||
|
<Icon icon="heart" size={24} onClick={onLike} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -66,22 +66,43 @@
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
margin: 0 $gap;
|
margin: 0 $gap;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: darken(white, 50%);
|
||||||
|
transition: fill 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
svg {
|
||||||
|
fill: $red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ' ';
|
||||||
|
flex: 0 0 6px;
|
||||||
|
height: $gap;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparentize(black, 0.7);
|
||||||
|
margin-left: $gap * 2;
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//height: 54px;
|
|
||||||
//border-radius: $radius $radius 0 0;
|
|
||||||
//background: linear-gradient(176deg, #f42a00, #5c1085);
|
|
||||||
//position: absolute;
|
|
||||||
//bottom: 0;
|
|
||||||
//right: 10px;
|
|
||||||
//width: 270px;
|
|
||||||
//display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mark {
|
.mark {
|
||||||
|
@ -102,9 +123,4 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sep {
|
.sep {
|
||||||
flex: 0 0 6px;
|
|
||||||
height: 6px;
|
|
||||||
width: 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparentize(black, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, createElement, useEffect, useCallback, useState } from 'react';
|
import React, { FC, createElement, useEffect, useCallback, useState, useMemo } from 'react';
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { canEditNode, canLikeNode } from '~/utils/node';
|
||||||
import { selectNode } from '~/redux/node/selectors';
|
import { selectNode } from '~/redux/node/selectors';
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Card } from '~/components/containers/Card';
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ const mapStateToProps = state => ({
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
nodeLoadNode: NODE_ACTIONS.nodeLoadNode,
|
nodeLoadNode: NODE_ACTIONS.nodeLoadNode,
|
||||||
nodeUpdateTags: NODE_ACTIONS.nodeUpdateTags,
|
nodeUpdateTags: NODE_ACTIONS.nodeUpdateTags,
|
||||||
|
nodeEdit: NODE_ACTIONS.nodeEdit,
|
||||||
|
nodeLike: NODE_ACTIONS.nodeLike,
|
||||||
};
|
};
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> &
|
type IProps = ReturnType<typeof mapStateToProps> &
|
||||||
|
@ -37,9 +39,12 @@ const NodeLayoutUnconnected: FC<IProps> = ({
|
||||||
params: { id },
|
params: { id },
|
||||||
},
|
},
|
||||||
node: { is_loading, is_loading_comments, comments = [], current: node },
|
node: { is_loading, is_loading_comments, comments = [], current: node },
|
||||||
user: { is_user },
|
user,
|
||||||
|
user: { is_user, role },
|
||||||
nodeLoadNode,
|
nodeLoadNode,
|
||||||
nodeUpdateTags,
|
nodeUpdateTags,
|
||||||
|
nodeEdit,
|
||||||
|
nodeLike,
|
||||||
}) => {
|
}) => {
|
||||||
const [layout, setLayout] = useState({});
|
const [layout, setLayout] = useState({});
|
||||||
|
|
||||||
|
@ -57,14 +62,27 @@ const NodeLayoutUnconnected: FC<IProps> = ({
|
||||||
[node, nodeUpdateTags]
|
[node, nodeUpdateTags]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const can_edit = useMemo(() => canEditNode(node, user), [node, user]);
|
||||||
|
const can_like = useMemo(() => canLikeNode(node, user), [node, user]);
|
||||||
|
|
||||||
const block = node && node.type && NODE_COMPONENTS[node.type];
|
const block = node && node.type && NODE_COMPONENTS[node.type];
|
||||||
const inline_block = node && node.type && NODE_INLINES[node.type];
|
const inline_block = node && node.type && NODE_INLINES[node.type];
|
||||||
|
|
||||||
|
const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]);
|
||||||
|
const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={styles.node} seamless>
|
<Card className={styles.node} seamless>
|
||||||
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
{block && createElement(block, { node, is_loading, updateLayout, layout })}
|
||||||
|
|
||||||
<NodePanel node={node} layout={layout} />
|
<NodePanel
|
||||||
|
node={node}
|
||||||
|
layout={layout}
|
||||||
|
can_edit={can_edit}
|
||||||
|
can_like={can_like}
|
||||||
|
onEdit={onEdit}
|
||||||
|
onLike={onLike}
|
||||||
|
/>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<Padder>
|
<Padder>
|
||||||
|
|
|
@ -71,7 +71,12 @@ export const nodeCreate = (node_type: INode['type']) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const nodeEdit = (id: INode['id']) => ({
|
export const nodeEdit = (id: INode['id']) => ({
|
||||||
type: NODE_ACTIONS.CREATE,
|
type: NODE_ACTIONS.EDIT,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const nodeLike = (id: INode['id']) => ({
|
||||||
|
type: NODE_ACTIONS.LIKE,
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ export const NODE_ACTIONS = {
|
||||||
LOAD_NODE: `${prefix}LOAD_NODE`,
|
LOAD_NODE: `${prefix}LOAD_NODE`,
|
||||||
|
|
||||||
EDIT: `${prefix}EDIT`,
|
EDIT: `${prefix}EDIT`,
|
||||||
|
LIKE: `${prefix}LIKE`,
|
||||||
CREATE: `${prefix}CREATE`,
|
CREATE: `${prefix}CREATE`,
|
||||||
|
|
||||||
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,
|
SET_SAVE_ERRORS: `${prefix}SET_SAVE_ERRORS`,
|
||||||
|
|
11
src/utils/node.ts
Normal file
11
src/utils/node.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { USER_ROLES } from '~/redux/auth/constants';
|
||||||
|
import { INode } from '~/redux/types';
|
||||||
|
import { IUser } from '~/redux/auth/types';
|
||||||
|
import path from 'ramda/es/path';
|
||||||
|
|
||||||
|
export const canEditNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
||||||
|
path(['role'], user) === USER_ROLES.ADMIN ||
|
||||||
|
(path(['user', 'id'], node) && path(['user', 'id'], node) === path(['id'], user));
|
||||||
|
|
||||||
|
export const canLikeNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
||||||
|
path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST;
|
Loading…
Add table
Add a link
Reference in a new issue