Quellcode durchsuchen

Merge pull request #1101 from pixelfed/frontend-ui-refactor

PostComponent refactor
daniel vor 6 Jahren
Ursprung
Commit
4ad0fb2b54

+ 5 - 5
app/Http/Controllers/PublicApiController.php

@@ -110,7 +110,7 @@ class PublicApiController extends Controller
         ]);
         $limit = $request->limit ?? 10;
         $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
-        $status = Status::whereProfileId($profile->id)->findOrFail($postId);
+        $status = Status::whereProfileId($profile->id)->whereCommentsDisabled(false)->findOrFail($postId);
         $this->scopeCheck($profile, $status);
         if($request->filled('min_id') || $request->filled('max_id')) {
             if($request->filled('min_id')) {
@@ -578,9 +578,9 @@ class PublicApiController extends Controller
                     $following = Follower::whereProfileId($pid)->pluck('following_id');
                     return $following->push($pid)->toArray();
                 });
-                $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public'];
+                $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
             } else {
-                $visibility = ['public'];
+                $visibility = ['public', 'unlisted'];
             }
         }
 
@@ -606,8 +606,8 @@ class PublicApiController extends Controller
           ->whereLocal(true)
           ->whereNull('uri')
           ->where('id', $dir, $id)
-          ->whereIn('visibility',$visibility)
-          ->orderBy('created_at', 'desc')
+          ->whereIn('visibility', $visibility)
+          ->latest()
           ->limit($limit)
           ->get();
 

+ 25 - 2
app/Http/Controllers/StatusController.php

@@ -12,8 +12,7 @@ use App\Status;
 use App\Transformer\ActivityPub\StatusTransformer;
 use App\Transformer\ActivityPub\Verb\Note;
 use App\User;
-use Auth;
-use Cache;
+use Auth, Cache;
 use Illuminate\Http\Request;
 use League\Fractal;
 use App\Util\Media\Filter;
@@ -22,6 +21,7 @@ class StatusController extends Controller
 {
     public function show(Request $request, $username, int $id)
     {
+        // $id = strlen($id) < 17 ? array_first(\Hashids::decode($id)) : $id;
         $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
 
         if($user->status != null) {
@@ -363,4 +363,27 @@ class StatusController extends Controller
             return 'photo:video:album';
         }
     }
+
+    public function toggleVisibility(Request $request) {
+        $this->authCheck();
+        $this->validate($request, [
+            'item' => 'required|string|min:1|max:20',
+            'disableComments' => 'required|boolean'
+        ]);
+
+        $user = Auth::user();
+        $id = $request->input('item');
+        $state = $request->input('disableComments');
+
+        $status = Status::findOrFail($id);
+
+        if($status->profile_id != $user->profile->id && $user->is_admin == false) {
+            abort(403);
+        }
+
+        $status->comments_disabled = $status->comments_disabled == true ? false : true;
+        $status->save();
+
+        return response()->json([200]);
+    }
 }

+ 1 - 1
app/Jobs/CommentPipeline/CommentPipeline.php

@@ -51,7 +51,7 @@ class CommentPipeline implements ShouldQueue
         $target = $status->profile;
         $actor = $comment->profile;
 
-        if ($actor->id === $target->id) {
+        if ($actor->id === $target->id || $status->comments_disabled == true) {
             return true;
         }
 

+ 2 - 0
app/Transformer/Api/StatusTransformer.php

@@ -43,6 +43,8 @@ class StatusTransformer extends Fractal\TransformerAbstract
             'pinned'                    => null,
 
             'pf_type'          => $status->type ?? $status->setType(),
+            'reply_count'      => $status->reply_count,
+            'comments_disabled' => $status->comments_disabled ? true : false
         ];
     }
 

+ 4 - 0
app/Util/ActivityPub/Helpers.php

@@ -135,6 +135,10 @@ class Helpers {
 			'127.0.0.1', 'localhost', '::1'
 		];
 
+		if(mb_substr($url, 0, 8) !== 'https://') {
+			return false;
+		}
+
 		$valid = filter_var($url, FILTER_VALIDATE_URL);
 
 		if(in_array(parse_url($valid, PHP_URL_HOST), $localhosts)) {

+ 52 - 50
resources/assets/js/components.js

@@ -33,14 +33,14 @@ try {
 }
 
 window.filesize = require('filesize');
-window.Plyr = require('plyr');
+// window.Plyr = require('plyr');
 import swal from 'sweetalert';
 
-require('./components/localstorage');
-require('./components/commentform');
+// require('./components/localstorage');
+// require('./components/commentform');
 require('./components/searchform');
-require('./components/bookmarkform');
-require('./components/statusform');
+// require('./components/bookmarkform');
+// require('./components/statusform');
 //require('./components/embed');
 //require('./components/notifications');
 
@@ -63,51 +63,53 @@ require('./components/statusform');
 // Initialize Notification Helper
 window.pixelfed.n = {};
 
-Vue.component(
-    'photo-presenter',
-    require('./components/presenter/PhotoPresenter.vue').default
-);
-
-Vue.component(
-    'video-presenter',
-    require('./components/presenter/VideoPresenter.vue').default
-);
-
-Vue.component(
-    'photo-album-presenter',
-    require('./components/presenter/PhotoAlbumPresenter.vue').default
-);
-
-Vue.component(
-    'video-album-presenter',
-    require('./components/presenter/VideoAlbumPresenter.vue').default
-);
-
-Vue.component(
-    'mixed-album-presenter',
-    require('./components/presenter/MixedAlbumPresenter.vue').default
-);
-
-Vue.component(
-    'post-menu',
-    require('./components/PostMenu.vue').default
-);
-
-
-Vue.component(
-    'passport-clients',
-    require('./components/passport/Clients.vue').default
-);
-
-Vue.component(
-    'passport-authorized-clients',
-    require('./components/passport/AuthorizedClients.vue').default
-);
-
-Vue.component(
-    'passport-personal-access-tokens',
-    require('./components/passport/PersonalAccessTokens.vue').default
-);
+// Vue.component(
+//     'photo-presenter',
+//     require('./components/presenter/PhotoPresenter.vue').default
+// );
+
+// Vue.component(
+//     'video-presenter',
+//     require('./components/presenter/VideoPresenter.vue').default
+// );
+
+// Vue.component(
+//     'photo-album-presenter',
+//     require('./components/presenter/PhotoAlbumPresenter.vue').default
+// );
+
+// Vue.component(
+//     'video-album-presenter',
+//     require('./components/presenter/VideoAlbumPresenter.vue').default
+// );
+
+// Vue.component(
+//     'mixed-album-presenter',
+//     require('./components/presenter/MixedAlbumPresenter.vue').default
+// );
+
+// Vue.component(
+//     'post-menu',
+//     require('./components/PostMenu.vue').default
+// );
+
+
+// Vue.component(
+//     'passport-clients',
+//     require('./components/passport/Clients.vue').default
+// );
+
+// Vue.component(
+//     'passport-authorized-clients',
+//     require('./components/passport/AuthorizedClients.vue').default
+// );
+
+// Vue.component(
+//     'passport-personal-access-tokens',
+//     require('./components/passport/PersonalAccessTokens.vue').default
+// );
+
+
 
 // Vue.component(
 //     'follow-suggestions',

+ 0 - 172
resources/assets/js/components/PostComments.vue

@@ -1,172 +0,0 @@
-<style scoped>
- span {
-  font-size: 14px;
- }
- .comment-text {
- }
- .comment-text p {
-  display: inline;
- }
-</style>
-
-<template>
-<div>
-  <div class="postCommentsLoader text-center">
-    <div class="lds-ring"><div></div><div></div><div></div><div></div></div> 
-  </div>
-  <div class="postCommentsContainer d-none">
-    <p class="mb-1 text-center load-more-link d-none"><a href="#" class="text-muted" v-on:click="loadMore">Load more comments</a></p>
-    <div class="comments" data-min-id="0" data-max-id="0">
-      <p v-for="(reply, index) in results" class="mb-1 d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
-        <span>
-          <a class="text-dark font-weight-bold mr-1" :href="reply.account.url" v-bind:title="reply.account.username">{{truncate(reply.account.username,15)}}</a>
-          <span class="text-break" v-html="reply.content"></span>
-        </span>
-        <span class="pl-2" style="min-width:38px">
-          <span v-on:click="likeStatus(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
-            <post-menu :status="reply" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteComment(reply.id, index)"></post-menu>
-        </span>
-      </p>
-    </div>
-  </div>
-</div>
-</template>
-
-<style type="text/css" scoped>
-  .text-lighter {
-    color:#B8C2CC !important;
-  }
-  .text-break {
-    word-break: break-all !important;
-  }
-  .comments p {
-    margin-bottom: 0;
-  }
-</style>
-
-<script>
-
-export default {
-    props: ['post-id', 'post-username', 'user'],
-    data() {
-        return {
-            results: null,
-            pagination: {},
-            min_id: 0,
-            max_id: 0,
-            reply_to_profile_id: 0,
-          }
-    },
-    mounted() {
-      this.fetchData();
-    },
-    updated() {
-      pixelfed.readmore();
-    },
-    methods: {
-      embed(e) {
-          //pixelfed.embed.build(e);
-      },
-      deleteComment(id, i) {
-        axios.post('/i/delete', {
-          type: 'comment',
-          item: id
-        }).then(res => {
-          this.results.splice(i, 1);
-        }).catch(err => {
-          swal('Something went wrong!', 'Please try again later', 'error');
-        });
-      },
-      l(e) {
-        let len = e.length;
-        if(len < 10) { return e; } 
-        return e.substr(0, 10)+'...';
-      },
-      reply(e) {
-          this.reply_to_profile_id = e.account.id;
-          $('.comment-form input[name=comment]').val('@'+e.account.username+' ');
-          $('.comment-form input[name=comment]').focus();
-      },
-      fetchData() {
-          let url = '/api/v2/comments/'+this.postUsername+'/status/'+this.postId;
-          axios.get(url)
-            .then(response => {
-                let self = this;
-                this.results = _.reverse(response.data.data);
-                this.pagination = response.data.meta.pagination;
-                if(this.results.length > 0) {
-                  $('.load-more-link').removeClass('d-none');
-                }
-                $('.postCommentsLoader').addClass('d-none');
-                $('.postCommentsContainer').removeClass('d-none');
-            }).catch(error => {
-              if(!error.response) {
-                $('.postCommentsLoader .lds-ring')
-                  .attr('style','width:100%')
-                  .addClass('pt-4 font-weight-bold text-muted')
-                  .text('An error occurred, cannot fetch comments. Please try again later.');
-              } else {
-                switch(error.response.status) {
-                  case 401:
-                    $('.postCommentsLoader .lds-ring')
-                      .attr('style','width:100%')
-                      .addClass('pt-4 font-weight-bold text-muted')
-                      .text('Please login to view.');
-                  break;
-
-                  default:
-                    $('.postCommentsLoader .lds-ring')
-                      .attr('style','width:100%')
-                      .addClass('pt-4 font-weight-bold text-muted')
-                      .text('An error occurred, cannot fetch comments. Please try again later.');
-                  break;
-                }
-              }
-            });
-      },
-      loadMore(e) {
-          e.preventDefault();
-          if(this.pagination.total_pages == 1 || this.pagination.current_page == this.pagination.total_pages) {
-            $('.load-more-link').addClass('d-none');
-            return;
-          }
-          $('.postCommentsLoader').removeClass('d-none');
-          let next = this.pagination.links.next;
-          axios.get(next)
-            .then(response => {
-                let self = this;
-                let res =  response.data.data;
-                $('.postCommentsLoader').addClass('d-none');
-                for(let i=0; i < res.length; i++) {
-                  this.results.unshift(res[i]);
-                }
-                this.pagination = response.data.meta.pagination;
-            });
-      },
-      likeStatus(status, $event) {
-        if($('body').hasClass('loggedIn') == false) {
-          return;
-        }
-        
-        axios.post('/i/like', {
-          item: status.id
-        }).then(res => {
-          status.favourites_count = res.data.count;
-          if(status.favourited == true) {
-            status.favourited = false;
-          } else {
-            status.favourited = true;
-          }
-        }).catch(err => {
-          swal('Error', 'Something went wrong, please try again later.', 'error');
-        });
-      },
-      truncate(str,lim) {
-        return _.truncate(str,{
-          length: lim
-        });
-      }
-    },
-
-}
-</script>

+ 23 - 23
resources/assets/js/components/PostMenu.vue

@@ -5,38 +5,38 @@
 				<span v-bind:class="[size =='lg' ? 'fas fa-ellipsis-v fa-lg text-muted' : 'fas fa-ellipsis-v fa-sm text-lighter']"></span>
 			</button>
 			<div class="dropdown-menu dropdown-menu-right">
-				<a class="dropdown-item font-weight-bold" :href="status.url">Go to post</a>
-				<a class="dropdown-item font-weight-bold" href="#">Share</a>
-				<a class="dropdown-item font-weight-bold" href="#">Embed</a>
+				<a class="dropdown-item font-weight-bold text-decoration-none" :href="status.url">Go to post</a>
+				<a class="dropdown-item font-weight-bold text-decoration-none" href="#">Share</a>
+				<a class="dropdown-item font-weight-bold text-decoration-none" href="#">Embed</a>
 				<span v-if="statusOwner(status) == false">
 					<a class="dropdown-item font-weight-bold" :href="reportUrl(status)">Report</a>
 				</span>
 				<span v-if="statusOwner(status) == true">
-					<a class="dropdown-item font-weight-bold" v-on:click="muteProfile(status)">Mute Profile</a>
-					<a class="dropdown-item font-weight-bold" v-on:click="blockProfile(status)">Block Profile</a>
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="muteProfile(status)">Mute Profile</a>
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="blockProfile(status)">Block Profile</a>
 				</span>
 				<span v-if="profile.is_admin == true">
 					<div class="dropdown-divider"></div>
-					<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
+					<a class="dropdown-item font-weight-bold text-danger text-decoration-none" v-on:click="deletePost(status)">Delete</a>
 					<div class="dropdown-divider"></div>
 					<h6 class="dropdown-header">Mod Tools</h6>
-					<a class="dropdown-item font-weight-bold" v-on:click="moderatePost(status, 'autocw')">
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'autocw')">
 						<p class="mb-0">Enforce CW</p>
 						<p class="mb-0  small text-muted">Adds a CW to every post <br> made by this account.</p>
 					</a>
-					<a class="dropdown-item font-weight-bold" v-on:click="moderatePost(status, 'noautolink')">
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'noautolink')">
 						<p class="mb-0">No Autolinking</p>
 						<p class="mb-0 small text-muted">Do not transform mentions, <br> hashtags or urls into HTML.</p>
 					</a>
-					<a class="dropdown-item font-weight-bold" v-on:click="moderatePost(status, 'unlisted')">
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'unlisted')">
 						<p class="mb-0">Unlisted Posts</p>
 						<p class="mb-0 small text-muted">Removes account from <br> public/network timelines.</p>
 					</a>
-					<a class="dropdown-item font-weight-bold" v-on:click="moderatePost(status, 'disable')">
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'disable')">
 						<p class="mb-0">Disable Account</p>
 						<p class="mb-0 small text-muted">Temporarily disable account <br> until next time user log in.</p>
 					</a>
-					<a class="dropdown-item font-weight-bold" v-on:click="moderatePost(status, 'suspend')">
+					<a class="dropdown-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'suspend')">
 						<p class="mb-0">Suspend Account</p>
 						<p class="mb-0 small text-muted">This prevents any new interactions, <br> without deleting existing data.</p>
 					</a>
@@ -53,35 +53,35 @@
 					<div class="modal-content">
 						<div class="modal-body">
 							<div class="list-group">
-								<a class="list-group-item font-weight-bold" :href="status.url">Go to post</a>
-								<a class="list-group-item font-weight-bold" :href="status.url">Share</a>
-								<a class="list-group-item font-weight-bold" :href="status.url">Embed</a>
+								<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Go to post</a>
+								<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Share</a>
+								<a class="list-group-item font-weight-bold text-decoration-none" :href="status.url">Embed</a>
 								<span v-if="statusOwner(status) == false">
-									<a class="list-group-item font-weight-bold" :href="reportUrl(status)">Report</a>
-									<a class="list-group-item font-weight-bold" v-on:click="muteProfile(status)" href="#">Mute Profile</a>
-									<a class="list-group-item font-weight-bold" v-on:click="blockProfile(status)" href="#">Block Profile</a>
+									<a class="list-group-item font-weight-bold text-decoration-none" :href="reportUrl(status)">Report</a>
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="muteProfile(status)" href="#">Mute Profile</a>
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="blockProfile(status)" href="#">Block Profile</a>
 								</span>
 								<span v-if="statusOwner(status) == true || profile.is_admin == true">
-									<a class="list-group-item font-weight-bold text-danger" v-on:click="deletePost">Delete</a>
+									<a class="list-group-item font-weight-bold text-danger text-decoration-none" v-on:click="deletePost">Delete</a>
 								</span>
 								<span v-if="profile.is_admin == true">
-									<a class="list-group-item font-weight-bold" v-on:click="moderatePost(status, 'autocw')" href="#">
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'autocw')" href="#">
 										<p class="mb-0">Enforce CW</p>
 										<p class="mb-0  small text-muted">Adds a CW to every post <br> made by this account.</p>
 									</a>
-									<a class="list-group-item font-weight-bold" v-on:click="moderatePost(status, 'noautolink')" href="#">
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'noautolink')" href="#">
 										<p class="mb-0">No Autolinking</p>
 										<p class="mb-0 small text-muted">Do not transform mentions, <br> hashtags or urls into HTML.</p>
 									</a>
-									<a class="list-group-item font-weight-bold" v-on:click="moderatePost(status, 'unlisted')" href="#">
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'unlisted')" href="#">
 										<p class="mb-0">Unlisted Posts</p>
 										<p class="mb-0 small text-muted">Removes account from <br> public/network timelines.</p>
 									</a>
-									<a class="list-group-item font-weight-bold" v-on:click="moderatePost(status, 'disable')" href="#">
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'disable')" href="#">
 										<p class="mb-0">Disable Account</p>
 										<p class="mb-0 small text-muted">Temporarily disable account <br> until next time user log in.</p>
 									</a>
-									<a class="list-group-item font-weight-bold" v-on:click="moderatePost(status, 'suspend')" href="#">
+									<a class="list-group-item font-weight-bold text-decoration-none" v-on:click="moderatePost(status, 'suspend')" href="#">
 										<p class="mb-0">Suspend Account</p>
 										<p class="mb-0 small text-muted">This prevents any new interactions, <br> without deleting existing data.</p>
 									</a>

+ 7 - 0
resources/assets/js/components/presenter/PhotoAlbumPresenter.vue

@@ -43,6 +43,13 @@
 	</div>
 </template>
 
+<style type="text/css" scoped>
+  .card-img-top {
+    border-top-left-radius: 0 !important;
+    border-top-right-radius: 0 !important;
+  }
+</style>
+
 <script type="text/javascript">
 	export default {
 		props: ['status'],

+ 7 - 0
resources/assets/js/components/presenter/PhotoPresenter.vue

@@ -17,6 +17,13 @@
 	</div>
 </template>
 
+<style type="text/css" scoped>
+  .card-img-top {
+    border-top-left-radius: 0 !important;
+    border-top-right-radius: 0 !important;
+  }
+</style>
+
 <script type="text/javascript">
 	export default {
 		props: ['status']

+ 30 - 0
resources/assets/js/profile.js

@@ -1,3 +1,33 @@
+Vue.component(
+    'photo-presenter',
+    require('./components/presenter/PhotoPresenter.vue').default
+);
+
+Vue.component(
+    'video-presenter',
+    require('./components/presenter/VideoPresenter.vue').default
+);
+
+Vue.component(
+    'photo-album-presenter',
+    require('./components/presenter/PhotoAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'video-album-presenter',
+    require('./components/presenter/VideoAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'mixed-album-presenter',
+    require('./components/presenter/MixedAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'post-menu',
+    require('./components/PostMenu.vue').default
+);
+
 Vue.component(
     'profile',
     require('./components/Profile.vue').default

+ 29 - 4
resources/assets/js/status.js

@@ -1,9 +1,34 @@
 Vue.component(
-    'post-component',
-    require('./components/PostComponent.vue').default
+    'photo-presenter',
+    require('./components/presenter/PhotoPresenter.vue').default
+);
+
+Vue.component(
+    'video-presenter',
+    require('./components/presenter/VideoPresenter.vue').default
 );
 
 Vue.component(
-    'post-comments',
-    require('./components/PostComments.vue').default
+    'photo-album-presenter',
+    require('./components/presenter/PhotoAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'video-album-presenter',
+    require('./components/presenter/VideoAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'mixed-album-presenter',
+    require('./components/presenter/MixedAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'post-menu',
+    require('./components/PostMenu.vue').default
+);
+
+Vue.component(
+    'post-component',
+    require('./components/PostComponent.vue').default
 );

+ 30 - 0
resources/assets/js/timeline.js

@@ -1,3 +1,33 @@
+Vue.component(
+    'photo-presenter',
+    require('./components/presenter/PhotoPresenter.vue').default
+);
+
+Vue.component(
+    'video-presenter',
+    require('./components/presenter/VideoPresenter.vue').default
+);
+
+Vue.component(
+    'photo-album-presenter',
+    require('./components/presenter/PhotoAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'video-album-presenter',
+    require('./components/presenter/VideoAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'mixed-album-presenter',
+    require('./components/presenter/MixedAlbumPresenter.vue').default
+);
+
+Vue.component(
+    'post-menu',
+    require('./components/PostMenu.vue').default
+);
+
 Vue.component(
     'timeline',
     require('./components/Timeline.vue').default