mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-19 13:01:26 +00:00
commit
a3fb4d24c3
18 changed files with 1514 additions and 50 deletions
|
@ -2,6 +2,7 @@ root = true
|
|||
|
||||
[*]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
|
|
@ -104,6 +104,11 @@
|
|||
- Update notifications component, improve UX with exponential retry and loading state ([937e6d07](https://github.com/pixelfed/pixelfed/commit/937e6d07))
|
||||
- Update likeModal and shareModal components, use new pagination logic and re-add Follow/Unfollow buttons ([b565ead6](https://github.com/pixelfed/pixelfed/commit/b565ead6))
|
||||
- Update profileFeed component, fix pagination ([7cf41628](https://github.com/pixelfed/pixelfed/commit/7cf41628))
|
||||
- Update ApiV1Controller, add BookmarkService logic to bookmark endpoints ([29b1af10](https://github.com/pixelfed/pixelfed/commit/29b1af10))
|
||||
- Update ApiV1Controller, filter conversations without last_status ([e8a6a8c7](https://github.com/pixelfed/pixelfed/commit/e8a6a8c7))
|
||||
- Update ApiV1Controller and BookmarkController, fix api differences and allow unbookmarking regardless of relationship ([e343061a](https://github.com/pixelfed/pixelfed/commit/e343061a))
|
||||
- Update ApiV1Controller, add pixelfed entity support to bookmarks endpoint ([94069db9](https://github.com/pixelfed/pixelfed/commit/94069db9))
|
||||
- Update PostReactions, reduce bookmark timeout to 2s from 5s ([a8094e6c](https://github.com/pixelfed/pixelfed/commit/a8094e6c))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
|
||||
## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
|
||||
|
|
|
@ -2335,7 +2335,7 @@ class ApiV1Controller extends Controller
|
|||
return $res;
|
||||
})
|
||||
->filter(function($dm) {
|
||||
return isset($dm['accounts']) && count($dm['accounts']);
|
||||
return isset($dm['accounts']) && count($dm['accounts']) && !empty($dm['last_status']);
|
||||
})
|
||||
->unique(function($item, $key) {
|
||||
return $item['accounts'][0]['id'];
|
||||
|
@ -2376,6 +2376,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$res['favourited'] = LikeService::liked($user->profile_id, $res['id']);
|
||||
$res['reblogged'] = ReblogService::get($user->profile_id, $res['id']);
|
||||
$res['bookmarked'] = BookmarkService::get($user->profile_id, $res['id']);
|
||||
|
||||
return $this->json($res);
|
||||
}
|
||||
|
@ -3004,6 +3005,7 @@ class ApiV1Controller extends Controller
|
|||
'min_id' => 'nullable|integer|min:0'
|
||||
]);
|
||||
|
||||
$pe = $request->has('_pe');
|
||||
$pid = $request->user()->profile_id;
|
||||
$limit = $request->input('limit') ?? 20;
|
||||
$max_id = $request->input('max_id');
|
||||
|
@ -3017,8 +3019,15 @@ class ApiV1Controller extends Controller
|
|||
->orderByDesc('id')
|
||||
->cursorPaginate($limit);
|
||||
|
||||
$bookmarks = $bookmarkQuery->map(function($bookmark) {
|
||||
return \App\Services\StatusService::getMastodon($bookmark->status_id);
|
||||
$bookmarks = $bookmarkQuery->map(function($bookmark) use($pid, $pe) {
|
||||
$status = $pe ? StatusService::get($bookmark->status_id, false) : StatusService::getMastodon($bookmark->status_id, false);
|
||||
|
||||
if($status) {
|
||||
$status['bookmarked'] = true;
|
||||
$status['favourited'] = LikeService::liked($pid, $status['id']);
|
||||
$status['reblogged'] = ReblogService::get($pid, $status['id']);
|
||||
}
|
||||
return $status;
|
||||
})
|
||||
->filter()
|
||||
->values()
|
||||
|
@ -3056,15 +3065,30 @@ class ApiV1Controller extends Controller
|
|||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$status = Status::whereNull('uri')
|
||||
->whereScope('public')
|
||||
->findOrFail($id);
|
||||
$status = Status::findOrFail($id);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||
abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404);
|
||||
|
||||
if($status->scope == 'private') {
|
||||
abort_if(
|
||||
$pid !== $status->profile_id && !FollowerService::follows($pid, $status->profile_id),
|
||||
404,
|
||||
'Error: You cannot bookmark private posts from accounts you do not follow.'
|
||||
);
|
||||
}
|
||||
|
||||
Bookmark::firstOrCreate([
|
||||
'status_id' => $status->id,
|
||||
'profile_id' => $request->user()->profile_id
|
||||
'profile_id' => $pid
|
||||
]);
|
||||
$res = StatusService::getMastodon($status->id);
|
||||
|
||||
BookmarkService::add($pid, $status->id);
|
||||
|
||||
$res = StatusService::getMastodon($status->id, false);
|
||||
$res['bookmarked'] = true;
|
||||
|
||||
return $this->json($res);
|
||||
}
|
||||
|
@ -3080,15 +3104,23 @@ class ApiV1Controller extends Controller
|
|||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$status = Status::whereNull('uri')
|
||||
->whereScope('public')
|
||||
->findOrFail($id);
|
||||
$status = Status::findOrFail($id);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||
abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404);
|
||||
|
||||
$bookmark = Bookmark::whereStatusId($status->id)
|
||||
->whereProfileId($request->user()->profile_id)
|
||||
->firstOrFail();
|
||||
$bookmark->delete();
|
||||
$res = StatusService::getMastodon($status->id);
|
||||
->whereProfileId($pid)
|
||||
->first();
|
||||
|
||||
if($bookmark) {
|
||||
BookmarkService::del($pid, $status->id);
|
||||
$bookmark->delete();
|
||||
}
|
||||
$res = StatusService::getMastodon($status->id, false);
|
||||
$res['bookmarked'] = false;
|
||||
|
||||
return $this->json($res);
|
||||
}
|
||||
|
|
|
@ -11,47 +11,57 @@ use App\Services\FollowerService;
|
|||
|
||||
class BookmarkController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'item' => 'required|integer|min:1',
|
||||
]);
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'item' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$profile = Auth::user()->profile;
|
||||
$status = Status::findOrFail($request->input('item'));
|
||||
$profile = Auth::user()->profile;
|
||||
$status = Status::findOrFail($request->input('item'));
|
||||
|
||||
abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||
abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||
abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404);
|
||||
|
||||
if($status->scope == 'private') {
|
||||
abort_if(
|
||||
$profile->id !== $status->profile_id && !FollowerService::follows($profile->id, $status->profile_id),
|
||||
404,
|
||||
'Error: You cannot bookmark private posts from accounts you do not follow.'
|
||||
);
|
||||
}
|
||||
if($status->scope == 'private') {
|
||||
if($profile->id !== $status->profile_id && !FollowerService::follows($profile->id, $status->profile_id)) {
|
||||
if($exists = Bookmark::whereStatusId($status->id)->whereProfileId($profile->id)->first()) {
|
||||
BookmarkService::del($profile->id, $status->id);
|
||||
$exists->delete();
|
||||
|
||||
$bookmark = Bookmark::firstOrCreate(
|
||||
['status_id' => $status->id], ['profile_id' => $profile->id]
|
||||
);
|
||||
if ($request->ajax()) {
|
||||
return ['code' => 200, 'msg' => 'Bookmark removed!'];
|
||||
} else {
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
abort(404, 'Error: You cannot bookmark private posts from accounts you do not follow.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bookmark->wasRecentlyCreated) {
|
||||
BookmarkService::del($profile->id, $status->id);
|
||||
$bookmark->delete();
|
||||
} else {
|
||||
BookmarkService::add($profile->id, $status->id);
|
||||
}
|
||||
$bookmark = Bookmark::firstOrCreate(
|
||||
['status_id' => $status->id], ['profile_id' => $profile->id]
|
||||
);
|
||||
|
||||
if ($request->ajax()) {
|
||||
$response = ['code' => 200, 'msg' => 'Bookmark saved!'];
|
||||
} else {
|
||||
$response = redirect()->back();
|
||||
}
|
||||
if (!$bookmark->wasRecentlyCreated) {
|
||||
BookmarkService::del($profile->id, $status->id);
|
||||
$bookmark->delete();
|
||||
} else {
|
||||
BookmarkService::add($profile->id, $status->id);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
if ($request->ajax()) {
|
||||
$response = ['code' => 200, 'msg' => 'Bookmark saved!'];
|
||||
} else {
|
||||
$response = redirect()->back();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/manifest.js
vendored
BIN
public/js/manifest.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/profile.chunk.99838eb369862e91.js
vendored
BIN
public/js/profile.chunk.99838eb369862e91.js
vendored
Binary file not shown.
BIN
public/js/profile.chunk.e6ac60336120dcd5.js
vendored
Normal file
BIN
public/js/profile.chunk.e6ac60336120dcd5.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
251
resources/assets/components/partials/post/PostReactions.vue
Normal file
251
resources/assets/components/partials/post/PostReactions.vue
Normal file
|
@ -0,0 +1,251 @@
|
|||
<template>
|
||||
<div class="px-3 my-3" style="z-index:3;">
|
||||
<div v-if="(status.favourites_count || status.reblogs_count) && ((status.hasOwnProperty('liked_by') && status.liked_by.url) || (status.hasOwnProperty('reblogs_count') && status.reblogs_count))" class="mb-0 d-flex justify-content-between">
|
||||
<p v-if="!hideCounts && status.favourites_count" class="mb-2 reaction-liked-by">
|
||||
Liked by
|
||||
<span v-if="status.favourites_count == 1 && status.favourited == true" class="font-weight-bold">me</span>
|
||||
<span v-else>
|
||||
<router-link :to="'/i/web/profile/' + status.liked_by.id" class="primary font-weight-bold"">@{{ status.liked_by.username}}</router-link>
|
||||
<span v-if="status.liked_by.others || status.favourites_count > 1">
|
||||
and <a href="#" class="primary font-weight-bold" @click.prevent="showLikes()">{{ count(status.favourites_count - 1) }} others</a>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p v-if="!hideCounts && status.reblogs_count" class="mb-2 reaction-liked-by">
|
||||
Shared by
|
||||
<span v-if="status.reblogs_count == 1 && status.reblogged == true" class="font-weight-bold">me</span>
|
||||
<a v-else class="primary font-weight-bold" href="#" @click.prevent="showShares()">
|
||||
{{ count(status.reblogs_count) }} {{ status.reblogs_count > 1 ? 'others' : 'other' }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between" style="font-size: 14px !important;">
|
||||
<div>
|
||||
<button type="button" class="btn btn-light font-weight-bold rounded-pill mr-2" @click.prevent="like()">
|
||||
<span v-if="status.favourited" class="primary">
|
||||
<i class="fas fa-heart mr-md-1 text-danger fa-lg"></i>
|
||||
</span>
|
||||
<span v-else>
|
||||
<i class="far fa-heart mr-md-2"></i>
|
||||
</span>
|
||||
<span v-if="likesCount && !hideCounts">
|
||||
{{ count(likesCount)}}
|
||||
<span class="d-none d-md-inline">{{ likesCount == 1 ? $t('common.like') : $t('common.likes') }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="d-none d-md-inline">{{ $t('common.like') }}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button v-if="!status.comments_disabled" type="button" class="btn btn-light font-weight-bold rounded-pill mr-2 px-3" @click="showComments()">
|
||||
<i class="far fa-comment mr-md-2"></i>
|
||||
<span v-if="replyCount && !hideCounts">
|
||||
{{ count(replyCount) }}
|
||||
<span class="d-none d-md-inline">{{ replyCount == 1 ? $t('common.comment') : $t('common.comments') }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="d-none d-md-inline">{{ $t('common.comment') }}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-light font-weight-bold rounded-pill"
|
||||
:disabled="isReblogging"
|
||||
@click="handleReblog()">
|
||||
<span v-if="isReblogging">
|
||||
<b-spinner variant="warning" small />
|
||||
</span>
|
||||
<span v-else>
|
||||
<i v-if="status.reblogged == true" class="fas fa-retweet fa-lg text-warning"></i>
|
||||
<i v-else class="far fa-retweet"></i>
|
||||
|
||||
<span v-if="status.reblogs_count && !hideCounts" class="ml-md-2">
|
||||
{{ count(status.reblogs_count) }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="!status.in_reply_to_id && !status.reblog_of_id"
|
||||
type="button"
|
||||
class="btn btn-light font-weight-bold rounded-pill ml-3"
|
||||
:disabled="isBookmarking"
|
||||
@click="handleBookmark()">
|
||||
<span v-if="isBookmarking">
|
||||
<b-spinner variant="warning" small />
|
||||
</span>
|
||||
<span v-else>
|
||||
<i v-if="status.hasOwnProperty('bookmarked_at') || (status.hasOwnProperty('bookmarked') && status.bookmarked == true)" class="fas fa-bookmark fa-lg text-warning"></i>
|
||||
<i v-else class="far fa-bookmark"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button v-if="admin" type="button" class="ml-3 btn btn-light font-weight-bold rounded-pill" v-b-tooltip.hover title="Moderation Tools" @click="openModTools()">
|
||||
<i class="far fa-user-crown"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
import CommentDrawer from './CommentDrawer.vue';
|
||||
import ProfileHoverCard from './../profile/ProfileHoverCard.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
status: {
|
||||
type: Object
|
||||
},
|
||||
|
||||
profile: {
|
||||
type: Object
|
||||
},
|
||||
|
||||
admin: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
"comment-drawer": CommentDrawer,
|
||||
"profile-hover-card": ProfileHoverCard
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
key: 1,
|
||||
menuLoading: true,
|
||||
sensitive: false,
|
||||
isReblogging: false,
|
||||
isBookmarking: false,
|
||||
owner: false,
|
||||
license: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
hideCounts: {
|
||||
get() {
|
||||
return this.$store.state.hideCounts == true;
|
||||
}
|
||||
},
|
||||
|
||||
autoloadComments: {
|
||||
get() {
|
||||
return this.$store.state.autoloadComments == true;
|
||||
}
|
||||
},
|
||||
|
||||
newReactions: {
|
||||
get() {
|
||||
return this.$store.state.newReactions;
|
||||
},
|
||||
},
|
||||
|
||||
likesCount: function() {
|
||||
return this.status.favourites_count;
|
||||
},
|
||||
|
||||
replyCount: function() {
|
||||
return this.status.reply_count;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
count(val) {
|
||||
return App.util.format.count(val);
|
||||
},
|
||||
|
||||
like() {
|
||||
event.currentTarget.blur();
|
||||
if(this.status.favourited) {
|
||||
this.$emit('unlike');
|
||||
} else {
|
||||
this.$emit('like');
|
||||
}
|
||||
},
|
||||
|
||||
showLikes() {
|
||||
event.currentTarget.blur();
|
||||
this.$emit('likes-modal');
|
||||
},
|
||||
|
||||
showShares() {
|
||||
event.currentTarget.blur();
|
||||
this.$emit('shares-modal');
|
||||
},
|
||||
|
||||
showComments() {
|
||||
event.currentTarget.blur();
|
||||
this.$emit('toggle-comments');
|
||||
},
|
||||
|
||||
copyLink() {
|
||||
event.currentTarget.blur();
|
||||
App.util.clipboard(this.status.url);
|
||||
},
|
||||
|
||||
shareToOther() {
|
||||
if (navigator.canShare) {
|
||||
navigator.share({
|
||||
url: this.status.url
|
||||
})
|
||||
.then(() => console.log('Share was successful.'))
|
||||
.catch((error) => console.log('Sharing failed', error));
|
||||
} else {
|
||||
swal('Not supported', 'Your current device does not support native sharing.', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
counterChange(type) {
|
||||
this.$emit('counter-change', type);
|
||||
},
|
||||
|
||||
showCommentLikes(post) {
|
||||
this.$emit('comment-likes-modal', post);
|
||||
},
|
||||
|
||||
handleReblog() {
|
||||
this.isReblogging = true;
|
||||
if(this.status.reblogged) {
|
||||
this.$emit('unshare');
|
||||
} else {
|
||||
this.$emit('share');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.isReblogging = false;
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
handleBookmark() {
|
||||
event.currentTarget.blur();
|
||||
this.isBookmarking = true;
|
||||
this.$emit('bookmark');
|
||||
|
||||
setTimeout(() => {
|
||||
this.isBookmarking = false;
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
getStatusAvatar() {
|
||||
if(window._sharedData.user.id == this.status.account.id) {
|
||||
return window._sharedData.user.avatar;
|
||||
}
|
||||
|
||||
return this.status.account.avatar;
|
||||
},
|
||||
|
||||
openModTools() {
|
||||
this.$emit('mod-tools');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
1165
resources/assets/components/partials/profile/ProfileFeed.vue
Normal file
1165
resources/assets/components/partials/profile/ProfileFeed.vue
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue