]> Untitled Git - lemmy.git/blobdiff - ui/src/components/comment-node.tsx
routes.api: fix get_captcha endpoint (#1135)
[lemmy.git] / ui / src / components / comment-node.tsx
index 4a063307c1a570d606ef1b491a77dbd2234d0423..1992c4fc846bb9c6d4b1b656c5d8cd90aac2eda1 100644 (file)
@@ -3,8 +3,10 @@ import { Link } from 'inferno-router';
 import {
   CommentNode as CommentNodeI,
   CommentLikeForm,
-  CommentForm as CommentFormI,
-  EditUserMentionForm,
+  DeleteCommentForm,
+  RemoveCommentForm,
+  MarkCommentAsReadForm,
+  MarkUserMentionAsReadForm,
   SaveCommentForm,
   BanFromCommunityForm,
   BanUserForm,
@@ -14,10 +16,9 @@ import {
   AddAdminForm,
   TransferCommunityForm,
   TransferSiteForm,
-  BanType,
-  CommentSortType,
   SortType,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { CommentSortType, BanType } from '../interfaces';
 import { WebSocketService, UserService } from '../services';
 import {
   mdToHtml,
@@ -32,6 +33,7 @@ import { MomentTime } from './moment-time';
 import { CommentForm } from './comment-form';
 import { CommentNodes } from './comment-nodes';
 import { UserListing } from './user-listing';
+import { CommunityLink } from './community-link';
 import { i18n } from '../i18next';
 
 interface CommentNodeState {
@@ -40,6 +42,7 @@ interface CommentNodeState {
   showRemoveDialog: boolean;
   removeReason: string;
   showBanDialog: boolean;
+  removeData: boolean;
   banReason: string;
   banExpires: string;
   banType: BanType;
@@ -61,6 +64,7 @@ interface CommentNodeState {
 
 interface CommentNodeProps {
   node: CommentNodeI;
+  noBorder?: boolean;
   noIndent?: boolean;
   viewOnly?: boolean;
   locked?: boolean;
@@ -73,6 +77,7 @@ interface CommentNodeProps {
   showCommunity?: boolean;
   sort?: CommentSortType;
   sortType?: SortType;
+  enableDownvotes: boolean;
 }
 
 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
@@ -82,6 +87,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     showRemoveDialog: false,
     removeReason: null,
     showBanDialog: false,
+    removeData: null,
     banReason: null,
     banExpires: null,
     banType: BanType.Community,
@@ -132,9 +138,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
       >
         <div
           id={`comment-${node.comment.id}`}
-          className={`details comment-node border-top border-light ${
-            this.isCommentNew ? 'mark' : ''
-          }`}
+          className={`details comment-node py-2 ${
+            !this.props.noBorder ? 'border-top border-light' : ''
+          } ${this.isCommentNew ? 'mark' : ''}`}
           style={
             !this.props.noIndent &&
             this.props.node.comment.parent_id &&
@@ -148,15 +154,21 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               'ml-2'
             }`}
           >
-            <div class="d-flex flex-wrap align-items-center mb-1 mt-1 text-muted small">
+            <div class="d-flex flex-wrap align-items-center text-muted small">
               <span class="mr-2">
                 <UserListing
                   user={{
                     name: node.comment.creator_name,
+                    preferred_username: node.comment.creator_preferred_username,
                     avatar: node.comment.creator_avatar,
+                    id: node.comment.creator_id,
+                    local: node.comment.creator_local,
+                    actor_id: node.comment.creator_actor_id,
+                    published: node.comment.creator_published,
                   }}
                 />
               </span>
+
               {this.isMod && (
                 <div className="badge badge-light d-none d-sm-inline mr-2">
                   {i18n.t('mod')}
@@ -180,13 +192,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               {this.props.showCommunity && (
                 <>
                   <span class="mx-1">{i18n.t('to')}</span>
-                  <Link class="mr-2" to={`/c/${node.comment.community_name}`}>
-                    {node.comment.community_name}
+                  <CommunityLink
+                    community={{
+                      name: node.comment.community_name,
+                      id: node.comment.community_id,
+                      local: node.comment.community_local,
+                      actor_id: node.comment.community_actor_id,
+                      icon: node.comment.community_icon,
+                    }}
+                  />
+                  <span class="mx-2">•</span>
+                  <Link class="mr-2" to={`/post/${node.comment.post_id}`}>
+                    {node.comment.post_name}
                   </Link>
                 </>
               )}
-              <div
-                className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"
+              <button
+                class="btn text-muted"
                 onClick={linkEvent(this, this.handleCommentCollapse)}
               >
                 {this.state.collapsed ? (
@@ -198,9 +220,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                     <use xlinkHref="#icon-minus-square"></use>
                   </svg>
                 )}
-              </div>
-              <span
-                className={`unselectable pointer ${this.scoreColor}`}
+              </button>
+              {/* This is an expanding spacer for mobile */}
+              <div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
+              <button
+                className={`btn p-0 unselectable pointer ${this.scoreColor}`}
                 onClick={linkEvent(node, this.handleCommentUpvote)}
                 data-tippy-content={this.pointsTippy}
               >
@@ -208,7 +232,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                   <use xlinkHref="#icon-zap"></use>
                 </svg>
                 <span class="mr-1">{this.state.score}</span>
-              </span>
+              </button>
               <span className="mr-1">•</span>
               <span>
                 <MomentTime data={node.comment} />
@@ -221,6 +245,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 edit
                 onReplyCancel={this.handleReplyCancel}
                 disabled={this.props.locked}
+                focus
               />
             )}
             {!this.state.showEdit && !this.state.collapsed && (
@@ -276,7 +301,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           <span class="ml-1">{this.state.upvotes}</span>
                         )}
                       </button>
-                      {WebSocketService.Instance.site.enable_downvotes && (
+                      {this.props.enableDownvotes && (
                         <button
                           className={`btn btn-link btn-animate ${
                             this.state.my_vote == -1
@@ -294,25 +319,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           )}
                         </button>
                       )}
-                      <button
-                        class="btn btn-link 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>
                       <button
                         class="btn btn-link btn-animate text-muted"
                         onClick={linkEvent(this, this.handleReplyClick)}
@@ -348,6 +354,30 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                             </button>
                           )}
                           {!this.props.showContext && this.linkBtn}
+                          <button
+                            class="btn btn-link 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>
                           <button
                             className="btn btn-link btn-animate text-muted"
                             onClick={linkEvent(this, this.handleViewSource)}
@@ -446,6 +476,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                                   </button>
                                 ))}
                               {!node.comment.banned_from_community &&
+                                node.comment.creator_local &&
                                 (!this.state.showConfirmAppointAsMod ? (
                                   <button
                                     class="btn btn-link btn-animate text-muted"
@@ -488,6 +519,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           {/* Community creators and admins can transfer community to another mod */}
                           {(this.amCommunityCreator || this.canAdmin) &&
                             this.isMod &&
+                            node.comment.creator_local &&
                             (!this.state.showConfirmTransferCommunity ? (
                               <button
                                 class="btn btn-link btn-animate text-muted"
@@ -550,6 +582,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                                   </button>
                                 ))}
                               {!node.comment.banned &&
+                                node.comment.creator_local &&
                                 (!this.state.showConfirmAppointAsAdmin ? (
                                   <button
                                     class="btn btn-link btn-animate text-muted"
@@ -592,6 +625,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           {/* Site Creator can transfer to another admin */}
                           {this.amSiteCreator &&
                             this.isAdmin &&
+                            node.comment.creator_local &&
                             (!this.state.showConfirmTransferSite ? (
                               <button
                                 class="btn btn-link btn-animate text-muted"
@@ -666,6 +700,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                 value={this.state.banReason}
                 onInput={linkEvent(this, this.handleModBanReasonChange)}
               />
+              <div class="form-group">
+                <div class="form-check">
+                  <input
+                    class="form-check-input"
+                    id="mod-ban-remove-data"
+                    type="checkbox"
+                    checked={this.state.removeData}
+                    onChange={linkEvent(this, this.handleModRemoveDataChange)}
+                  />
+                  <label class="form-check-label" htmlFor="mod-ban-remove-data">
+                    {i18n.t('remove_posts_comments')}
+                  </label>
+                </div>
+              </div>
             </div>
             {/* TODO hold off on expires until later */}
             {/* <div class="form-group row"> */}
@@ -684,6 +732,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             node={node}
             onReplyCancel={this.handleReplyCancel}
             disabled={this.props.locked}
+            focus
           />
         )}
         {node.children && !this.state.collapsed && (
@@ -695,6 +744,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
             postCreatorId={this.props.postCreatorId}
             sort={this.props.sort}
             sortType={this.props.sortType}
+            enableDownvotes={this.props.enableDownvotes}
           />
         )}
         {/* A collapsed clearfix */}
@@ -822,16 +872,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   }
 
   handleDeleteClick(i: CommentNode) {
-    let deleteForm: CommentFormI = {
-      content: i.props.node.comment.content,
+    let deleteForm: DeleteCommentForm = {
       edit_id: i.props.node.comment.id,
-      creator_id: i.props.node.comment.creator_id,
-      post_id: i.props.node.comment.post_id,
-      parent_id: i.props.node.comment.parent_id,
       deleted: !i.props.node.comment.deleted,
       auth: null,
     };
-    WebSocketService.Instance.editComment(deleteForm);
+    WebSocketService.Instance.deleteComment(deleteForm);
   }
 
   handleSaveCommentClick(i: CommentNode) {
@@ -875,7 +921,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
 
     let form: CommentLikeForm = {
       comment_id: i.comment.id,
-      post_id: i.comment.post_id,
       score: this.state.my_vote,
     };
 
@@ -903,7 +948,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
 
     let form: CommentLikeForm = {
       comment_id: i.comment.id,
-      post_id: i.comment.post_id,
       score: this.state.my_vote,
     };
 
@@ -922,19 +966,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     i.setState(i.state);
   }
 
+  handleModRemoveDataChange(i: CommentNode, event: any) {
+    i.state.removeData = event.target.checked;
+    i.setState(i.state);
+  }
+
   handleModRemoveSubmit(i: CommentNode) {
     event.preventDefault();
-    let form: CommentFormI = {
-      content: i.props.node.comment.content,
+    let form: RemoveCommentForm = {
       edit_id: i.props.node.comment.id,
-      creator_id: i.props.node.comment.creator_id,
-      post_id: i.props.node.comment.post_id,
-      parent_id: i.props.node.comment.parent_id,
       removed: !i.props.node.comment.removed,
       reason: i.state.removeReason,
       auth: null,
     };
-    WebSocketService.Instance.editComment(form);
+    WebSocketService.Instance.removeComment(form);
 
     i.state.showRemoveDialog = false;
     i.setState(i.state);
@@ -943,22 +988,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   handleMarkRead(i: CommentNode) {
     // if it has a user_mention_id field, then its a mention
     if (i.props.node.comment.user_mention_id) {
-      let form: EditUserMentionForm = {
+      let form: MarkUserMentionAsReadForm = {
         user_mention_id: i.props.node.comment.user_mention_id,
         read: !i.props.node.comment.read,
       };
-      WebSocketService.Instance.editUserMention(form);
+      WebSocketService.Instance.markUserMentionAsRead(form);
     } else {
-      let form: CommentFormI = {
-        content: i.props.node.comment.content,
+      let form: MarkCommentAsReadForm = {
         edit_id: i.props.node.comment.id,
-        creator_id: i.props.node.comment.creator_id,
-        post_id: i.props.node.comment.post_id,
-        parent_id: i.props.node.comment.parent_id,
         read: !i.props.node.comment.read,
         auth: null,
       };
-      WebSocketService.Instance.editComment(form);
+      WebSocketService.Instance.markCommentAsRead(form);
     }
 
     i.state.readLoading = true;
@@ -1003,18 +1044,30 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
     event.preventDefault();
 
     if (i.state.banType == BanType.Community) {
+      // If its an unban, restore all their data
+      let ban = !i.props.node.comment.banned_from_community;
+      if (ban == false) {
+        i.state.removeData = false;
+      }
       let form: BanFromCommunityForm = {
         user_id: i.props.node.comment.creator_id,
         community_id: i.props.node.comment.community_id,
-        ban: !i.props.node.comment.banned_from_community,
+        ban,
+        remove_data: i.state.removeData,
         reason: i.state.banReason,
         expires: getUnixTime(i.state.banExpires),
       };
       WebSocketService.Instance.banFromCommunity(form);
     } else {
+      // If its an unban, restore all their data
+      let ban = !i.props.node.comment.banned;
+      if (ban == false) {
+        i.state.removeData = false;
+      }
       let form: BanUserForm = {
         user_id: i.props.node.comment.creator_id,
-        ban: !i.props.node.comment.banned,
+        ban,
+        remove_data: i.state.removeData,
         reason: i.state.banReason,
         expires: getUnixTime(i.state.banExpires),
       };