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
97
ui/src/components/comment-node.tsx
vendored
97
ui/src/components/comment-node.tsx
vendored
|
@ -56,6 +56,8 @@ interface CommentNodeState {
|
||||||
upvotes: number;
|
upvotes: number;
|
||||||
downvotes: number;
|
downvotes: number;
|
||||||
borderColor: string;
|
borderColor: string;
|
||||||
|
readLoading: boolean;
|
||||||
|
saveLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentNodeProps {
|
interface CommentNodeProps {
|
||||||
|
@ -97,6 +99,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
borderColor: this.props.node.comment.depth
|
borderColor: this.props.node.comment.depth
|
||||||
? colorList[this.props.node.comment.depth % colorList.length]
|
? colorList[this.props.node.comment.depth % colorList.length]
|
||||||
: colorList[0],
|
: colorList[0],
|
||||||
|
readLoading: false,
|
||||||
|
saveLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
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.upvotes = nextProps.node.comment.upvotes;
|
||||||
this.state.downvotes = nextProps.node.comment.downvotes;
|
this.state.downvotes = nextProps.node.comment.downvotes;
|
||||||
this.state.score = nextProps.node.comment.score;
|
this.state.score = nextProps.node.comment.score;
|
||||||
|
this.state.readLoading = false;
|
||||||
|
this.state.saveLoading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,12 +261,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
: i18n.t('mark_as_read')
|
: i18n.t('mark_as_read')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{this.state.readLoading ? (
|
||||||
|
this.loadingIcon
|
||||||
|
) : (
|
||||||
<svg
|
<svg
|
||||||
class={`icon icon-inline ${node.comment.read &&
|
class={`icon icon-inline ${node.comment.read &&
|
||||||
'text-success'}`}
|
'text-success'}`}
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-check"></use>
|
<use xlinkHref="#icon-check"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
@ -305,6 +315,28 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</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">
|
<li className="list-inline-item">
|
||||||
<button
|
<button
|
||||||
class="btn btn-link btn-sm btn-animate text-muted"
|
class="btn btn-link btn-sm btn-animate text-muted"
|
||||||
|
@ -316,17 +348,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item">
|
{this.props.markable && this.linkBtn}
|
||||||
<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.state.showAdvanced ? (
|
{!this.state.showAdvanced ? (
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<button
|
<button
|
||||||
|
@ -354,27 +376,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
<li className="list-inline-item">
|
{!this.props.markable && this.linkBtn}
|
||||||
<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>
|
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<button
|
<button
|
||||||
className="btn btn-link btn-sm btn-animate text-muted"
|
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 {
|
get myComment(): boolean {
|
||||||
return (
|
return (
|
||||||
UserService.Instance.user &&
|
UserService.Instance.user &&
|
||||||
|
@ -875,6 +902,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
WebSocketService.Instance.saveComment(form);
|
WebSocketService.Instance.saveComment(form);
|
||||||
|
|
||||||
|
i.state.saveLoading = true;
|
||||||
|
i.setState(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReplyCancel() {
|
handleReplyCancel() {
|
||||||
|
@ -987,6 +1017,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
};
|
};
|
||||||
WebSocketService.Instance.editComment(form);
|
WebSocketService.Instance.editComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.state.readLoading = true;
|
||||||
|
i.setState(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModBanFromCommunityShow(i: CommentNode) {
|
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}
|
post={post}
|
||||||
showCommunity={this.props.showCommunity}
|
showCommunity={this.props.showCommunity}
|
||||||
/>
|
/>
|
||||||
<hr class="d-md-none my-2" />
|
<hr class="my-2" />
|
||||||
<div class="d-none d-md-block my-2"></div>
|
|
||||||
</>
|
</>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|
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() {
|
sortRadios() {
|
||||||
return (
|
return (
|
||||||
<div class="btn-group btn-group-toggle">
|
<div class="btn-group btn-group-toggle mb-2">
|
||||||
<label
|
<label
|
||||||
className={`btn btn-sm btn-secondary pointer ${this.state
|
className={`btn btn-sm btn-secondary pointer ${this.state
|
||||||
.commentSort === CommentSortType.Hot && 'active'}`}
|
.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() {
|
render() {
|
||||||
let message = this.props.privateMessage;
|
let message = this.props.privateMessage;
|
||||||
return (
|
return (
|
||||||
<div class="mb-2">
|
<div class="border-top border-light">
|
||||||
<div>
|
<div>
|
||||||
<ul class="list-inline mb-0 text-muted small">
|
<ul class="list-inline mb-0 text-muted small">
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
|
@ -129,12 +129,12 @@ export class PrivateMessage extends Component<
|
||||||
dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
|
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 && (
|
{!this.mine && (
|
||||||
<>
|
<>
|
||||||
<li className="list-inline-item-action">
|
<li className="list-inline-item">
|
||||||
<span
|
<button
|
||||||
class="pointer"
|
class="btn btn-link btn-sm btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleMarkRead)}
|
onClick={linkEvent(this, this.handleMarkRead)}
|
||||||
data-tippy-content={
|
data-tippy-content={
|
||||||
message.read
|
message.read
|
||||||
|
@ -148,37 +148,37 @@ export class PrivateMessage extends Component<
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-check"></use>
|
<use xlinkHref="#icon-check"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item-action">
|
<li className="list-inline-item">
|
||||||
<span
|
<button
|
||||||
class="pointer"
|
class="btn btn-link btn-sm btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleReplyClick)}
|
onClick={linkEvent(this, this.handleReplyClick)}
|
||||||
data-tippy-content={i18n.t('reply')}
|
data-tippy-content={i18n.t('reply')}
|
||||||
>
|
>
|
||||||
<svg class="icon icon-inline">
|
<svg class="icon icon-inline">
|
||||||
<use xlinkHref="#icon-reply1"></use>
|
<use xlinkHref="#icon-reply1"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{this.mine && (
|
{this.mine && (
|
||||||
<>
|
<>
|
||||||
<li className="list-inline-item-action">
|
<li className="list-inline-item">
|
||||||
<span
|
<button
|
||||||
class="pointer"
|
class="btn btn-link btn-sm btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleEditClick)}
|
onClick={linkEvent(this, this.handleEditClick)}
|
||||||
data-tippy-content={i18n.t('edit')}
|
data-tippy-content={i18n.t('edit')}
|
||||||
>
|
>
|
||||||
<svg class="icon icon-inline">
|
<svg class="icon icon-inline">
|
||||||
<use xlinkHref="#icon-edit"></use>
|
<use xlinkHref="#icon-edit"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item-action">
|
<li className="list-inline-item">
|
||||||
<span
|
<button
|
||||||
class="pointer"
|
class="btn btn-link btn-sm btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleDeleteClick)}
|
onClick={linkEvent(this, this.handleDeleteClick)}
|
||||||
data-tippy-content={
|
data-tippy-content={
|
||||||
!message.deleted
|
!message.deleted
|
||||||
|
@ -192,13 +192,13 @@ export class PrivateMessage extends Component<
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-trash"></use>
|
<use xlinkHref="#icon-trash"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<li className="list-inline-item-action">
|
<li className="list-inline-item">
|
||||||
<span
|
<button
|
||||||
className="pointer"
|
class="btn btn-link btn-sm btn-animate text-muted"
|
||||||
onClick={linkEvent(this, this.handleViewSource)}
|
onClick={linkEvent(this, this.handleViewSource)}
|
||||||
data-tippy-content={i18n.t('view_source')}
|
data-tippy-content={i18n.t('view_source')}
|
||||||
>
|
>
|
||||||
|
@ -208,7 +208,7 @@ export class PrivateMessage extends Component<
|
||||||
>
|
>
|
||||||
<use xlinkHref="#icon-file-text"></use>
|
<use xlinkHref="#icon-file-text"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue