diff options
Diffstat (limited to 'ui/src/components')
24 files changed, 1119 insertions, 295 deletions
diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index dddcbe72..b8ea0a5a 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -16,6 +16,7 @@ import { mdToHtml, randomStr, markdownHelpUrl, + toast, } from '../utils'; import { WebSocketService, UserService } from '../services'; import autosize from 'autosize'; @@ -293,7 +294,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { .catch(error => { i.state.imageLoading = false; i.setState(i.state); - alert(error); + toast(error, 'danger'); }); } diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 21a82954..046fc88d 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -47,8 +47,8 @@ interface CommentNodeState { showConfirmAppointAsAdmin: boolean; collapsed: boolean; viewSource: boolean; - my_vote: number; - score: number; + upvoteLoading: boolean; + downvoteLoading: boolean; } interface CommentNodeProps { @@ -78,8 +78,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { showConfirmTransferCommunity: false, showConfirmAppointAsMod: false, showConfirmAppointAsAdmin: false, - my_vote: this.props.node.comment.my_vote, - score: this.props.node.comment.score, + upvoteLoading: this.props.node.comment.upvoteLoading, + downvoteLoading: this.props.node.comment.downvoteLoading, }; constructor(props: any, context: any) { @@ -87,8 +87,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { this.state = this.emptyState; this.handleReplyCancel = this.handleReplyCancel.bind(this); - this.handleCommentLike = this.handleCommentLike.bind(this); - this.handleCommentDisLike = this.handleCommentDisLike.bind(this); + this.handleCommentUpvote = this.handleCommentUpvote.bind(this); + this.handleCommentDownvote = this.handleCommentDownvote.bind(this); + } + + componentWillReceiveProps(nextProps: CommentNodeProps) { + if ( + nextProps.node.comment.upvoteLoading !== this.state.upvoteLoading || + nextProps.node.comment.downvoteLoading !== this.state.downvoteLoading + ) { + this.setState({ + upvoteLoading: false, + downvoteLoading: false, + }); + } } render() { @@ -107,26 +119,40 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { <button disabled={!UserService.Instance.user} className={`btn p-0 ${ - this.state.my_vote == 1 ? 'text-info' : 'text-muted' + node.comment.my_vote == 1 ? 'text-info' : 'text-muted' }`} - onClick={linkEvent(node, this.handleCommentLike)} + onClick={linkEvent(node, this.handleCommentUpvote)} > - <svg class="icon upvote"> - <use xlinkHref="#icon-arrow-up"></use> - </svg> + {this.state.upvoteLoading ? ( + <svg class="icon icon-spinner spin upvote"> + <use xlinkHref="#icon-spinner"></use> + </svg> + ) : ( + <svg class="icon upvote"> + <use xlinkHref="#icon-arrow-up"></use> + </svg> + )} </button> - <div class={`font-weight-bold text-muted`}>{this.state.score}</div> + <div class={`font-weight-bold text-muted`}> + {node.comment.score} + </div> {WebSocketService.Instance.site.enable_downvotes && ( <button disabled={!UserService.Instance.user} className={`btn p-0 ${ - this.state.my_vote == -1 ? 'text-danger' : 'text-muted' + node.comment.my_vote == -1 ? 'text-danger' : 'text-muted' }`} - onClick={linkEvent(node, this.handleCommentDisLike)} + onClick={linkEvent(node, this.handleCommentDownvote)} > - <svg class="icon downvote"> - <use xlinkHref="#icon-arrow-down"></use> - </svg> + {this.state.downvoteLoading ? ( + <svg class="icon icon-spinner spin downvote"> + <use xlinkHref="#icon-spinner"></use> + </svg> + ) : ( + <svg class="icon downvote"> + <use xlinkHref="#icon-arrow-down"></use> + </svg> + )} </button> )} </div> @@ -267,6 +293,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { </li> </> )} + {!this.myComment && ( + <li className="list-inline-item"> + <Link + class="text-muted" + to={`/create_private_message?recipient_id=${node.comment.creator_id}`} + > + {i18n.t('message').toLowerCase()} + </Link> + </li> + )} <li className="list-inline-item">•</li> <li className="list-inline-item"> <span @@ -724,41 +760,26 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { this.setState(this.state); } - handleCommentLike(i: CommentNodeI) { - this.state.my_vote = i.comment.my_vote == 1 ? 0 : 1; - let add = 1; - if (i.comment.my_vote == 1) { - add = -1; - } else if (i.comment.my_vote == -1) { - add = 2; - } - - this.state.score = i.comment.score + add; - this.setState(this.state); - + handleCommentUpvote(i: CommentNodeI) { + this.setState({ + upvoteLoading: true, + }); let form: CommentLikeForm = { comment_id: i.comment.id, post_id: i.comment.post_id, - score: this.state.my_vote, + score: i.comment.my_vote == 1 ? 0 : 1, }; WebSocketService.Instance.likeComment(form); } - handleCommentDisLike(i: CommentNodeI) { - this.state.my_vote = i.comment.my_vote == -1 ? 0 : -1; - let add = -1; - if (i.comment.my_vote == 1) { - add = -2; - } else if (i.comment.my_vote == -1) { - add = 1; - } - this.state.score = i.comment.score + add; - this.setState(this.state); - + handleCommentDownvote(i: CommentNodeI) { + this.setState({ + downvoteLoading: true, + }); let form: CommentLikeForm = { comment_id: i.comment.id, post_id: i.comment.post_id, - score: this.state.my_vote, + score: i.comment.my_vote == -1 ? 0 : -1, }; WebSocketService.Instance.likeComment(form); } diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index ebcbc345..867cfd81 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -13,12 +13,14 @@ import { WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService } from '../services'; -import { wsJsonToRes } from '../utils'; +import { wsJsonToRes, toast } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; declare const Sortable: any; +const communityLimit = 100; + interface CommunitiesState { communities: Array<Community>; page: number; @@ -174,12 +176,14 @@ export class Communities extends Component<any, CommunitiesState> { <T i18nKey="prev">#</T> </button> )} - <button - class="btn btn-sm btn-secondary" - onClick={linkEvent(this, this.nextPage)} - > - <T i18nKey="next">#</T> - </button> + {this.state.communities.length == communityLimit && ( + <button + class="btn btn-sm btn-secondary" + onClick={linkEvent(this, this.nextPage)} + > + <T i18nKey="next">#</T> + </button> + )} </div> ); } @@ -221,7 +225,7 @@ export class Communities extends Component<any, CommunitiesState> { refetch() { let listCommunitiesForm: ListCommunitiesForm = { sort: SortType[SortType.TopAll], - limit: 100, + limit: communityLimit, page: this.state.page, }; @@ -232,7 +236,7 @@ export class Communities extends Component<any, CommunitiesState> { console.log(msg); let res = wsJsonToRes(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); return; } else if (res.op == UserOperation.ListCommunities) { let data = res.data as ListCommunitiesResponse; diff --git a/ui/src/components/community-form.tsx b/ui/src/components/community-form.tsx index 14cd8e4f..4dc7bfcb 100644 --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@ -8,9 +8,10 @@ import { ListCategoriesResponse, CommunityResponse, GetSiteResponse, + WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService } from '../services'; -import { wsJsonToRes, capitalizeFirstLetter } from '../utils'; +import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils'; import autosize from 'autosize'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -243,7 +244,7 @@ export class CommunityForm extends Component< let res = wsJsonToRes(msg); console.log(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); this.state.loading = false; this.setState(this.state); return; diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 357fe260..9d02dd86 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -25,6 +25,7 @@ import { routeSortTypeToEnum, fetchLimit, postRefetchSeconds, + toast, } from '../utils'; import { T } from 'inferno-i18next'; import { i18n } from '../i18next'; @@ -148,7 +149,7 @@ export class Community extends Component<any, State> { )} </h5> {this.selects()} - {this.state.posts && <PostListings posts={this.state.posts} />} + <PostListings posts={this.state.posts} /> {this.paginator()} </div> <div class="col-12 col-md-4"> @@ -193,12 +194,14 @@ export class Community extends Component<any, State> { <T i18nKey="prev">#</T> </button> )} - <button - class="btn btn-sm btn-secondary" - onClick={linkEvent(this, this.nextPage)} - > - <T i18nKey="next">#</T> - </button> + {this.state.posts.length == fetchLimit && ( + <button + class="btn btn-sm btn-secondary" + onClick={linkEvent(this, this.nextPage)} + > + <T i18nKey="next">#</T> + </button> + )} </div> ); } @@ -256,7 +259,7 @@ export class Community extends Component<any, State> { console.log(msg); let res = wsJsonToRes(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); this.context.router.history.push('/'); return; } else if (res.op == UserOperation.GetCommunity) { @@ -279,12 +282,6 @@ export class Community extends Component<any, State> { this.setState(this.state); } else if (res.op == UserOperation.GetPosts) { let data = res.data as GetPostsResponse; - - // TODO rework this - // This is needed to refresh the view - this.state.posts = undefined; - this.setState(this.state); - this.state.posts = data.posts; this.state.loading = false; this.setState(this.state); diff --git a/ui/src/components/create-post.tsx b/ui/src/components/create-post.tsx index eeb9bc6c..ad013d09 100644 --- a/ui/src/components/create-post.tsx +++ b/ui/src/components/create-post.tsx @@ -35,7 +35,7 @@ export class CreatePost extends Component<any, any> { get params(): PostFormParams { let urlParams = new URLSearchParams(this.props.location.search); let params: PostFormParams = { - name: urlParams.get('name'), + name: urlParams.get('title'), community: urlParams.get('community') || this.prevCommunityName, body: urlParams.get('body'), url: urlParams.get('url'), diff --git a/ui/src/components/create-private-message.tsx b/ui/src/components/create-private-message.tsx new file mode 100644 index 00000000..7160bc52 --- /dev/null +++ b/ui/src/components/create-private-message.tsx @@ -0,0 +1,53 @@ +import { Component } from 'inferno'; +import { PrivateMessageForm } from './private-message-form'; +import { WebSocketService } from '../services'; +import { PrivateMessageFormParams } from '../interfaces'; +import { toast } from '../utils'; +import { i18n } from '../i18next'; + +export class CreatePrivateMessage extends Component<any, any> { + constructor(props: any, context: any) { + super(props, context); + this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind( + this + ); + } + + componentDidMount() { + document.title = `${i18n.t('create_private_message')} - ${ + WebSocketService.Instance.site.name + }`; + } + + render() { + return ( + <div class="container"> + <div class="row"> + <div class="col-12 col-lg-6 offset-lg-3 mb-4"> + <h5>{i18n.t('create_private_message')}</h5> + <PrivateMessageForm + onCreate={this.handlePrivateMessageCreate} + params={this.params} + /> + </div> + </div> + </div> + ); + } + + get params(): PrivateMessageFormParams { + let urlParams = new URLSearchParams(this.props.location.search); + let params: PrivateMessageFormParams = { + recipient_id: Number(urlParams.get('recipient_id')), + }; + + return params; + } + + handlePrivateMessageCreate() { + toast(i18n.t('message_sent')); + + // Navigate to the front + this.props.history.push(`/`); + } +} diff --git a/ui/src/components/footer.tsx b/ui/src/components/footer.tsx index 8aa05072..5451da2c 100644 --- a/ui/src/components/footer.tsx +++ b/ui/src/components/footer.tsx @@ -29,7 +29,7 @@ export class Footer extends Component<any, any> { </li> <li class="nav-item"> <Link class="nav-link" to="/sponsors"> - <T i18nKey="sponsors">#</T> + <T i18nKey="donate">#</T> </Link> </li> <li class="nav-item"> diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx index 4aa9cebe..5c3ff6d2 100644 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@ -13,10 +13,15 @@ import { UserMentionResponse, CommentResponse, WebSocketJsonResponse, + PrivateMessage as PrivateMessageI, + GetPrivateMessagesForm, + PrivateMessagesResponse, + PrivateMessageResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { wsJsonToRes, fetchLimit } from '../utils'; +import { wsJsonToRes, fetchLimit, isCommentType, toast } from '../utils'; import { CommentNodes } from './comment-nodes'; +import { PrivateMessage } from './private-message'; import { SortSelect } from './sort-select'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -27,9 +32,10 @@ enum UnreadOrAll { } enum UnreadType { - Both, + All, Replies, Mentions, + Messages, } interface InboxState { @@ -37,6 +43,7 @@ interface InboxState { unreadType: UnreadType; replies: Array<Comment>; mentions: Array<Comment>; + messages: Array<PrivateMessageI>; sort: SortType; page: number; } @@ -45,9 +52,10 @@ export class Inbox extends Component<any, InboxState> { private subscription: Subscription; private emptyState: InboxState = { unreadOrAll: UnreadOrAll.Unread, - unreadType: UnreadType.Both, + unreadType: UnreadType.All, replies: [], mentions: [], + messages: [], sort: SortType.New, page: 1, }; @@ -104,7 +112,10 @@ export class Inbox extends Component<any, InboxState> { </a> </small> </h5> - {this.state.replies.length + this.state.mentions.length > 0 && + {this.state.replies.length + + this.state.mentions.length + + this.state.messages.length > + 0 && this.state.unreadOrAll == UnreadOrAll.Unread && ( <ul class="list-inline mb-1 text-muted small font-weight-bold"> <li className="list-inline-item"> @@ -115,9 +126,10 @@ export class Inbox extends Component<any, InboxState> { </ul> )} {this.selects()} - {this.state.unreadType == UnreadType.Both && this.both()} + {this.state.unreadType == UnreadType.All && this.all()} {this.state.unreadType == UnreadType.Replies && this.replies()} {this.state.unreadType == UnreadType.Mentions && this.mentions()} + {this.state.unreadType == UnreadType.Messages && this.messages()} {this.paginator()} </div> </div> @@ -151,8 +163,8 @@ export class Inbox extends Component<any, InboxState> { <option disabled> <T i18nKey="type">#</T> </option> - <option value={UnreadType.Both}> - <T i18nKey="both">#</T> + <option value={UnreadType.All}> + <T i18nKey="all">#</T> </option> <option value={UnreadType.Replies}> <T i18nKey="replies">#</T> @@ -160,6 +172,9 @@ export class Inbox extends Component<any, InboxState> { <option value={UnreadType.Mentions}> <T i18nKey="mentions">#</T> </option> + <option value={UnreadType.Messages}> + <T i18nKey="messages">#</T> + </option> </select> <SortSelect sort={this.state.sort} @@ -170,33 +185,25 @@ export class Inbox extends Component<any, InboxState> { ); } - both() { - let combined: Array<{ - type_: string; - data: Comment; - }> = []; - let replies = this.state.replies.map(e => { - return { type_: 'replies', data: e }; - }); - let mentions = this.state.mentions.map(e => { - return { type_: 'mentions', data: e }; - }); + all() { + let combined: Array<Comment | PrivateMessageI> = []; - combined.push(...replies); - combined.push(...mentions); + combined.push(...this.state.replies); + combined.push(...this.state.mentions); + combined.push(...this.state.messages); // Sort it - if (this.state.sort == SortType.New) { - combined.sort((a, b) => b.data.published.localeCompare(a.data.published)); - } else { - combined.sort((a, b) => b.data.score - a.data.score); - } + combined.sort((a, b) => b.published.localeCompare(a.published)); return ( <div> - {combined.map(i => ( - <CommentNodes nodes={[{ comment: i.data }]} noIndent markable /> - ))} + {combined.map(i => + isCommentType(i) ? ( + <CommentNodes nodes={[{ comment: i }]} noIndent markable /> + ) : ( + <PrivateMessage privateMessage={i} /> + ) + )} </div> ); } @@ -221,6 +228,16 @@ export class Inbox extends Component<any, InboxState> { ); } + messages() { + return ( + <div> + {this.state.messages.map(message => ( + <PrivateMessage privateMessage={message} /> + ))} + </div> + ); + } + paginator() { return ( <div class="mt-2"> @@ -284,6 +301,13 @@ export class Inbox extends Component<any, InboxState> { limit: fetchLimit, }; WebSocketService.Instance.getUserMentions(userMentionsForm); + + let privateMessagesForm: GetPrivateMessagesForm = { + unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, + page: this.state.page, + limit: fetchLimit, + }; + WebSocketService.Instance.getPrivateMessages(privateMessagesForm); } handleSortChange(val: SortType) { @@ -301,7 +325,7 @@ export class Inbox extends Component<any, InboxState> { console.log(msg); let res = wsJsonToRes(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); return; } else if (res.op == UserOperation.GetReplies) { let data = res.data as GetRepliesResponse; @@ -315,9 +339,37 @@ export class Inbox extends Component<any, InboxState> { this.sendUnreadCount(); window.scrollTo(0, 0); this.setState(this.state); + } else if (res.op == UserOperation.GetPrivateMessages) { + let data = res.data as PrivateMessagesResponse; + this.state.messages = data.messages; + this.sendUnreadCount(); + window.scrollTo(0, 0); + this.setState(this.state); + } else if (res.op == UserOperation.EditPrivateMessage) { + let data = res.data as PrivateMessageResponse; + let found: PrivateMessageI = this.state.messages.find( + m => m.id === data.message.id + ); + found.content = data.message.content; + found.updated = data.message.updated; + found.deleted = data.message.deleted; + // If youre in the unread view, just remove it from the list + if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) { + this.state.messages = this.state.messages.filter( + r => r.id !== data.message.id + ); + } else { + let found = this.state.messages.find(c => c.id == data.message.id); + found.read = data.message.read; + } + this.sendUnreadCount(); + window.scrollTo(0, 0); + this.setState(this.state); } else if (res.op == UserOperation.MarkAllAsRead) { this.state.replies = []; this.state.mentions = []; + this.state.messages = []; + this.sendUnreadCount(); window.scrollTo(0, 0); this.setState(this.state); } else if (res.op == UserOperation.EditComment) { @@ -368,7 +420,7 @@ export class Inbox extends Component<any, InboxState> { this.setState(this.state); } else if (res.op == UserOperation.CreateComment) { // let res: CommentResponse = msg; - alert(i18n.t('reply_sent')); + toast(i18n.t('reply_sent')); // this.state.replies.unshift(res.comment); // TODO do this right // this.setState(this.state); } else if (res.op == UserOperation.SaveComment) { @@ -392,7 +444,10 @@ export class Inbox extends Component<any, InboxState> { sendUnreadCount() { let count = this.state.replies.filter(r => !r.read).length + - this.state.mentions.filter(r => !r.read).length; + this.state.mentions.filter(r => !r.read).length + + this.state.messages.filter( + r => !r.read && r.creator_id !== UserService.Instance.user.id + ).length; UserService.Instance.sub.next({ user: UserService.Instance.user, unreadCount: count, diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index 29482f45..ac60ba74 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -11,7 +11,7 @@ import { WebSocketJsonResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { wsJsonToRes, validEmail } from '../utils'; +import { wsJsonToRes, validEmail, toast } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -296,7 +296,7 @@ export class Login extends Component<any, State> { parseMessage(msg: WebSocketJsonResponse) { let res = wsJsonToRes(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); this.state = this.emptyState; this.setState(this.state); return; @@ -306,6 +306,7 @@ export class Login extends Component<any, State> { this.state = this.emptyState; this.setState(this.state); UserService.Instance.login(data); + toast(i18n.t('logged_in')); this.props.history.push('/'); } else if (res.op == UserOperation.Register) { let data = res.data as LoginResponse; @@ -314,7 +315,7 @@ export class Login extends Component<any, State> { UserService.Instance.login(data); this.props.history.push('/communities'); } else if (res.op == UserOperation.PasswordReset) { - alert(i18n.t('reset_password_mail_sent')); + toast(i18n.t('reset_password_mail_sent')); } else if (res.op == UserOperation.GetSite) { let data = res.data as GetSiteResponse; this.state.enable_nsfw = data.site.enable_nsfw; diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 1ccebc80..9f16edb5 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -34,6 +34,7 @@ import { postRefetchSeconds, pictshareAvatarThumbnail, showAvatars, + toast, } from '../utils'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; @@ -422,9 +423,7 @@ export class Main extends Component<any, MainState> { ) : ( <div> {this.selects()} - {this.state.posts && ( - <PostListings posts={this.state.posts} showCommunity /> - )} + <PostListings posts={this.state.posts} showCommunity /> {this.paginator()} </div> )} @@ -480,12 +479,14 @@ export class Main extends Component<any, MainState> { <T i18nKey="prev">#</T> </button> )} - <button - class="btn btn-sm btn-secondary" - onClick={linkEvent(this, this.nextPage)} - > - <T i18nKey="next">#</T> - </button> + {this.state.posts.length == fetchLimit && ( + <button + class="btn btn-sm btn-secondary" + onClick={linkEvent(this, this.nextPage)} + > + <T i18nKey="next">#</T> + </button> + )} </div> ); } @@ -566,7 +567,7 @@ export class Main extends Component<any, MainState> { console.log(msg); let res = wsJsonToRes(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); return; } else if (res.op == UserOperation.GetFollowedCommunities) { let data = res.data as GetFollowedCommunitiesResponse; @@ -596,12 +597,6 @@ export class Main extends Component<any, MainState> { this.setState(this.state); } else if (res.op == UserOperation.GetPosts) { let data = res.data as GetPostsResponse; - - // This is needed to refresh the view - // TODO mess with this - this.state.posts = undefined; - this.setState(this.state); - this.state.posts = data.posts; this.state.loading = false; this.setState(this.state); diff --git a/ui/src/components/modlog.tsx b/ui/src/components/modlog.tsx index b2011af5..dd651092 100644 --- a/ui/src/components/modlog.tsx +++ b/ui/src/components/modlog.tsx @@ -17,7 +17,7 @@ import { ModAdd, } from '../interfaces'; import { WebSocketService } from '../services'; -import { wsJsonToRes, addTypeInfo, fetchLimit } from '../utils'; +import { wsJsonToRes, addTypeInfo, fetchLimit, toast } from '../utils'; import { MomentTime } from './moment-time'; import moment from 'moment'; import { i18n } from '../i18next'; @@ -426,7 +426,7 @@ export class Modlog extends Component<any, ModlogState> { console.log(msg); let res = wsJsonToRes(msg); if (res.error) { - alert(i18n.t(res.error)); + toast(i18n.t(msg.error), 'danger'); return; } else if (res.op == UserOperation.GetModlog) { let data = res.data as GetModlogResponse; diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.ts |