mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-27 16:53:17 +00:00
commit
1518d632d9
52 changed files with 995 additions and 3284 deletions
|
@ -118,6 +118,13 @@
|
||||||
- Update ApiV1Controller, fix media update. Fixes #4196 ([f3164650](https://github.com/pixelfed/pixelfed/commit/f3164650))
|
- Update ApiV1Controller, fix media update. Fixes #4196 ([f3164650](https://github.com/pixelfed/pixelfed/commit/f3164650))
|
||||||
- Update SearchApiV2Service, fix hashtag search. ([1992b5bc](https://github.com/pixelfed/pixelfed/commit/1992b5bc))
|
- Update SearchApiV2Service, fix hashtag search. ([1992b5bc](https://github.com/pixelfed/pixelfed/commit/1992b5bc))
|
||||||
- Update ApiV1Controller, allow optional mastodonMode on v2/search endpoint. ([f4a69631](https://github.com/pixelfed/pixelfed/commit/f4a69631))
|
- Update ApiV1Controller, allow optional mastodonMode on v2/search endpoint. ([f4a69631](https://github.com/pixelfed/pixelfed/commit/f4a69631))
|
||||||
|
- Update ApiV1Controller, add cursor pagination and pagination link headers to account/{id}/followers and account/{id}/following endpoints with legacy support for `page=` simple pagination ([713aa5fd](https://github.com/pixelfed/pixelfed/commit/713aa5fd))
|
||||||
|
- Update legacy Profile component to use new cursor pagination for following/follower modals ([7a1495e6](https://github.com/pixelfed/pixelfed/commit/7a1495e6))
|
||||||
|
- Update ApiV1Controller, fix link header pagination in /api/v1/statuses/{id}/favourited_by ([adc82eca](https://github.com/pixelfed/pixelfed/commit/adc82eca))
|
||||||
|
- Update ApiV1Controller, fix link header pagination in /api/v1/statuses/{id}/reblogged_by ([e346b675](https://github.com/pixelfed/pixelfed/commit/e346b675))
|
||||||
|
- Update ApiV1Controller, fix following/follower entities, use masto schema by default and update components accordingly ([4716c280](https://github.com/pixelfed/pixelfed/commit/4716c280))
|
||||||
|
- Update FollowerController, remove deprecated /i/follow endpoint ([4739d614](https://github.com/pixelfed/pixelfed/commit/4739d614))
|
||||||
|
- Update queue config, set "after_commit" to true ([304ea956](https://github.com/pixelfed/pixelfed/commit/304ea956))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
|
## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
|
||||||
|
|
|
@ -467,6 +467,11 @@ class ApiV1Controller extends Controller
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(!$account, 404);
|
abort_if(!$account, 404);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'sometimes|integer|min:1|max:80'
|
||||||
|
]);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
if(intval($pid) !== intval($account['id'])) {
|
if(intval($pid) !== intval($account['id'])) {
|
||||||
if($account['locked']) {
|
if($account['locked']) {
|
||||||
|
@ -479,18 +484,56 @@ class ApiV1Controller extends Controller
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($request->has('page') && $request->page >= 5) {
|
if($request->has('page') && $request->user()->is_admin == false) {
|
||||||
return [];
|
$page = (int) $request->input('page');
|
||||||
|
if(($page * $limit) >= 100) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if($request->has('page')) {
|
||||||
|
$res = DB::table('followers')
|
||||||
|
->select('id', 'profile_id', 'following_id')
|
||||||
|
->whereFollowingId($account['id'])
|
||||||
|
->orderByDesc('id')
|
||||||
|
->simplePaginate($limit)
|
||||||
|
->map(function($follower) use($napi) {
|
||||||
|
return $napi ? AccountService::get($follower->profile_id, true) : AccountService::getMastodon($follower->profile_id, true);
|
||||||
|
})
|
||||||
|
->filter(function($account) {
|
||||||
|
return $account && isset($account['id']);
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
$res = DB::table('followers')
|
return $this->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
$paginator = DB::table('followers')
|
||||||
->select('id', 'profile_id', 'following_id')
|
->select('id', 'profile_id', 'following_id')
|
||||||
->whereFollowingId($account['id'])
|
->whereFollowingId($account['id'])
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->simplePaginate(10)
|
->cursorPaginate($limit)
|
||||||
->map(function($follower) {
|
->withQueryString();
|
||||||
return AccountService::getMastodon($follower->profile_id);
|
|
||||||
|
$link = null;
|
||||||
|
|
||||||
|
if($paginator->onFirstPage()) {
|
||||||
|
if($paginator->hasMorePages()) {
|
||||||
|
$link = '<'.$paginator->nextPageUrl().'>; rel="prev"';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if($paginator->previousPageUrl()) {
|
||||||
|
$link = '<'.$paginator->previousPageUrl().'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($paginator->hasMorePages()) {
|
||||||
|
$link .= ($link ? ', ' : '') . '<'.$paginator->nextPageUrl().'>; rel="prev"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $paginator->map(function($follower) use($napi) {
|
||||||
|
return $napi ? AccountService::get($follower->profile_id, true) : AccountService::getMastodon($follower->profile_id, true);
|
||||||
})
|
})
|
||||||
->filter(function($account) {
|
->filter(function($account) {
|
||||||
return $account && isset($account['id']);
|
return $account && isset($account['id']);
|
||||||
|
@ -498,7 +541,8 @@ class ApiV1Controller extends Controller
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
$headers = isset($link) ? ['Link' => $link] : [];
|
||||||
|
return $this->json($res, 200, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -514,6 +558,11 @@ class ApiV1Controller extends Controller
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(!$account, 404);
|
abort_if(!$account, 404);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'sometimes|integer|min:1|max:80'
|
||||||
|
]);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
if(intval($pid) !== intval($account['id'])) {
|
if(intval($pid) !== intval($account['id'])) {
|
||||||
if($account['locked']) {
|
if($account['locked']) {
|
||||||
|
@ -526,18 +575,56 @@ class ApiV1Controller extends Controller
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($request->has('page') && $request->page >= 5) {
|
if($request->has('page') && $request->user()->is_admin == false) {
|
||||||
return [];
|
$page = (int) $request->input('page');
|
||||||
|
if(($page * $limit) >= 100) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = DB::table('followers')
|
if($request->has('page')) {
|
||||||
|
$res = DB::table('followers')
|
||||||
|
->select('id', 'profile_id', 'following_id')
|
||||||
|
->whereProfileId($account['id'])
|
||||||
|
->orderByDesc('id')
|
||||||
|
->simplePaginate($limit)
|
||||||
|
->map(function($follower) use($napi) {
|
||||||
|
return $napi ? AccountService::get($follower->following_id, true) : AccountService::getMastodon($follower->following_id, true);
|
||||||
|
})
|
||||||
|
->filter(function($account) {
|
||||||
|
return $account && isset($account['id']);
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
return $this->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
$paginator = DB::table('followers')
|
||||||
->select('id', 'profile_id', 'following_id')
|
->select('id', 'profile_id', 'following_id')
|
||||||
->whereProfileId($account['id'])
|
->whereProfileId($account['id'])
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->simplePaginate(10)
|
->cursorPaginate($limit)
|
||||||
->map(function($follower) {
|
->withQueryString();
|
||||||
return AccountService::get($follower->following_id);
|
|
||||||
|
$link = null;
|
||||||
|
|
||||||
|
if($paginator->onFirstPage()) {
|
||||||
|
if($paginator->hasMorePages()) {
|
||||||
|
$link = '<'.$paginator->nextPageUrl().'>; rel="prev"';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if($paginator->previousPageUrl()) {
|
||||||
|
$link = '<'.$paginator->previousPageUrl().'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($paginator->hasMorePages()) {
|
||||||
|
$link .= ($link ? ', ' : '') . '<'.$paginator->nextPageUrl().'>; rel="prev"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $paginator->map(function($follower) use($napi) {
|
||||||
|
return $napi ? AccountService::get($follower->following_id, true) : AccountService::getMastodon($follower->following_id, true);
|
||||||
})
|
})
|
||||||
->filter(function($account) {
|
->filter(function($account) {
|
||||||
return $account && isset($account['id']);
|
return $account && isset($account['id']);
|
||||||
|
@ -545,7 +632,8 @@ class ApiV1Controller extends Controller
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
$headers = isset($link) ? ['Link' => $link] : [];
|
||||||
|
return $this->json($res, 200, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2496,13 +2584,17 @@ class ApiV1Controller extends Controller
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'nullable|integer|min:1|max:100'
|
'limit' => 'sometimes|integer|min:1|max:80'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$limit = $request->input('limit') ?? 10;
|
$limit = $request->input('limit', 10);
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
$pid = $user->profile_id;
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$author = intval($status->profile_id) === intval($user->profile_id) || $user->is_admin;
|
$account = AccountService::get($status->profile_id, true);
|
||||||
|
abort_if(!$account, 404);
|
||||||
|
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
||||||
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
abort_if(
|
abort_if(
|
||||||
!$status->type ||
|
!$status->type ||
|
||||||
|
@ -2512,10 +2604,14 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
if(!$author) {
|
if(!$author) {
|
||||||
if($status->scope == 'private') {
|
if($status->scope == 'private') {
|
||||||
abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403);
|
abort_if(!FollowerService::follows($pid, $status->profile_id), 403);
|
||||||
} else {
|
} else {
|
||||||
abort_if(!in_array($status->scope, ['public','unlisted']), 403);
|
abort_if(!in_array($status->scope, ['public','unlisted']), 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($request->has('cursor')) {
|
||||||
|
return $this->json([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = Status::where('reblog_of_id', $status->id)
|
$res = Status::where('reblog_of_id', $status->id)
|
||||||
|
@ -2530,26 +2626,34 @@ class ApiV1Controller extends Controller
|
||||||
$headers = [];
|
$headers = [];
|
||||||
if($author && $res->hasPages()) {
|
if($author && $res->hasPages()) {
|
||||||
$links = '';
|
$links = '';
|
||||||
if($res->previousPageUrl()) {
|
if($res->onFirstPage()) {
|
||||||
$links = '<' . $res->previousPageUrl() .'>; rel="prev"';
|
if($res->nextPageUrl()) {
|
||||||
}
|
$links = '<' . $res->nextPageUrl() .'>; rel="prev"';
|
||||||
|
}
|
||||||
if($res->nextPageUrl()) {
|
} else {
|
||||||
if(!empty($links)) {
|
if($res->previousPageUrl()) {
|
||||||
$links .= ', ';
|
$links = '<' . $res->previousPageUrl() .'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($res->nextPageUrl()) {
|
||||||
|
if(!empty($links)) {
|
||||||
|
$links .= ', ';
|
||||||
|
}
|
||||||
|
$links .= '<' . $res->nextPageUrl() .'>; rel="prev"';
|
||||||
}
|
}
|
||||||
$links .= '<' . $res->nextPageUrl() .'>; rel="next"';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = ['Link' => $links];
|
$headers = ['Link' => $links];
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $res->map(function($status) use($user) {
|
$res = $res->map(function($status) use($pid, $napi) {
|
||||||
$account = AccountService::getMastodon($status->profile_id, true);
|
$account = $napi ? AccountService::get($status->profile_id, true) : AccountService::getMastodon($status->profile_id, true);
|
||||||
if(!$account) {
|
if(!$account) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$account['follows'] = $status->profile_id == $user->profile_id ? null : FollowerService::follows($user->profile_id, $status->profile_id);
|
if($napi) {
|
||||||
|
$account['follows'] = $status->profile_id == $pid ? null : FollowerService::follows($pid, $status->profile_id);
|
||||||
|
}
|
||||||
return $account;
|
return $account;
|
||||||
})
|
})
|
||||||
->filter(function($account) {
|
->filter(function($account) {
|
||||||
|
@ -2572,13 +2676,17 @@ class ApiV1Controller extends Controller
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'nullable|integer|min:1|max:100'
|
'limit' => 'nullable|integer|min:1|max:80'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$limit = $request->input('limit') ?? 10;
|
$limit = $request->input('limit', 10);
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
$pid = $user->profile_id;
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$author = intval($status->profile_id) === intval($user->profile_id) || $user->is_admin;
|
$account = AccountService::get($status->profile_id, true);
|
||||||
|
abort_if(!$account, 404);
|
||||||
|
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
||||||
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
abort_if(
|
abort_if(
|
||||||
!$status->type ||
|
!$status->type ||
|
||||||
|
@ -2588,7 +2696,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
if(!$author) {
|
if(!$author) {
|
||||||
if($status->scope == 'private') {
|
if($status->scope == 'private') {
|
||||||
abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403);
|
abort_if(!FollowerService::follows($pid, $status->profile_id), 403);
|
||||||
} else {
|
} else {
|
||||||
abort_if(!in_array($status->scope, ['public','unlisted']), 403);
|
abort_if(!in_array($status->scope, ['public','unlisted']), 403);
|
||||||
}
|
}
|
||||||
|
@ -2610,29 +2718,39 @@ class ApiV1Controller extends Controller
|
||||||
$headers = [];
|
$headers = [];
|
||||||
if($author && $res->hasPages()) {
|
if($author && $res->hasPages()) {
|
||||||
$links = '';
|
$links = '';
|
||||||
if($res->previousPageUrl()) {
|
|
||||||
$links = '<' . $res->previousPageUrl() .'>; rel="prev"';
|
|
||||||
}
|
|
||||||
|
|
||||||
if($res->nextPageUrl()) {
|
if($res->onFirstPage()) {
|
||||||
if(!empty($links)) {
|
if($res->nextPageUrl()) {
|
||||||
$links .= ', ';
|
$links = '<' . $res->nextPageUrl() .'>; rel="prev"';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if($res->previousPageUrl()) {
|
||||||
|
$links = '<' . $res->previousPageUrl() .'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if($res->nextPageUrl()) {
|
||||||
|
if(!empty($links)) {
|
||||||
|
$links .= ', ';
|
||||||
|
}
|
||||||
|
$links .= '<' . $res->nextPageUrl() .'>; rel="prev"';
|
||||||
}
|
}
|
||||||
$links .= '<' . $res->nextPageUrl() .'>; rel="next"';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$headers = ['Link' => $links];
|
$headers = ['Link' => $links];
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $res->map(function($like) use($user) {
|
$res = $res->map(function($like) use($pid, $napi) {
|
||||||
$account = AccountService::getMastodon($like->profile_id, true);
|
$account = $napi ? AccountService::get($like->profile_id, true) : AccountService::getMastodon($like->profile_id, true);
|
||||||
if(!$account) {
|
if(!$account) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$account['follows'] = $like->profile_id == $user->profile_id ? null : FollowerService::follows($user->profile_id, $like->profile_id);
|
|
||||||
|
if($napi) {
|
||||||
|
$account['follows'] = $like->profile_id == $pid ? null : FollowerService::follows($pid, $like->profile_id);
|
||||||
|
}
|
||||||
return $account;
|
return $account;
|
||||||
})
|
})
|
||||||
->filter(function($account) use($user) {
|
->filter(function($account) {
|
||||||
return $account && isset($account['id']);
|
return $account && isset($account['id']);
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
|
|
|
@ -23,109 +23,7 @@ class FollowerController extends Controller
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
abort(422, 'Deprecated API Endpoint, use /api/v1/accounts/{id}/follow or /api/v1/accounts/{id}/unfollow instead.');
|
||||||
'item' => 'required|string',
|
|
||||||
'force' => 'nullable|boolean',
|
|
||||||
]);
|
|
||||||
$force = (bool) $request->input('force', true);
|
|
||||||
$item = (int) $request->input('item');
|
|
||||||
$url = $this->handleFollowRequest($item, $force);
|
|
||||||
if($request->wantsJson() == true) {
|
|
||||||
return response()->json(200);
|
|
||||||
} else {
|
|
||||||
return redirect($url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function handleFollowRequest($item, $force)
|
|
||||||
{
|
|
||||||
$user = Auth::user()->profile;
|
|
||||||
|
|
||||||
$target = Profile::where('id', '!=', $user->id)->whereNull('status')->findOrFail($item);
|
|
||||||
$private = (bool) $target->is_private;
|
|
||||||
$remote = (bool) $target->domain;
|
|
||||||
$blocked = UserFilter::whereUserId($target->id)
|
|
||||||
->whereFilterType('block')
|
|
||||||
->whereFilterableId($user->id)
|
|
||||||
->whereFilterableType('App\Profile')
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
if($blocked == true) {
|
|
||||||
abort(400, 'You cannot follow this user.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$isFollowing = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->exists();
|
|
||||||
|
|
||||||
if($private == true && $isFollowing == 0) {
|
|
||||||
if($user->following()->count() >= Follower::MAX_FOLLOWING) {
|
|
||||||
abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
|
|
||||||
}
|
|
||||||
|
|
||||||
if($user->following()->where('followers.created_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
|
|
||||||
abort(400, 'You can only follow ' . Follower::FOLLOW_PER_HOUR . ' users per hour');
|
|
||||||
}
|
|
||||||
|
|
||||||
$follow = FollowRequest::firstOrCreate([
|
|
||||||
'follower_id' => $user->id,
|
|
||||||
'following_id' => $target->id
|
|
||||||
]);
|
|
||||||
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
|
||||||
$this->sendFollow($user, $target);
|
|
||||||
}
|
|
||||||
|
|
||||||
FollowerService::add($user->id, $target->id);
|
|
||||||
} elseif ($private == false && $isFollowing == 0) {
|
|
||||||
if($user->following()->count() >= Follower::MAX_FOLLOWING) {
|
|
||||||
abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
|
|
||||||
}
|
|
||||||
|
|
||||||
if($user->following()->where('followers.created_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
|
|
||||||
abort(400, 'You can only follow ' . Follower::FOLLOW_PER_HOUR . ' users per hour');
|
|
||||||
}
|
|
||||||
$follower = new Follower();
|
|
||||||
$follower->profile_id = $user->id;
|
|
||||||
$follower->following_id = $target->id;
|
|
||||||
$follower->save();
|
|
||||||
|
|
||||||
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
|
||||||
$this->sendFollow($user, $target);
|
|
||||||
}
|
|
||||||
FollowerService::add($user->id, $target->id);
|
|
||||||
FollowPipeline::dispatch($follower);
|
|
||||||
} else {
|
|
||||||
if($force == true) {
|
|
||||||
$request = FollowRequest::whereFollowerId($user->id)->whereFollowingId($target->id)->exists();
|
|
||||||
$follower = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->exists();
|
|
||||||
if($remote == true && $request && !$follower) {
|
|
||||||
$this->sendFollow($user, $target);
|
|
||||||
}
|
|
||||||
if($remote == true && $follower) {
|
|
||||||
$this->sendUndoFollow($user, $target);
|
|
||||||
}
|
|
||||||
Follower::whereProfileId($user->id)
|
|
||||||
->whereFollowingId($target->id)
|
|
||||||
->delete();
|
|
||||||
FollowerService::remove($user->id, $target->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::forget('profile:following:'.$target->id);
|
|
||||||
Cache::forget('profile:followers:'.$target->id);
|
|
||||||
Cache::forget('profile:following:'.$user->id);
|
|
||||||
Cache::forget('profile:followers:'.$user->id);
|
|
||||||
Cache::forget('api:local:exp:rec:'.$user->id);
|
|
||||||
Cache::forget('user:account:id:'.$target->user_id);
|
|
||||||
Cache::forget('user:account:id:'.$user->user_id);
|
|
||||||
Cache::forget('px:profile:followers-v1.3:'.$user->id);
|
|
||||||
Cache::forget('px:profile:followers-v1.3:'.$target->id);
|
|
||||||
Cache::forget('px:profile:following-v1.3:'.$user->id);
|
|
||||||
Cache::forget('px:profile:following-v1.3:'.$target->id);
|
|
||||||
Cache::forget('profile:follower_count:'.$target->id);
|
|
||||||
Cache::forget('profile:follower_count:'.$user->id);
|
|
||||||
Cache::forget('profile:following_count:'.$target->id);
|
|
||||||
Cache::forget('profile:following_count:'.$user->id);
|
|
||||||
|
|
||||||
return $target->url();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendFollow($user, $target)
|
public function sendFollow($user, $target)
|
||||||
|
|
|
@ -62,36 +62,6 @@ class PublicApiController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getLikes($status)
|
|
||||||
{
|
|
||||||
if(false == Auth::check()) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
$profile = Auth::user()->profile;
|
|
||||||
if($profile->status) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
$likes = $status->likedBy()->orderBy('created_at','desc')->paginate(10);
|
|
||||||
$collection = new Fractal\Resource\Collection($likes, new AccountTransformer());
|
|
||||||
return $this->fractal->createData($collection)->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getShares($status)
|
|
||||||
{
|
|
||||||
if(false == Auth::check()) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
$profile = Auth::user()->profile;
|
|
||||||
if($profile->status) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
$shares = $status->sharedBy()->orderBy('created_at','desc')->paginate(10);
|
|
||||||
$collection = new Fractal\Resource\Collection($shares, new AccountTransformer());
|
|
||||||
return $this->fractal->createData($collection)->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatus(Request $request, $id)
|
public function getStatus(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
@ -216,41 +186,6 @@ class PublicApiController extends Controller
|
||||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function statusLikes(Request $request, $username, $id)
|
|
||||||
{
|
|
||||||
abort_if(!$request->user(), 404);
|
|
||||||
$status = Status::findOrFail($id);
|
|
||||||
$this->scopeCheck($status->profile, $status);
|
|
||||||
$page = $request->input('page');
|
|
||||||
if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
|
|
||||||
return response()->json([
|
|
||||||
'data' => []
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$likes = $this->getLikes($status);
|
|
||||||
return response()->json([
|
|
||||||
'data' => $likes
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function statusShares(Request $request, $username, $id)
|
|
||||||
{
|
|
||||||
abort_if(!$request->user(), 404);
|
|
||||||
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
|
|
||||||
$status = Status::whereProfileId($profile->id)->findOrFail($id);
|
|
||||||
$this->scopeCheck($profile, $status);
|
|
||||||
$page = $request->input('page');
|
|
||||||
if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
|
|
||||||
return response()->json([
|
|
||||||
'data' => []
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$shares = $this->getShares($status);
|
|
||||||
return response()->json([
|
|
||||||
'data' => $shares
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function scopeCheck(Profile $profile, Status $status)
|
protected function scopeCheck(Profile $profile, Status $status)
|
||||||
{
|
{
|
||||||
if($profile->is_private == true && Auth::check() == false) {
|
if($profile->is_private == true && Auth::check() == false) {
|
||||||
|
@ -811,68 +746,6 @@ class PublicApiController extends Controller
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function accountFollowers(Request $request, $id)
|
|
||||||
{
|
|
||||||
abort_if(!$request->user(), 403);
|
|
||||||
$account = AccountService::get($id, true);
|
|
||||||
abort_if(!$account, 404);
|
|
||||||
$pid = $request->user()->profile_id;
|
|
||||||
|
|
||||||
if($pid != $account['id']) {
|
|
||||||
if($account['locked']) {
|
|
||||||
if(!FollowerService::follows($pid, $account['id'])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(AccountService::hiddenFollowers($id)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($request->has('page') && $request->page >= 10) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = collect(FollowerService::followersPaginate($account['id'], $request->input('page', 1)))
|
|
||||||
->map(fn($id) => AccountService::get($id, true))
|
|
||||||
->filter()
|
|
||||||
->values();
|
|
||||||
|
|
||||||
return response()->json($res);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function accountFollowing(Request $request, $id)
|
|
||||||
{
|
|
||||||
abort_if(!$request->user(), 403);
|
|
||||||
$account = AccountService::get($id, true);
|
|
||||||
abort_if(!$account, 404);
|
|
||||||
$pid = $request->user()->profile_id;
|
|
||||||
|
|
||||||
if($pid != $account['id']) {
|
|
||||||
if($account['locked']) {
|
|
||||||
if(!FollowerService::follows($pid, $account['id'])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(AccountService::hiddenFollowing($id)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($request->has('page') && $request->page >= 10) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = collect(FollowerService::followingPaginate($account['id'], $request->input('page', 1)))
|
|
||||||
->map(fn($id) => AccountService::get($id, true))
|
|
||||||
->filter()
|
|
||||||
->values();
|
|
||||||
|
|
||||||
return response()->json($res);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function accountStatuses(Request $request, $id)
|
public function accountStatuses(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
|
|
@ -63,6 +63,7 @@ return [
|
||||||
'queue' => 'default',
|
'queue' => 'default',
|
||||||
'retry_after' => 1800,
|
'retry_after' => 1800,
|
||||||
'block_for' => null,
|
'block_for' => null,
|
||||||
|
'after_commit' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
BIN
public/js/activity.js
vendored
BIN
public/js/activity.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~myhashtags.chunk.e466a09b04298c01.js
vendored
Normal file
BIN
public/js/discover~myhashtags.chunk.e466a09b04298c01.js
vendored
Normal file
Binary file not shown.
BIN
public/js/home.chunk.02577ae670229a49.js
vendored
Normal file
BIN
public/js/home.chunk.02577ae670229a49.js
vendored
Normal file
Binary file not shown.
BIN
public/js/home.chunk.e9544807955a793b.js
vendored
BIN
public/js/home.chunk.e9544807955a793b.js
vendored
Binary file not shown.
BIN
public/js/manifest.js
vendored
BIN
public/js/manifest.js
vendored
Binary file not shown.
BIN
public/js/notifications.chunk.d0d53f16d9998dd2.js
vendored
BIN
public/js/notifications.chunk.d0d53f16d9998dd2.js
vendored
Binary file not shown.
BIN
public/js/notifications.chunk.ee310da155305164.js
vendored
Normal file
BIN
public/js/notifications.chunk.ee310da155305164.js
vendored
Normal file
Binary file not shown.
BIN
public/js/post.chunk.3ea384fc9ebf43c7.js
vendored
BIN
public/js/post.chunk.3ea384fc9ebf43c7.js
vendored
Binary file not shown.
BIN
public/js/post.chunk.bad88be5fc04f10b.js
vendored
Normal file
BIN
public/js/post.chunk.bad88be5fc04f10b.js
vendored
Normal file
Binary file not shown.
BIN
public/js/profile.chunk.4e79ac3514929747.js
vendored
BIN
public/js/profile.chunk.4e79ac3514929747.js
vendored
Binary file not shown.
BIN
public/js/profile.chunk.d6cf0345664c2864.js
vendored
Normal file
BIN
public/js/profile.chunk.d6cf0345664c2864.js
vendored
Normal file
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/profile~followers.bundle.54e01681585449b4.js
vendored
Normal file
BIN
public/js/profile~followers.bundle.54e01681585449b4.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/js/profile~following.bundle.f9d69ec593f3d011.js
vendored
Normal file
BIN
public/js/profile~following.bundle.f9d69ec593f3d011.js
vendored
Normal file
Binary file not shown.
BIN
public/js/rempos.js
vendored
BIN
public/js/rempos.js
vendored
Binary file not shown.
BIN
public/js/rempro.js
vendored
BIN
public/js/rempro.js
vendored
Binary file not shown.
BIN
public/js/search.js
vendored
BIN
public/js/search.js
vendored
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -104,7 +104,7 @@
|
||||||
isFetchingMore: false,
|
isFetchingMore: false,
|
||||||
likes: [],
|
likes: [],
|
||||||
ids: [],
|
ids: [],
|
||||||
page: undefined,
|
cursor: undefined,
|
||||||
isUpdatingFollowState: false,
|
isUpdatingFollowState: false,
|
||||||
followStateIndex: undefined,
|
followStateIndex: undefined,
|
||||||
user: window._sharedData.user
|
user: window._sharedData.user
|
||||||
|
@ -119,13 +119,14 @@
|
||||||
this.isFetchingMore = false;
|
this.isFetchingMore = false;
|
||||||
this.likes = [];
|
this.likes = [];
|
||||||
this.ids = [];
|
this.ids = [];
|
||||||
this.page = undefined;
|
this.cursor = undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchLikes() {
|
fetchLikes() {
|
||||||
axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', {
|
axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', {
|
||||||
params: {
|
params: {
|
||||||
limit: 40
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -133,19 +134,21 @@
|
||||||
this.likes = res.data;
|
this.likes = res.data;
|
||||||
if(res.headers && res.headers.link) {
|
if(res.headers && res.headers.link) {
|
||||||
const links = parseLinkHeader(res.headers.link);
|
const links = parseLinkHeader(res.headers.link);
|
||||||
if(links.next) {
|
if(links.prev) {
|
||||||
this.page = links.next.cursor;
|
this.cursor = links.prev.cursor;
|
||||||
this.canLoadMore = true;
|
this.canLoadMore = true;
|
||||||
} else {
|
} else {
|
||||||
this.canLoadMore = false;
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
if(this.page) {
|
if(this.cursor) {
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
@ -163,7 +166,8 @@
|
||||||
axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', {
|
axios.get('/api/v1/statuses/'+this.status.id+'/favourited_by', {
|
||||||
params: {
|
params: {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
cursor: this.page
|
cursor: this.cursor,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if(!res.data || !res.data.length) {
|
if(!res.data || !res.data.length) {
|
||||||
|
@ -179,11 +183,13 @@
|
||||||
})
|
})
|
||||||
if(res.headers && res.headers.link) {
|
if(res.headers && res.headers.link) {
|
||||||
const links = parseLinkHeader(res.headers.link);
|
const links = parseLinkHeader(res.headers.link);
|
||||||
if(links.next) {
|
if(links.prev) {
|
||||||
this.page = links.next.cursor;
|
this.cursor = links.prev.cursor;
|
||||||
} else {
|
} else {
|
||||||
this.canLoadMore = false;
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
this.isFetchingMore = false;
|
this.isFetchingMore = false;
|
||||||
})
|
})
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
isFetchingMore: false,
|
isFetchingMore: false,
|
||||||
likes: [],
|
likes: [],
|
||||||
ids: [],
|
ids: [],
|
||||||
page: undefined,
|
cursor: undefined,
|
||||||
isUpdatingFollowState: false,
|
isUpdatingFollowState: false,
|
||||||
followStateIndex: undefined,
|
followStateIndex: undefined,
|
||||||
user: window._sharedData.user
|
user: window._sharedData.user
|
||||||
|
@ -119,13 +119,14 @@
|
||||||
this.isFetchingMore = false;
|
this.isFetchingMore = false;
|
||||||
this.likes = [];
|
this.likes = [];
|
||||||
this.ids = [];
|
this.ids = [];
|
||||||
this.page = undefined;
|
this.cursor = undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchShares() {
|
fetchShares() {
|
||||||
axios.get('/api/v1/statuses/'+this.status.id+'/reblogged_by', {
|
axios.get('/api/v1/statuses/'+this.status.id+'/reblogged_by', {
|
||||||
params: {
|
params: {
|
||||||
limit: 40
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -133,19 +134,21 @@
|
||||||
this.likes = res.data;
|
this.likes = res.data;
|
||||||
if(res.headers && res.headers.link) {
|
if(res.headers && res.headers.link) {
|
||||||
const links = parseLinkHeader(res.headers.link);
|
const links = parseLinkHeader(res.headers.link);
|
||||||
if(links.next) {
|
if(links.prev) {
|
||||||
this.page = links.next.cursor;
|
this.cursor = links.prev.cursor;
|
||||||
this.canLoadMore = true;
|
this.canLoadMore = true;
|
||||||
} else {
|
} else {
|
||||||
this.canLoadMore = false;
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
if(this.page) {
|
if(this.cursor) {
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
@ -163,7 +166,8 @@
|
||||||
axios.get('/api/v1/statuses/'+this.status.id+'/reblogged_by', {
|
axios.get('/api/v1/statuses/'+this.status.id+'/reblogged_by', {
|
||||||
params: {
|
params: {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
cursor: this.page
|
cursor: this.cursor,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if(!res.data || !res.data.length) {
|
if(!res.data || !res.data.length) {
|
||||||
|
@ -179,11 +183,13 @@
|
||||||
})
|
})
|
||||||
if(res.headers && res.headers.link) {
|
if(res.headers && res.headers.link) {
|
||||||
const links = parseLinkHeader(res.headers.link);
|
const links = parseLinkHeader(res.headers.link);
|
||||||
if(links.next) {
|
if(links.prev) {
|
||||||
this.page = links.next.cursor;
|
this.cursor = links.prev.cursor;
|
||||||
} else {
|
} else {
|
||||||
this.canLoadMore = false;
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
this.isFetchingMore = false;
|
this.isFetchingMore = false;
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
<template>
|
||||||
|
<div class="profile-followers-component">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-8">
|
||||||
|
<div v-if="isLoaded" class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-dark rounded-pill font-weight-bold"
|
||||||
|
@click="goBack()">
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center justify-content-center flex-column w-100 overflow-hidden">
|
||||||
|
<p class="small text-muted mb-0 text-uppercase font-weight-light cursor-pointer text-truncate text-center" style="width: 70%;" @click="goBack()">@{{ profile.acct }}</p>
|
||||||
|
<p class="lead font-weight-bold mt-n1 mb-0">{{ $t('profile.followers') }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-dark rounded-pill font-weight-bold spacer-btn" href="#">Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isLoaded" class="list-group scroll-card">
|
||||||
|
<div v-for="(account, idx) in feed" class="list-group-item">
|
||||||
|
<a
|
||||||
|
:id="'apop_'+account.id"
|
||||||
|
:href="account.url"
|
||||||
|
@click.prevent="goToProfile(account)"
|
||||||
|
class="text-decoration-none">
|
||||||
|
<div class="media">
|
||||||
|
<img
|
||||||
|
:src="account.avatar"
|
||||||
|
width="40"
|
||||||
|
height="40"
|
||||||
|
style="border-radius: 8px;"
|
||||||
|
class="mr-3 shadow-sm"
|
||||||
|
draggable="false"
|
||||||
|
loading="lazy"
|
||||||
|
onerror="this.src='/storage/avatars/default.jpg?v=0';this.onerror=null;">
|
||||||
|
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 text-truncate">
|
||||||
|
<span class="text-dark font-weight-bold text-decoration-none" v-html="getUsername(account)"></span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 mt-n1 text-muted small text-break">@{{ account.acct }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<b-popover :target="'apop_'+account.id" triggers="hover" placement="left" delay="1000" custom-class="shadow border-0 rounded-px">
|
||||||
|
<profile-hover-card :profile="account" />
|
||||||
|
</b-popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="canLoadMore">
|
||||||
|
<intersect @enter="enterIntersect">
|
||||||
|
<placeholder />
|
||||||
|
</intersect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!canLoadMore && !feed.length">
|
||||||
|
<div class="list-group-item text-center">
|
||||||
|
<div v-if="isWarmingCache" class="px-4">
|
||||||
|
<p class="mb-0 lead font-weight-bold">Loading Followers...</p>
|
||||||
|
<div class="py-3">
|
||||||
|
<b-spinner variant="primary" style="width: 1.5rem; height: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
<p class="small text-muted mb-0">Please wait while we collect followers of this account, this shouldn't take long!</p>
|
||||||
|
</div>
|
||||||
|
<p v-else class="mb-0 font-weight-bold">No followers yet!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="list-group">
|
||||||
|
<placeholder />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
import Intersect from 'vue-intersect'
|
||||||
|
import Placeholder from './../post/LikeListPlaceholder.vue';
|
||||||
|
import ProfileHoverCard from './ProfileHoverCard.vue';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { parseLinkHeader } from '@web3-storage/parse-link-header';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
profile: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ProfileHoverCard,
|
||||||
|
Intersect,
|
||||||
|
Placeholder
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'getCustomEmoji'
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoaded: false,
|
||||||
|
feed: [],
|
||||||
|
page: 1,
|
||||||
|
cursor: null,
|
||||||
|
canLoadMore: true,
|
||||||
|
isFetchingMore: false,
|
||||||
|
isWarmingCache: false,
|
||||||
|
cacheWarmTimeout: undefined,
|
||||||
|
cacheWarmInterations: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetchFollowers();
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
clearTimeout(this.cacheWarmTimeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetchFollowers() {
|
||||||
|
axios.get('/api/v1/accounts/'+this.profile.id+'/followers', {
|
||||||
|
params: {
|
||||||
|
cursor: this.cursor,
|
||||||
|
'_pe': 1
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
if(!res.data.length) {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
this.isLoaded = true;
|
||||||
|
if(this.cursor == null && this.profile.followers_count) {
|
||||||
|
this.isWarmingCache = true;
|
||||||
|
this.setCacheWarmTimeout();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(res.headers && res.headers.link) {
|
||||||
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.cursor = links.prev.cursor;
|
||||||
|
this.canLoadMore = true;
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
}
|
||||||
|
this.feed.push(...res.data);
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.isFetchingMore = false;
|
||||||
|
if(this.isWarmingCache || this.cacheWarmTimeout) {
|
||||||
|
this.isWarmingCache = false;
|
||||||
|
clearTimeout(this.cacheWarmTimeout);
|
||||||
|
this.cacheWarmTimeout = undefined;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.isFetchingMore = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
enterIntersect() {
|
||||||
|
if(this.isFetchingMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isFetchingMore = true;
|
||||||
|
this.fetchFollowers();
|
||||||
|
},
|
||||||
|
|
||||||
|
getUsername(profile) {
|
||||||
|
let self = this;
|
||||||
|
let dn = profile.display_name;
|
||||||
|
if(!dn || !dn.trim().length) {
|
||||||
|
return profile.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dn.includes(':')) {
|
||||||
|
let re = /(<a?)?:\w+:(\d{18}>)?/g;
|
||||||
|
let un = dn.replaceAll(re, function(em) {
|
||||||
|
let shortcode = em.slice(1, em.length - 1);
|
||||||
|
let emoji = self.getCustomEmoji.filter(e => {
|
||||||
|
return e.shortcode == shortcode;
|
||||||
|
});
|
||||||
|
return emoji.length ? `<img draggable="false" class="emojione custom-emoji" alt="${emoji[0].shortcode}" title="${emoji[0].shortcode}" src="${emoji[0].url}" data-original="${emoji[0].url}" data-static="${emoji[0].static_url}" width="16" height="16" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`: em;
|
||||||
|
});
|
||||||
|
return un;
|
||||||
|
} else {
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goToProfile(account) {
|
||||||
|
this.$router.push({
|
||||||
|
path: `/i/web/profile/${account.id}`,
|
||||||
|
params: {
|
||||||
|
id: account.id,
|
||||||
|
cachedProfile: account,
|
||||||
|
cachedUser: this.profile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.$emit('back');
|
||||||
|
},
|
||||||
|
|
||||||
|
setCacheWarmTimeout() {
|
||||||
|
if(this.cacheWarmInterations >= 5) {
|
||||||
|
this.isWarmingCache = false;
|
||||||
|
swal('Oops', 'Its taking longer than expected to collect this account followers. Please try again later', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cacheWarmTimeout = setTimeout(() => {
|
||||||
|
this.cacheWarmInterations++;
|
||||||
|
this.fetchFollowers();
|
||||||
|
}, 45000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.profile-followers-component {
|
||||||
|
.list-group-item {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-card {
|
||||||
|
max-height: calc(100vh - 250px);
|
||||||
|
overflow-y: auto;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer-btn {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,259 @@
|
||||||
|
<template>
|
||||||
|
<div class="profile-following-component">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-7">
|
||||||
|
<div v-if="isLoaded" class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-dark rounded-pill font-weight-bold"
|
||||||
|
@click="goBack()">
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center justify-content-center flex-column w-100 overflow-hidden">
|
||||||
|
<p class="small text-muted mb-0 text-uppercase font-weight-light cursor-pointer text-truncate text-center" style="width: 70%;" @click="goBack()">@{{ profile.acct }}</p>
|
||||||
|
<p class="lead font-weight-bold mt-n1 mb-0">{{ $t('profile.following') }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-dark rounded-pill font-weight-bold spacer-btn" href="#">Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isLoaded" class="list-group scroll-card">
|
||||||
|
<div v-for="(account, idx) in feed" class="list-group-item">
|
||||||
|
<a
|
||||||
|
:id="'apop_'+account.id"
|
||||||
|
:href="account.url"
|
||||||
|
@click.prevent="goToProfile(account)"
|
||||||
|
class="text-decoration-none">
|
||||||
|
<div class="media">
|
||||||
|
<img
|
||||||
|
:src="account.avatar"
|
||||||
|
width="40"
|
||||||
|
height="40"
|
||||||
|
style="border-radius: 8px;"
|
||||||
|
class="mr-3 shadow-sm"
|
||||||
|
draggable="false"
|
||||||
|
loading="lazy"
|
||||||
|
onerror="this.src='/storage/avatars/default.jpg?v=0';this.onerror=null;">
|
||||||
|
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 text-truncate">
|
||||||
|
<span class="text-dark font-weight-bold text-decoration-none" v-html="getUsername(account)"></span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0 mt-n1 text-muted small text-break">@{{ account.acct }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<b-popover :target="'apop_'+account.id" triggers="hover" placement="left" delay="1000" custom-class="shadow border-0 rounded-px">
|
||||||
|
<profile-hover-card :profile="account" />
|
||||||
|
</b-popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="canLoadMore">
|
||||||
|
<intersect @enter="enterIntersect">
|
||||||
|
<placeholder />
|
||||||
|
</intersect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!canLoadMore && !feed.length">
|
||||||
|
<div class="list-group-item text-center">
|
||||||
|
<div v-if="isWarmingCache" class="px-4">
|
||||||
|
<p class="mb-0 lead font-weight-bold">Loading Following...</p>
|
||||||
|
<div class="py-3">
|
||||||
|
<b-spinner variant="primary" style="width: 1.5rem; height: 1.5rem;" />
|
||||||
|
</div>
|
||||||
|
<p class="small text-muted mb-0">Please wait while we collect following accounts, this shouldn't take long!</p>
|
||||||
|
</div>
|
||||||
|
<p v-else class="mb-0 font-weight-bold">No following anyone yet!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="list-group">
|
||||||
|
<placeholder />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
import Intersect from 'vue-intersect'
|
||||||
|
import Placeholder from './../post/LikeListPlaceholder.vue';
|
||||||
|
import ProfileHoverCard from './ProfileHoverCard.vue';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { parseLinkHeader } from '@web3-storage/parse-link-header';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
profile: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ProfileHoverCard,
|
||||||
|
Intersect,
|
||||||
|
Placeholder
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'getCustomEmoji'
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoaded: false,
|
||||||
|
feed: [],
|
||||||
|
cursor: null,
|
||||||
|
canLoadMore: true,
|
||||||
|
isFetchingMore: false,
|
||||||
|
cacheWarmTimeout: undefined,
|
||||||
|
cacheWarmInterations: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetchFollowers();
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
clearTimeout(this.cacheWarmTimeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetchFollowers() {
|
||||||
|
axios.get('/api/v1/accounts/'+this.profile.id+'/following', {
|
||||||
|
params: {
|
||||||
|
cursor: this.cursor,
|
||||||
|
'_pe': 1
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
if(!res.data.length) {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
this.isLoaded = true;
|
||||||
|
if(this.cursor == null && this.profile.following_count) {
|
||||||
|
this.isWarmingCache = true;
|
||||||
|
this.setCacheWarmTimeout();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(res.headers && res.headers.link) {
|
||||||
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.cursor = links.prev.cursor;
|
||||||
|
this.canLoadMore = true;
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
}
|
||||||
|
this.feed.push(...res.data);
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.isFetchingMore = false;
|
||||||
|
if(this.isWarmingCache || this.cacheWarmTimeout) {
|
||||||
|
this.isWarmingCache = false;
|
||||||
|
clearTimeout(this.cacheWarmTimeout);
|
||||||
|
this.cacheWarmTimeout = undefined;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.isFetchingMore = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
enterIntersect() {
|
||||||
|
if(this.isFetchingMore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isFetchingMore = true;
|
||||||
|
this.fetchFollowers();
|
||||||
|
},
|
||||||
|
|
||||||
|
getUsername(profile) {
|
||||||
|
let self = this;
|
||||||
|
let dn = profile.display_name;
|
||||||
|
if(!dn || !dn.trim().length) {
|
||||||
|
return profile.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dn.includes(':')) {
|
||||||
|
let re = /(<a?)?:\w+:(\d{18}>)?/g;
|
||||||
|
let un = dn.replaceAll(re, function(em) {
|
||||||
|
let shortcode = em.slice(1, em.length - 1);
|
||||||
|
let emoji = self.getCustomEmoji.filter(e => {
|
||||||
|
return e.shortcode == shortcode;
|
||||||
|
});
|
||||||
|
return emoji.length ? `<img draggable="false" class="emojione custom-emoji" alt="${emoji[0].shortcode}" title="${emoji[0].shortcode}" src="${emoji[0].url}" data-original="${emoji[0].url}" data-static="${emoji[0].static_url}" width="16" height="16" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`: em;
|
||||||
|
});
|
||||||
|
return un;
|
||||||
|
} else {
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goToProfile(account) {
|
||||||
|
this.$router.push({
|
||||||
|
path: `/i/web/profile/${account.id}`,
|
||||||
|
params: {
|
||||||
|
id: account.id,
|
||||||
|
cachedProfile: account,
|
||||||
|
cachedUser: this.profile
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.$emit('back');
|
||||||
|
},
|
||||||
|
|
||||||
|
setCacheWarmTimeout() {
|
||||||
|
if(this.cacheWarmInterations >= 5) {
|
||||||
|
this.isWarmingCache = false;
|
||||||
|
swal('Oops', 'Its taking longer than expected to collect following accounts. Please try again later', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cacheWarmTimeout = setTimeout(() => {
|
||||||
|
this.cacheWarmInterations++;
|
||||||
|
this.fetchFollowers();
|
||||||
|
}, 45000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.profile-following-component {
|
||||||
|
.list-group-item {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-card {
|
||||||
|
max-height: calc(100vh - 250px);
|
||||||
|
overflow-y: auto;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer-btn {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -122,12 +122,6 @@
|
||||||
</a>
|
</a>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<!-- <div v-else-if="n.type == 'follow' && n.relationship.following == false">
|
|
||||||
<a href="#" class="btn btn-primary py-0 font-weight-bold" @click.prevent="followProfile(n);">
|
|
||||||
Follow
|
|
||||||
</a>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- <div v-else-if="n.status && n.status.parent && !n.status.parent.media_attachments && n.type == 'like' && n.relationship.following == false">
|
<!-- <div v-else-if="n.status && n.status.parent && !n.status.parent.media_attachments && n.type == 'like' && n.relationship.following == false">
|
||||||
<a href="#" class="btn btn-primary py-0 font-weight-bold">
|
<a href="#" class="btn btn-primary py-0 font-weight-bold">
|
||||||
Follow
|
Follow
|
||||||
|
@ -290,24 +284,6 @@ export default {
|
||||||
return '/p/' + username + '/' + id;
|
return '/p/' + username + '/' + id;
|
||||||
},
|
},
|
||||||
|
|
||||||
followProfile(n) {
|
|
||||||
let self = this;
|
|
||||||
let id = n.account.id;
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
self.notifications.map(notification => {
|
|
||||||
if(notification.account.id === id) {
|
|
||||||
notification.relationship.following = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
if(err.response.data.message) {
|
|
||||||
swal('Error', err.response.data.message, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
viewContext(n) {
|
viewContext(n) {
|
||||||
switch(n.type) {
|
switch(n.type) {
|
||||||
case 'follow':
|
case 'follow':
|
||||||
|
|
|
@ -371,7 +371,7 @@
|
||||||
centered
|
centered
|
||||||
title="Likes"
|
title="Likes"
|
||||||
body-class="list-group-flush py-3 px-0">
|
body-class="list-group-flush py-3 px-0">
|
||||||
<div class="list-group">
|
<div v-if="likedLoaded" class="list-group">
|
||||||
<div class="list-group-item border-0 py-1" v-for="(user, index) in likes" :key="'modal_likes_'+index">
|
<div class="list-group-item border-0 py-1" v-for="(user, index) in likes" :key="'modal_likes_'+index">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<a :href="user.url">
|
<a :href="user.url">
|
||||||
|
@ -392,44 +392,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<infinite-loading @infinite="infiniteLikesHandler" spinner="spiral">
|
<infinite-loading v-if="likesCanLoadMore" @infinite="infiniteLikesHandler" spinner="spiral">
|
||||||
<div slot="no-more"></div>
|
<div slot="no-more"></div>
|
||||||
<div slot="no-results"></div>
|
<div slot="no-results"></div>
|
||||||
</infinite-loading>
|
</infinite-loading>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
<div v-else class="d-flex justify-content-center align-items-center h-100">
|
||||||
<b-modal ref="sharesModal"
|
<b-spinner />
|
||||||
id="s-modal"
|
|
||||||
hide-footer
|
|
||||||
centered
|
|
||||||
title="Shares"
|
|
||||||
body-class="list-group-flush py-3 px-0">
|
|
||||||
<div class="list-group">
|
|
||||||
<div class="list-group-item border-0 py-1" v-for="(user, index) in shares" :key="'modal_shares_'+index">
|
|
||||||
<div class="media">
|
|
||||||
<a :href="user.url">
|
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px">
|
|
||||||
</a>
|
|
||||||
<div class="media-body">
|
|
||||||
<div class="d-inline-block">
|
|
||||||
<p class="mb-0" style="font-size: 14px">
|
|
||||||
<a :href="user.url" class="font-weight-bold text-dark">
|
|
||||||
{{user.username}}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p class="text-muted mb-0" style="font-size: 14px">
|
|
||||||
{{user.display_name}}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p class="float-right"><!-- <a class="btn btn-primary font-weight-bold py-1" href="#">Follow</a> --></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<infinite-loading @infinite="infiniteSharesHandler" spinner="spiral">
|
|
||||||
<div slot="no-more"></div>
|
|
||||||
<div slot="no-results"></div>
|
|
||||||
</infinite-loading>
|
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<b-modal ref="lightboxModal"
|
<b-modal ref="lightboxModal"
|
||||||
|
@ -515,8 +484,6 @@
|
||||||
size="sm"
|
size="sm"
|
||||||
body-class="list-group-flush p-0 rounded">
|
body-class="list-group-flush p-0 rounded">
|
||||||
<div class="list-group text-center">
|
<div class="list-group text-center">
|
||||||
<!-- <div v-if="user && user.id != status.account.id && relationship && relationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
|
|
||||||
<div v-if="user && user.id != status.account.id && relationship && !relationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div> -->
|
|
||||||
<div v-if="status && status.local == true" class="list-group-item rounded cursor-pointer" @click="showEmbedPostModal()">Embed</div>
|
<div v-if="status && status.local == true" class="list-group-item rounded cursor-pointer" @click="showEmbedPostModal()">Embed</div>
|
||||||
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
|
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
|
||||||
<div v-if="status && user.id == status.account.id" class="list-group-item rounded cursor-pointer" @click="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</div>
|
<div v-if="status && user.id == status.account.id" class="list-group-item rounded cursor-pointer" @click="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</div>
|
||||||
|
@ -667,6 +634,7 @@ import VueTribute from 'vue-tribute';
|
||||||
import PollCard from './partials/PollCard.vue';
|
import PollCard from './partials/PollCard.vue';
|
||||||
import CommentFeed from './partials/CommentFeed.vue';
|
import CommentFeed from './partials/CommentFeed.vue';
|
||||||
import StatusCard from './partials/StatusCard.vue';
|
import StatusCard from './partials/StatusCard.vue';
|
||||||
|
import { parseLinkHeader } from '@web3-storage/parse-link-header';
|
||||||
|
|
||||||
pixelfed.postComponent = {};
|
pixelfed.postComponent = {};
|
||||||
|
|
||||||
|
@ -702,9 +670,10 @@ export default {
|
||||||
shared: false
|
shared: false
|
||||||
},
|
},
|
||||||
likes: [],
|
likes: [],
|
||||||
likesPage: 1,
|
likesCursor: null,
|
||||||
|
likesCanLoadMore: true,
|
||||||
|
likedLoaded: false,
|
||||||
shares: [],
|
shares: [],
|
||||||
sharesPage: 1,
|
|
||||||
lightboxMedia: false,
|
lightboxMedia: false,
|
||||||
replyText: '',
|
replyText: '',
|
||||||
replyStatus: {},
|
replyStatus: {},
|
||||||
|
@ -847,8 +816,6 @@ export default {
|
||||||
let img = `<img draggable="false" class="emojione custom-emoji" alt="${emoji.shortcode}" title="${emoji.shortcode}" src="${emoji.url}" data-original="${emoji.url}" data-static="${emoji.static_url}" width="18" height="18" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`;
|
let img = `<img draggable="false" class="emojione custom-emoji" alt="${emoji.shortcode}" title="${emoji.shortcode}" src="${emoji.url}" data-original="${emoji.url}" data-static="${emoji.static_url}" width="18" height="18" onerror="this.onerror=null;this.src='/storage/emoji/missing.png';" />`;
|
||||||
self.content = self.content.replace(`:${emoji.shortcode}:`, img);
|
self.content = self.content.replace(`:${emoji.shortcode}:`, img);
|
||||||
});
|
});
|
||||||
self.likesPage = 2;
|
|
||||||
self.sharesPage = 2;
|
|
||||||
self.showCaption = !response.data.status.sensitive;
|
self.showCaption = !response.data.status.sensitive;
|
||||||
if(self.status.comments_disabled == false) {
|
if(self.status.comments_disabled == false) {
|
||||||
self.showComments = true;
|
self.showComments = true;
|
||||||
|
@ -886,59 +853,68 @@ export default {
|
||||||
window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode);
|
window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.likes.length) {
|
if(this.likes && this.likes.length) {
|
||||||
this.$refs.likesModal.show();
|
this.$refs.likesModal.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
axios.get('/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId)
|
axios.get('/api/v1/statuses/'+ this.statusId + '/favourited_by', {
|
||||||
|
params: {
|
||||||
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
|
}
|
||||||
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.likes = res.data.data;
|
this.likes = res.data;
|
||||||
this.$refs.likesModal.show();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
sharesModal() {
|
if(res.headers && res.headers.link) {
|
||||||
if(this.status.reblogs_count == 0 || $('body').hasClass('loggedIn') == false) {
|
const links = parseLinkHeader(res.headers.link);
|
||||||
window.location.href = '/login?next=' + encodeURIComponent('/p/' + this.status.shortcode);
|
if(links.prev) {
|
||||||
return;
|
this.likesCursor = links.prev.cursor;
|
||||||
}
|
this.likesCanLoadMore = true;
|
||||||
if(this.shares.length) {
|
} else {
|
||||||
this.$refs.sharesModal.show();
|
this.likesCanLoadMore = false;
|
||||||
return;
|
}
|
||||||
}
|
} else {
|
||||||
axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId)
|
this.likesCanLoadMore = false;
|
||||||
.then(res => {
|
}
|
||||||
this.shares = res.data.data;
|
this.$refs.likesModal.show();
|
||||||
this.$refs.sharesModal.show();
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
setTimeout(() => { this.likedLoaded = true }, 1000);
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
infiniteLikesHandler($state) {
|
infiniteLikesHandler($state) {
|
||||||
let api = '/api/v2/likes/profile/'+this.statusUsername+'/status/'+this.statusId;
|
if(!this.likesCanLoadMore) {
|
||||||
axios.get(api, {
|
$state.complete();
|
||||||
params: {
|
return;
|
||||||
page: this.likesPage,
|
}
|
||||||
},
|
|
||||||
}).then(({ data }) => {
|
|
||||||
if (data.data.length > 0) {
|
|
||||||
this.likes.push(...data.data);
|
|
||||||
this.likesPage++;
|
|
||||||
$state.loaded();
|
|
||||||
} else {
|
|
||||||
$state.complete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
infiniteSharesHandler($state) {
|
axios.get('/api/v1/statuses/'+ this.statusId + '/favourited_by', {
|
||||||
axios.get('/api/v2/shares/profile/'+this.statusUsername+'/status/'+this.statusId, {
|
|
||||||
params: {
|
params: {
|
||||||
page: this.sharesPage,
|
cursor: this.likesCursor,
|
||||||
|
limit: 20,
|
||||||
|
'_pe': 1
|
||||||
},
|
},
|
||||||
}).then(({ data }) => {
|
}).then(res => {
|
||||||
if (data.data.length > 0) {
|
if (res && res.data.length) {
|
||||||
this.shares.push(...data.data);
|
this.likes.push(...res.data);
|
||||||
this.sharesPage++;
|
}
|
||||||
|
|
||||||
|
if(res.headers && res.headers.link) {
|
||||||
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.likesCursor = links.prev.cursor;
|
||||||
|
this.likesCanLoadMore = true;
|
||||||
|
} else {
|
||||||
|
this.likesCanLoadMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.likesCanLoadMore = false;
|
||||||
|
}
|
||||||
|
return this.likesCanLoadMore;
|
||||||
|
}).then(res => {
|
||||||
|
if(res) {
|
||||||
$state.loaded();
|
$state.loaded();
|
||||||
} else {
|
} else {
|
||||||
$state.complete();
|
$state.complete();
|
||||||
|
@ -1627,34 +1603,6 @@ export default {
|
||||||
return;
|
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);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
archivePost(status) {
|
archivePost(status) {
|
||||||
if(window.confirm('Are you sure you want to archive this post?') == false) {
|
if(window.confirm('Are you sure you want to archive this post?') == false) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -133,10 +133,10 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="d-flex align-items-center mb-1">
|
<div class="d-md-flex align-items-center mb-1 text-break">
|
||||||
<span class="font-weight-bold mr-1">{{profile.display_name}}</span>
|
<div class="font-weight-bold mr-1">{{profile.display_name}}</div>
|
||||||
<span v-if="profile.pronouns" class="text-muted small">{{profile.pronouns.join('/')}}</span>
|
<div v-if="profile.pronouns" class="text-muted small">{{profile.pronouns.join('/')}}</div>
|
||||||
</p>
|
</div>
|
||||||
<p v-if="profile.note" class="mb-0" v-html="profile.note"></p>
|
<p v-if="profile.note" class="mb-0" v-html="profile.note"></p>
|
||||||
<p v-if="profile.website"><a :href="profile.website" class="profile-website small" rel="me external nofollow noopener" target="_blank">{{formatWebsite(profile.website)}}</a></p>
|
<p v-if="profile.website"><a :href="profile.website" class="profile-website small" rel="me external nofollow noopener" target="_blank">{{formatWebsite(profile.website)}}</a></p>
|
||||||
<p class="d-flex small text-muted align-items-center">
|
<p class="d-flex small text-muted align-items-center">
|
||||||
|
@ -154,8 +154,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-block d-md-none my-0 pt-3 border-bottom">
|
<div v-if="user && user.hasOwnProperty('id')" class="d-block d-md-none my-0 pt-3 border-bottom">
|
||||||
<p v-if="user && user.hasOwnProperty('id')" class="pt-3">
|
<p class="pt-3">
|
||||||
<button v-if="owner" class="btn btn-outline-secondary bg-white btn-sm py-1 btn-block text-center font-weight-bold text-dark border border-lighter" @click.prevent="redirect('/settings/home')">Edit Profile</button>
|
<button v-if="owner" class="btn btn-outline-secondary bg-white btn-sm py-1 btn-block text-center font-weight-bold text-dark border border-lighter" @click.prevent="redirect('/settings/home')">Edit Profile</button>
|
||||||
<button v-if="!owner && relationship.following" class="btn btn-outline-secondary bg-white btn-sm py-1 px-5 font-weight-bold text-dark border border-lighter" @click="followProfile"> Unfollow </button>
|
<button v-if="!owner && relationship.following" class="btn btn-outline-secondary bg-white btn-sm py-1 px-5 font-weight-bold text-dark border border-lighter" @click="followProfile"> Unfollow </button>
|
||||||
<button v-if="!owner && !relationship.following" class="btn btn-primary btn-sm py-1 px-5 font-weight-bold" @click="followProfile">{{relationship.followed_by ? 'Follow Back' : ' Follow '}}</button>
|
<button v-if="!owner && !relationship.following" class="btn btn-primary btn-sm py-1 px-5 font-weight-bold" @click="followProfile">{{relationship.followed_by ? 'Follow Back' : ' Follow '}}</button>
|
||||||
|
@ -339,16 +339,10 @@
|
||||||
<span class="text-dark">{{profileUsername}}</span> is not following yet</p>
|
<span class="text-dark">{{profileUsername}}</span> is not following yet</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="owner == true" class="list-group-item border-0 pt-0 px-0 mt-n2 mb-3">
|
|
||||||
<span class="d-flex px-4 pb-0 align-items-center">
|
|
||||||
<i class="fas fa-search text-lighter"></i>
|
|
||||||
<input type="text" class="form-control border-0 shadow-0 no-focus" placeholder="Search Following..." v-model="followingModalSearch" v-on:keyup="followingModalSearchHandler">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0 py-1 mb-1" v-for="(user, index) in following" :key="'following_'+index">
|
<div class="list-group-item border-0 py-1 mb-1" v-for="(user, index) in following" :key="'following_'+index">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<a :href="profileUrlRedirect(user)">
|
<a :href="profileUrlRedirect(user)">
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0'">
|
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy" onerror="this.src='/storage/avatars/default.jpg?v=0';this.onerror=null;" v-once>
|
||||||
</a>
|
</a>
|
||||||
<div class="media-body text-truncate">
|
<div class="media-body text-truncate">
|
||||||
<p class="mb-0" style="font-size: 14px">
|
<p class="mb-0" style="font-size: 14px">
|
||||||
|
@ -368,7 +362,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="followingModalSearch && following.length == 0" class="list-group-item border-0">
|
<div v-if="!followingLoading && following.length == 0" class="list-group-item border-0">
|
||||||
<div class="list-group-item border-0 pt-5">
|
<div class="list-group-item border-0 pt-5">
|
||||||
<p class="p-3 text-center mb-0 lead">No Results Found</p>
|
<p class="p-3 text-center mb-0 lead">No Results Found</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -394,7 +388,7 @@
|
||||||
dialog-class="follow-modal"
|
dialog-class="follow-modal"
|
||||||
>
|
>
|
||||||
<div v-if="!followerLoading" class="list-group" style="max-height: 60vh;">
|
<div v-if="!followerLoading" class="list-group" style="max-height: 60vh;">
|
||||||
<div v-if="!followers.length" class="list-group-item border-0">
|
<div v-if="!followerLoading && !followers.length" class="list-group-item border-0">
|
||||||
<p class="text-center mb-0 font-weight-bold text-muted py-5">
|
<p class="text-center mb-0 font-weight-bold text-muted py-5">
|
||||||
<span class="text-dark">{{profileUsername}}</span> has no followers yet</p>
|
<span class="text-dark">{{profileUsername}}</span> has no followers yet</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -403,7 +397,7 @@
|
||||||
<div class="list-group-item border-0 py-1 mb-1" v-for="(user, index) in followers" :key="'follower_'+index">
|
<div class="list-group-item border-0 py-1 mb-1" v-for="(user, index) in followers" :key="'follower_'+index">
|
||||||
<div class="media mb-0">
|
<div class="media mb-0">
|
||||||
<a :href="profileUrlRedirect(user)">
|
<a :href="profileUrlRedirect(user)">
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" height="30px" loading="lazy" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0'">
|
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" height="30px" loading="lazy" onerror="this.src='/storage/avatars/default.jpg?v=0';this.onerror=null;" v-once>
|
||||||
</a>
|
</a>
|
||||||
<div class="media-body mb-0">
|
<div class="media-body mb-0">
|
||||||
<p class="mb-0" style="font-size: 14px">
|
<p class="mb-0" style="font-size: 14px">
|
||||||
|
@ -593,6 +587,7 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
import VueMasonry from 'vue-masonry-css'
|
import VueMasonry from 'vue-masonry-css'
|
||||||
import StatusCard from './partials/StatusCard.vue';
|
import StatusCard from './partials/StatusCard.vue';
|
||||||
|
import { parseLinkHeader } from '@web3-storage/parse-link-header';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: [
|
props: [
|
||||||
|
@ -623,11 +618,11 @@
|
||||||
modalStatus: false,
|
modalStatus: false,
|
||||||
relationship: {},
|
relationship: {},
|
||||||
followers: [],
|
followers: [],
|
||||||
followerCursor: 1,
|
followerCursor: null,
|
||||||
followerMore: true,
|
followerMore: true,
|
||||||
followerLoading: true,
|
followerLoading: true,
|
||||||
following: [],
|
following: [],
|
||||||
followingCursor: 1,
|
followingCursor: null,
|
||||||
followingMore: true,
|
followingMore: true,
|
||||||
followingLoading: true,
|
followingLoading: true,
|
||||||
warning: false,
|
warning: false,
|
||||||
|
@ -641,8 +636,6 @@
|
||||||
ctxEmbedPayload: null,
|
ctxEmbedPayload: null,
|
||||||
copiedEmbed: false,
|
copiedEmbed: false,
|
||||||
hasStory: null,
|
hasStory: null,
|
||||||
followingModalSearch: null,
|
|
||||||
followingModalSearchCache: null,
|
|
||||||
followingModalTab: 'following',
|
followingModalTab: 'following',
|
||||||
bookmarksLoading: true,
|
bookmarksLoading: true,
|
||||||
archives: [],
|
archives: [],
|
||||||
|
@ -1056,19 +1049,22 @@
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
axios.post('/i/follow', {
|
this.$refs.visitorContextMenu.hide();
|
||||||
item: this.profileId
|
const curState = this.relationship.following;
|
||||||
}).then(res => {
|
const apiUrl = curState ?
|
||||||
this.$refs.visitorContextMenu.hide();
|
'/api/v1/accounts/' + this.profileId + '/unfollow' :
|
||||||
if(this.relationship.following) {
|
'/api/v1/accounts/' + this.profileId + '/follow';
|
||||||
|
axios.post(apiUrl)
|
||||||
|
.then(res => {
|
||||||
|
if(curState) {
|
||||||
this.profile.followers_count--;
|
this.profile.followers_count--;
|
||||||
if(this.profile.locked == true) {
|
if(this.profile.locked) {
|
||||||
window.location.href = '/';
|
location.reload();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.profile.followers_count++;
|
this.profile.followers_count++;
|
||||||
}
|
}
|
||||||
this.relationship.following = !this.relationship.following;
|
this.relationship = res.data;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if(err.response.data.message) {
|
if(err.response.data.message) {
|
||||||
swal('Error', err.response.data.message, 'error');
|
swal('Error', err.response.data.message, 'error');
|
||||||
|
@ -1084,23 +1080,34 @@
|
||||||
if(this.profileSettings.following.list == false) {
|
if(this.profileSettings.following.list == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.followingCursor > 1) {
|
if(this.followingCursor) {
|
||||||
this.$refs.followingModal.show();
|
this.$refs.followingModal.show();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profileId+'/following', {
|
axios.get('/api/v1/accounts/'+this.profileId+'/following', {
|
||||||
params: {
|
params: {
|
||||||
page: this.followingCursor
|
cursor: this.followingCursor,
|
||||||
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.following = res.data;
|
this.following = res.data;
|
||||||
this.followingModalSearchCache = res.data;
|
|
||||||
this.followingCursor++;
|
if(res.headers && res.headers.link) {
|
||||||
if(res.data.length < 10) {
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.followingCursor = links.prev.cursor;
|
||||||
|
this.followingMore = true;
|
||||||
|
} else {
|
||||||
|
this.followingMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.followingMore = false;
|
this.followingMore = false;
|
||||||
}
|
}
|
||||||
this.followingLoading = false;
|
})
|
||||||
|
.then(() => {
|
||||||
|
setTimeout(() => { this.followingLoading = false }, 1000);
|
||||||
});
|
});
|
||||||
this.$refs.followingModal.show();
|
this.$refs.followingModal.show();
|
||||||
return;
|
return;
|
||||||
|
@ -1119,19 +1126,30 @@
|
||||||
this.$refs.followerModal.show();
|
this.$refs.followerModal.show();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profileId+'/followers', {
|
axios.get('/api/v1/accounts/'+this.profileId+'/followers', {
|
||||||
params: {
|
params: {
|
||||||
page: this.followerCursor
|
cursor: this.followerCursor,
|
||||||
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.followers.push(...res.data);
|
this.followers.push(...res.data);
|
||||||
this.followerCursor++;
|
if(res.headers && res.headers.link) {
|
||||||
if(res.data.length < 10) {
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.followerCursor = links.prev.cursor;
|
||||||
|
this.followerMore = true;
|
||||||
|
} else {
|
||||||
|
this.followerMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.followerMore = false;
|
this.followerMore = false;
|
||||||
}
|
}
|
||||||
this.followerLoading = false;
|
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
setTimeout(() => { this.followerLoading = false }, 1000);
|
||||||
|
});
|
||||||
this.$refs.followerModal.show();
|
this.$refs.followerModal.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1142,20 +1160,27 @@
|
||||||
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/following', {
|
axios.get('/api/v1/accounts/'+this.profile.id+'/following', {
|
||||||
params: {
|
params: {
|
||||||
page: this.followingCursor,
|
cursor: this.followingCursor,
|
||||||
fbu: this.followingModalSearch
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if(res.data.length > 0) {
|
if(res.data.length > 0) {
|
||||||
this.following.push(...res.data);
|
this.following.push(...res.data);
|
||||||
this.followingCursor++;
|
|
||||||
this.followingModalSearchCache = this.following;
|
|
||||||
}
|
}
|
||||||
if(res.data.length < 10) {
|
|
||||||
this.followingModalSearchCache = this.following;
|
if(res.headers && res.headers.link) {
|
||||||
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.followingCursor = links.prev.cursor;
|
||||||
|
this.followingMore = true;
|
||||||
|
} else {
|
||||||
|
this.followingMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.followingMore = false;
|
this.followingMore = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1165,17 +1190,27 @@
|
||||||
if($('body').hasClass('loggedIn') == false) {
|
if($('body').hasClass('loggedIn') == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/followers', {
|
axios.get('/api/v1/accounts/'+this.profile.id+'/followers', {
|
||||||
params: {
|
params: {
|
||||||
page: this.followerCursor
|
cursor: this.followerCursor,
|
||||||
|
limit: 40,
|
||||||
|
'_pe': 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if(res.data.length > 0) {
|
if(res.data.length > 0) {
|
||||||
this.followers.push(...res.data);
|
this.followers.push(...res.data);
|
||||||
this.followerCursor++;
|
|
||||||
}
|
}
|
||||||
if(res.data.length < 10) {
|
|
||||||
|
if(res.headers && res.headers.link) {
|
||||||
|
const links = parseLinkHeader(res.headers.link);
|
||||||
|
if(links.prev) {
|
||||||
|
this.followerCursor = links.prev.cursor;
|
||||||
|
this.followerMore = true;
|
||||||
|
} else {
|
||||||
|
this.followerMore = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.followerMore = false;
|
this.followerMore = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1186,9 +1221,11 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
followModalAction(id, index, type = 'following') {
|
followModalAction(id, index, type = 'following') {
|
||||||
axios.post('/i/follow', {
|
const apiUrl = type === 'following' ?
|
||||||
item: id
|
'/api/v1/accounts/' + id + '/unfollow' :
|
||||||
}).then(res => {
|
'/api/v1/accounts/' + id + '/follow';
|
||||||
|
axios.post(apiUrl)
|
||||||
|
.then(res => {
|
||||||
if(type == 'following') {
|
if(type == 'following') {
|
||||||
this.following.splice(index, 1);
|
this.following.splice(index, 1);
|
||||||
this.profile.following_count--;
|
this.profile.following_count--;
|
||||||
|
@ -1284,28 +1321,6 @@
|
||||||
window.location.href = '/stories/' + this.profileUsername + '?t=4';
|
window.location.href = '/stories/' + this.profileUsername + '?t=4';
|
||||||
},
|
},
|
||||||
|
|
||||||
followingModalSearchHandler() {
|
|
||||||
let self = this;
|
|
||||||
let q = this.followingModalSearch;
|
|
||||||
|
|
||||||
if(q.length == 0) {
|
|
||||||
this.following = this.followingModalSearchCache;
|
|
||||||
this.followingModalSearch = null;
|
|
||||||
}
|
|
||||||
if(q.length > 0) {
|
|
||||||
let url = '/api/pixelfed/v1/accounts/' +
|
|
||||||
self.profileId + '/following?page=1&fbu=' +
|
|
||||||
q;
|
|
||||||
|
|
||||||
axios.get(url).then(res => {
|
|
||||||
this.following = res.data;
|
|
||||||
}).catch(err => {
|
|
||||||
self.following = self.followingModalSearchCache;
|
|
||||||
self.followingModalSearch = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
truncate(str, len) {
|
truncate(str, len) {
|
||||||
return _.truncate(str, {
|
return _.truncate(str, {
|
||||||
length: len
|
length: len
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,746 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-if="relationship && relationship.blocking && warning" class="bg-white pt-3 border-bottom">
|
|
||||||
<div class="container">
|
|
||||||
<p class="text-center font-weight-bold">You are blocking this account</p>
|
|
||||||
<p class="text-center font-weight-bold">Click <a href="#" class="cursor-pointer" @click.prevent="warning = false;">here</a> to view profile</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="loading" style="height: 80vh;" class="d-flex justify-content-center align-items-center">
|
|
||||||
<img src="/img/pixelfed-icon-grey.svg" class="">
|
|
||||||
</div>
|
|
||||||
<div v-if="!loading && !warning" class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12 col-md-4 pt-5">
|
|
||||||
<div class="card shadow-none border">
|
|
||||||
<div class="card-header p-0 m-0">
|
|
||||||
<img v-if="profile.header_bg" :src="profile.header_bg" style="width: 100%; height: 140px; object-fit: cover;">
|
|
||||||
<div v-else class="bg-primary" style="width: 100%;height: 140px;"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body pb-0">
|
|
||||||
<div class="mt-n5 mb-3">
|
|
||||||
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" :src="profile.avatar" width="90px" height="90px;" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
|
|
||||||
<span class="float-right mt-n1">
|
|
||||||
<span>
|
|
||||||
<button v-if="relationship && relationship.following == false" class="btn btn-outline-light py-0 px-3 mt-n1" style="font-size:13px; font-weight: 500;" @click="followProfile();">Follow</button>
|
|
||||||
<button v-if="relationship && relationship.following == true" class="btn btn-outline-light py-0 px-3 mt-n1" style="font-size:13px; font-weight: 500;" @click="unfollowProfile();">Unfollow</button>
|
|
||||||
</span>
|
|
||||||
<span class="mx-2">
|
|
||||||
<a :href="'/account/direct/t/' + profile.id" class="btn btn-outline-light btn-sm mt-n1" style="padding-top:2px;padding-bottom:1px;">
|
|
||||||
<i class="far fa-comment-dots cursor-pointer" style="font-size:13px;"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<button class="btn btn-outline-light btn-sm mt-n1" @click="showCtxMenu()" style="padding-top:2px;padding-bottom:1px;">
|
|
||||||
<i class="fas fa-cog cursor-pointer" style="font-size:13px;"></i>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="pl-2 h4 font-weight-bold mb-1">{{profile.display_name}}</p>
|
|
||||||
<p class="pl-2 font-weight-bold mb-2"><a class="text-muted" :href="profile.url" @click.prevent="urlRedirectHandler(profile.url)">{{profile.acct}}</a></p>
|
|
||||||
<p class="pl-2 text-muted small d-flex justify-content-between">
|
|
||||||
<span>
|
|
||||||
<span class="font-weight-bold text-dark">{{profile.statuses_count}}</span>
|
|
||||||
<span>Posts</span>
|
|
||||||
</span>
|
|
||||||
<span class="cursor-pointer" @click="followingModal()">
|
|
||||||
<span class="font-weight-bold text-dark">{{profile.following_count}}</span>
|
|
||||||
<span>Following</span>
|
|
||||||
</span>
|
|
||||||
<span class="cursor-pointer" @click="followersModal()">
|
|
||||||
<span class="font-weight-bold text-dark">{{profile.followers_count}}</span>
|
|
||||||
<span>Followers</span>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="pl-2 text-muted small pt-2" v-html="profile.note"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="small text-lighter p-2">Last updated: <time :datetime="profile.last_fetched_at">{{timeAgo(profile.last_fetched_at, 'ago')}}</time></p>
|
|
||||||
<p class="card border-left-primary card-body small py-2 text-muted font-weight-bold shadow-none border-top border-bottom border-right">You are viewing a profile from a remote server, it may not contain up-to-date information.</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-8 pt-5">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12" v-for="(status, index) in feed" :key="'remprop' + index">
|
|
||||||
<status-card
|
|
||||||
:class="{'border-top': index === 0}"
|
|
||||||
:status="status" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="feed.length == 0" class="col-12 mb-2">
|
|
||||||
<div class="d-flex justify-content-center align-items-center bg-white border rounded" style="height:60vh;">
|
|
||||||
<div class="text-center">
|
|
||||||
<p class="lead">We haven't seen any posts from this account.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="col-12 mt-4">
|
|
||||||
<p v-if="showLoadMore" class="text-center mb-0 px-0">
|
|
||||||
<button @click="loadMorePosts()" class="btn btn-outline-primary btn-block font-weight-bold">
|
|
||||||
<span v-if="!loadingMore">Load More</span>
|
|
||||||
<span v-else>
|
|
||||||
<div class="spinner-border spinner-border-sm" role="status">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<b-modal
|
|
||||||
v-if="profile && following"
|
|
||||||
ref="followingModal"
|
|
||||||
id="following-modal"
|
|
||||||
hide-footer
|
|
||||||
centered
|
|
||||||
scrollable
|
|
||||||
title="Following"
|
|
||||||
body-class="list-group-flush py-3 px-0"
|
|
||||||
dialog-class="follow-modal">
|
|
||||||
<div v-if="!followingLoading" class="list-group" style="max-height: 60vh;">
|
|
||||||
<div v-if="!following.length" class="list-group-item border-0">
|
|
||||||
<p class="text-center mb-0 font-weight-bold text-muted py-5">
|
|
||||||
<span class="text-dark">{{profileUsername}}</span> is not following yet</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div v-if="owner == true" class="list-group-item border-0 pt-0 px-0 mt-n2 mb-3">
|
|
||||||
<span class="d-flex px-4 pb-0 align-items-center">
|
|
||||||
<i class="fas fa-search text-lighter"></i>
|
|
||||||
<input type="text" class="form-control border-0 shadow-0 no-focus" placeholder="Search Following..." v-model="followingModalSearch" v-on:keyup="followingModalSearchHandler">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0 py-1 mb-1" v-for="(user, index) in following" :key="'following_'+index">
|
|
||||||
<div class="media">
|
|
||||||
<a :href="profileUrlRedirect(user)">
|
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0'">
|
|
||||||
</a>
|
|
||||||
<div class="media-body text-truncate">
|
|
||||||
<p class="mb-0" style="font-size: 14px">
|
|
||||||
<a :href="profileUrlRedirect(user)" class="font-weight-bold text-dark">
|
|
||||||
{{user.username}}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p v-if="!user.local" class="text-muted mb-0 text-break mr-3" style="font-size: 14px" :title="user.acct" data-toggle="dropdown" data-placement="bottom">
|
|
||||||
<span class="font-weight-bold">{{user.acct.split('@')[0]}}</span><span class="text-lighter">@{{user.acct.split('@')[1]}}</span>
|
|
||||||
</p>
|
|
||||||
<p v-else class="text-muted mb-0 text-truncate" style="font-size: 14px">
|
|
||||||
{{user.display_name ? user.display_name : user.username}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="owner">
|
|
||||||
<a class="btn btn-outline-dark btn-sm font-weight-bold" href="#" @click.prevent="followModalAction(user.id, index, 'following')">Following</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="followingModalSearch && following.length == 0" class="list-group-item border-0">
|
|
||||||
<div class="list-group-item border-0 pt-5">
|
|
||||||
<p class="p-3 text-center mb-0 lead">No Results Found</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="following.length > 0 && followingMore" class="list-group-item text-center" v-on:click="followingLoadMore()">
|
|
||||||
<p class="mb-0 small text-muted font-weight-light cursor-pointer">Load more</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="text-center py-5">
|
|
||||||
<div class="spinner-border" role="status">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
<b-modal ref="followerModal"
|
|
||||||
id="follower-modal"
|
|
||||||
hide-footer
|
|
||||||
centered
|
|
||||||
scrollable
|
|
||||||
title="Followers"
|
|
||||||
body-class="list-group-flush py-3 px-0"
|
|
||||||
dialog-class="follow-modal"
|
|
||||||
>
|
|
||||||
<div v-if="!followerLoading" class="list-group" style="max-height: 60vh;">
|
|
||||||
<div v-if="!followers.length" class="list-group-item border-0">
|
|
||||||
<p class="text-center mb-0 font-weight-bold text-muted py-5">
|
|
||||||
<span class="text-dark">{{profileUsername}}</span> has no followers yet</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<div class="list-group-item border-0 py-1 mb-1" v-for="(user, index) in followers" :key="'follower_'+index">
|
|
||||||
<div class="media mb-0">
|
|
||||||
<a :href="profileUrlRedirect(user)">
|
|
||||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" height="30px" loading="lazy" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0'">
|
|
||||||
</a>
|
|
||||||
<div class="media-body mb-0">
|
|
||||||
<p class="mb-0" style="font-size: 14px">
|
|
||||||
<a :href="profileUrlRedirect(user)" class="font-weight-bold text-dark">
|
|
||||||
{{user.username}}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p v-if="!user.local" class="text-muted mb-0 text-break mr-3" style="font-size: 14px" :title="user.acct" data-toggle="dropdown" data-placement="bottom">
|
|
||||||
<span class="font-weight-bold">{{user.acct.split('@')[0]}}</span><span class="text-lighter">@{{user.acct.split('@')[1]}}</span>
|
|
||||||
</p>
|
|
||||||
<p v-else class="text-muted mb-0 text-truncate" style="font-size: 14px">
|
|
||||||
{{user.display_name ? user.display_name : user.username}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<!-- <button class="btn btn-primary font-weight-bold btn-sm py-1">FOLLOW</button> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="followers.length && followerMore" class="list-group-item text-center" v-on:click="followersLoadMore()">
|
|
||||||
<p class="mb-0 small text-muted font-weight-light cursor-pointer">Load more</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="text-center py-5">
|
|
||||||
<div class="spinner-border" role="status">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
<b-modal ref="visitorContextMenu"
|
|
||||||
id="visitor-context-menu"
|
|
||||||
hide-footer
|
|
||||||
hide-header
|
|
||||||
centered
|
|
||||||
size="sm"
|
|
||||||
body-class="list-group-flush p-0">
|
|
||||||
<div class="list-group" v-if="relationship">
|
|
||||||
<div class="list-group-item cursor-pointer text-center rounded text-dark" @click="copyProfileLink">
|
|
||||||
Copy Link
|
|
||||||
</div>
|
|
||||||
<div v-if="user && !owner && !relationship.muting" class="list-group-item cursor-pointer text-center rounded" @click="muteProfile">
|
|
||||||
Mute
|
|
||||||
</div>
|
|
||||||
<div v-if="user && !owner && relationship.muting" class="list-group-item cursor-pointer text-center rounded" @click="unmuteProfile">
|
|
||||||
Unmute
|
|
||||||
</div>
|
|
||||||
<div v-if="user && !owner" class="list-group-item cursor-pointer text-center rounded text-dark" @click="reportProfile">
|
|
||||||
Report User
|
|
||||||
</div>
|
|
||||||
<div v-if="user && !owner && !relationship.blocking" class="list-group-item cursor-pointer text-center rounded text-dark" @click="blockProfile">
|
|
||||||
Block
|
|
||||||
</div>
|
|
||||||
<div v-if="user && !owner && relationship.blocking" class="list-group-item cursor-pointer text-center rounded text-dark" @click="unblockProfile">
|
|
||||||
Unblock
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="list-group-item cursor-pointer text-center rounded text-muted" @click="$refs.visitorContextMenu.hide()">
|
|
||||||
Close
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
<b-modal ref="ctxModal"
|
|
||||||
id="ctx-modal"
|
|
||||||
hide-header
|
|
||||||
hide-footer
|
|
||||||
centered
|
|
||||||
rounded
|
|
||||||
size="sm"
|
|
||||||
body-class="list-group-flush p-0 rounded">
|
|
||||||
<div class="list-group text-center">
|
|
||||||
<div v-if="ctxMenuStatus && profile.id != profile.id" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuReportPost()">Report inappropriate</div>
|
|
||||||
<div v-if="ctxMenuStatus && profile.id != profile.id && ctxMenuRelationship && ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
|
|
||||||
<div v-if="ctxMenuStatus && profile.id != profile.id && ctxMenuRelationship && !ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div>
|
|
||||||
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">Go to post</div>
|
|
||||||
<div class="list-group-item rounded cursor-pointer" @click="ctxMenuCopyLink()">Copy Link</div>
|
|
||||||
<div v-if="profile && profile.is_admin == true" class="list-group-item rounded cursor-pointer" @click="ctxModMenuShow()">Moderation Tools</div>
|
|
||||||
<div v-if="ctxMenuStatus && (profile.is_admin || profile.id == profile.id)" class="list-group-item rounded cursor-pointer" @click="deletePost(ctxMenuStatus)">Delete</div>
|
|
||||||
<div class="list-group-item rounded cursor-pointer text-lighter" @click="closeCtxMenu()">Cancel</div>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
import StatusCard from './partials/StatusCard.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: [
|
|
||||||
'profile-id',
|
|
||||||
],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
StatusCard
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
id: [],
|
|
||||||
ids: [],
|
|
||||||
user: false,
|
|
||||||
profile: {},
|
|
||||||
feed: [],
|
|
||||||
min_id: null,
|
|
||||||
max_id: null,
|
|
||||||
loading: true,
|
|
||||||
owner: false,
|
|
||||||
layoutType: true,
|
|
||||||
relationship: null,
|
|
||||||
warning: false,
|
|
||||||
ctxMenuStatus: false,
|
|
||||||
ctxMenuRelationship: false,
|
|
||||||
fetchingRemotePosts: false,
|
|
||||||
showMutualFollowers: false,
|
|
||||||
loadingMore: false,
|
|
||||||
showLoadMore: true,
|
|
||||||
followers: [],
|
|
||||||
followerCursor: 1,
|
|
||||||
followerMore: true,
|
|
||||||
followerLoading: true,
|
|
||||||
following: [],
|
|
||||||
followingCursor: 1,
|
|
||||||
followingMore: true,
|
|
||||||
followingLoading: true,
|
|
||||||
followingModalSearch: null,
|
|
||||||
followingModalSearchCache: null,
|
|
||||||
followingModalTab: 'following',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeMount() {
|
|
||||||
this.fetchRelationships();
|
|
||||||
this.fetchProfile();
|
|
||||||
},
|
|
||||||
|
|
||||||
updated() {
|
|
||||||
document.querySelectorAll('.hashtag').forEach(function(i, e) {
|
|
||||||
i.href = App.util.format.rewriteLinks(i);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
fetchProfile() {
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
|
||||||
this.user = res.data
|
|
||||||
window._sharedData.curUser = res.data;
|
|
||||||
window.App.util.navatar();
|
|
||||||
});
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/' + this.profileId)
|
|
||||||
.then(res => {
|
|
||||||
this.profile = res.data;
|
|
||||||
this.fetchPosts();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchPosts() {
|
|
||||||
let apiUrl = '/api/pixelfed/v1/accounts/' + this.profileId + '/statuses';
|
|
||||||
axios.get(apiUrl, {
|
|
||||||
params: {
|
|
||||||
only_media: true,
|
|
||||||
min_id: 1,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
let data = res.data
|
|
||||||
.filter(status => status.media_attachments.length > 0);
|
|
||||||
let ids = data.map(status => status.id);
|
|
||||||
this.ids = ids;
|
|
||||||
this.min_id = Math.max(...ids);
|
|
||||||
this.max_id = Math.min(...ids);
|
|
||||||
this.feed = data;
|
|
||||||
this.loading = false;
|
|
||||||
//this.loadSponsor();
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Oops, something went wrong',
|
|
||||||
'Please release the page.',
|
|
||||||
'error');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
loadMorePosts() {
|
|
||||||
this.loadingMore = true;
|
|
||||||
let apiUrl = '/api/pixelfed/v1/accounts/' + this.profileId + '/statuses';
|
|
||||||
axios.get(apiUrl, {
|
|
||||||
params: {
|
|
||||||
only_media: true,
|
|
||||||
max_id: this.max_id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
let data = res.data
|
|
||||||
.filter(status => this.ids.indexOf(status.id) === -1)
|
|
||||||
.filter(status => status.media_attachments.length > 0)
|
|
||||||
.map(status => {
|
|
||||||
return {
|
|
||||||
id: status.id,
|
|
||||||
caption: {
|
|
||||||
text: status.content_text,
|
|
||||||
html: status.content
|
|
||||||
},
|
|
||||||
count: {
|
|
||||||
likes: status.favourites_count,
|
|
||||||
shares: status.reblogs_count,
|
|
||||||
comments: status.reply_count
|
|
||||||
},
|
|
||||||
thumb: status.media_attachments[0].url,
|
|
||||||
media: status.media_attachments,
|
|
||||||
timestamp: status.created_at,
|
|
||||||
type: status.pf_type,
|
|
||||||
url: status.url,
|
|
||||||
sensitive: status.sensitive,
|
|
||||||
cw: status.sensitive,
|
|
||||||
spoiler_text: status.spoiler_text
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let ids = data.map(status => status.id);
|
|
||||||
this.ids.push(...ids);
|
|
||||||
this.max_id = Math.min(...ids);
|
|
||||||
this.feed.push(...data);
|
|
||||||
this.loadingMore = false;
|
|
||||||
}).catch(err => {
|
|
||||||
this.loadingMore = false;
|
|
||||||
this.showLoadMore = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchRelationships() {
|
|
||||||
if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/relationships', {
|
|
||||||
params: {
|
|
||||||
'id[]': this.profileId
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
if(res.data.length) {
|
|
||||||
this.relationship = res.data[0];
|
|
||||||
if(res.data[0].blocking == true) {
|
|
||||||
this.loading = false;
|
|
||||||
this.warning = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
postPreviewUrl(post) {
|
|
||||||
return 'background: url("'+post.thumb+'");background-size:cover';
|
|
||||||
},
|
|
||||||
|
|
||||||
timestampFormat(timestamp) {
|
|
||||||
let ts = new Date(timestamp);
|
|
||||||
return ts.toDateString() + ' ' + ts.toLocaleTimeString();
|
|
||||||
},
|
|
||||||
|
|
||||||
remoteProfileUrl(profile) {
|
|
||||||
return '/i/web/profile/_/' + profile.id;
|
|
||||||
},
|
|
||||||
|
|
||||||
remotePostUrl(status) {
|
|
||||||
return '/i/web/post/_/' + this.profile.id + '/' + status.id;
|
|
||||||
},
|
|
||||||
|
|
||||||
followProfile() {
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: this.profileId
|
|
||||||
}).then(res => {
|
|
||||||
swal('Followed', 'You are now following ' + this.profile.username +'!', 'success');
|
|
||||||
this.relationship.following = true;
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Oops!', 'Something went wrong, please try again later.', 'error');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
unfollowProfile() {
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: this.profileId
|
|
||||||
}).then(res => {
|
|
||||||
swal('Unfollowed', 'You are no longer following ' + this.profile.username +'.', 'warning');
|
|
||||||
this.relationship.following = false;
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Oops!', 'Something went wrong, please try again later.', 'error');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
showCtxMenu() {
|
|
||||||
this.$refs.visitorContextMenu.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
copyProfileLink() {
|
|
||||||
navigator.clipboard.writeText(window.location.href);
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
muteProfile() {
|
|
||||||
let id = this.profileId;
|
|
||||||
axios.post('/i/mute', {
|
|
||||||
type: 'user',
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.fetchRelationships();
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
swal('Success', 'You have successfully muted ' + this.profile.acct, 'success');
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
|
||||||
});
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
unmuteProfile() {
|
|
||||||
let id = this.profileId;
|
|
||||||
axios.post('/i/unmute', {
|
|
||||||
type: 'user',
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.fetchRelationships();
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
swal('Success', 'You have successfully unmuted ' + this.profile.acct, 'success');
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
|
||||||
});
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
blockProfile() {
|
|
||||||
let id = this.profileId;
|
|
||||||
axios.post('/i/block', {
|
|
||||||
type: 'user',
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.warning = true;
|
|
||||||
this.fetchRelationships();
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
swal('Success', 'You have successfully blocked ' + this.profile.acct, 'success');
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
|
||||||
});
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
unblockProfile() {
|
|
||||||
let id = this.profileId;
|
|
||||||
axios.post('/i/unblock', {
|
|
||||||
type: 'user',
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.warning = false;
|
|
||||||
this.fetchRelationships();
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
swal('Success', 'You have successfully unblocked ' + this.profile.acct, 'success');
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
|
||||||
});
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
reportProfile() {
|
|
||||||
window.location.href = '/l/i/report?type=profile&id=' + this.profileId;
|
|
||||||
this.$refs.visitorContextMenu.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
ctxMenu(status) {
|
|
||||||
this.ctxMenuStatus = status;
|
|
||||||
let self = this;
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/relationships', {
|
|
||||||
params: {
|
|
||||||
'id[]': self.profileId
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
self.ctxMenuRelationship = res.data[0];
|
|
||||||
self.$refs.ctxModal.show();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
closeCtxMenu() {
|
|
||||||
this.ctxMenuStatus = false;
|
|
||||||
this.ctxMenuRelationship = false;
|
|
||||||
this.$refs.ctxModal.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
ctxMenuCopyLink() {
|
|
||||||
let status = this.ctxMenuStatus;
|
|
||||||
navigator.clipboard.writeText(status.url);
|
|
||||||
this.closeCtxMenu();
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
ctxMenuGoToPost() {
|
|
||||||
let status = this.ctxMenuStatus;
|
|
||||||
window.location.href = this.statusUrl(status);
|
|
||||||
this.closeCtxMenu();
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
statusUrl(status) {
|
|
||||||
return '/i/web/post/_/' + this.profile.id + '/' + status.id;
|
|
||||||
},
|
|
||||||
|
|
||||||
deletePost(status) {
|
|
||||||
if(this.user.is_admin == 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.feed = this.feed.filter(s => {
|
|
||||||
return s.id != status.id;
|
|
||||||
});
|
|
||||||
this.$refs.ctxModal.hide();
|
|
||||||
}).catch(err => {
|
|
||||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
manuallyFetchRemotePosts($event) {
|
|
||||||
this.fetchingRemotePosts = true;
|
|
||||||
event.target.blur();
|
|
||||||
swal(
|
|
||||||
'Fetching Remote Posts',
|
|
||||||
'Check back in a few minutes!',
|
|
||||||
'info'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
timeAgo(ts, suffix = false) {
|
|
||||||
if(ts == null) {
|
|
||||||
return 'never';
|
|
||||||
}
|
|
||||||
suffix = suffix ? ' ' + suffix : '';
|
|
||||||
return App.util.format.timeAgo(ts) + suffix;
|
|
||||||
},
|
|
||||||
|
|
||||||
urlRedirectHandler(url) {
|
|
||||||
let p = new URL(url);
|
|
||||||
let path = '';
|
|
||||||
if(p.hostname == window.location.hostname) {
|
|
||||||
path = url;
|
|
||||||
} else {
|
|
||||||
path = '/i/redirect?url=';
|
|
||||||
path += encodeURI(url);
|
|
||||||
}
|
|
||||||
window.location.href = path;
|
|
||||||
},
|
|
||||||
|
|
||||||
followingModal() {
|
|
||||||
if(this.followingCursor > 1) {
|
|
||||||
this.$refs.followingModal.show();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profileId+'/following', {
|
|
||||||
params: {
|
|
||||||
page: this.followingCursor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
this.following = res.data;
|
|
||||||
this.followingModalSearchCache = res.data;
|
|
||||||
this.followingCursor++;
|
|
||||||
if(res.data.length < 10) {
|
|
||||||
this.followingMore = false;
|
|
||||||
}
|
|
||||||
this.followingLoading = false;
|
|
||||||
});
|
|
||||||
this.$refs.followingModal.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
followersModal() {
|
|
||||||
if(this.followerCursor > 1) {
|
|
||||||
this.$refs.followerModal.show();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profileId+'/followers', {
|
|
||||||
params: {
|
|
||||||
page: this.followerCursor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
this.followers.push(...res.data);
|
|
||||||
this.followerCursor++;
|
|
||||||
if(res.data.length < 10) {
|
|
||||||
this.followerMore = false;
|
|
||||||
}
|
|
||||||
this.followerLoading = false;
|
|
||||||
})
|
|
||||||
this.$refs.followerModal.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
followingLoadMore() {
|
|
||||||
axios.get('/api/pixelfed/v1/accounts/'+this.profile.id+'/following', {
|
|
||||||
params: {
|
|
||||||
page: this.followingCursor,
|
|
||||||
fbu: this.followingModalSearch
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if(res.data.length > 0) {
|
|
||||||
this.following.push(...res.data);
|
|
||||||
this.followingCursor++;
|
|
||||||
this.followingModalSearchCache = this.following;
|
|
||||||
}
|
|
||||||
if(res.data.length < 10) {
|
|
||||||
this.followingModalSearchCache = this.following;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
profileUrlRedirect(profile) {
|
|
||||||
if(profile.local == true) {
|
|
||||||
return profile.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '/i/web/profile/_/' + profile.id;
|
|
||||||
},
|
|
||||||
|
|
||||||
followingModalSearchHandler() {
|
|
||||||
let self = this;
|
|
||||||
let q = this.followingModalSearch;
|
|
||||||
|
|
||||||
if(q.length == 0) {
|
|
||||||
this.following = this.followingModalSearchCache;
|
|
||||||
this.followingModalSearch = null;
|
|
||||||
}
|
|
||||||
if(q.length > 0) {
|
|
||||||
let url = '/api/pixelfed/v1/accounts/' +
|
|
||||||
self.profileId + '/following?page=1&fbu=' +
|
|
||||||
q;
|
|
||||||
|
|
||||||
axios.get(url).then(res => {
|
|
||||||
this.following = res.data;
|
|
||||||
}).catch(err => {
|
|
||||||
self.following = self.followingModalSearchCache;
|
|
||||||
self.followingModalSearch = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style type="text/css" scoped>
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1050px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -379,26 +379,6 @@ export default {
|
||||||
this.searchContext(this.analysis);
|
this.searchContext(this.analysis);
|
||||||
},
|
},
|
||||||
|
|
||||||
followProfile(profile, index) {
|
|
||||||
this.loading = true;
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: profile.entity.id
|
|
||||||
}).then(res => {
|
|
||||||
if(profile.entity.local == true) {
|
|
||||||
this.fetchSearchResults();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.loading = false;
|
|
||||||
this.results.profiles[index].entity.follow_request = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
if(err.response.data.message) {
|
|
||||||
swal('Error', err.response.data.message, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
searchLexer() {
|
searchLexer() {
|
||||||
let q = this.query;
|
let q = this.query;
|
||||||
|
|
||||||
|
|
|
@ -283,21 +283,21 @@
|
||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-transparent border-0 pt-0 pb-1">
|
<div class="card-footer bg-transparent border-0 pt-0 pb-1">
|
||||||
<div class="d-flex justify-content-between text-center">
|
<div class="d-flex justify-content-between text-center">
|
||||||
<span class="cursor-pointer" @click="redirect(profile.url)">
|
<span class="cursor-pointer" @click="redirect(profile.url)">
|
||||||
<p class="mb-0 font-weight-bold">{{formatCount(profile.statuses_count)}}</p>
|
<p class="mb-0 font-weight-bold">{{formatCount(profile.statuses_count)}}</p>
|
||||||
<p class="mb-0 small text-muted">Posts</p>
|
<p class="mb-0 small text-muted">Posts</p>
|
||||||
</span>
|
</span>
|
||||||
<span class="cursor-pointer" @click="redirect(profile.url+'?md=followers')">
|
<span class="cursor-pointer" @click="redirect(profile.url+'?md=followers')">
|
||||||
<p class="mb-0 font-weight-bold">{{formatCount(profile.followers_count)}}</p>
|
<p class="mb-0 font-weight-bold">{{formatCount(profile.followers_count)}}</p>
|
||||||
<p class="mb-0 small text-muted">Followers</p>
|
<p class="mb-0 small text-muted">Followers</p>
|
||||||
</span>
|
</span>
|
||||||
<span class="cursor-pointer" @click="redirect(profile.url+'?md=following')">
|
<span class="cursor-pointer" @click="redirect(profile.url+'?md=following')">
|
||||||
<p class="mb-0 font-weight-bold">{{formatCount(profile.following_count)}}</p>
|
<p class="mb-0 font-weight-bold">{{formatCount(profile.following_count)}}</p>
|
||||||
<p class="mb-0 small text-muted">Following</p>
|
<p class="mb-0 small text-muted">Following</p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="modes.notify == true && !loading" class="mb-4">
|
<div v-show="modes.notify == true && !loading" class="mb-4">
|
||||||
|
@ -324,7 +324,6 @@
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-0 small text-muted">{{rec.message}}</p>
|
<p class="mb-0 small text-muted">{{rec.message}}</p>
|
||||||
</div>
|
</div>
|
||||||
<a class="font-weight-bold small" href="#" @click.prevent="expRecFollow(rec.id, index)">Follow</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -761,24 +760,6 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
expRecFollow(id, index) {
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(this.config.ab.rec == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.suggestions.splice(index, 1);
|
|
||||||
}).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;
|
||||||
},
|
},
|
||||||
|
@ -865,34 +846,34 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
commentFocus(status, $event) {
|
commentFocus(status, $event) {
|
||||||
if(status.comments_disabled) {
|
if(status.comments_disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if(this.status && this.status.id == status.id) {
|
// if(this.status && this.status.id == status.id) {
|
||||||
// this.$refs.replyModal.show();
|
// this.$refs.replyModal.show();
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.replies = {};
|
this.replies = {};
|
||||||
this.replyStatus = {};
|
this.replyStatus = {};
|
||||||
this.replyText = '';
|
this.replyText = '';
|
||||||
this.replyId = status.id;
|
this.replyId = status.id;
|
||||||
this.replyStatus = status;
|
this.replyStatus = status;
|
||||||
// this.$refs.replyModal.show();
|
// this.$refs.replyModal.show();
|
||||||
this.fetchStatusComments(status, '');
|
this.fetchStatusComments(status, '');
|
||||||
|
|
||||||
$('nav').hide();
|
$('nav').hide();
|
||||||
$('footer').hide();
|
$('footer').hide();
|
||||||
$('.mobile-footer-spacer').attr('style', 'display:none !important');
|
$('.mobile-footer-spacer').attr('style', 'display:none !important');
|
||||||
$('.mobile-footer').attr('style', 'display:none !important');
|
$('.mobile-footer').attr('style', 'display:none !important');
|
||||||
this.currentLayout = 'comments';
|
this.currentLayout = 'comments';
|
||||||
window.history.pushState({}, '', this.statusUrl(status));
|
window.history.pushState({}, '', this.statusUrl(status));
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchStatusComments(status, card) {
|
fetchStatusComments(status, card) {
|
||||||
let url = '/api/v2/comments/'+status.account.id+'/status/'+status.id;
|
let url = '/api/v2/comments/'+status.account.id+'/status/'+status.id;
|
||||||
axios.get(url)
|
axios.get(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
size="sm"
|
size="sm"
|
||||||
body-class="list-group-flush p-0 rounded">
|
body-class="list-group-flush p-0 rounded">
|
||||||
<div class="list-group text-center">
|
<div class="list-group text-center">
|
||||||
<!-- <div v-if="status && status.account.id != profile.id && ctxMenuRelationship && ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-danger" @click="ctxMenuUnfollow()">Unfollow</div>
|
|
||||||
<div v-if="status && status.account.id != profile.id && ctxMenuRelationship && !ctxMenuRelationship.following" class="list-group-item rounded cursor-pointer font-weight-bold text-primary" @click="ctxMenuFollow()">Follow</div> -->
|
|
||||||
<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">View Post</div>
|
<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToPost()">View Post</div>
|
||||||
<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToProfile()">View Profile</div>
|
<div v-if="status.visibility !== 'archived'" class="list-group-item rounded cursor-pointer" @click="ctxMenuGoToProfile()">View Profile</div>
|
||||||
<!-- <div v-if="status && status.local == true && !status.in_reply_to_id" class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
|
<!-- <div v-if="status && status.local == true && !status.in_reply_to_id" class="list-group-item rounded cursor-pointer" @click="ctxMenuEmbed()">Embed</div>
|
||||||
|
@ -289,37 +287,6 @@
|
||||||
return;
|
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() {
|
ctxMenuReportPost() {
|
||||||
this.$refs.ctxModal.hide();
|
this.$refs.ctxModal.hide();
|
||||||
this.$refs.ctxReport.show();
|
this.$refs.ctxReport.show();
|
||||||
|
|
|
@ -71,14 +71,7 @@
|
||||||
<a v-if="status.place" class="small text-decoration-none text-muted" :href="'/discover/places/'+status.place.id+'/'+status.place.slug" title="Location" data-toggle="tooltip"><i class="fas fa-map-marked-alt"></i> {{status.place.name}}, {{status.place.country}}</a>
|
<a v-if="status.place" class="small text-decoration-none text-muted" :href="'/discover/places/'+status.place.id+'/'+status.place.slug" title="Location" data-toggle="tooltip"><i class="fas fa-map-marked-alt"></i> {{status.place.name}}, {{status.place.country}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="canFollow(status)">
|
|
||||||
<span class="px-2"></span>
|
|
||||||
<button class="btn btn-primary btn-sm font-weight-bold py-1 px-3 rounded-lg" @click="follow(status.account.id)"><i class="far fa-user-plus mr-1"></i> Follow</button>
|
|
||||||
</div>
|
|
||||||
<div v-if="status.hasOwnProperty('relationship') && status.relationship.hasOwnProperty('following') && status.relationship.following">
|
|
||||||
<span class="px-2"></span>
|
|
||||||
<button class="btn btn-outline-primary btn-sm font-weight-bold py-1 px-3 rounded-lg" @click="unfollow(status.account.id)"><i class="far fa-user-check mr-1"></i> Following</button>
|
|
||||||
</div>
|
|
||||||
<div class="text-right" style="flex-grow:1;">
|
<div class="text-right" style="flex-grow:1;">
|
||||||
<button class="btn btn-link text-dark py-0" type="button" @click="ctxMenu()">
|
<button class="btn btn-link text-dark py-0" type="button" @click="ctxMenu()">
|
||||||
<span class="fas fa-ellipsis-h text-lighter"></span>
|
<span class="fas fa-ellipsis-h text-lighter"></span>
|
||||||
|
@ -397,52 +390,6 @@
|
||||||
|
|
||||||
statusDeleted(status) {
|
statusDeleted(status) {
|
||||||
this.$emit('status-delete', status);
|
this.$emit('status-delete', status);
|
||||||
},
|
|
||||||
|
|
||||||
canFollow(status) {
|
|
||||||
if(!status.hasOwnProperty('relationship')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!status.hasOwnProperty('account') || !status.account.hasOwnProperty('id')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(status.account.id == this.profile.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !status.relationship.following;
|
|
||||||
},
|
|
||||||
|
|
||||||
follow(id) {
|
|
||||||
event.currentTarget.blur();
|
|
||||||
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.status.relationship.following = true;
|
|
||||||
this.$emit('followed', id);
|
|
||||||
}).catch(err => {
|
|
||||||
if(err.response.data.message) {
|
|
||||||
swal('Error', err.response.data.message, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
unfollow(id) {
|
|
||||||
event.currentTarget.blur();
|
|
||||||
|
|
||||||
axios.post('/i/follow', {
|
|
||||||
item: id
|
|
||||||
}).then(res => {
|
|
||||||
this.status.relationship.following = false;
|
|
||||||
this.$emit('unfollowed', id);
|
|
||||||
}).catch(err => {
|
|
||||||
if(err.response.data.message) {
|
|
||||||
swal('Error', err.response.data.message, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
resources/assets/js/rempos.js
vendored
34
resources/assets/js/rempos.js
vendored
|
@ -1,34 +0,0 @@
|
||||||
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(
|
|
||||||
'remote-post',
|
|
||||||
require('./components/RemotePost.vue').default
|
|
||||||
);
|
|
29
resources/assets/js/rempro.js
vendored
29
resources/assets/js/rempro.js
vendored
|
@ -1,29 +0,0 @@
|
||||||
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(
|
|
||||||
'remote-profile',
|
|
||||||
require('./components/RemoteProfile.vue').default
|
|
||||||
);
|
|
|
@ -1,121 +0,0 @@
|
||||||
@extends('layouts.app',['title' => $user->username . " on " . config('app.name')])
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
@if (session('error'))
|
|
||||||
<div class="alert alert-danger text-center font-weight-bold mb-0">
|
|
||||||
{{ session('error') }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@include('profile.partial.user-info')
|
|
||||||
|
|
||||||
@if(true === $owner)
|
|
||||||
<div>
|
|
||||||
<ul class="nav nav-topbar d-flex justify-content-center border-0">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {{request()->is($user->username) ? 'active': ''}} font-weight-bold text-uppercase" href="{{$user->url()}}">Posts</a>
|
|
||||||
</li>
|
|
||||||
{{-- <li class="nav-item">
|
|
||||||
<a class="nav-link {{request()->is('*/collections') ? 'active': ''}} font-weight-bold text-uppercase" href="{{$user->url()}}/collections">Collections</a>
|
|
||||||
</li> --}}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {{request()->is('*/saved') ? 'active':''}} font-weight-bold text-uppercase" href="{{$user->url('/saved')}}">Saved</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
<div class="container">
|
|
||||||
@if($owner && request()->is('*/saved'))
|
|
||||||
<div class="col-12">
|
|
||||||
<p class="text-muted font-weight-bold small">{{__('profile.savedWarning')}}</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
<div class="profile-timeline mt-2 mt-md-4">
|
|
||||||
<div class="row">
|
|
||||||
@if($timeline->count() > 0)
|
|
||||||
@foreach($timeline as $status)
|
|
||||||
<div class="col-4 p-0 p-sm-2 p-md-3">
|
|
||||||
<a class="card info-overlay card-md-border-0" href="{{$status->url()}}">
|
|
||||||
<div class="square {{$status->firstMedia()->filter_class}}">
|
|
||||||
@switch($status->viewType())
|
|
||||||
@case('album')
|
|
||||||
@case('photo:album')
|
|
||||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6;text-shadow: 3px 3px 16px #272634;"><i class="fas fa-images fa-2x"></i></span>
|
|
||||||
@break
|
|
||||||
@case('video')
|
|
||||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6;text-shadow: 3px 3px 16px #272634;"><i class="fas fa-video fa-2x"></i></span>
|
|
||||||
@break
|
|
||||||
@case('video-album')
|
|
||||||
<span class="float-right mr-3" style="color:#fff;position:relative;margin-top:10px;z-index: 999999;opacity:0.6;text-shadow: 3px 3px 16px #272634;"><i class="fas fa-film fa-2x"></i></span>
|
|
||||||
@break
|
|
||||||
@endswitch
|
|
||||||
<div class="square-content" style="background-image: url('{{$status->thumb()}}')">
|
|
||||||
</div>
|
|
||||||
<div class="info-overlay-text">
|
|
||||||
<h5 class="text-white m-auto font-weight-bold">
|
|
||||||
<span>
|
|
||||||
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
|
|
||||||
<span class="d-flex-inline">{{App\Util\Lexer\PrettyNumber::convert($status->likes_count)}}</span>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
|
|
||||||
<span class="d-flex-inline">{{App\Util\Lexer\PrettyNumber::convert($status->comments_count)}}</span>
|
|
||||||
</span>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pagination-container">
|
|
||||||
<div class="d-flex justify-content-center">
|
|
||||||
{{$timeline->links()}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body py-5 my-5">
|
|
||||||
<div class="d-flex my-5 py-5 justify-content-center align-items-center">
|
|
||||||
@if($owner && request()->is('*/saved'))
|
|
||||||
<p class="lead font-weight-bold">{{ __('profile.emptySaved') }}</p>
|
|
||||||
@else
|
|
||||||
<p class="lead font-weight-bold">{{ __('profile.emptyTimeline') }}</p>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@push('meta')<meta property="og:description" content="{{$user->bio}}">
|
|
||||||
<meta property="og:image" content="{{$user->avatarUrl()}}">
|
|
||||||
<link href="{{$user->permalink('.atom')}}" rel="alternate" title="{{$user->username}} on Pixelfed" type="application/atom+xml">
|
|
||||||
@if(false == $settings->crawlable || $user->remote_url)
|
|
||||||
<meta name="robots" content="noindex, nofollow">
|
|
||||||
@endif
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('.pagination-container').hide();
|
|
||||||
$('.pagination').hide();
|
|
||||||
let elem = document.querySelector('.profile-timeline');
|
|
||||||
let infScroll = new InfiniteScroll( elem, {
|
|
||||||
path: '.pagination__next',
|
|
||||||
append: '.profile-timeline .row',
|
|
||||||
status: '.page-load-status',
|
|
||||||
history: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
@endpush
|
|
||||||
|
|
|
@ -10,28 +10,20 @@
|
||||||
<div class="profile-details">
|
<div class="profile-details">
|
||||||
<div class="username-bar pb-2 d-flex align-items-center">
|
<div class="username-bar pb-2 d-flex align-items-center">
|
||||||
<span class="font-weight-ultralight h3">{{$user->username}}</span>
|
<span class="font-weight-ultralight h3">{{$user->username}}</span>
|
||||||
@if(Auth::check() && $is_following == true)
|
@auth
|
||||||
|
@if($is_following == true)
|
||||||
<span class="pl-4">
|
<span class="pl-4">
|
||||||
<form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="unfollow">
|
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0" type="button" onclick="unfollowProfile()">Unfollow</button>
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="item" value="{{$user->id}}">
|
|
||||||
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0" type="submit">Unfollow</button>
|
|
||||||
</form>
|
|
||||||
</span>
|
</span>
|
||||||
@elseif(Auth::check() && $requested == true)
|
@elseif($requested == true)
|
||||||
<span class="pl-4">
|
<span class="pl-4">
|
||||||
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0 disabled" disabled type="button">Follow Requested</button>
|
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0" type="button" onclick="unfollowProfile()">Follow Requested</button>
|
||||||
</span>
|
</span>
|
||||||
@elseif(Auth::check() && $is_following == false)
|
@elseif($is_following == false)
|
||||||
<span class="pl-4">
|
<span class="pl-4">
|
||||||
<form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="follow">
|
<button class="btn btn-primary font-weight-bold px-4 py-0" type="button" onclick="followProfile()">Follow</button>
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="item" value="{{$user->id}}">
|
|
||||||
<button class="btn btn-primary font-weight-bold px-4 py-0" type="submit">Follow</button>
|
|
||||||
</form>
|
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
@auth
|
|
||||||
<span class="pl-4">
|
<span class="pl-4">
|
||||||
<i class="fas fa-cog fa-lg text-muted cursor-pointer" data-toggle="modal" data-target="#ctxProfileMenu"></i>
|
<i class="fas fa-cog fa-lg text-muted cursor-pointer" data-toggle="modal" data-target="#ctxProfileMenu"></i>
|
||||||
<div class="modal" tabindex="-1" role="dialog" id="ctxProfileMenu">
|
<div class="modal" tabindex="-1" role="dialog" id="ctxProfileMenu">
|
||||||
|
@ -81,6 +73,7 @@
|
||||||
swal('Muted Profile', 'You have successfully muted this profile.', 'success');
|
swal('Muted Profile', 'You have successfully muted this profile.', 'success');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function blockProfile() {
|
function blockProfile() {
|
||||||
axios.post('/i/block', {
|
axios.post('/i/block', {
|
||||||
type: 'user',
|
type: 'user',
|
||||||
|
@ -92,6 +85,20 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function followProfile() {
|
||||||
|
axios.post('/api/v1/accounts/{{$user->id}}/follow')
|
||||||
|
.then(res => {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function unfollowProfile() {
|
||||||
|
axios.post('/api/v1/accounts/{{$user->id}}/unfollow')
|
||||||
|
.then(res => {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@endauth
|
@endauth
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
<div class="bg-white py-5 border-bottom">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12 col-md-4 d-flex">
|
|
||||||
<div class="profile-avatar mx-auto">
|
|
||||||
<img class="rounded-circle box-shadow" src="{{$user->avatarUrl()}}" width="172px" height="172px">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-8 d-flex align-items-center">
|
|
||||||
<div class="profile-details">
|
|
||||||
<div class="username-bar pb-2 d-flex align-items-center">
|
|
||||||
<span class="font-weight-ultralight h1">{{$user->username}}</span>
|
|
||||||
@if($is_admin == true)
|
|
||||||
<span class="pl-4">
|
|
||||||
<span class="btn btn-outline-danger font-weight-bold py-0">ADMIN</span>
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
@if($owner == true)
|
|
||||||
<span class="pl-4">
|
|
||||||
<a class="fas fa-cog fa-lg text-muted" href="{{route('settings')}}"></a>
|
|
||||||
</span>
|
|
||||||
@elseif (Auth::check() && $is_following == true)
|
|
||||||
<span class="pl-4">
|
|
||||||
<form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="unfollow">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="item" value="{{$user->id}}">
|
|
||||||
<button class="btn btn-outline-secondary font-weight-bold px-4 py-0" type="submit">Unfollow</button>
|
|
||||||
</form>
|
|
||||||
</span>
|
|
||||||
@elseif (Auth::check() && $is_following == false)
|
|
||||||
<span class="pl-4">
|
|
||||||
<form class="follow-form" method="post" action="/i/follow" style="display: inline;" data-id="{{$user->id}}" data-action="follow">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="item" value="{{$user->id}}">
|
|
||||||
<button class="btn btn-primary font-weight-bold px-4 py-0" type="submit">Follow</button>
|
|
||||||
</form>
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
{{-- <span class="pl-4">
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-link text-muted dropdown-toggle py-0" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" style="text-decoration: none;">
|
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
|
||||||
<a class="dropdown-item font-weight-bold" href="#">Report User</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item font-weight-bold" href="#">Mute User</a>
|
|
||||||
<a class="dropdown-item font-weight-bold" href="#">Block User</a>
|
|
||||||
<a class="dropdown-item font-weight-bold mute-users" href="#">Mute User & User Followers</a>
|
|
||||||
<a class="dropdown-item font-weight-bold" href="#">Block User & User Followers</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
--}}
|
|
||||||
</div>
|
|
||||||
<div class="profile-stats pb-3 d-inline-flex lead">
|
|
||||||
<div class="font-weight-light pr-5">
|
|
||||||
<a class="text-dark" href="{{$user->url()}}">
|
|
||||||
<span class="font-weight-bold">{{$user->statusCount()}}</span>
|
|
||||||
Posts
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@if($settings->show_profile_follower_count)
|
|
||||||
<div class="font-weight-light pr-5">
|
|
||||||
<a class="text-dark" href="{{$user->url('/followers')}}">
|
|
||||||
<span class="font-weight-bold">{{$user->followerCount(true)}}</span>
|
|
||||||
Followers
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if($settings->show_profile_following_count)
|
|
||||||
<div class="font-weight-light pr-5">
|
|
||||||
<a class="text-dark" href="{{$user->url('/following')}}">
|
|
||||||
<span class="font-weight-bold">{{$user->followingCount(true)}}</span>
|
|
||||||
Following
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<p class="lead mb-0 d-flex align-items-center">
|
|
||||||
<span class="font-weight-bold pr-3">{{$user->name}}</span>
|
|
||||||
@if($user->remote_url)
|
|
||||||
<span class="btn btn-outline-secondary btn-sm py-0">REMOTE PROFILE</span>
|
|
||||||
@endif
|
|
||||||
</p>
|
|
||||||
<div class="mb-0 lead" v-pre>{!!str_limit($user->bio, 127)!!}</div>
|
|
||||||
<p class="mb-0"><a href="{{$user->website}}" class="font-weight-bold" rel="me external nofollow noopener" target="_blank">{{str_limit($user->website, 30)}}</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,16 +0,0 @@
|
||||||
@extends('layouts.app')
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<remote-profile profile-id="{{$profile->id}}"></remote-profile>
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@push('meta')
|
|
||||||
<meta name="robots" content="noindex, noimageindex, nofollow, nosnippet, noarchive">
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script type="text/javascript" src="{{mix('js/rempro.js')}}"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
App.boot();
|
|
||||||
</script>
|
|
||||||
@endpush
|
|
|
@ -131,14 +131,20 @@
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unfollow':
|
case 'unfollow':
|
||||||
axios.post('/i/follow', {
|
axios.post('/api/v1/accounts/' + id + '/unfollow')
|
||||||
item: id
|
.then(res => {
|
||||||
}).then(res => {
|
|
||||||
swal(
|
swal(
|
||||||
'Unfollow Successful',
|
'Unfollow Successful',
|
||||||
'You have successfully unfollowed that user',
|
'You have successfully unfollowed that user',
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
swal(
|
||||||
|
'Error',
|
||||||
|
'An error occured when attempting to unfollow this user',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -11,25 +11,35 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="text-center mt-n5 mb-4">
|
<div class="text-center mt-n5 mb-4">
|
||||||
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" src="{{$profile->avatarUrl()}}" width="90px" height="90px;">
|
<img
|
||||||
|
class="rounded-circle p-1 border mt-n4 bg-white shadow"
|
||||||
|
src="{{$profile->avatarUrl()}}"
|
||||||
|
width="90"
|
||||||
|
height="90"
|
||||||
|
loading="lazy"
|
||||||
|
onerror="this.src='/storage/avatars/default.jpg?v=0';this.onerror=null;">
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center lead font-weight-bold mb-1">{{$profile->username}}</p>
|
<p class="text-center lead font-weight-bold mb-1">{{$profile->username}}</p>
|
||||||
<p class="text-center text-muted small text-uppercase mb-4">{{$profile->followerCount()}} followers</p>
|
<p class="text-center text-muted small text-uppercase mb-4">{{$profile->followerCount()}} followers</p>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
@if($following == true)
|
@if($following == true)
|
||||||
<form class="d-inline-block" action="/i/follow" method="post">
|
<button
|
||||||
@csrf
|
id="unfollow"
|
||||||
<input type="hidden" name="item" value="{{(string)$profile->id}}">
|
type="button"
|
||||||
<input type="hidden" name="force" value="0">
|
class="btn btn-outline-secondary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3"
|
||||||
<button type="submit" class="btn btn-outline-secondary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3" style="font-weight: 500">Unfollow</button>
|
style="font-weight: 500"
|
||||||
</form>
|
onclick="unfollowProfile()">
|
||||||
|
Unfollow
|
||||||
|
</button>
|
||||||
@else
|
@else
|
||||||
<form class="d-inline-block" action="/i/follow" method="post">
|
<button
|
||||||
@csrf
|
id="follow"
|
||||||
<input type="hidden" name="item" value="{{(string)$profile->id}}">
|
type="button"
|
||||||
<input type="hidden" name="force" value="0">
|
class="btn btn-primary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3"
|
||||||
<button type="submit" class="btn btn-primary btn-sm py-1 px-4 text-uppercase font-weight-bold mr-3" style="font-weight: 500">Follow</button>
|
style="font-weight: 500"
|
||||||
</form>
|
onclick="followProfile()">
|
||||||
|
Follow
|
||||||
|
</button>
|
||||||
@endif
|
@endif
|
||||||
<a class="btn btn-outline-primary btn-sm py-1 px-4 text-uppercase font-weight-bold" href="{{$profile->url()}}" style="font-weight: 500">View Profile</a>
|
<a class="btn btn-outline-primary btn-sm py-1 px-4 text-uppercase font-weight-bold" href="{{$profile->url()}}" style="font-weight: 500">View Profile</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,3 +61,32 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
function followProfile() {
|
||||||
|
let btn = document.querySelector('#follow');
|
||||||
|
btn.setAttribute('disabled', 'disabled');
|
||||||
|
axios.post('/api/v1/accounts/{{$profile->id}}/follow')
|
||||||
|
.then(res => {
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
location.href = '/login?next=' + encodeURI(location.href);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function unfollowProfile() {
|
||||||
|
let btn = document.querySelector('#unfollow');
|
||||||
|
btn.setAttribute('disabled', 'disabled');
|
||||||
|
axios.post('/api/v1/accounts/{{$profile->id}}/unfollow')
|
||||||
|
.then(res => {
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
location.href = '/login?next=' + encodeURI(location.href);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
@extends('layouts.app')
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<div class="mt-md-4"></div>
|
|
||||||
<remote-post status-template="{{$status->viewType()}}" status-id="{{$status->id}}" status-username="{{$status->profile->username}}" status-url="{{$status->url()}}" status-profile-url="{{$status->profile->url()}}" status-avatar="{{$status->profile->avatarUrl()}}" status-profile-id="{{$status->profile_id}}" profile-layout="metro"></remote-post>
|
|
||||||
|
|
||||||
|
|
||||||
@endsection
|
|
||||||
|
|
||||||
@push('meta')
|
|
||||||
<meta name="robots" content="noindex, noimageindex, nofollow, nosnippet, noarchive">
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
@push('scripts')
|
|
||||||
<script type="text/javascript" src="{{ mix('js/rempos.js') }}"></script>
|
|
||||||
<script type="text/javascript">App.boot()</script>
|
|
||||||
@endpush
|
|
|
@ -189,8 +189,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
|
Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
|
||||||
Route::get('profile/{username}/status/{postid}/state', 'PublicApiController@statusState');
|
Route::get('profile/{username}/status/{postid}/state', 'PublicApiController@statusState');
|
||||||
Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
|
Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
|
||||||
Route::get('likes/profile/{username}/status/{id}', 'PublicApiController@statusLikes');
|
|
||||||
Route::get('shares/profile/{username}/status/{id}', 'PublicApiController@statusShares');
|
|
||||||
Route::get('status/{id}/replies', 'InternalApiController@statusReplies');
|
Route::get('status/{id}/replies', 'InternalApiController@statusReplies');
|
||||||
Route::post('moderator/action', 'InternalApiController@modAction');
|
Route::post('moderator/action', 'InternalApiController@modAction');
|
||||||
Route::get('discover/categories', 'InternalApiController@discoverCategories');
|
Route::get('discover/categories', 'InternalApiController@discoverCategories');
|
||||||
|
@ -207,8 +205,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('accounts/relationships', 'Api\ApiV1Controller@accountRelationshipsById');
|
Route::get('accounts/relationships', 'Api\ApiV1Controller@accountRelationshipsById');
|
||||||
Route::get('accounts/search', 'Api\ApiV1Controller@accountSearch');
|
Route::get('accounts/search', 'Api\ApiV1Controller@accountSearch');
|
||||||
Route::get('accounts/{id}/statuses', 'PublicApiController@accountStatuses');
|
Route::get('accounts/{id}/statuses', 'PublicApiController@accountStatuses');
|
||||||
Route::get('accounts/{id}/following', 'PublicApiController@accountFollowing');
|
|
||||||
Route::get('accounts/{id}/followers', 'PublicApiController@accountFollowers');
|
|
||||||
Route::post('accounts/{id}/block', 'Api\ApiV1Controller@accountBlockById');
|
Route::post('accounts/{id}/block', 'Api\ApiV1Controller@accountBlockById');
|
||||||
Route::post('accounts/{id}/unblock', 'Api\ApiV1Controller@accountUnblockById');
|
Route::post('accounts/{id}/unblock', 'Api\ApiV1Controller@accountUnblockById');
|
||||||
Route::get('statuses/{id}', 'PublicApiController@getStatus');
|
Route::get('statuses/{id}', 'PublicApiController@getStatus');
|
||||||
|
@ -236,8 +232,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('discover/profiles', 'DiscoverController@profilesDirectoryApi');
|
Route::get('discover/profiles', 'DiscoverController@profilesDirectoryApi');
|
||||||
Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
|
Route::get('profile/{username}/status/{postid}', 'PublicApiController@status');
|
||||||
Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
|
Route::get('comments/{username}/status/{postId}', 'PublicApiController@statusComments');
|
||||||
Route::get('likes/profile/{username}/status/{id}', 'PublicApiController@statusLikes');
|
|
||||||
Route::get('shares/profile/{username}/status/{id}', 'PublicApiController@statusShares');
|
|
||||||
Route::post('moderator/action', 'InternalApiController@modAction');
|
Route::post('moderator/action', 'InternalApiController@modAction');
|
||||||
Route::get('discover/categories', 'InternalApiController@discoverCategories');
|
Route::get('discover/categories', 'InternalApiController@discoverCategories');
|
||||||
Route::get('loops', 'DiscoverController@loopsApi');
|
Route::get('loops', 'DiscoverController@loopsApi');
|
||||||
|
|
Loading…
Reference in a new issue