Adding loading indicators to save and mark as read. #519
This commit is contained in:
parent
409c14b5e0
commit
763c2a4610
4 changed files with 95 additions and 63 deletions
109
ui/src/components/comment-node.tsx
vendored
109
ui/src/components/comment-node.tsx
vendored
|
@ -56,6 +56,8 @@ interface CommentNodeState {
|
|||
upvotes: number;
|
||||
downvotes: number;
|
||||
borderColor: string;
|
||||
readLoading: boolean;
|
||||
saveLoading: boolean;
|
||||
}
|
||||
|
||||
interface CommentNodeProps {
|
||||
|
@ -97,6 +99,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
borderColor: this.props.node.comment.depth
|
||||
? colorList[this.props.node.comment.depth % colorList.length]
|
||||
: colorList[0],
|
||||
readLoading: false,
|
||||
saveLoading: false,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -113,6 +117,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.state.upvotes = nextProps.node.comment.upvotes;
|
||||
this.state.downvotes = nextProps.node.comment.downvotes;
|
||||
this.state.score = nextProps.node.comment.score;
|
||||
this.state.readLoading = false;
|
||||
this.state.saveLoading = false;
|
||||
this.setState(this.state);
|
||||
}
|
||||
|
||||
|
@ -255,12 +261,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
: i18n.t('mark_as_read')
|
||||
}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment.read &&
|
||||
'text-success'}`}
|
||||
>
|
||||
<use xlinkHref="#icon-check"></use>
|
||||
</svg>
|
||||
{this.state.readLoading ? (
|
||||
this.loadingIcon
|
||||
) : (
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment.read &&
|
||||
'text-success'}`}
|
||||
>
|
||||
<use xlinkHref="#icon-check"></use>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
|
@ -305,6 +315,28 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
</button>
|
||||
</li>
|
||||
)}
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleSaveCommentClick)}
|
||||
data-tippy-content={
|
||||
node.comment.saved
|
||||
? i18n.t('unsave')
|
||||
: i18n.t('save')
|
||||
}
|
||||
>
|
||||
{this.state.saveLoading ? (
|
||||
this.loadingIcon
|
||||
) : (
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment.saved &&
|
||||
'text-warning'}`}
|
||||
>
|
||||
<use xlinkHref="#icon-star"></use>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
|
@ -316,17 +348,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<Link
|
||||
className="btn btn-link btn-sm btn-animate text-muted"
|
||||
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
|
||||
title={i18n.t('link')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-link"></use>
|
||||
</svg>
|
||||
</Link>
|
||||
</li>
|
||||
{this.props.markable && this.linkBtn}
|
||||
{!this.state.showAdvanced ? (
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
|
@ -354,27 +376,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(
|
||||
this,
|
||||
this.handleSaveCommentClick
|
||||
)}
|
||||
data-tippy-content={
|
||||
node.comment.saved
|
||||
? i18n.t('unsave')
|
||||
: i18n.t('save')
|
||||
}
|
||||
>
|
||||
<svg
|
||||
class={`icon icon-inline ${node.comment.saved &&
|
||||
'text-warning'}`}
|
||||
>
|
||||
<use xlinkHref="#icon-star"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
{!this.props.markable && this.linkBtn}
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
className="btn btn-link btn-sm btn-animate text-muted"
|
||||
|
@ -756,6 +758,31 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
);
|
||||
}
|
||||
|
||||
get linkBtn() {
|
||||
let node = this.props.node;
|
||||
return (
|
||||
<li className="list-inline-item">
|
||||
<Link
|
||||
className="btn btn-link btn-sm btn-animate text-muted"
|
||||
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
|
||||
title={i18n.t('link')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-link"></use>
|
||||
</svg>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
get loadingIcon() {
|
||||
return (
|
||||
<svg class="icon icon-spinner spin">
|
||||
<use xlinkHref="#icon-spinner"></use>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
get myComment(): boolean {
|
||||
return (
|
||||
UserService.Instance.user &&
|
||||
|
@ -875,6 +902,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
};
|
||||
|
||||
WebSocketService.Instance.saveComment(form);
|
||||
|
||||
i.state.saveLoading = true;
|
||||
i.setState(this.state);
|
||||
}
|
||||
|
||||
handleReplyCancel() {
|
||||
|
@ -987,6 +1017,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
};
|
||||
WebSocketService.Instance.editComment(form);
|
||||
}
|
||||
|
||||
i.state.readLoading = true;
|
||||
i.setState(this.state);
|
||||
}
|
||||
|
||||
handleModBanFromCommunityShow(i: CommentNode) {
|
||||
|
|
3
ui/src/components/post-listings.tsx
vendored
3
ui/src/components/post-listings.tsx
vendored
|
@ -28,8 +28,7 @@ export class PostListings extends Component<PostListingsProps, any> {
|
|||
post={post}
|
||||
showCommunity={this.props.showCommunity}
|
||||
/>
|
||||
<hr class="d-md-none my-2" />
|
||||
<div class="d-none d-md-block my-2"></div>
|
||||
<hr class="my-2" />
|
||||
</>
|
||||
))
|
||||
) : (
|
||||
|
|
2
ui/src/components/post.tsx
vendored
2
ui/src/components/post.tsx
vendored
|
@ -211,7 +211,7 @@ export class Post extends Component<any, PostState> {
|
|||
|
||||
sortRadios() {
|
||||
return (
|
||||
<div class="btn-group btn-group-toggle">
|
||||
<div class="btn-group btn-group-toggle mb-2">
|
||||
<label
|
||||
className={`btn btn-sm btn-secondary pointer ${this.state
|
||||
.commentSort === CommentSortType.Hot && 'active'}`}
|
||||
|
|
44
ui/src/components/private-message.tsx
vendored
44
ui/src/components/private-message.tsx
vendored
|
@ -55,7 +55,7 @@ export class PrivateMessage extends Component<
|
|||
render() {
|
||||
let message = this.props.privateMessage;
|
||||
return (
|
||||
<div class="mb-2">
|
||||
<div class="border-top border-light">
|
||||
<div>
|
||||
<ul class="list-inline mb-0 text-muted small">
|
||||
<li className="list-inline-item">
|
||||
|
@ -129,12 +129,12 @@ export class PrivateMessage extends Component<
|
|||
dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
|
||||
/>
|
||||
)}
|
||||
<ul class="list-inline mb-1 text-muted h5 font-weight-bold">
|
||||
<ul class="list-inline mb-0 text-muted font-weight-bold">
|
||||
{!this.mine && (
|
||||
<>
|
||||
<li className="list-inline-item-action">
|
||||
<span
|
||||
class="pointer"
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleMarkRead)}
|
||||
data-tippy-content={
|
||||
message.read
|
||||
|
@ -148,37 +148,37 @@ export class PrivateMessage extends Component<
|
|||
>
|
||||
<use xlinkHref="#icon-check"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className="list-inline-item-action">
|
||||
<span
|
||||
class="pointer"
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleReplyClick)}
|
||||
data-tippy-content={i18n.t('reply')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-reply1"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{this.mine && (
|
||||
<>
|
||||
<li className="list-inline-item-action">
|
||||
<span
|
||||
class="pointer"
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleEditClick)}
|
||||
data-tippy-content={i18n.t('edit')}
|
||||
>
|
||||
<svg class="icon icon-inline">
|
||||
<use xlinkHref="#icon-edit"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className="list-inline-item-action">
|
||||
<span
|
||||
class="pointer"
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleDeleteClick)}
|
||||
data-tippy-content={
|
||||
!message.deleted
|
||||
|
@ -192,13 +192,13 @@ export class PrivateMessage extends Component<
|
|||
>
|
||||
<use xlinkHref="#icon-trash"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<li className="list-inline-item-action">
|
||||
<span
|
||||
className="pointer"
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
class="btn btn-link btn-sm btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleViewSource)}
|
||||
data-tippy-content={i18n.t('view_source')}
|
||||
>
|
||||
|
@ -208,7 +208,7 @@ export class PrivateMessage extends Component<
|
|||
>
|
||||
<use xlinkHref="#icon-file-text"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
Reference in a new issue