]> Untitled Git - lemmy.git/blobdiff - ui/src/components/user.tsx
Merge branch 'master' into federation_merge_from_master_2
[lemmy.git] / ui / src / components / user.tsx
index ebdf6d5b0019a9898ff06bd70a395f4169747118..f635a1cd0b1d8eb48582b62f9c06fdb7234a0bb7 100644 (file)
@@ -32,8 +32,15 @@ import {
   languages,
   showAvatars,
   toast,
+  editCommentRes,
+  saveCommentRes,
+  createCommentLikeRes,
+  createPostLikeFindRes,
+  commentsToFlatNodes,
+  setupTippy,
 } from '../utils';
 import { PostListing } from './post-listing';
+import { UserListing } from './user-listing';
 import { SortSelect } from './sort-select';
 import { ListingTypeSelect } from './listing-type-select';
 import { CommentNodes } from './comment-nodes';
@@ -75,7 +82,6 @@ export class User extends Component<any, UserState> {
     user: {
       id: null,
       name: null,
-      fedi_name: null,
       published: null,
       number_of_posts: null,
       post_score: null,
@@ -85,6 +91,8 @@ export class User extends Component<any, UserState> {
       avatar: null,
       show_avatars: null,
       send_notifications_to_email: null,
+      actor_id: null,
+      local: null,
     },
     user_id: null,
     username: null,
@@ -236,32 +244,80 @@ export class User extends Component<any, UserState> {
     );
   }
 
-  selects() {
+  viewRadios() {
     return (
-      <div className="mb-2">
-        <select
-          value={this.state.view}
-          onChange={linkEvent(this, this.handleViewChange)}
-          class="custom-select custom-select-sm w-auto"
+      <div class="btn-group btn-group-toggle">
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Overview && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={View.Overview}
+            checked={this.state.view == View.Overview}
+            onChange={linkEvent(this, this.handleViewChange)}
+          />
+          {i18n.t('overview')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Comments && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={View.Comments}
+            checked={this.state.view == View.Comments}
+            onChange={linkEvent(this, this.handleViewChange)}
+          />
+          {i18n.t('comments')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Posts && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={View.Posts}
+            checked={this.state.view == View.Posts}
+            onChange={linkEvent(this, this.handleViewChange)}
+          />
+          {i18n.t('posts')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Saved && 'active'}
+          `}
         >
-          <option disabled>{i18n.t('view')}</option>
-          <option value={View.Overview}>{i18n.t('overview')}</option>
-          <option value={View.Comments}>{i18n.t('comments')}</option>
-          <option value={View.Posts}>{i18n.t('posts')}</option>
-          <option value={View.Saved}>{i18n.t('saved')}</option>
-        </select>
-        <span class="ml-2">
-          <SortSelect
-            sort={this.state.sort}
-            onChange={this.handleSortChange}
-            hideHot
+          <input
+            type="radio"
+            value={View.Saved}
+            checked={this.state.view == View.Saved}
+            onChange={linkEvent(this, this.handleViewChange)}
           />
-        </span>
+          {i18n.t('saved')}
+        </label>
+      </div>
+    );
+  }
+
+  selects() {
+    return (
+      <div className="mb-2">
+        <span class="mr-3">{this.viewRadios()}</span>
+        <SortSelect
+          sort={this.state.sort}
+          onChange={this.handleSortChange}
+          hideHot
+        />
         <a
           href={`/feeds/u/${this.state.username}.xml?sort=${
             SortType[this.state.sort]
           }`}
           target="_blank"
+          title="RSS"
         >
           <svg class="icon mx-2 text-muted small">
             <use xlinkHref="#icon-rss">#</use>
@@ -305,6 +361,7 @@ export class User extends Component<any, UserState> {
                 nodes={[{ comment: i.data as Comment }]}
                 admins={this.state.admins}
                 noIndent
+                showContext
               />
             )}
           </div>
@@ -316,13 +373,12 @@ export class User extends Component<any, UserState> {
   comments() {
     return (
       <div>
-        {this.state.comments.map(comment => (
-          <CommentNodes
-            nodes={[{ comment: comment }]}
-            admins={this.state.admins}
-            noIndent
-          />
-        ))}
+        <CommentNodes
+          nodes={commentsToFlatNodes(this.state.comments)}
+          admins={this.state.admins}
+          noIndent
+          showContext
+        />
       </div>
     );
   }
@@ -345,7 +401,9 @@ export class User extends Component<any, UserState> {
           <div class="card-body">
             <h5>
               <ul class="list-inline mb-0">
-                <li className="list-inline-item">{user.name}</li>
+                <li className="list-inline-item">
+                  <UserListing user={user} realLink />
+                </li>
                 {user.banned && (
                   <li className="list-inline-item badge badge-danger">
                     {i18n.t('banned')}
@@ -354,22 +412,35 @@ export class User extends Component<any, UserState> {
               </ul>
             </h5>
             <div>
-              {i18n.t('joined')} <MomentTime data={user} />
+              {i18n.t('joined')} <MomentTime data={user} showAgo />
             </div>
-            <div class="table-responsive">
+            <div class="table-responsive mt-1">
               <table class="table table-bordered table-sm mt-2 mb-0">
+                {/*
                 <tr>
+                  <td class="text-center" colSpan={2}>
+                    {i18n.t('number_of_points', {
+                      count: user.post_score + user.comment_score,
+                    })}
+                  </td>
+                </tr>
+                */}
+                <tr>
+                  {/* 
                   <td>
                     {i18n.t('number_of_points', { count: user.post_score })}
                   </td>
+                  */}
                   <td>
                     {i18n.t('number_of_posts', { count: user.number_of_posts })}
                   </td>
+                  {/* 
                 </tr>
                 <tr>
                   <td>
                     {i18n.t('number_of_points', { count: user.comment_score })}
                   </td>
+                  */}
                   <td>
                     {i18n.t('number_of_comments', {
                       count: user.number_of_comments,
@@ -388,8 +459,9 @@ export class User extends Component<any, UserState> {
             ) : (
               <>
                 <a
-                  className={`btn btn-block btn-secondary mt-3 ${!this.state
-                    .user.matrix_user_id && 'disabled'}`}
+                  className={`btn btn-block btn-secondary mt-3 ${
+                    !this.state.user.matrix_user_id && 'disabled'
+                  }`}
                   target="_blank"
                   href={`https://matrix.to/#/${this.state.user.matrix_user_id}`}
                 >
@@ -542,6 +614,7 @@ export class User extends Component<any, UserState> {
                     id="user-password"
                     class="form-control"
                     value={this.state.userSettingsForm.new_password}
+                    autoComplete="new-password"
                     onInput={linkEvent(
                       this,
                       this.handleUserSettingsNewPasswordChange
@@ -562,6 +635,7 @@ export class User extends Component<any, UserState> {
                     id="user-verify-password"
                     class="form-control"
                     value={this.state.userSettingsForm.new_password_verify}
+                    autoComplete="new-password"
                     onInput={linkEvent(
                       this,
                       this.handleUserSettingsNewPasswordVerifyChange
@@ -582,6 +656,7 @@ export class User extends Component<any, UserState> {
                     id="user-old-password"
                     class="form-control"
                     value={this.state.userSettingsForm.old_password}
+                    autoComplete="new-password"
                     onInput={linkEvent(
                       this,
                       this.handleUserSettingsOldPasswordChange
@@ -678,6 +753,7 @@ export class User extends Component<any, UserState> {
                     <input
                       type="password"
                       value={this.state.deleteAccountForm.password}
+                      autoComplete="new-password"
                       onInput={linkEvent(
                         this,
                         this.handleDeleteAccountPasswordChange
@@ -851,7 +927,7 @@ export class User extends Component<any, UserState> {
 
   handleUserSettingsThemeChange(i: User, event: any) {
     i.state.userSettingsForm.theme = event.target.value;
-    setTheme(event.target.value);
+    setTheme(event.target.value, true);
     i.setState(i.state);
   }
 
@@ -917,9 +993,9 @@ export class User extends Component<any, UserState> {
   handleImageUpload(i: User, event: any) {
     event.preventDefault();
     let file = event.target.files[0];
-    const imageUploadUrl = `/pictshare/api/upload.php`;
+    const imageUploadUrl = `/pictrs/image`;
     const formData = new FormData();
-    formData.append('file', file);
+    formData.append('images[]', file);
 
     i.state.avatarLoading = true;
     i.setState(i.state);
@@ -930,14 +1006,19 @@ export class User extends Component<any, UserState> {
     })
       .then(res => res.json())
       .then(res => {
-        let url = `${window.location.origin}/pictshare/${res.url}`;
-        if (res.filetype == 'mp4') {
-          url += '/raw';
+        console.log('pictrs upload:');
+        console.log(res);
+        if (res.msg == 'ok') {
+          let hash = res.files[0].file;
+          let url = `${window.location.origin}/pictrs/image/${hash}`;
+          i.state.userSettingsForm.avatar = url;
+          i.state.avatarLoading = false;
+          i.setState(i.state);
+        } else {
+          i.state.avatarLoading = false;
+          i.setState(i.state);
+          toast(JSON.stringify(res), 'danger');
         }
-        i.state.userSettingsForm.avatar = url;
-        console.log(url);
-        i.state.avatarLoading = false;
-        i.setState(i.state);
       })
       .catch(error => {
         i.state.avatarLoading = false;
@@ -991,6 +1072,8 @@ export class User extends Component<any, UserState> {
       }
       this.setState(this.state);
       return;
+    } else if (msg.reconnect) {
+      this.refetch();
     } else if (res.op == UserOperation.GetUserDetails) {
       let data = res.data as UserDetailsResponse;
       this.state.user = data.user;
@@ -1021,46 +1104,30 @@ export class User extends Component<any, UserState> {
       document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
       window.scrollTo(0, 0);
       this.setState(this.state);
+      setupTippy();
     } else if (res.op == UserOperation.EditComment) {
       let data = res.data as CommentResponse;
-
-      let found = this.state.comments.find(c => c.id == data.comment.id);
-      found.content = data.comment.content;
-      found.updated = data.comment.updated;
-      found.removed = data.comment.removed;
-      found.deleted = data.comment.deleted;
-      found.upvotes = data.comment.upvotes;
-      found.downvotes = data.comment.downvotes;
-      found.score = data.comment.score;
-
+      editCommentRes(data, this.state.comments);
       this.setState(this.state);
     } else if (res.op == UserOperation.CreateComment) {
-      // let res: CommentResponse = msg;
-      toast(i18n.t('reply_sent'));
-      // this.state.comments.unshift(res.comment); // TODO do this right
-      // this.setState(this.state);
+      let data = res.data as CommentResponse;
+      if (
+        UserService.Instance.user &&
+        data.comment.creator_id == UserService.Instance.user.id
+      ) {
+        toast(i18n.t('reply_sent'));
+      }
     } else if (res.op == UserOperation.SaveComment) {
       let data = res.data as CommentResponse;
-      let found = this.state.comments.find(c => c.id == data.comment.id);
-      found.saved = data.comment.saved;
+      saveCommentRes(data, this.state.comments);
       this.setState(this.state);
     } else if (res.op == UserOperation.CreateCommentLike) {
       let data = res.data as CommentResponse;
-      let found: Comment = this.state.comments.find(
-        c => c.id === data.comment.id
-      );
-      found.score = data.comment.score;
-      found.upvotes = data.comment.upvotes;
-      found.downvotes = data.comment.downvotes;
-      if (data.comment.my_vote !== null) found.my_vote = data.comment.my_vote;
+      createCommentLikeRes(data, this.state.comments);
       this.setState(this.state);
     } else if (res.op == UserOperation.CreatePostLike) {
       let data = res.data as PostResponse;
-      let found = this.state.posts.find(c => c.id == data.post.id);
-      found.my_vote = data.post.my_vote;
-      found.score = data.post.score;
-      found.upvotes = data.post.upvotes;
-      found.downvotes = data.post.downvotes;
+      createPostLikeFindRes(data, this.state.posts);
       this.setState(this.state);
     } else if (res.op == UserOperation.BanUser) {
       let data = res.data as BanUserResponse;