From 90c8a721c54ebb6463ebf89b7cdd5b7661352c64 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 13 Dec 2020 17:34:15 -0700 Subject: [PATCH 1/8] Update horizon config, add new default values --- config/horizon.php | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/config/horizon.php b/config/horizon.php index 01ae01e19..62320ee8b 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -97,7 +97,29 @@ return [ 'trim' => [ 'recent' => 60, + 'pending' => 60, + 'completed' => 60, + 'recent_failed' => 10080, 'failed' => 10080, + 'monitored' => 10080, + ], + + /* + |-------------------------------------------------------------------------- + | Metrics + |-------------------------------------------------------------------------- + | + | Here you can configure how many snapshots should be kept to display in + | the metrics graph. This will get used in combination with Horizon's + | `horizon:snapshot` schedule to define how long to retain metrics. + | + */ + + 'metrics' => [ + 'trim_snapshots' => [ + 'job' => 24, + 'queue' => 24, + ], ], /* @@ -142,21 +164,25 @@ return [ 'environments' => [ 'production' => [ 'supervisor-1' => [ - 'connection' => 'redis', - 'queue' => ['high', 'default', 'feed'], - 'balance' => 'auto', - 'processes' => 20, - 'tries' => 3, + 'connection' => 'redis', + 'queue' => ['high', 'default', 'feed'], + 'balance' => 'auto', + 'maxProcesses' => 20, + 'memory' => 128, + 'tries' => 3, + 'nice' => 0, ], ], 'local' => [ 'supervisor-1' => [ - 'connection' => 'redis', - 'queue' => ['high', 'default', 'feed'], - 'balance' => 'auto', - 'processes' => 20, - 'tries' => 3, + 'connection' => 'redis', + 'queue' => ['high', 'default', 'feed'], + 'balance' => 'auto', + 'maxProcesses' => 20, + 'memory' => 128, + 'tries' => 3, + 'nice' => 0, ], ], ], From 526b55311a02070fe0ea1ef1b24cf507c4e53527 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 13 Dec 2020 17:38:41 -0700 Subject: [PATCH 2/8] Update ComposeModal, add maxlength attribute to alt text input. Fixes #2490 --- resources/assets/js/components/ComposeModal.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index a6194f37f..a1f64732d 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -327,6 +327,7 @@

When you tag someone, they are sent a notification.
For more information on tagging, click here.

+

Tagging someone is like mentioning them, with the option to make it private between you.

@@ -420,7 +421,7 @@

- +

{{m.alt ? m.alt.length : 0}}/140

@@ -468,7 +469,7 @@
- +

Describe your photo for people with visual impairments. {{media[carouselCursor].alt ? media[carouselCursor].alt.length : 0}}/140 From cc515c177120a26ac7717563207f32673f86bca3 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 13 Dec 2020 17:40:39 -0700 Subject: [PATCH 3/8] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a753772..dcec76092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -135,6 +135,8 @@ - Updated NotificationTransformer, add missing types. ([3a428366](https://github.com/pixelfed/pixelfed/commit/3a428366)) - Updated StatusService, fix json bug. ([1ea2db74](https://github.com/pixelfed/pixelfed/commit/1ea2db74)) - Updated NotificationTransformer, handle tagged deletes. ([881fa865](https://github.com/pixelfed/pixelfed/commit/881fa865)) +- Updated horizon config, add new default values. ([90c8a721](https://github.com/pixelfed/pixelfed/commit/90c8a721)) +- Updated ComposeModal, add maxlength attribute to alt text input. Fixes ([#2490](https://github.com/pixelfed/pixelfed/issues/2490)). ([526b5531](https://github.com/pixelfed/pixelfed/commit/526b5531)) ## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9) ### Added From 9fc5a80cd334fcad94278ebf3c200d5b9c9de976 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 13 Dec 2020 22:51:44 -0700 Subject: [PATCH 4/8] Update PublicApiController, add state endpoint --- app/Http/Controllers/PublicApiController.php | 51 +++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index 33e8d9c6f..5659dcbcb 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -92,32 +92,47 @@ class PublicApiController extends Controller $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); $res = [ 'status' => $this->fractal->createData($item)->toArray(), - 'user' => [], - 'likes' => [], - 'shares' => [], - 'reactions' => [ - 'liked' => false, - 'shared' => false, - 'bookmarked' => false, - ], ]; - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + return $res; }); - return $res; + return response()->json($res); } - $item = new Fractal\Resource\Item($status, new StatusTransformer()); + $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); $res = [ 'status' => $this->fractal->createData($item)->toArray(), - 'user' => $this->getUserData($request->user()), - 'likes' => $this->getLikes($status), - 'shares' => $this->getShares($status), + ]; + return response()->json($res); + } + + public function statusState(Request $request, $username, int $postid) + { + $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail(); + $status = Status::whereProfileId($profile->id)->findOrFail($postid); + $this->scopeCheck($profile, $status); + if(!Auth::check()) { + $res = [ + 'user' => [], + 'likes' => [], + 'shares' => [], + 'reactions' => [ + 'liked' => false, + 'shared' => false, + 'bookmarked' => false, + ], + ]; + return response()->json($res); + } + $res = [ + 'user' => $this->getUserData($request->user()), + 'likes' => [], + 'shares' => [], 'reactions' => [ - 'liked' => $status->liked(), - 'shared' => $status->shared(), - 'bookmarked' => $status->bookmarked(), + 'liked' => (bool) $status->liked(), + 'shared' => (bool) $status->shared(), + 'bookmarked' => (bool) $status->bookmarked(), ], ]; - return response()->json($res, 200, [], JSON_PRETTY_PRINT); + return response()->json($res); } public function statusComments(Request $request, $username, int $postId) From a10d851fb7d3b50bf730066f5e5703da43b8a63e Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 13 Dec 2020 22:54:24 -0700 Subject: [PATCH 5/8] Update PostComponent, add reply modal --- .../assets/js/components/PostComponent.vue | 433 ++++++++++++++---- resources/assets/js/components/PostMenu.vue | 31 +- resources/assets/js/components/Timeline.vue | 15 +- 3 files changed, 350 insertions(+), 129 deletions(-) diff --git a/resources/assets/js/components/PostComponent.vue b/resources/assets/js/components/PostComponent.vue index b35292490..cdea3e58a 100644 --- a/resources/assets/js/components/PostComponent.vue +++ b/resources/assets/js/components/PostComponent.vue @@ -35,24 +35,10 @@

@@ -108,30 +94,16 @@
-
+
@@ -227,7 +199,12 @@
-
+
+
+ Loading... +
+
+

@@ -252,18 +229,13 @@
-
@@ -271,9 +243,6 @@
-

More posts from {{this.statusUsername}}

@@ -474,6 +443,7 @@
+ + body-class="list-group-flush py-3 px-0">
-
+

Learn more about Tagging People.

+ +
+
Unfollow
+
Follow
+
Embed
+
Copy Link
+
{{ showComments ? 'Disable' : 'Enable'}} Comments
+ Edit +
ModTools
+
Block
+
Unblock
+ Report +
Delete
+
Cancel
+
+
+ +
+
{{ showComments ? 'Disable' : 'Enable'}} Comments
+ +
Unlist from Timelines
+
Remove Content Warning
+
Add Content Warning
+
Cancel
+
+
+ +
+ + +
+ +
+
+
+ + {{replyText.length > config.uploader.max_caption_length ? config.uploader.max_caption_length - replyText.length : replyText.length}}/{{config.uploader.max_caption_length}} + +
+
+
+ + +
+ + +
+
+
+
@@ -742,6 +794,7 @@ export default { loaded: false, loading: null, replyingToId: this.statusId, + replyingToUsername: this.statusUsername, replyToIndex: 0, replySending: false, emoji: window.App.util.emoji, @@ -753,9 +806,10 @@ export default { ctxEmbedShowLikes: false, ctxEmbedCompactMode: false, layout: this.profileLayout, - canEdit: false, showProfileMorePosts: false, - profileMorePosts: [] + profileMorePosts: [], + replySending: false, + reactionBarLoading: true, } }, watch: { @@ -811,16 +865,6 @@ export default { }, methods: { - showMuteBlock() { - let sid = this.status.account.id; - let uid = this.user.id; - if(sid == uid) { - $('.post-actions .menu-author').removeClass('d-none'); - } else { - $('.post-actions .menu-user').removeClass('d-none'); - } - }, - reportUrl() { return '/i/report?type=post&id=' + this.status.id; }, @@ -839,33 +883,20 @@ export default { axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId) .then(response => { self.status = response.data.status; - self.user = response.data.user; - window._sharedData.curUser = self.user; - window.App.util.navatar(); self.media = self.status.media_attachments; - self.reactions = response.data.reactions; - self.likes = response.data.likes; - self.shares = response.data.shares; self.likesPage = 2; self.sharesPage = 2; - this.showMuteBlock(); self.showCaption = !response.data.status.sensitive; if(self.status.comments_disabled == false) { self.showComments = true; this.fetchComments(); } - if(this.ownerOrAdmin()) { - let od = new Date(this.status.created_at).getTime() + (1 * 24 * 60 * 60 * 1000); - let now = new Date().getTime(); - if(od > now) { - this.canEdit = true; - } - } this.loaded = true; setTimeout(function() { self.fetchProfilePosts(); }, 3000); setTimeout(function() { + self.fetchState(); document.querySelectorAll('.status-comment .comment-text a').forEach(function(i, e) { if(i.href.startsWith(window.location.origin)) { return; @@ -882,6 +913,20 @@ export default { }); }, + fetchState() { + let self = this; + axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId+'/state') + .then(res => { + self.user = res.data.user; + window._sharedData.curUser = self.user; + window.App.util.navatar(); + self.likes = res.data.likes; + self.shares = res.data.shares; + self.reactions = res.data.reactions; + self.reactionBarLoading = false; + }); + }, + likesModal() { if($('body').hasClass('loggedIn') == false) { window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); @@ -890,14 +935,31 @@ export default { if(this.status.favourites_count == 0) { return; } - this.$refs.likesModal.show(); + if(this.likes.length) { + this.$refs.likesModal.show(); + return; + } + axios.get('/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId) + .then(res => { + this.likes = res.data.data; + this.$refs.likesModal.show(); + }); }, sharesModal() { if(this.status.reblogs_count == 0 || $('body').hasClass('loggedIn') == false) { + window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode); return; } - this.$refs.sharesModal.show(); + if(this.shares.length) { + this.$refs.sharesModal.show(); + return; + } + axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId) + .then(res => { + this.shares = res.data.data; + this.$refs.sharesModal.show(); + }); }, infiniteLikesHandler($state) { @@ -1010,21 +1072,6 @@ export default { }); }, - muteProfile() { - if($('body').hasClass('loggedIn') == false) { - return; - } - - axios.post('/i/mute', { - type: 'user', - item: this.status.account.id - }).then(res => { - swal('Success', 'You have successfully muted ' + this.status.account.acct, 'success'); - }).catch(err => { - swal('Error', 'Something went wrong. Please try again later.', 'error'); - }); - }, - blockProfile() { if($('body').hasClass('loggedIn') == false) { return; @@ -1034,12 +1081,31 @@ export default { type: 'user', item: this.status.account.id }).then(res => { + this.$refs.ctxModal.hide(); + this.relationship.blocking = true; swal('Success', 'You have successfully blocked ' + this.status.account.acct, 'success'); }).catch(err => { swal('Error', 'Something went wrong. Please try again later.', 'error'); }); }, + unblockProfile() { + if($('body').hasClass('loggedIn') == false) { + return; + } + + axios.post('/i/unblock', { + type: 'user', + item: this.status.account.id + }).then(res => { + this.relationship.blocking = false; + this.$refs.ctxModal.hide(); + swal('Success', 'You have successfully unblocked ' + this.status.account.acct, 'success'); + }).catch(err => { + swal('Error', 'Something went wrong. Please try again later.', 'error'); + }); + }, + deletePost(status) { if(!this.ownerOrAdmin()) { return; @@ -1082,6 +1148,7 @@ export default { postReply() { let self = this; + this.replySending = true; if(this.replyText.length == 0 || this.replyText.trim() == '@'+this.status.account.acct) { self.replyText = null; @@ -1106,7 +1173,7 @@ export default { self.results.unshift(entity); } let elem = $('.status-comments')[0]; - elem.scrollTop = elem.clientHeight; + elem.scrollTop = elem.clientHeight * 2; } else { if(self.replyToIndex >= 0) { let el = self.results[self.replyToIndex]; @@ -1114,6 +1181,8 @@ export default { el.reply_count = el.reply_count + 1; } } + self.$refs.replyModal.hide(); + self.replySending = false; }); }, @@ -1147,15 +1216,25 @@ export default { }, replyFocus(e, index, prependUsername = false) { + if($('body').hasClass('loggedIn') == false) { + this.redirect('/login?next=' + encodeURIComponent(window.location.pathname)); + return; + } + + if(this.status.comments_disabled) { + return; + } + this.replyToIndex = index; this.replyingToId = e.id; + this.replyingToUsername = e.account.username; this.reply_to_profile_id = e.account.id; let username = e.account.local ? '@' + e.account.username + ' ' : '@' + e.account.acct + ' '; if(prependUsername == true) { this.replyText = username; } - $('textarea[name="comment"]').focus(); + this.$refs.replyModal.show(); }, fetchComments() { @@ -1289,7 +1368,9 @@ export default { item: self.status.id, disableComments: false }).then(function(res) { - window.location.href = self.status.url; + self.status.comments_disabled = false; + self.$refs.ctxModal.hide(); + window.location.reload(); }).catch(function(err) { return; }); @@ -1299,8 +1380,9 @@ export default { item: self.status.id, disableComments: true }).then(function(res) { - self.status.comments_disabled = false; + self.status.comments_disabled = true; self.showComments = false; + self.$refs.ctxModal.hide(); }).catch(function(err) { return; }); @@ -1374,6 +1456,7 @@ export default { showEmbedPostModal() { let mode = this.ctxEmbedCompactMode ? 'compact' : 'full'; this.ctxEmbedPayload = window.App.util.embed.post(this.status.url, this.ctxEmbedShowCaption, this.ctxEmbedShowLikes, mode); + this.$refs.ctxModal.hide(); this.$refs.embedModal.show(); }, @@ -1461,10 +1544,166 @@ export default { swal('An Error Occurred', 'Please try again later.', 'error'); }); }, + copyPostUrl() { navigator.clipboard.writeText(this.statusUrl); return; - } + }, + + moderatePost(action, $event) { + let status = this.status; + let username = status.account.username; + let msg = ''; + let self = this; + switch(action) { + case 'addcw': + msg = 'Are you sure you want to add a content warning to this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully added content warning', 'success'); + status.sensitive = true; + self.ctxModMenuClose(); + }).catch(err => { + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + self.ctxModMenuClose(); + }); + } + }); + break; + + case 'remcw': + msg = 'Are you sure you want to remove the content warning on this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + swal('Success', 'Successfully added content warning', 'success'); + status.sensitive = false; + self.ctxModMenuClose(); + }).catch(err => { + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + self.ctxModMenuClose(); + }); + } + }); + break; + + case 'unlist': + msg = 'Are you sure you want to unlist this post?'; + swal({ + title: 'Confirm', + text: msg, + icon: 'warning', + buttons: true, + dangerMode: true + }).then(res => { + if(res) { + axios.post('/api/v2/moderator/action', { + action: action, + item_id: status.id, + item_type: 'status' + }).then(res => { + // this.feed = this.feed.filter(f => { + // return f.id != status.id; + // }); + swal('Success', 'Successfully unlisted post', 'success'); + self.ctxModMenuClose(); + }).catch(err => { + self.ctxModMenuClose(); + swal( + 'Error', + 'Something went wrong, please try again later.', + 'error' + ); + }); + } + }); + break; + } + }, + + ctxMenu() { + this.$refs.ctxModal.show(); + return; + }, + + closeCtxMenu(truncate) { + this.$refs.ctxModal.hide(); + }, + + ctxModMenu() { + this.$refs.ctxModal.hide(); + this.$refs.ctxModModal.show(); + }, + + ctxModMenuClose() { + this.$refs.ctxModal.hide(); + this.$refs.ctxModModal.hide(); + }, + + ctxMenuCopyLink() { + let status = this.status; + navigator.clipboard.writeText(status.url); + this.closeCtxMenu(); + return; + }, + + ctxMenuFollow() { + let id = this.status.account.id; + axios.post('/i/follow', { + item: id + }).then(res => { + let username = this.status.account.acct; + this.relationship.following = true; + this.$refs.ctxModal.hide(); + setTimeout(function() { + swal('Follow successful!', 'You are now following ' + username, 'success'); + }, 500); + }); + }, + + ctxMenuUnfollow() { + let id = this.status.account.id; + axios.post('/i/follow', { + item: id + }).then(res => { + let username = this.status.account.acct; + this.relationship.following = false; + this.$refs.ctxModal.hide(); + setTimeout(function() { + swal('Unfollow successful!', 'You are no longer following ' + username, 'success'); + }, 500); + }); + }, + }, } diff --git a/resources/assets/js/components/PostMenu.vue b/resources/assets/js/components/PostMenu.vue index 771dc4733..a4d12d866 100644 --- a/resources/assets/js/components/PostMenu.vue +++ b/resources/assets/js/components/PostMenu.vue @@ -57,35 +57,8 @@ Hide - Report - Mute Profile - Block Profile - - Delete - - - -

Enforce CW

-

Adds a CW to every post
made by this account.

-
- -

No Autolinking

-

Do not transform mentions,
hashtags or urls into HTML.

-
- -

Unlisted Posts

-

Removes account from
public/network timelines.

-
- -

Disable Account

-

Temporarily disable account
until next time user log in.

-
- -

Suspend Account

-

This prevents any new interactions,
without deleting existing data.

-
- -
+ Report +
Delete
diff --git a/resources/assets/js/components/Timeline.vue b/resources/assets/js/components/Timeline.vue index 8e102feb0..62d555940 100644 --- a/resources/assets/js/components/Timeline.vue +++ b/resources/assets/js/components/Timeline.vue @@ -438,11 +438,11 @@ size="sm" body-class="list-group-flush p-0 rounded">
-
Report inappropriate
+
Report
Unfollow
Follow
Go to post
-
Embed
+
Embed
Copy Link
Moderation Tools
@@ -558,6 +558,10 @@
+
+ + +