mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-26 23:13:17 +00:00
Merge pull request #1461 from pixelfed/frontend-ui-refactor
ActivityPub improvements (Announce + Like)
This commit is contained in:
commit
d316a647c3
17 changed files with 150 additions and 131 deletions
|
@ -56,7 +56,7 @@ class SearchController extends Controller
|
|||
]
|
||||
]];
|
||||
} else if ($type == 'Note') {
|
||||
$item = Helpers::statusFirstOrFetch($tag, false);
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
|
|
|
@ -30,11 +30,12 @@ class StatusController extends Controller
|
|||
}
|
||||
|
||||
$status = Status::whereProfileId($user->id)
|
||||
->whereNull('reblog_of_id')
|
||||
->whereNotIn('visibility',['draft','direct'])
|
||||
->findOrFail($id);
|
||||
|
||||
if($status->uri) {
|
||||
$url = $status->uri;
|
||||
if($status->uri || $status->url) {
|
||||
$url = $status->uri ?? $status->url;
|
||||
if(ends_with($url, '/activity')) {
|
||||
$url = str_replace('/activity', '', $url);
|
||||
}
|
||||
|
@ -102,109 +103,6 @@ class StatusController extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
return;
|
||||
|
||||
$this->authCheck();
|
||||
$user = Auth::user();
|
||||
|
||||
$size = Media::whereUserId($user->id)->sum('size') / 1000;
|
||||
$limit = (int) config('pixelfed.max_account_size');
|
||||
if ($size >= $limit) {
|
||||
return redirect()->back()->with('error', 'You have exceeded your storage limit. Please click <a href="#">here</a> for more info.');
|
||||
}
|
||||
|
||||
$this->validate($request, [
|
||||
'photo.*' => 'required|mimetypes:' . config('pixelfed.media_types').'|max:' . config('pixelfed.max_photo_size'),
|
||||
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length'),
|
||||
'cw' => 'nullable|string',
|
||||
'filter_class' => 'nullable|alpha_dash|max:30',
|
||||
'filter_name' => 'nullable|string',
|
||||
'visibility' => 'required|string|min:5|max:10',
|
||||
]);
|
||||
|
||||
if (count($request->file('photo')) > config('pixelfed.max_album_length')) {
|
||||
return redirect()->back()->with('error', 'Too many files, max limit per post: '.config('pixelfed.max_album_length'));
|
||||
}
|
||||
$cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
|
||||
$monthHash = hash('sha1', date('Y').date('m'));
|
||||
$userHash = hash('sha1', $user->id.(string) $user->created_at);
|
||||
$profile = $user->profile;
|
||||
$visibility = $this->validateVisibility($request->visibility);
|
||||
|
||||
$cw = $profile->cw == true ? true : $cw;
|
||||
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
||||
|
||||
if(config('costar.enabled') == true) {
|
||||
$blockedKeywords = config('costar.keyword.block');
|
||||
if($blockedKeywords !== null) {
|
||||
$keywords = config('costar.keyword.block');
|
||||
foreach($keywords as $kw) {
|
||||
if(Str::contains($request->caption, $kw) == true) {
|
||||
abort(400, 'Invalid object');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$status = new Status();
|
||||
$status->profile_id = $profile->id;
|
||||
$status->caption = strip_tags($request->caption);
|
||||
$status->is_nsfw = $cw;
|
||||
|
||||
// TODO: remove deprecated visibility in favor of scope
|
||||
$status->visibility = $visibility;
|
||||
$status->scope = $visibility;
|
||||
|
||||
$status->save();
|
||||
|
||||
$photos = $request->file('photo');
|
||||
$order = 1;
|
||||
$mimes = [];
|
||||
$medias = 0;
|
||||
|
||||
foreach ($photos as $k => $v) {
|
||||
|
||||
$allowedMimes = explode(',', config('pixelfed.media_types'));
|
||||
if(in_array($v->getMimeType(), $allowedMimes) == false) {
|
||||
continue;
|
||||
}
|
||||
$filter_class = $request->input('filter_class');
|
||||
$filter_name = $request->input('filter_name');
|
||||
|
||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
||||
$path = $v->store($storagePath);
|
||||
$hash = \hash_file('sha256', $v);
|
||||
$media = new Media();
|
||||
$media->status_id = $status->id;
|
||||
$media->profile_id = $profile->id;
|
||||
$media->user_id = $user->id;
|
||||
$media->media_path = $path;
|
||||
$media->original_sha256 = $hash;
|
||||
$media->size = $v->getSize();
|
||||
$media->mime = $v->getMimeType();
|
||||
|
||||
$media->filter_class = in_array($filter_class, Filter::classes()) ? $filter_class : null;
|
||||
$media->filter_name = in_array($filter_name, Filter::names()) ? $filter_name : null;
|
||||
$media->order = $order;
|
||||
$media->save();
|
||||
array_push($mimes, $media->mime);
|
||||
ImageOptimize::dispatch($media);
|
||||
$order++;
|
||||
$medias++;
|
||||
}
|
||||
|
||||
if($medias == 0) {
|
||||
$status->delete();
|
||||
return;
|
||||
}
|
||||
$status->type = (new self)::mimeTypeCheck($mimes);
|
||||
$status->save();
|
||||
|
||||
Cache::forget('profile:status_count:'.$profile->id);
|
||||
NewStatusPipeline::dispatch($status);
|
||||
|
||||
// TODO: Send to subscribers
|
||||
|
||||
return redirect($status->url());
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
|
@ -238,7 +136,9 @@ class StatusController extends Controller
|
|||
|
||||
$user = Auth::user();
|
||||
$profile = $user->profile;
|
||||
$status = Status::withCount('shares')->findOrFail($request->input('item'));
|
||||
$status = Status::withCount('shares')
|
||||
->whereIn('scope', ['public', 'unlisted'])
|
||||
->findOrFail($request->input('item'));
|
||||
|
||||
$count = $status->shares_count;
|
||||
|
||||
|
|
|
@ -2,16 +2,17 @@
|
|||
|
||||
namespace App\Jobs\LikePipeline;
|
||||
|
||||
use App\Like;
|
||||
use App\Notification;
|
||||
use Cache;
|
||||
use Cache, Log, Redis;
|
||||
use App\{Like, Notification};
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Log;
|
||||
use Redis;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use App\Transformer\ActivityPub\Verb\Like as LikeTransformer;
|
||||
|
||||
class LikePipeline implements ShouldQueue
|
||||
{
|
||||
|
@ -48,11 +49,15 @@ class LikePipeline implements ShouldQueue
|
|||
$status = $this->like->status;
|
||||
$actor = $this->like->actor;
|
||||
|
||||
if (!$status || $status->url !== null) {
|
||||
// Ignore notifications to remote statuses, or deleted statuses
|
||||
if (!$status) {
|
||||
// Ignore notifications to deleted statuses
|
||||
return;
|
||||
}
|
||||
|
||||
if($status->url && $actor->domain == null) {
|
||||
return $this->remoteLikeDeliver();
|
||||
}
|
||||
|
||||
$exists = Notification::whereProfileId($status->profile_id)
|
||||
->whereActorId($actor->id)
|
||||
->whereAction('like')
|
||||
|
@ -78,4 +83,20 @@ class LikePipeline implements ShouldQueue
|
|||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function remoteLikeDeliver()
|
||||
{
|
||||
$like = $this->like;
|
||||
$status = $this->like->status;
|
||||
$actor = $this->like->actor;
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($like, new LikeTransformer());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
|
||||
$url = $status->profile->sharedInbox ?? $status->profile->inbox_url;
|
||||
|
||||
Helpers::sendSignedObject($actor, $url, $activity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use App\Transformer\ActivityPub\Verb\Announce;
|
||||
use GuzzleHttp\{Pool, Client, Promise};
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
|
||||
class SharePipeline implements ShouldQueue
|
||||
{
|
||||
|
@ -60,6 +65,8 @@ class SharePipeline implements ShouldQueue
|
|||
return true;
|
||||
}
|
||||
|
||||
$this->remoteAnnounceDeliver();
|
||||
|
||||
try {
|
||||
$notification = new Notification;
|
||||
$notification->profile_id = $target->id;
|
||||
|
@ -78,4 +85,56 @@ class SharePipeline implements ShouldQueue
|
|||
Log::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function remoteAnnounceDeliver()
|
||||
{
|
||||
$status = $this->status;
|
||||
$profile = $status->profile;
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new Announce());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
|
||||
if(empty($audience) || $status->scope != 'public') {
|
||||
// Return on profiles with no remote followers
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = json_encode($activity);
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout')
|
||||
]);
|
||||
|
||||
$requests = function($audience) use ($client, $activity, $profile, $payload) {
|
||||
foreach($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity);
|
||||
yield function() use ($client, $url, $headers, $payload) {
|
||||
return $client->postAsync($url, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true
|
||||
]
|
||||
]);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$pool = new Pool($client, $requests($audience), [
|
||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
||||
'fulfilled' => function ($response, $index) {
|
||||
},
|
||||
'rejected' => function ($reason, $index) {
|
||||
}
|
||||
]);
|
||||
|
||||
$promise = $pool->promise();
|
||||
|
||||
$promise->wait();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ class StatusActivityPubDeliver implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$profile = $status->profile;
|
||||
|
||||
if($status->local == false || $status->url || $status->uri) {
|
||||
return;
|
||||
|
@ -56,12 +57,11 @@ class StatusActivityPubDeliver implements ShouldQueue
|
|||
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
|
||||
if(empty($audience) || $status->visibility != 'public') {
|
||||
if(empty($audience) || $status->scope != 'public') {
|
||||
// Return on profiles with no remote followers
|
||||
return;
|
||||
}
|
||||
|
||||
$profile = $status->profile;
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
|
|
|
@ -11,9 +11,16 @@ class Announce extends Fractal\TransformerAbstract
|
|||
{
|
||||
return [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $status->permalink(),
|
||||
'type' => 'Announce',
|
||||
'actor' => $status->profile->permalink(),
|
||||
'object' => $status->parent()->url()
|
||||
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
'cc' => [
|
||||
$status->profile->permalink(),
|
||||
$status->profile->follower_url ?? $status->profile->permalink('/followers')
|
||||
],
|
||||
'published' => $status->created_at->format(DATE_ISO8601),
|
||||
'object' => $status->parent()->url(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ class Like extends Fractal\TransformerAbstract
|
|||
{
|
||||
return [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $like->actor->permalink('#likes/'.$like->id),
|
||||
'type' => 'Like',
|
||||
'actor' => $like->actor->permalink(),
|
||||
'object' => $like->status->url()
|
||||
|
|
|
@ -34,7 +34,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
'muted' => null,
|
||||
'sensitive' => (bool) $status->is_nsfw,
|
||||
'spoiler_text' => $status->cw_summary,
|
||||
'visibility' => $status->visibility,
|
||||
'visibility' => $status->visibility ?? $status->scope,
|
||||
'application' => [
|
||||
'name' => 'web',
|
||||
'website' => null
|
||||
|
|
|
@ -206,7 +206,7 @@ class Helpers {
|
|||
return self::fetchFromUrl($url);
|
||||
}
|
||||
|
||||
public static function statusFirstOrFetch($url, $replyTo = true)
|
||||
public static function statusFirstOrFetch($url, $replyTo = false)
|
||||
{
|
||||
$url = self::validateUrl($url);
|
||||
if($url == false) {
|
||||
|
@ -337,6 +337,11 @@ class Helpers {
|
|||
}
|
||||
}
|
||||
|
||||
public static function statusFetch($url)
|
||||
{
|
||||
return self::statusFirstOrFetch($url);
|
||||
}
|
||||
|
||||
public static function importNoteAttachment($data, Status $status)
|
||||
{
|
||||
if(self::verifyAttachments($data) == false) {
|
||||
|
@ -435,6 +440,11 @@ class Helpers {
|
|||
return $profile;
|
||||
}
|
||||
|
||||
public static function profileFetch($url)
|
||||
{
|
||||
return self::profileFirstOrNew($url);
|
||||
}
|
||||
|
||||
public static function sendSignedObject($senderProfile, $url, $body)
|
||||
{
|
||||
abort_if(!self::validateUrl($url), 400);
|
||||
|
|
|
@ -151,7 +151,7 @@ class Inbox
|
|||
if(Status::whereUrl($url)->exists()) {
|
||||
return;
|
||||
}
|
||||
Helpers::statusFirstOrFetch($url, false);
|
||||
Helpers::statusFetch($url);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -205,21 +205,27 @@ class Inbox
|
|||
{
|
||||
$actor = $this->actorFirstOrCreate($this->payload['actor']);
|
||||
$activity = $this->payload['object'];
|
||||
|
||||
if(!$actor || $actor->domain == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Helpers::validateLocalUrl($activity) == false) {
|
||||
return;
|
||||
}
|
||||
$parent = Helpers::statusFirstOrFetch($activity, true);
|
||||
if(!$parent) {
|
||||
|
||||
$parent = Helpers::statusFetch($activity);
|
||||
|
||||
if(empty($parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = Status::firstOrCreate([
|
||||
'profile_id' => $actor->id,
|
||||
'reblog_of_id' => $parent->id,
|
||||
'type' => 'reply'
|
||||
'type' => 'share'
|
||||
]);
|
||||
|
||||
Notification::firstOrCreate([
|
||||
'profile_id' => $parent->profile->id,
|
||||
'actor_id' => $actor->id,
|
||||
|
@ -229,6 +235,7 @@ class Inbox
|
|||
'item_id' => $parent->id,
|
||||
'item_type' => 'App\Status'
|
||||
]);
|
||||
|
||||
$parent->reblogs_count = $parent->shares()->count();
|
||||
$parent->save();
|
||||
}
|
||||
|
@ -316,6 +323,20 @@ class Inbox
|
|||
break;
|
||||
|
||||
case 'Announce':
|
||||
abort_if(!Helpers::validateLocalUrl($obj), 400);
|
||||
$status = Helpers::statusFetch($obj);
|
||||
if(!$status) {
|
||||
return;
|
||||
}
|
||||
Status::whereProfileId($profile->id)
|
||||
->whereReblogOfId($status->id)
|
||||
->forceDelete();
|
||||
Notification::whereProfileId($status->profile->id)
|
||||
->whereActorId($profile->id)
|
||||
->whereAction('share')
|
||||
->whereItemId($status->reblog_of_id)
|
||||
->whereItemType('App\Status')
|
||||
->forceDelete();
|
||||
break;
|
||||
|
||||
case 'Block':
|
||||
|
@ -347,6 +368,6 @@ class Inbox
|
|||
->forceDelete();
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/js/profile.js
vendored
BIN
public/js/profile.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.
Binary file not shown.
|
@ -179,14 +179,14 @@
|
|||
<div class="reactions my-1">
|
||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
||||
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
<h3 v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
||||
</div>
|
||||
<div class="reaction-counts font-weight-bold mb-0">
|
||||
<span style="cursor:pointer;" v-on:click="likesModal">
|
||||
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
||||
</span>
|
||||
<span class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span v-if="status.visibility == 'public'" class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
||||
</span>
|
||||
</div>
|
||||
|
@ -268,13 +268,13 @@
|
|||
<div class="reactions py-2">
|
||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
||||
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary float-right cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn float-right cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary float-right cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn float-right cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
</div>
|
||||
<div class="reaction-counts font-weight-bold mb-0">
|
||||
<span style="cursor:pointer;" v-on:click="likesModal">
|
||||
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
||||
</span>
|
||||
<span class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span v-if="status.visibility == 'public'" class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -242,7 +242,7 @@
|
|||
<div class="reactions my-1" v-if="user.hasOwnProperty('id')">
|
||||
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
|
||||
<h3 class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
|
||||
<h3 v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||
</div>
|
||||
|
||||
<div class="likes font-weight-bold">
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
<div v-if="!modes.distractionFree" class="reactions my-1">
|
||||
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
|
||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
|
||||
<h3 v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||
<h3 v-if="status.visibility == 'public'" v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||
</div>
|
||||
|
||||
<div class="likes font-weight-bold" v-if="expLc(status) == true && !modes.distractionFree">
|
||||
|
|
Loading…
Reference in a new issue