Merge pull request #2838 from pixelfed/staging

Update PublicApiController
This commit is contained in:
daniel 2021-07-05 23:46:11 -06:00 committed by GitHub
commit 2ee4b5f853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 985 deletions

View file

@ -39,6 +39,8 @@
- Updated PostComponent, show like count to owner using MomentUI. ([e9c46bab](https://github.com/pixelfed/pixelfed/commit/e9c46bab)) - Updated PostComponent, show like count to owner using MomentUI. ([e9c46bab](https://github.com/pixelfed/pixelfed/commit/e9c46bab))
- Updated ContextMenu, add missing statusUrl method. ([3cffdb11](https://github.com/pixelfed/pixelfed/commit/3cffdb11)) - Updated ContextMenu, add missing statusUrl method. ([3cffdb11](https://github.com/pixelfed/pixelfed/commit/3cffdb11))
- Updated PublicApiController, add LikeService to Network timeline. ([82895591](https://github.com/pixelfed/pixelfed/commit/82895591)) - Updated PublicApiController, add LikeService to Network timeline. ([82895591](https://github.com/pixelfed/pixelfed/commit/82895591))
- Updated moderator api, expire cached status in StatusService. ([f215ee26](https://github.com/pixelfed/pixelfed/commit/f215ee26))
- Updated StatusHashtagService, fix null status bug. ([51a277e1](https://github.com/pixelfed/pixelfed/commit/51a277e1))
- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0) ## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0)

View file

@ -333,6 +333,7 @@ class InternalApiController extends Controller
Cache::forget('_api:statuses:recent_9:' . $status->profile_id); Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
Cache::forget('profile:embed:' . $status->profile_id); Cache::forget('profile:embed:' . $status->profile_id);
StatusService::del($status->id);
return ['msg' => 200]; return ['msg' => 200];
} }

View file

@ -290,6 +290,7 @@ class PublicApiController extends Controller
$max = $request->input('max_id'); $max = $request->input('max_id');
$limit = $request->input('limit') ?? 3; $limit = $request->input('limit') ?? 3;
$user = $request->user(); $user = $request->user();
$amin = SnowflakeService::byDate(now()->subDays(90));
$key = 'user:last_active_at:id:'.$user->id; $key = 'user:last_active_at:id:'.$user->id;
$ttl = now()->addMinutes(5); $ttl = now()->addMinutes(5);
@ -309,64 +310,49 @@ class PublicApiController extends Controller
$id = $min ?? $max; $id = $min ?? $max;
$timeline = Status::select( $timeline = Status::select(
'id', 'id',
'uri',
'caption',
'rendered',
'profile_id', 'profile_id',
'type', 'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope', 'scope',
'local', 'local'
'reply_count',
'comments_disabled',
'place_id',
'likes_count',
'reblogs_count',
'created_at',
'updated_at'
)->where('id', $dir, $id) )->where('id', $dir, $id)
->where('id', '>', $amin)
->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) ->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->whereLocal(true) ->whereLocal(true)
->whereScope('public') ->whereScope('public')
->where('created_at', '>', now()->subMonths(6)) ->orderBy('id', 'desc')
->orderBy('created_at', 'desc')
->limit($limit) ->limit($limit)
->get(); ->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} else { } else {
$timeline = Status::select( $timeline = Status::select(
'id', 'id',
'uri',
'caption',
'rendered',
'profile_id', 'profile_id',
'type', 'type',
'in_reply_to_id',
'reblog_of_id',
'is_nsfw',
'scope', 'scope',
'local', 'local'
'reply_count',
'comments_disabled',
'created_at',
'place_id',
'likes_count',
'reblogs_count',
'updated_at'
)->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) )->whereIn('type', ['text', 'photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->where('id', '>', $amin)
->whereNotIn('profile_id', $filtered) ->whereNotIn('profile_id', $filtered)
->with('profile', 'hashtags', 'mentions') ->with('profile', 'hashtags', 'mentions')
->whereLocal(true) ->whereLocal(true)
->whereScope('public') ->whereScope('public')
->where('created_at', '>', now()->subMonths(6)) ->orderBy('id', 'desc')
->orderBy('created_at', 'desc') ->limit($limit)
->simplePaginate($limit); ->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} }
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray();
return response()->json($res); return response()->json($res);
} }

View file

@ -30,6 +30,9 @@ class StatusHashtagService {
->map(function ($i, $k) use ($id) { ->map(function ($i, $k) use ($id) {
return self::getStatus($i, $id); return self::getStatus($i, $id);
}) })
->filter(function ($i) {
return isset($i['status']) && !empty($i['status']);
})
->all(); ->all();
} }

BIN
public/js/timeline.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -398,140 +398,6 @@
</div> </div>
</div> </div>
<!-- <div v-if="currentLayout === 'comments'" class="container p-0 overflow-hidden">
<div class="row">
<div class="col-12 col-md-6 offset-md-3">
<div class="card shadow-none border" style="height:100vh;">
<div class="card-header d-flex justify-content-between align-items-center">
<div
@click="commentNavigateBack(status.id)"
class="cursor-pointer"
>
<i class="fas fa-chevron-left fa-lg px-2"></i>
</div>
<div>
<p class="font-weight-bold mb-0 h5">Comments</p>
</div>
<div>
<i class="fas fa-cog fa-lg text-white"></i>
</div>
</div>
<div class="card-body" style="overflow-y: auto !important">
<div class="media">
<img :src="status.account.avatar" class="rounded-circle border mr-3" width="32px" height="32px">
<div class="media-body">
<p class="d-flex justify-content-between align-items-top mb-0" style="overflow-y: hidden;">
<span class="mr-2" style="font-size: 13px;">
<a class="text-dark font-weight-bold mr-1 text-break" :href="status.account.url" v-bind:title="status.account.username">{{trimCaption(status.account.username,15)}}</a>
<span class="text-break comment-body" style="word-break: break-all;" v-html="status.content"></span>
</span>
</p>
</div>
</div>
<hr>
<div class="postCommentsLoader text-center py-2">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<div class="postCommentsContainer d-none">
<p v-if="replies.length" class="mb-1 text-center load-more-link my-4">
<a
href="#"
class="text-dark"
title="Load more comments"
@click.prevent="loadMoreComments"
>
<svg class="bi bi-plus-circle" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="font-size:2em;"> <path fill-rule="evenodd" d="M8 3.5a.5.5 0 01.5.5v4a.5.5 0 01-.5.5H4a.5.5 0 010-1h3.5V4a.5.5 0 01.5-.5z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M7.5 8a.5.5 0 01.5-.5h4a.5.5 0 010 1H8.5V12a.5.5 0 01-1 0V8z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M8 15A7 7 0 108 1a7 7 0 000 14zm0 1A8 8 0 108 0a8 8 0 000 16z" clip-rule="evenodd"/></svg>
</a>
</p>
<div v-for="(reply, index) in replies" class="pb-3 media" :key="'tl' + reply.id + '_' + index">
<img :src="reply.account.avatar" class="rounded-circle border mr-3" width="32px" height="32px">
<div class="media-body">
<div v-if="reply.sensitive == true">
<span class="py-3">
<a class="text-dark font-weight-bold mr-3" style="font-size: 13px;" :href="reply.account.url" v-bind:title="reply.account.username">{{trimCaption(reply.account.username,15)}}</a>
<span class="text-break" style="font-size: 13px;">
<span class="font-italic text-muted">This comment may contain sensitive material</span>
<span class="text-primary cursor-pointer pl-1" @click="reply.sensitive = false;">Show</span>
</span>
</span>
</div>
<div v-else>
<p class="d-flex justify-content-between align-items-top read-more mb-0" style="overflow-y: hidden;">
<span class="mr-3" style="font-size: 13px;">
<a class="text-dark font-weight-bold mr-1 text-break" :href="reply.account.url" v-bind:title="reply.account.username">{{trimCaption(reply.account.username,15)}}</a>
<span class="text-break comment-body" style="word-break: break-all;" v-html="reply.content"></span>
</span>
<span class="text-right" style="min-width: 30px;">
<span v-on:click="likeReply(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
<span class="pl-2 text-lighter cursor-pointer" @click="ctxMenu(reply)">
<span class="fas fa-ellipsis-v text-lighter"></span>
</span>
</span>
</p>
<p class="mb-0">
<a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(reply.created_at)" :href="reply.url"></a>
<span v-if="reply.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3 small">{{reply.favourites_count == 1 ? '1 like' : reply.favourites_count + ' likes'}}</span>
<span class="small text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply, index, true)">Reply</span>
</p>
<div v-if="reply.reply_count > 0" class="cursor-pointer pb-2" v-on:click="toggleReplies(reply)">
<span class="show-reply-bar"></span>
<span class="comment-reaction small font-weight-bold">{{reply.thread ? 'Hide' : 'View'}} Replies ({{reply.reply_count}})</span>
</div>
<div v-if="reply.thread == true" class="comment-thread">
<div v-for="(s, sindex) in reply.replies" class="py-1 media" :key="'cr' + s.id + '_' + index">
<img :src="s.account.avatar" class="rounded-circle border mr-3" width="25px" height="25px">
<div class="media-body">
<p class="d-flex justify-content-between align-items-top read-more mb-0" style="overflow-y: hidden;">
<span class="mr-2" style="font-size: 13px;">
<a class="text-dark font-weight-bold mr-1" :href="s.account.url" :title="s.account.username">{{s.account.username}}</a>
<span class="text-break comment-body" style="word-break: break-all;" v-html="s.content"></span>
</span>
<span>
<span v-on:click="likeReply(s, $event)"><i v-bind:class="[s.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
</span>
</p>
<p class="mb-0">
<a v-once class="text-muted mr-3 text-decoration-none small" style="width: 20px;" v-text="timeAgo(s.created_at)" :href="s.url"></a>
<span v-if="s.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{s.favourites_count == 1 ? '1 like' : s.favourites_count + ' likes'}}</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="!replies.length">
<p class="text-center text-muted font-weight-bold small">No comments yet</p>
</div>
</div>
</div>
<div class="card-footer mb-3">
<div class="align-middle d-flex">
<img
:src="profile.avatar"
width="36"
height="36"
class="rounded-circle border mr-3">
<textarea
class="form-control rounded-pill"
name="comment"
placeholder="Add a comment…"
autocomplete="off"
autocorrect="off"
rows="1"
maxlength="0"
style="resize: none;overflow-y: hidden"
@click="replyFocus(status)">
</textarea>
</div>
</div>
</div>
</div>
</div>
</div> -->
<comment-card <comment-card
v-if="replyStatus && replyStatus.hasOwnProperty('id')" v-if="replyStatus && replyStatus.hasOwnProperty('id')"
:status="replyStatus" :status="replyStatus"
@ -928,271 +794,11 @@
}); });
}, },
reportUrl(status) {
let type = status.in_reply_to ? 'comment' : 'post';
let id = status.id;
return '/i/report?type=' + type + '&id=' + id;
},
commentFocus(status, $event) {
if(status.comments_disabled) {
return;
}
// if(this.status && this.status.id == status.id) {
// this.$refs.replyModal.show();
// return;
// }
this.status = status;
this.replies = {};
this.replyStatus = {};
this.replyText = '';
this.replyId = status.id;
this.replyStatus = status;
// this.$refs.replyModal.show();
this.fetchStatusComments(status, '');
$('nav').hide();
$('footer').hide();
$('.mobile-footer-spacer').attr('style', 'display:none !important');
$('.mobile-footer').attr('style', 'display:none !important');
this.currentLayout = 'comments';
window.history.pushState({}, '', this.statusUrl(status));
return;
},
commentNavigateBack(id) {
$('nav').show();
$('footer').show();
$('.mobile-footer-spacer').attr('style', 'display:block');
$('.mobile-footer').attr('style', 'display:block');
this.currentLayout = 'feed';
let path = this.scope == 'home' ? '/' : '/timeline/public';
window.history.pushState({}, '', path);
},
likeStatus(status, event) {
if($('body').hasClass('loggedIn') == false) {
return;
}
let count = status.favourites_count;
status.favourited = !status.favourited;
axios.post('/i/like', {
item: status.id
}).then(res => {
status.favourites_count = res.data.count;
}).catch(err => {
status.favourited = !status.favourited;
status.favourites_count = count;
swal('Error', 'Something went wrong, please try again later.', 'error');
});
window.navigator.vibrate(200);
if(status.favourited) {
setTimeout(function() {
event.target.classList.add('animate__animated', 'animate__bounce');
},100);
}
},
shareStatus(status, $event) {
if($('body').hasClass('loggedIn') == false) {
return;
}
this.closeModals();
axios.post('/i/share', {
item: status.id
}).then(res => {
status.reblogs_count = res.data.count;
status.reblogged = !status.reblogged;
if(status.reblogged) {
swal('Success', 'You shared this post', 'success');
} else {
swal('Success', 'You unshared this post', 'success');
}
}).catch(err => {
swal('Error', 'Something went wrong, please try again later.', 'error');
});
},
timestampFormat(timestamp) {
let ts = new Date(timestamp);
return ts.toDateString() + ' ' + ts.toLocaleTimeString();
},
redirect(url) { redirect(url) {
window.location.href = url; window.location.href = url;
return; return;
}, },
statusOwner(status) {
let sid = status.account.id;
let uid = this.profile.id;
if(sid == uid) {
return true;
} else {
return false;
}
},
fetchStatusComments(status, card) {
// axios.get('/api/v2/status/'+status.id+'/replies',
// {
// params: {
// limit: 6
// }
// })
// .then(res => {
// let data = res.data.filter(res => {
// return res.sensitive == false;
// });
// this.replies = _.reverse(data);
// setTimeout(function() {
// document.querySelectorAll('.timeline .card-body .comments .comment-body a').forEach(function(i, e) {
// i.href = App.util.format.rewriteLinks(i);
// });
// }, 500);
// }).catch(err => {
// })
let url = '/api/v2/comments/'+status.account.id+'/status/'+status.id;
axios.get(url)
.then(response => {
let self = this;
// this.results = this.layout == 'metro' ?
// _.reverse(response.data.data) :
// response.data.data;
this.replies = _.reverse(response.data.data);
this.pagination = response.data.meta.pagination;
if(this.replies.length > 0) {
$('.load-more-link').removeClass('d-none');
}
$('.postCommentsLoader').addClass('d-none');
$('.postCommentsContainer').removeClass('d-none');
// setTimeout(function() {
// document.querySelectorAll('.status-comment .postCommentsContainer .comment-body a').forEach(function(i, e) {
// i.href = App.util.format.rewriteLinks(i);
// });
// }, 500);
}).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;
}
}
});
},
followingModal() {
if(this.following.length > 0) {
this.$refs.followingModal.show();
return;
}
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/following', {
params: {
page: this.followingCursor
}
})
.then(res => {
this.following = res.data;
this.followingCursor++;
});
if(res.data.length < 10) {
this.followingMore = false;
}
this.$refs.followingModal.show();
},
followersModal() {
if(this.followers.length > 0) {
this.$refs.followerModal.show();
return;
}
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/followers', {
params: {
page: this.followerCursor
}
})
.then(res => {
this.followers = res.data;
this.followerCursor++;
})
if(res.data.length < 10) {
this.followerMore = false;
}
this.$refs.followerModal.show();
},
followingLoadMore() {
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/following', {
params: {
page: this.followingCursor
}
})
.then(res => {
if(res.data.length > 0) {
this.following.push(...res.data);
this.followingCursor++;
}
if(res.data.length < 10) {
this.followingMore = false;
}
});
},
followersLoadMore() {
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/followers', {
params: {
page: this.followerCursor
}
})
.then(res => {
if(res.data.length > 0) {
this.followers.push(...res.data);
this.followerCursor++;
}
if(res.data.length < 10) {
this.followerMore = false;
}
});
},
lightbox(status) {
window.location.href = status.media_attachments[0].url;
return;
this.lightboxMedia = status.media_attachments[0];
this.$refs.lightboxModal.show();
},
expLc(status) {
if(this.config.ab.lc == false) {
return true;
}
if(this.statusOwner(status) == true) {
return true;
}
return false;
},
expRec() { expRec() {
//return; //return;
@ -1223,33 +829,6 @@
}); });
}, },
followAction(status) {
let id = status.account.id;
axios.post('/i/follow', {
item: id
}).then(res => {
this.feed.forEach(s => {
if(s.account.id == id) {
s.account.relationship.following = !s.account.relationship.following;
}
});
let username = status.account.acct;
if(status.account.relationship.following) {
swal('Follow successful!', 'You are now following ' + username, 'success');
} else {
swal('Unfollow successful!', 'You are no longer following ' + username, 'success');
}
}).catch(err => {
if(err.response.data.message) {
swal('Error', err.response.data.message, 'error');
}
});
},
owner(status) { owner(status) {
return this.profile.id === status.account.id; return this.profile.id === status.account.id;
}, },
@ -1335,80 +914,38 @@
}) })
}, },
ctxCopyEmbed() { commentFocus(status, $event) {
navigator.clipboard.writeText(this.ctxEmbedPayload); if(status.comments_disabled) {
this.ctxEmbedShowCaption = true; return;
this.ctxEmbedShowLikes = false; }
this.ctxEmbedCompactMode = false;
this.$refs.ctxEmbedModal.hide(); // if(this.status && this.status.id == status.id) {
}, // this.$refs.replyModal.show();
// return;
// }
this.status = status;
this.replies = {};
this.replyStatus = {};
this.replyText = '';
this.replyId = status.id;
this.replyStatus = status;
// this.$refs.replyModal.show();
this.fetchStatusComments(status, '');
$('nav').hide();
$('footer').hide();
$('.mobile-footer-spacer').attr('style', 'display:none !important');
$('.mobile-footer').attr('style', 'display:none !important');
this.currentLayout = 'comments';
window.history.pushState({}, '', this.statusUrl(status));
return;
},
formatCount(count) { formatCount(count) {
return App.util.format.count(count); return App.util.format.count(count);
}, },
statusUrl(status) {
if(status.local == true) {
return status.url;
}
return '/i/web/post/_/' + status.account.id + '/' + status.id;
},
profileUrl(status) {
if(status.local == true) {
return status.account.url;
}
return '/i/web/profile/_/' + status.account.id;
},
statusCardUsernameFormat(status) {
if(status.account.local == true) {
return status.account.username;
}
let fmt = window.App.config.username.remote.format;
let txt = window.App.config.username.remote.custom;
let usr = status.account.username;
let dom = document.createElement('a');
dom.href = status.account.url;
dom = dom.hostname;
switch(fmt) {
case '@':
return usr + '<span class="text-lighter font-weight-bold">@' + dom + '</span>';
break;
case 'from':
return usr + '<span class="text-lighter font-weight-bold"> <span class="font-weight-normal">from</span> ' + dom + '</span>';
break;
case 'custom':
return usr + '<span class="text-lighter font-weight-bold"> ' + txt + ' ' + dom + '</span>';
break;
default:
return usr + '<span class="text-lighter font-weight-bold">@' + dom + '</span>';
break;
}
},
previewUrl(status) {
return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url;
},
previewBackground(status) {
let preview = this.previewUrl(status);
return 'background-image: url(' + preview + ');';
},
trimCaption(caption, len = 60) {
return _.truncate(caption, {
length: len
});
},
hasStory() { hasStory() {
axios.get('/api/stories/v0/exists/'+this.profile.id) axios.get('/api/stories/v0/exists/'+this.profile.id)
.then(res => { .then(res => {

View file

@ -356,7 +356,6 @@
ctxModMenuClose() { ctxModMenuClose() {
this.closeModals(); this.closeModals();
this.$refs.ctxModal.show();
}, },
ctxModOtherMenuClose() { ctxModOtherMenuClose() {
@ -490,6 +489,7 @@
}).then(res => { }).then(res => {
swal('Success', 'Successfully added content warning', 'success'); swal('Success', 'Successfully added content warning', 'success');
status.sensitive = true; status.sensitive = true;
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
}).catch(err => { }).catch(err => {
swal( swal(
@ -497,6 +497,7 @@
'Something went wrong, please try again later.', 'Something went wrong, please try again later.',
'error' 'error'
); );
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
}); });
} }
@ -520,6 +521,7 @@
}).then(res => { }).then(res => {
swal('Success', 'Successfully added content warning', 'success'); swal('Success', 'Successfully added content warning', 'success');
status.sensitive = false; status.sensitive = false;
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
}).catch(err => { }).catch(err => {
swal( swal(
@ -527,6 +529,7 @@
'Something went wrong, please try again later.', 'Something went wrong, please try again later.',
'error' 'error'
); );
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
}); });
} }
@ -552,8 +555,10 @@
return f.id != status.id; return f.id != status.id;
}); });
swal('Success', 'Successfully unlisted post', 'success'); swal('Success', 'Successfully unlisted post', 'success');
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
}).catch(err => { }).catch(err => {
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
swal( swal(
'Error', 'Error',
@ -581,9 +586,10 @@
item_type: 'status' item_type: 'status'
}).then(res => { }).then(res => {
swal('Success', 'Successfully marked account as spammer', 'success'); swal('Success', 'Successfully marked account as spammer', 'success');
self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
}).catch(err => { }).catch(err => {
console.log(err); self.closeModals();
self.ctxModMenuClose(); self.ctxModMenuClose();
swal( swal(
'Error', 'Error',
@ -625,7 +631,39 @@
} }
return '/i/web/post/_/' + status.account.id + '/' + status.id; return '/i/web/post/_/' + status.account.id + '/' + status.id;
} },
deletePost(status) {
if($('body').hasClass('loggedIn') == false || this.ownerOrAdmin(status) == false) {
return;
}
if(window.confirm('Are you sure you want to delete this post?') == false) {
return;
}
axios.post('/i/delete', {
type: 'status',
item: status.id
}).then(res => {
this.$emit('status-delete', status.id);
this.closeModals();
}).catch(err => {
swal('Error', 'Something went wrong. Please try again later.', 'error');
});
},
owner(status) {
return this.profile.id === status.account.id;
},
admin() {
return this.profile.is_admin == true;
},
ownerOrAdmin(status) {
return this.owner(status) || this.admin();
},
} }
} }
</script> </script>

View file

@ -124,6 +124,7 @@
ref="contextMenu" ref="contextMenu"
:status="status" :status="status"
:profile="profile" :profile="profile"
v-on:status-delete="statusDeleted"
/> />
</div> </div>
</template> </template>
@ -166,49 +167,6 @@
replyText: '', replyText: '',
replyNsfw: false, replyNsfw: false,
emoji: window.App.util.emoji, emoji: window.App.util.emoji,
ctxMenuStatus: false,
ctxMenuRelationship: false,
ctxEmbedPayload: false,
copiedEmbed: false,
replySending: false,
ctxEmbedShowCaption: true,
ctxEmbedShowLikes: false,
ctxEmbedCompactMode: false,
confirmModalTitle: 'Are you sure?',
confirmModalIdentifer: null,
confirmModalType: false,
tributeSettings: {
collection: [
{
trigger: '@',
menuShowMinLength: 2,
values: (function (text, cb) {
let url = '/api/compose/v0/search/mention';
axios.get(url, { params: { q: text }})
.then(res => {
cb(res.data);
})
.catch(err => {
console.log(err);
})
})
},
{
trigger: '#',
menuShowMinLength: 2,
values: (function (text, cb) {
let url = '/api/compose/v0/search/hashtag';
axios.get(url, { params: { q: text }})
.then(res => {
cb(res.data);
})
.catch(err => {
console.log(err);
})
})
}
]
},
} }
}, },
@ -305,65 +263,10 @@
} }
}, },
// ctxMenu() {
// this.$emit('ctx-menu', this.status);
// },
commentFocus(status, $event) { commentFocus(status, $event) {
this.$emit('comment-focus', status); this.$emit('comment-focus', status);
}, },
muteProfile(status) {
if($('body').hasClass('loggedIn') == false) {
return;
}
axios.post('/i/mute', {
type: 'user',
item: status.account.id
}).then(res => {
this.feed = this.feed.filter(s => s.account.id !== status.account.id);
swal('Success', 'You have successfully muted ' + status.account.acct, 'success');
}).catch(err => {
swal('Error', 'Something went wrong. Please try again later.', 'error');
});
},
blockProfile(status) {
if($('body').hasClass('loggedIn') == false) {
return;
}
axios.post('/i/block', {
type: 'user',
item: status.account.id
}).then(res => {
this.feed = this.feed.filter(s => s.account.id !== status.account.id);
swal('Success', 'You have successfully blocked ' + status.account.acct, 'success');
}).catch(err => {
swal('Error', 'Something went wrong. Please try again later.', 'error');
});
},
deletePost(status) {
if($('body').hasClass('loggedIn') == false || this.ownerOrAdmin(status) == false) {
return;
}
if(window.confirm('Are you sure you want to delete this post?') == false) {
return;
}
axios.post('/i/delete', {
type: 'status',
item: status.id
}).then(res => {
this.$emit('status-delete', status.id);
this.closeModals();
}).catch(err => {
swal('Error', 'Something went wrong. Please try again later.', 'error');
});
},
commentSubmit(status, $event) { commentSubmit(status, $event) {
this.replySending = true; this.replySending = true;
let id = status.id; let id = status.id;
@ -386,132 +289,6 @@
this.replySending = false; this.replySending = false;
}, },
moderatePost(status, action, $event) {
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;
}
},
followAction(status) {
let id = status.account.id;
axios.post('/i/follow', {
item: id
}).then(res => {
this.feed.forEach(s => {
if(s.account.id == id) {
s.account.relationship.following = !s.account.relationship.following;
}
});
let username = status.account.acct;
if(status.account.relationship.following) {
swal('Follow successful!', 'You are now following ' + username, 'success');
} else {
swal('Unfollow successful!', 'You are no longer following ' + username, 'success');
}
}).catch(err => {
if(err.response.data.message) {
swal('Error', err.response.data.message, 'error');
}
});
},
owner(status) { owner(status) {
return this.profile.id === status.account.id; return this.profile.id === status.account.id;
}, },
@ -526,242 +303,15 @@
ctxMenu() { ctxMenu() {
this.$refs.contextMenu.open(); this.$refs.contextMenu.open();
// let status = this.status;
// this.ctxMenuStatus = status;
// this.ctxEmbedPayload = window.App.util.embed.post(status.url);
// if(status.account.id == this.profile.id) {
// this.ctxMenuRelationship = false;
// this.$refs.ctxModal.show();
// } else {
// axios.get('/api/pixelfed/v1/accounts/relationships', {
// params: {
// 'id[]': status.account.id
// }
// }).then(res => {
// this.ctxMenuRelationship = res.data[0];
// this.$refs.ctxModal.show();
// });
// }
},
closeCtxMenu(truncate) {
this.copiedEmbed = false;
this.ctxMenuStatus = false;
this.ctxMenuRelationship = false;
this.$refs.ctxModal.hide();
this.$refs.ctxReport.hide();
this.$refs.ctxReportOther.hide();
this.closeModals();
},
ctxMenuCopyLink() {
let status = this.ctxMenuStatus;
navigator.clipboard.writeText(status.url);
this.closeModals();
return;
},
ctxMenuGoToPost() {
let status = this.ctxMenuStatus;
window.location.href = this.statusUrl(status);
this.closeCtxMenu();
return;
},
ctxMenuFollow() {
let id = this.ctxMenuStatus.account.id;
axios.post('/i/follow', {
item: id
}).then(res => {
let username = this.ctxMenuStatus.account.acct;
this.closeCtxMenu();
setTimeout(function() {
swal('Follow successful!', 'You are now following ' + username, 'success');
}, 500);
});
},
ctxMenuUnfollow() {
let id = this.ctxMenuStatus.account.id;
axios.post('/i/follow', {
item: id
}).then(res => {
let username = this.ctxMenuStatus.account.acct;
if(this.scope == 'home') {
this.feed = this.feed.filter(s => {
return s.account.id != this.ctxMenuStatus.account.id;
});
}
this.closeCtxMenu();
setTimeout(function() {
swal('Unfollow successful!', 'You are no longer following ' + username, 'success');
}, 500);
});
},
ctxMenuReportPost() {
this.$refs.ctxModal.hide();
this.$refs.ctxReport.show();
return;
},
ctxMenuEmbed() {
this.closeModals();
this.$refs.ctxEmbedModal.show();
},
ctxMenuShare() {
this.$refs.ctxModal.hide();
this.$refs.ctxShareModal.show();
},
closeCtxShareMenu() {
this.$refs.ctxShareModal.hide();
this.$refs.ctxModal.show();
},
ctxCopyEmbed() {
navigator.clipboard.writeText(this.ctxEmbedPayload);
this.ctxEmbedShowCaption = true;
this.ctxEmbedShowLikes = false;
this.ctxEmbedCompactMode = false;
this.$refs.ctxEmbedModal.hide();
},
ctxModMenuShow() {
this.$refs.ctxModal.hide();
this.$refs.ctxModModal.show();
},
ctxModOtherMenuShow() {
this.$refs.ctxModal.hide();
this.$refs.ctxModModal.hide();
this.$refs.ctxModOtherModal.show();
},
ctxModMenu() {
this.$refs.ctxModal.hide();
},
ctxModMenuClose() {
this.closeModals();
this.$refs.ctxModal.show();
},
ctxModOtherMenuClose() {
this.closeModals();
this.$refs.ctxModModal.show();
},
formatCount(count) {
return App.util.format.count(count);
},
openCtxReportOtherMenu() {
let s = this.ctxMenuStatus;
this.closeCtxMenu();
this.ctxMenuStatus = s;
this.$refs.ctxReportOther.show();
},
ctxReportMenuGoBack() {
this.$refs.ctxReportOther.hide();
this.$refs.ctxReport.hide();
this.$refs.ctxModal.show();
},
ctxReportOtherMenuGoBack() {
this.$refs.ctxReportOther.hide();
this.$refs.ctxModal.hide();
this.$refs.ctxReport.show();
},
sendReport(type) {
let id = this.ctxMenuStatus.id;
swal({
'title': 'Confirm Report',
'text': 'Are you sure you want to report this post?',
'icon': 'warning',
'buttons': true,
'dangerMode': true
}).then((res) => {
if(res) {
axios.post('/i/report/', {
'report': type,
'type': 'post',
'id': id,
}).then(res => {
this.closeCtxMenu();
swal('Report Sent!', 'We have successfully received your report.', 'success');
}).catch(err => {
swal('Oops!', 'There was an issue reporting this post.', 'error');
})
} else {
this.closeCtxMenu();
}
});
},
closeModals() {
this.$refs.ctxModal.hide();
this.$refs.ctxModModal.hide();
this.$refs.ctxModOtherModal.hide();
this.$refs.ctxShareModal.hide();
this.$refs.ctxEmbedModal.hide();
this.$refs.ctxReport.hide();
this.$refs.ctxReportOther.hide();
this.$refs.ctxConfirm.hide();
this.$refs.lightboxModal.hide();
this.$refs.replyModal.hide();
this.$refs.ctxStatusModal.hide();
},
openCtxStatusModal() {
this.closeModals();
this.$refs.ctxStatusModal.show();
},
openConfirmModal() {
this.closeModals();
this.$refs.ctxConfirm.show();
},
closeConfirmModal() {
this.closeModals();
this.confirmModalTitle = 'Are you sure?';
this.confirmModalType = false;
this.confirmModalIdentifer = null;
},
confirmModalConfirm() {
switch(this.confirmModalType) {
case 'post.delete':
axios.post('/i/delete', {
type: 'status',
item: this.confirmModalIdentifer
}).then(res => {
this.feed = this.feed.filter(s => {
return s.id != this.confirmModalIdentifer;
});
this.closeConfirmModal();
}).catch(err => {
this.closeConfirmModal();
swal('Error', 'Something went wrong. Please try again later.', 'error');
});
break;
}
this.closeConfirmModal();
},
confirmModalCancel() {
this.closeConfirmModal();
}, },
timeAgo(ts) { timeAgo(ts) {
return App.util.format.timeAgo(ts); return App.util.format.timeAgo(ts);
}, },
statusDeleted(status) {
this.$emit('status-delete', status);
}
} }
} }
</script> </script>