mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-10 08:44:49 +00:00
commit
c643dc307b
39 changed files with 692 additions and 144 deletions
|
@ -29,6 +29,12 @@
|
||||||
- Update ApiV1Controller, fix hashtag timeline ([fc1a385c](https://github.com/pixelfed/pixelfed/commit/fc1a385c))
|
- Update ApiV1Controller, fix hashtag timeline ([fc1a385c](https://github.com/pixelfed/pixelfed/commit/fc1a385c))
|
||||||
- Update settings view, add fallback avatar ([1a83c585](https://github.com/pixelfed/pixelfed/commit/1a83c585))
|
- Update settings view, add fallback avatar ([1a83c585](https://github.com/pixelfed/pixelfed/commit/1a83c585))
|
||||||
- Update HashtagFollow model, add MAX_LIMIT of 250 tags per account ([ed352141](https://github.com/pixelfed/pixelfed/commit/ed352141))
|
- Update HashtagFollow model, add MAX_LIMIT of 250 tags per account ([ed352141](https://github.com/pixelfed/pixelfed/commit/ed352141))
|
||||||
|
- Update Notification logic, remove message and rendered fields ([6cdb5bc6](https://github.com/pixelfed/pixelfed/commit/6cdb5bc6))
|
||||||
|
- Update InstanceService, fix banner blurhash memory bug ([3aad75ab](https://github.com/pixelfed/pixelfed/commit/3aad75ab))
|
||||||
|
- Update models, remove deprecated toText and toHtml method ([ea943333](https://github.com/pixelfed/pixelfed/commit/ea943333))
|
||||||
|
- Update Notification components, add autospam notification support ([0d3b4bc2](https://github.com/pixelfed/pixelfed/commit/0d3b4bc2))
|
||||||
|
- Update AutoSpam Bouncer, generate notification on positive detections ([d5f63f8a](https://github.com/pixelfed/pixelfed/commit/d5f63f8a))
|
||||||
|
- Update admin autospam apis, remove autospam warning notifications when appropriate ([588ca653](https://github.com/pixelfed/pixelfed/commit/588ca653))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.6 (2023-05-03)](https://github.com/pixelfed/pixelfed/compare/v0.11.5...v0.11.6)
|
## [v0.11.6 (2023-05-03)](https://github.com/pixelfed/pixelfed/compare/v0.11.5...v0.11.6)
|
||||||
|
|
|
@ -31,20 +31,4 @@ class DirectMessage extends Model
|
||||||
{
|
{
|
||||||
return Auth::user()->profile->id === $this->from_id;
|
return Auth::user()->profile->id === $this->from_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toText()
|
|
||||||
{
|
|
||||||
$actorName = $this->author->username;
|
|
||||||
|
|
||||||
return "{$actorName} sent a direct message.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toHtml()
|
|
||||||
{
|
|
||||||
$actorName = $this->author->username;
|
|
||||||
$actorUrl = $this->author->url();
|
|
||||||
$url = $this->url();
|
|
||||||
|
|
||||||
return "{$actorName} sent a direct message.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,20 +32,4 @@ class Follower extends Model
|
||||||
$path = $this->actor->permalink("#accepts/follows/{$this->id}{$append}");
|
$path = $this->actor->permalink("#accepts/follows/{$this->id}{$append}");
|
||||||
return url($path);
|
return url($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toText()
|
|
||||||
{
|
|
||||||
$actorName = $this->actor->username;
|
|
||||||
|
|
||||||
return "{$actorName} ".__('notification.startedFollowingYou');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toHtml()
|
|
||||||
{
|
|
||||||
$actorName = $this->actor->username;
|
|
||||||
$actorUrl = $this->actor->url();
|
|
||||||
|
|
||||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
|
|
||||||
__('notification.startedFollowingYou');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use App\{
|
||||||
Contact,
|
Contact,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
Newsroom,
|
Newsroom,
|
||||||
|
Notification,
|
||||||
OauthClient,
|
OauthClient,
|
||||||
Profile,
|
Profile,
|
||||||
Report,
|
Report,
|
||||||
|
@ -30,6 +31,7 @@ use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
use App\Http\Resources\AdminReport;
|
use App\Http\Resources\AdminReport;
|
||||||
use App\Http\Resources\AdminSpamReport;
|
use App\Http\Resources\AdminSpamReport;
|
||||||
|
use App\Services\NotificationService;
|
||||||
use App\Services\PublicTimelineService;
|
use App\Services\PublicTimelineService;
|
||||||
use App\Services\NetworkTimelineService;
|
use App\Services\NetworkTimelineService;
|
||||||
|
|
||||||
|
@ -1126,6 +1128,14 @@ trait AdminReportController
|
||||||
$appeal->appeal_handled_at = now();
|
$appeal->appeal_handled_at = now();
|
||||||
$appeal->save();
|
$appeal->save();
|
||||||
|
|
||||||
|
Notification::whereAction('autospam.warning')
|
||||||
|
->whereProfileId($appeal->user->profile_id)
|
||||||
|
->get()
|
||||||
|
->each(function($n) use($appeal) {
|
||||||
|
NotificationService::del($appeal->user->profile_id, $n->id);
|
||||||
|
$n->forceDelete();
|
||||||
|
});
|
||||||
|
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1157,6 +1167,13 @@ trait AdminReportController
|
||||||
$status->save();
|
$status->save();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
}
|
}
|
||||||
|
Notification::whereAction('autospam.warning')
|
||||||
|
->whereProfileId($report->user->profile_id)
|
||||||
|
->get()
|
||||||
|
->each(function($n) use($report) {
|
||||||
|
NotificationService::del($report->user->profile_id, $n->id);
|
||||||
|
$n->forceDelete();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\{
|
||||||
AccountInterstitial,
|
AccountInterstitial,
|
||||||
Instance,
|
Instance,
|
||||||
Like,
|
Like,
|
||||||
|
Notification,
|
||||||
Media,
|
Media,
|
||||||
Profile,
|
Profile,
|
||||||
Report,
|
Report,
|
||||||
|
@ -140,6 +141,14 @@ class AdminApiController extends Controller
|
||||||
|
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
|
|
||||||
|
Notification::whereAction('autospam.warning')
|
||||||
|
->whereProfileId($appeal->user->profile_id)
|
||||||
|
->get()
|
||||||
|
->each(function($n) use($appeal) {
|
||||||
|
NotificationService::del($appeal->user->profile_id, $n->id);
|
||||||
|
$n->forceDelete();
|
||||||
|
});
|
||||||
|
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
|
||||||
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
@ -164,6 +173,14 @@ class AdminApiController extends Controller
|
||||||
$status->save();
|
$status->save();
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Notification::whereAction('autospam.warning')
|
||||||
|
->whereProfileId($report->user->profile_id)
|
||||||
|
->get()
|
||||||
|
->each(function($n) use($report) {
|
||||||
|
NotificationService::del($report->user->profile_id, $n->id);
|
||||||
|
$n->forceDelete();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
|
||||||
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
|
||||||
|
|
|
@ -368,8 +368,6 @@ class DirectMessageController extends Controller
|
||||||
$notification->profile_id = $recipient->id;
|
$notification->profile_id = $recipient->id;
|
||||||
$notification->actor_id = $profile->id;
|
$notification->actor_id = $profile->id;
|
||||||
$notification->action = 'dm';
|
$notification->action = 'dm';
|
||||||
$notification->message = $dm->toText();
|
|
||||||
$notification->rendered = $dm->toHtml();
|
|
||||||
$notification->item_id = $dm->id;
|
$notification->item_id = $dm->id;
|
||||||
$notification->item_type = "App\DirectMessage";
|
$notification->item_type = "App\DirectMessage";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
|
@ -328,8 +328,6 @@ class StoryApiV1Controller extends Controller
|
||||||
$n->item_id = $dm->id;
|
$n->item_id = $dm->id;
|
||||||
$n->item_type = 'App\DirectMessage';
|
$n->item_type = 'App\DirectMessage';
|
||||||
$n->action = 'story:comment';
|
$n->action = 'story:comment';
|
||||||
$n->message = "{$request->user()->username} commented on story";
|
|
||||||
$n->rendered = "{$request->user()->username} commented on story";
|
|
||||||
$n->save();
|
$n->save();
|
||||||
} else {
|
} else {
|
||||||
StoryReplyDeliver::dispatch($story, $status)->onQueue('story');
|
StoryReplyDeliver::dispatch($story, $status)->onQueue('story');
|
||||||
|
|
|
@ -442,8 +442,6 @@ class StoryComposeController extends Controller
|
||||||
$n->item_id = $dm->id;
|
$n->item_id = $dm->id;
|
||||||
$n->item_type = 'App\DirectMessage';
|
$n->item_type = 'App\DirectMessage';
|
||||||
$n->action = 'story:react';
|
$n->action = 'story:react';
|
||||||
$n->message = "{$request->user()->username} reacted to your story";
|
|
||||||
$n->rendered = "{$request->user()->username} reacted to your story";
|
|
||||||
$n->save();
|
$n->save();
|
||||||
} else {
|
} else {
|
||||||
StoryReactionDeliver::dispatch($story, $status)->onQueue('story');
|
StoryReactionDeliver::dispatch($story, $status)->onQueue('story');
|
||||||
|
@ -516,8 +514,6 @@ class StoryComposeController extends Controller
|
||||||
$n->item_id = $dm->id;
|
$n->item_id = $dm->id;
|
||||||
$n->item_type = 'App\DirectMessage';
|
$n->item_type = 'App\DirectMessage';
|
||||||
$n->action = 'story:comment';
|
$n->action = 'story:comment';
|
||||||
$n->message = "{$request->user()->username} commented on story";
|
|
||||||
$n->rendered = "{$request->user()->username} commented on story";
|
|
||||||
$n->save();
|
$n->save();
|
||||||
} else {
|
} else {
|
||||||
StoryReplyDeliver::dispatch($story, $status)->onQueue('story');
|
StoryReplyDeliver::dispatch($story, $status)->onQueue('story');
|
||||||
|
|
|
@ -94,8 +94,6 @@ class CommentPipeline implements ShouldQueue
|
||||||
$notification->profile_id = $target->id;
|
$notification->profile_id = $target->id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'comment';
|
$notification->action = 'comment';
|
||||||
$notification->message = $comment->replyToText();
|
|
||||||
$notification->rendered = $comment->replyToHtml();
|
|
||||||
$notification->item_id = $comment->id;
|
$notification->item_id = $comment->id;
|
||||||
$notification->item_type = "App\Status";
|
$notification->item_type = "App\Status";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
|
@ -97,8 +97,6 @@ class FollowPipeline implements ShouldQueue
|
||||||
$notification->profile_id = $target->id;
|
$notification->profile_id = $target->id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'follow';
|
$notification->action = 'follow';
|
||||||
$notification->message = $follower->toText();
|
|
||||||
$notification->rendered = $follower->toHtml();
|
|
||||||
$notification->item_id = $target->id;
|
$notification->item_id = $target->id;
|
||||||
$notification->item_type = "App\Profile";
|
$notification->item_type = "App\Profile";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
|
@ -84,8 +84,6 @@ class LikePipeline implements ShouldQueue
|
||||||
$notification->profile_id = $status->profile_id;
|
$notification->profile_id = $status->profile_id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'like';
|
$notification->action = 'like';
|
||||||
$notification->message = $like->toText($status->in_reply_to_id ? 'comment' : 'post');
|
|
||||||
$notification->rendered = $like->toHtml($status->in_reply_to_id ? 'comment' : 'post');
|
|
||||||
$notification->item_id = $status->id;
|
$notification->item_id = $status->id;
|
||||||
$notification->item_type = "App\Status";
|
$notification->item_type = "App\Status";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
|
@ -67,10 +67,6 @@ class MentionPipeline implements ShouldQueue
|
||||||
'action' => 'mention',
|
'action' => 'mention',
|
||||||
'item_type' => 'App\Status',
|
'item_type' => 'App\Status',
|
||||||
'item_id' => $status->id,
|
'item_id' => $status->id,
|
||||||
],
|
|
||||||
[
|
|
||||||
'message' => $mention->toText(),
|
|
||||||
'rendered' => $mention->toHtml()
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -76,10 +76,6 @@ class SharePipeline implements ShouldQueue
|
||||||
'action' => 'share',
|
'action' => 'share',
|
||||||
'item_type' => 'App\Status',
|
'item_type' => 'App\Status',
|
||||||
'item_id' => $status->reblog_of_id ?? $status->id,
|
'item_id' => $status->reblog_of_id ?? $status->id,
|
||||||
],
|
|
||||||
[
|
|
||||||
'message' => $status->shareToText(),
|
|
||||||
'rendered' => $status->shareToHtml()
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -90,8 +90,6 @@ class StatusReplyPipeline implements ShouldQueue
|
||||||
$notification->profile_id = $target->id;
|
$notification->profile_id = $target->id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'comment';
|
$notification->action = 'comment';
|
||||||
$notification->message = $status->replyToText();
|
|
||||||
$notification->rendered = $status->replyToHtml();
|
|
||||||
$notification->item_id = $status->id;
|
$notification->item_id = $status->id;
|
||||||
$notification->item_type = "App\Status";
|
$notification->item_type = "App\Status";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
|
17
app/Like.php
17
app/Like.php
|
@ -31,21 +31,4 @@ class Like extends Model
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Status::class);
|
return $this->belongsTo(Status::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toText($type = 'post')
|
|
||||||
{
|
|
||||||
$actorName = $this->actor->username;
|
|
||||||
$msg = $type == 'post' ? __('notification.likedPhoto') : __('notification.likedComment');
|
|
||||||
|
|
||||||
return "{$actorName} ".$msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toHtml($type = 'post')
|
|
||||||
{
|
|
||||||
$actorName = $this->actor->username;
|
|
||||||
$actorUrl = $this->actor->url();
|
|
||||||
$msg = $type == 'post' ? __('notification.likedPhoto') : __('notification.likedComment');
|
|
||||||
|
|
||||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".$msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,20 +29,4 @@ class Mention extends Model
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Status::class, 'status_id', 'id');
|
return $this->belongsTo(Status::class, 'status_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toText()
|
|
||||||
{
|
|
||||||
$actorName = $this->status->profile->username;
|
|
||||||
|
|
||||||
return "{$actorName} ".__('notification.mentionedYou');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toHtml()
|
|
||||||
{
|
|
||||||
$actorName = $this->status->profile->username;
|
|
||||||
$actorUrl = $this->status->profile->url();
|
|
||||||
|
|
||||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
|
|
||||||
__('notification.mentionedYou');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ class ConfigCacheService
|
||||||
'instance.landing.show_directory',
|
'instance.landing.show_directory',
|
||||||
'instance.landing.show_explore',
|
'instance.landing.show_explore',
|
||||||
'instance.admin.pid',
|
'instance.admin.pid',
|
||||||
|
'instance.banner.blurhash'
|
||||||
// 'system.user_mode'
|
// 'system.user_mode'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Services;
|
||||||
use Cache;
|
use Cache;
|
||||||
use App\Instance;
|
use App\Instance;
|
||||||
use App\Util\Blurhash\Blurhash;
|
use App\Util\Blurhash\Blurhash;
|
||||||
|
use App\Services\ConfigCacheService;
|
||||||
|
|
||||||
class InstanceService
|
class InstanceService
|
||||||
{
|
{
|
||||||
|
@ -13,7 +14,12 @@ class InstanceService
|
||||||
const CACHE_KEY_UNLISTED_DOMAINS = 'instances:unlisted:domains';
|
const CACHE_KEY_UNLISTED_DOMAINS = 'instances:unlisted:domains';
|
||||||
const CACHE_KEY_NSFW_DOMAINS = 'instances:auto_cw:domains';
|
const CACHE_KEY_NSFW_DOMAINS = 'instances:auto_cw:domains';
|
||||||
const CACHE_KEY_STATS = 'pf:services:instances:stats';
|
const CACHE_KEY_STATS = 'pf:services:instances:stats';
|
||||||
const CACHE_KEY_BANNER_BLURHASH = 'pf:services:instance:header-blurhash';
|
const CACHE_KEY_BANNER_BLURHASH = 'pf:services:instance:header-blurhash:v1';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
ini_set('memory_limit', config('pixelfed.memory_limit', '1024M'));
|
||||||
|
}
|
||||||
|
|
||||||
public static function getByDomain($domain)
|
public static function getByDomain($domain)
|
||||||
{
|
{
|
||||||
|
@ -87,6 +93,12 @@ class InstanceService
|
||||||
if(str_ends_with(config_cache('app.banner_image'), 'headers/default.jpg')) {
|
if(str_ends_with(config_cache('app.banner_image'), 'headers/default.jpg')) {
|
||||||
return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt';
|
return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt';
|
||||||
}
|
}
|
||||||
|
$cached = config_cache('instance.banner.blurhash');
|
||||||
|
|
||||||
|
if($cached) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
$file = config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg'));
|
$file = config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg'));
|
||||||
|
|
||||||
$image = imagecreatefromstring(file_get_contents($file));
|
$image = imagecreatefromstring(file_get_contents($file));
|
||||||
|
@ -115,6 +127,8 @@ class InstanceService
|
||||||
return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt';
|
return 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConfigCacheService::put('instance.banner.blurhash', $blurhash);
|
||||||
|
|
||||||
return $blurhash;
|
return $blurhash;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,16 +74,13 @@ class MediaTagService
|
||||||
{
|
{
|
||||||
$p = $tag->status->profile;
|
$p = $tag->status->profile;
|
||||||
$actor = $p->username;
|
$actor = $p->username;
|
||||||
$message = "{$actor} tagged you in a post.";
|
|
||||||
$rendered = "<a href='/{$actor}' class='profile-link'>{$actor}</a> tagged you in a post.";
|
|
||||||
$n = new Notification;
|
$n = new Notification;
|
||||||
$n->profile_id = $tag->profile_id;
|
$n->profile_id = $tag->profile_id;
|
||||||
$n->actor_id = $p->id;
|
$n->actor_id = $p->id;
|
||||||
$n->item_id = $tag->id;
|
$n->item_id = $tag->id;
|
||||||
$n->item_type = 'App\MediaTag';
|
$n->item_type = 'App\MediaTag';
|
||||||
$n->action = 'tagged';
|
$n->action = 'tagged';
|
||||||
$n->message = $message;
|
|
||||||
$n->rendered = $rendered;
|
|
||||||
$n->save();
|
$n->save();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,8 +108,6 @@ class ModLogService {
|
||||||
{
|
{
|
||||||
$log = $this->log;
|
$log = $this->log;
|
||||||
|
|
||||||
$msg = "{$log->user_username} commented on a modlog";
|
|
||||||
$rendered = "<span class='font-weight-bold'>{$log->user_username}</span> commented on a <a href='/i/admin/users/modlogs/{$log->user_id}}' class='font-weight-bold text-decoration-none'>modlog</a>";
|
|
||||||
$item_id = $log->id;
|
$item_id = $log->id;
|
||||||
$item_type = 'App\ModLog';
|
$item_type = 'App\ModLog';
|
||||||
$action = 'admin.user.modlog.comment';
|
$action = 'admin.user.modlog.comment';
|
||||||
|
@ -127,8 +125,6 @@ class ModLogService {
|
||||||
$n->item_id = $item_id;
|
$n->item_id = $item_id;
|
||||||
$n->item_type = $item_type;
|
$n->item_type = $item_type;
|
||||||
$n->action = $action;
|
$n->action = $action;
|
||||||
$n->message = $msg;
|
|
||||||
$n->rendered = $rendered;
|
|
||||||
$n->save();
|
$n->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,38 +285,6 @@ class Status extends Model
|
||||||
return $obj;
|
return $obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function replyToText()
|
|
||||||
{
|
|
||||||
$actorName = $this->profile->username;
|
|
||||||
|
|
||||||
return "{$actorName} ".__('notification.commented');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function replyToHtml()
|
|
||||||
{
|
|
||||||
$actorName = $this->profile->username;
|
|
||||||
$actorUrl = $this->profile->url();
|
|
||||||
|
|
||||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
|
|
||||||
__('notification.commented');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function shareToText()
|
|
||||||
{
|
|
||||||
$actorName = $this->profile->username;
|
|
||||||
|
|
||||||
return "{$actorName} ".__('notification.shared');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function shareToHtml()
|
|
||||||
{
|
|
||||||
$actorName = $this->profile->username;
|
|
||||||
$actorUrl = $this->profile->url();
|
|
||||||
|
|
||||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
|
|
||||||
__('notification.shared');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function recentComments()
|
public function recentComments()
|
||||||
{
|
{
|
||||||
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
||||||
|
|
|
@ -23,7 +23,9 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
||||||
|
|
||||||
if($n->actor_id) {
|
if($n->actor_id) {
|
||||||
$res['account'] = AccountService::get($n->actor_id);
|
$res['account'] = AccountService::get($n->actor_id);
|
||||||
$res['relationship'] = RelationshipService::get($n->actor_id, $n->profile_id);
|
if($n->profile_id != $n->actor_id) {
|
||||||
|
$res['relationship'] = RelationshipService::get($n->actor_id, $n->profile_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($n->item_id && $n->item_type == 'App\Status') {
|
if($n->item_id && $n->item_type == 'App\Status') {
|
||||||
|
@ -66,11 +68,8 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
||||||
'comment' => 'comment',
|
'comment' => 'comment',
|
||||||
'admin.user.modlog.comment' => 'modlog',
|
'admin.user.modlog.comment' => 'modlog',
|
||||||
'tagged' => 'tagged',
|
'tagged' => 'tagged',
|
||||||
'group:comment' => 'group:comment',
|
|
||||||
'story:react' => 'story:react',
|
'story:react' => 'story:react',
|
||||||
'story:comment' => 'story:comment',
|
'story:comment' => 'story:comment',
|
||||||
'group:join:approved' => 'group:join:approved',
|
|
||||||
'group:join:rejected' => 'group:join:rejected'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if(!isset($verbs[$verb])) {
|
if(!isset($verbs[$verb])) {
|
||||||
|
|
|
@ -483,8 +483,6 @@ class Inbox
|
||||||
$notification->profile_id = $profile->id;
|
$notification->profile_id = $profile->id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'dm';
|
$notification->action = 'dm';
|
||||||
$notification->message = $dm->toText();
|
|
||||||
$notification->rendered = $dm->toHtml();
|
|
||||||
$notification->item_id = $dm->id;
|
$notification->item_id = $dm->id;
|
||||||
$notification->item_type = "App\DirectMessage";
|
$notification->item_type = "App\DirectMessage";
|
||||||
$notification->save();
|
$notification->save();
|
||||||
|
@ -594,9 +592,6 @@ class Inbox
|
||||||
'action' => 'share',
|
'action' => 'share',
|
||||||
'item_id' => $parent->id,
|
'item_id' => $parent->id,
|
||||||
'item_type' => 'App\Status',
|
'item_type' => 'App\Status',
|
||||||
], [
|
|
||||||
'message' => $status->replyToText(),
|
|
||||||
'rendered' => $status->replyToHtml(),
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1023,8 +1018,6 @@ class Inbox
|
||||||
$n->item_id = $dm->id;
|
$n->item_id = $dm->id;
|
||||||
$n->item_type = 'App\DirectMessage';
|
$n->item_type = 'App\DirectMessage';
|
||||||
$n->action = 'story:react';
|
$n->action = 'story:react';
|
||||||
$n->message = "{$actorProfile->username} reacted to your story";
|
|
||||||
$n->rendered = "{$actorProfile->username} reacted to your story";
|
|
||||||
$n->save();
|
$n->save();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1134,8 +1127,6 @@ class Inbox
|
||||||
$n->item_id = $dm->id;
|
$n->item_id = $dm->id;
|
||||||
$n->item_type = 'App\DirectMessage';
|
$n->item_type = 'App\DirectMessage';
|
||||||
$n->action = 'story:comment';
|
$n->action = 'story:comment';
|
||||||
$n->message = "{$actorProfile->username} commented on story";
|
|
||||||
$n->rendered = "{$actorProfile->username} commented on story";
|
|
||||||
$n->save();
|
$n->save();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -6,8 +6,10 @@ use App\AccountInterstitial;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use App\Services\NotificationService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
use App\Jobs\ReportPipeline\AutospamNotifyAdminViaEmail;
|
use App\Jobs\ReportPipeline\AutospamNotifyAdminViaEmail;
|
||||||
|
use App\Notification;
|
||||||
|
|
||||||
class Bouncer {
|
class Bouncer {
|
||||||
|
|
||||||
|
@ -140,6 +142,15 @@ class Bouncer {
|
||||||
// $status->is_nsfw = true;
|
// $status->is_nsfw = true;
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
$notification = new Notification();
|
||||||
|
$notification->profile_id = $status->profile_id;
|
||||||
|
$notification->actor_id = $status->profile_id;
|
||||||
|
$notification->action = 'autospam.warning';
|
||||||
|
$notification->item_id = $status->id;
|
||||||
|
$notification->item_type = "App\Status";
|
||||||
|
$notification->save();
|
||||||
|
NotificationService::add($notification->profile_id, $notification->id);
|
||||||
|
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
|
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $status->profile_id);
|
||||||
|
|
|
@ -124,5 +124,9 @@ return [
|
||||||
'landing' => [
|
'landing' => [
|
||||||
'show_directory' => env('INSTANCE_LANDING_SHOW_DIRECTORY', true),
|
'show_directory' => env('INSTANCE_LANDING_SHOW_DIRECTORY', true),
|
||||||
'show_explore' => env('INSTANCE_LANDING_SHOW_EXPLORE', true),
|
'show_explore' => env('INSTANCE_LANDING_SHOW_EXPLORE', true),
|
||||||
|
],
|
||||||
|
|
||||||
|
'banner' => [
|
||||||
|
'blurhash' => env('INSTANCE_BANNER_BLURHASH', 'UzJR]l{wHZRjM}R%XRkCH?X9xaWEjZj]kAjt')
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
BIN
public/js/discover.chunk.4f1b3ea93df06670.js
vendored
BIN
public/js/discover.chunk.4f1b3ea93df06670.js
vendored
Binary file not shown.
BIN
public/js/discover.chunk.5ceb85dcb38dfbef.js
vendored
Normal file
BIN
public/js/discover.chunk.5ceb85dcb38dfbef.js
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/js/discover~hashtag.bundle.b8319d6999d3e2e3.js
vendored
Normal file
BIN
public/js/discover~hashtag.bundle.b8319d6999d3e2e3.js
vendored
Normal file
Binary file not shown.
BIN
public/js/home.chunk.25bd77760873ee83.js
vendored
BIN
public/js/home.chunk.25bd77760873ee83.js
vendored
Binary file not shown.
BIN
public/js/home.chunk.af8ef7b54f61b18d.js
vendored
Normal file
BIN
public/js/home.chunk.af8ef7b54f61b18d.js
vendored
Normal file
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.1a834e4a7bdbf21a.js
vendored
BIN
public/js/notifications.chunk.1a834e4a7bdbf21a.js
vendored
Binary file not shown.
BIN
public/js/notifications.chunk.9de71a122956c663.js
vendored
Normal file
BIN
public/js/notifications.chunk.9de71a122956c663.js
vendored
Normal file
Binary file not shown.
BIN
public/js/post.chunk.62a9d21c9016fd95.js
vendored
Normal file
BIN
public/js/post.chunk.62a9d21c9016fd95.js
vendored
Normal file
Binary file not shown.
BIN
public/js/post.chunk.881f8b0a9934e053.js
vendored
BIN
public/js/post.chunk.881f8b0a9934e053.js
vendored
Binary file not shown.
Binary file not shown.
585
resources/assets/components/Notifications.vue
Normal file
585
resources/assets/components/Notifications.vue
Normal file
|
@ -0,0 +1,585 @@
|
||||||
|
<template>
|
||||||
|
<div class="web-wrapper notification-metro-component">
|
||||||
|
<div v-if="isLoaded" class="container-fluid mt-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 d-md-block">
|
||||||
|
<sidebar :user="profile" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9 col-lg-9 col-xl-5 offset-xl-1">
|
||||||
|
<template v-if="tabIndex === 0">
|
||||||
|
<h1 class="font-weight-bold">
|
||||||
|
Notifications
|
||||||
|
</h1>
|
||||||
|
<p class="small mt-n2"> </p>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="tabIndex === 10">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<a class="text-muted" href="#" @click.prevent="tabIndex = 0" style="opacity:0.3">
|
||||||
|
<i class="far fa-chevron-circle-left fa-2x mr-3" title="Go back to notifications"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="font-weight-bold">
|
||||||
|
Follow Requests
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<h1 class="font-weight-bold">
|
||||||
|
{{ tabs[tabIndex].name }}
|
||||||
|
</h1>
|
||||||
|
<p class="small text-lighter mt-n2">{{ tabs[tabIndex].description }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="!notificationsLoaded">
|
||||||
|
<placeholder />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<ul v-if="tabIndex != 10 && notificationsLoaded && notifications && notifications.length" class="notification-filters nav nav-tabs nav-fill mb-3">
|
||||||
|
<li v-for="(item, idx) in tabs" class="nav-item">
|
||||||
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
:class="{ active: tabIndex === idx }"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="toggleTab(idx)">
|
||||||
|
<i
|
||||||
|
class="mr-1 nav-link-icon"
|
||||||
|
:class="[ item.icon ]"
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
<span class="d-none d-xl-inline-block">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div v-if="notificationsEmpty && followRequestsChecked && !followRequests.accounts.length && notificationRetries < 2">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-10 text-center">
|
||||||
|
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
|
||||||
|
<p class="lead text-muted font-weight-bold">{{ $t('notifications.noneFound') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!notificationsLoaded || tabSwitching || ((notificationsEmpty && notificationRetries < 2 ) || !notifications && !followRequests && !followRequests.accounts && !followRequests.accounts.length)">
|
||||||
|
<placeholder />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="tabIndex === 0">
|
||||||
|
<div
|
||||||
|
v-if="followRequests && followRequests.hasOwnProperty('accounts') && followRequests.accounts.length"
|
||||||
|
class="card card-body shadow-none border border-warning rounded-pill mb-3 py-2">
|
||||||
|
<div class="media align-items-center">
|
||||||
|
<i class="far fa-exclamation-circle mr-3 text-warning"></i>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0">
|
||||||
|
<strong>{{ followRequests.count }} follow {{ followRequests.count > 1 ? 'requests' : 'request' }}</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
class="ml-2 small d-flex font-weight-bold primary text-uppercase mb-0"
|
||||||
|
href="#"
|
||||||
|
@click.prevent="showFollowRequests()">
|
||||||
|
View<span class="d-none d-md-block"> Follow Requests</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="notificationsLoaded">
|
||||||
|
<notification
|
||||||
|
v-for="(n, index) in notifications"
|
||||||
|
:key="`notification:${index}:${n.id}`"
|
||||||
|
:n="n" />
|
||||||
|
|
||||||
|
<div v-if="notifications && notificationsLoaded && !notifications.length && notificationRetries <= 2">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-10 text-center">
|
||||||
|
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
|
||||||
|
<p class="lead text-muted font-weight-bold">{{ $t('notifications.noneFound') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="canLoadMore">
|
||||||
|
<intersect @enter="enterIntersect">
|
||||||
|
<placeholder />
|
||||||
|
</intersect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tabIndex === 10">
|
||||||
|
<div v-if="followRequests && followRequests.accounts && followRequests.accounts.length" class="list-group">
|
||||||
|
<div v-for="(acct, index) in followRequests.accounts" class="list-group-item">
|
||||||
|
<div class="media align-items-center">
|
||||||
|
<router-link :to="`/i/web/profile/${acct.account.id}`" class="primary">
|
||||||
|
<img :src="acct.avatar" width="80" height="80" class="rounded-lg shadow mr-3" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
|
||||||
|
</router-link>
|
||||||
|
<div class="media-body mr-3">
|
||||||
|
<p class="font-weight-bold mb-0 text-break" style="font-size:17px">
|
||||||
|
<router-link :to="`/i/web/profile/${acct.account.id}`" class="primary">
|
||||||
|
{{ acct.username }}
|
||||||
|
</router-link>
|
||||||
|
</p>
|
||||||
|
<p class="mb-1 text-muted text-break" style="font-size:11px">{{ truncate(acct.account.note_text, 100) }}</p>
|
||||||
|
<div class="d-flex text-lighter" style="font-size:11px">
|
||||||
|
<span class="mr-3">
|
||||||
|
<span class="font-weight-bold">{{ acct.account.statuses_count }}</span>
|
||||||
|
<span>Posts</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="font-weight-bold">{{ acct.account.followers_count }}</span>
|
||||||
|
<span>Followers</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column d-md-block">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-success py-1 btn-sm font-weight-bold rounded-pill mr-2 mb-1"
|
||||||
|
@click.prevent="handleFollowRequest('accept', index)"
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-outline-lighter py-1 btn-sm font-weight-bold rounded-pill mb-1"
|
||||||
|
@click.prevent="handleFollowRequest('reject', index)"
|
||||||
|
>
|
||||||
|
Reject
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="filteredLoaded">
|
||||||
|
<div class="card card-body bg-transparent shadow-none border p-2 mb-3 rounded-pill text-lighter">
|
||||||
|
<div class="media align-items-center small">
|
||||||
|
<i class="far fa-exclamation-triangle mx-2"></i>
|
||||||
|
<div class="media-body">
|
||||||
|
<p class="mb-0 font-weight-bold">Filtering results may not include older notifications</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filteredFeed.length">
|
||||||
|
<notification
|
||||||
|
v-for="(n, index) in filteredFeed"
|
||||||
|
:key="`notification:filtered:${index}:${n.id}`"
|
||||||
|
:n="n" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="filteredEmpty && notificationRetries <= 2">
|
||||||
|
<div class="card card-body shadow-sm border-0 d-flex flex-row align-items-center" style="border-radius: 20px;gap:1rem;">
|
||||||
|
<i class="far fa-inbox fa-2x text-muted"></i>
|
||||||
|
<div class="font-weight-bold">No recent {{ tabs[tabIndex].name }}!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<placeholder v-else />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="canLoadMoreFiltered">
|
||||||
|
<intersect @enter="enterFilteredIntersect">
|
||||||
|
<placeholder />
|
||||||
|
</intersect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<placeholder />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<drawer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
import Drawer from './partials/drawer.vue';
|
||||||
|
import Sidebar from './partials/sidebar.vue';
|
||||||
|
import Notification from './partials/timeline/Notification.vue';
|
||||||
|
import Placeholder from './partials/placeholders/NotificationPlaceholder.vue';
|
||||||
|
import Intersect from 'vue-intersect';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
"drawer": Drawer,
|
||||||
|
"sidebar": Sidebar,
|
||||||
|
"intersect": Intersect,
|
||||||
|
"notification": Notification,
|
||||||
|
"placeholder": Placeholder,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoaded: false,
|
||||||
|
profile: undefined,
|
||||||
|
ids: [],
|
||||||
|
notifications: undefined,
|
||||||
|
notificationsLoaded: false,
|
||||||
|
notificationRetries: 0,
|
||||||
|
notificationsEmpty: true,
|
||||||
|
notificationRetryTimeout: undefined,
|
||||||
|
max_id: undefined,
|
||||||
|
canLoadMore: false,
|
||||||
|
isIntersecting: false,
|
||||||
|
tabIndex: 0,
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: 'all',
|
||||||
|
name: 'All',
|
||||||
|
icon: 'far fa-bell',
|
||||||
|
types: []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'mentions',
|
||||||
|
name: 'Mentions',
|
||||||
|
description: 'Replies to your posts and posts you were mentioned in',
|
||||||
|
icon: 'far fa-at',
|
||||||
|
types: ['comment', 'mention']
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'likes',
|
||||||
|
name: 'Likes',
|
||||||
|
description: 'Accounts that liked your posts',
|
||||||
|
icon: 'far fa-heart',
|
||||||
|
types: ['favourite']
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'followers',
|
||||||
|
name: 'Followers',
|
||||||
|
description: 'Accounts that followed you',
|
||||||
|
icon: 'far fa-user-plus',
|
||||||
|
types: ['follow']
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'reblogs',
|
||||||
|
name: 'Reblogs',
|
||||||
|
description: 'Accounts that shared or reblogged your posts',
|
||||||
|
icon: 'far fa-retweet',
|
||||||
|
types: ['share']
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'direct',
|
||||||
|
name: 'DMs',
|
||||||
|
description: 'Direct messages you have with other accounts',
|
||||||
|
icon: 'far fa-envelope',
|
||||||
|
types: ['direct']
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tabSwitching: false,
|
||||||
|
filteredFeed: [],
|
||||||
|
filteredLoaded: false,
|
||||||
|
filteredIsIntersecting: false,
|
||||||
|
filteredMaxId: undefined,
|
||||||
|
canLoadMoreFiltered: true,
|
||||||
|
filterPaginationTimeout: undefined,
|
||||||
|
filteredIterations: 0,
|
||||||
|
filteredEmpty: false,
|
||||||
|
followRequests: [],
|
||||||
|
followRequestsChecked: false,
|
||||||
|
followRequestsPage: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updated() {
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.profile = window._sharedData.user;
|
||||||
|
this.isLoaded = true;
|
||||||
|
if(this.profile.locked) {
|
||||||
|
this.fetchFollowRequests();
|
||||||
|
}
|
||||||
|
this.fetchNotifications();
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
clearTimeout(this.notificationRetryTimeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fetchNotifications() {
|
||||||
|
this.notificationRetries++;
|
||||||
|
axios.get('/api/pixelfed/v1/notifications?pg=true')
|
||||||
|
.then(res => {
|
||||||
|
if(!res || !res.data || !res.data.length) {
|
||||||
|
if(this.notificationRetries == 2) {
|
||||||
|
clearTimeout(this.notificationRetryTimeout);
|
||||||
|
this.canLoadMore = false;
|
||||||
|
this.notificationsLoaded = true;
|
||||||
|
this.notificationsEmpty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.notificationRetryTimeout = setTimeout(() => {
|
||||||
|
this.fetchNotifications();
|
||||||
|
}, 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = res.data.filter(n => {
|
||||||
|
if(n.type == 'share' && !n.status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(n.type == 'comment' && !n.status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(n.type == 'mention' && !n.status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(n.type == 'favourite' && !n.status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(n.type == 'follow' && !n.account) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
let ids = res.data.map(n => n.id);
|
||||||
|
this.max_id = Math.min(...ids);
|
||||||
|
this.ids.push(...ids);
|
||||||
|
this.notifications = data;
|
||||||
|
this.notificationsLoaded = true;
|
||||||
|
this.notificationsEmpty = false;
|
||||||
|
this.canLoadMore = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
enterIntersect() {
|
||||||
|
if(this.isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isFinite(this.max_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isIntersecting = true;
|
||||||
|
|
||||||
|
axios.get('/api/pixelfed/v1/notifications', {
|
||||||
|
params: {
|
||||||
|
max_id: this.max_id
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
if(!res.data.length) {
|
||||||
|
this.canLoadMore = false;
|
||||||
|
}
|
||||||
|
let ids = res.data.map(n => n.id);
|
||||||
|
this.max_id = Math.min(...ids);
|
||||||
|
this.notifications.push(...res.data);
|
||||||
|
this.isIntersecting = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleTab(idx) {
|
||||||
|
this.tabSwitching = true;
|
||||||
|
this.canLoadMoreFiltered = true;
|
||||||
|
this.filteredEmpty = false;
|
||||||
|
this.filteredIterations = 0;
|
||||||
|
this.filterFeed(this.tabs[idx].id);
|
||||||
|
},
|
||||||
|
|
||||||
|
filterFeed(type) {
|
||||||
|
switch(type) {
|
||||||
|
case 'all':
|
||||||
|
this.tabIndex = 0;
|
||||||
|
this.filteredFeed = [];
|
||||||
|
this.filteredLoaded = false;
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
this.filteredMaxId = undefined;
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
this.tabSwitching = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mentions':
|
||||||
|
this.tabIndex = 1;
|
||||||
|
this.filteredMaxId = this.max_id;
|
||||||
|
this.filteredFeed = this.notifications.filter(n => this.tabs[this.tabIndex].types.includes(n.type));
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
this.tabSwitching = false;
|
||||||
|
this.filteredLoaded = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'likes':
|
||||||
|
this.tabIndex = 2;
|
||||||
|
this.filteredMaxId = this.max_id;
|
||||||
|
this.filteredFeed = this.notifications.filter(n => n.type === 'favourite');
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
this.tabSwitching = false;
|
||||||
|
this.filteredLoaded = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'followers':
|
||||||
|
this.tabIndex = 3;
|
||||||
|
this.filteredMaxId = this.max_id;
|
||||||
|
this.filteredFeed = this.notifications.filter(n => n.type === 'follow');
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
this.tabSwitching = false;
|
||||||
|
this.filteredLoaded = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reblogs':
|
||||||
|
this.tabIndex = 4;
|
||||||
|
this.filteredMaxId = this.max_id;
|
||||||
|
this.filteredFeed = this.notifications.filter(n => n.type === 'share');
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
this.tabSwitching = false;
|
||||||
|
this.filteredLoaded = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'direct':
|
||||||
|
this.tabIndex = 5;
|
||||||
|
this.filteredMaxId = this.max_id;
|
||||||
|
this.filteredFeed = this.notifications.filter(n => n.type === 'direct');
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
this.tabSwitching = false;
|
||||||
|
this.filteredLoaded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
enterFilteredIntersect() {
|
||||||
|
if( !this.canLoadMoreFiltered ||
|
||||||
|
this.filteredIsIntersecting ||
|
||||||
|
this.filteredIterations > 10
|
||||||
|
) {
|
||||||
|
if(this.filteredFeed.length == 0) {
|
||||||
|
this.filteredEmpty = true;
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isFinite(this.max_id) || !isFinite(this.filteredMaxId)) {
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filteredIsIntersecting = true;
|
||||||
|
|
||||||
|
axios.get('/api/pixelfed/v1/notifications', {
|
||||||
|
params: {
|
||||||
|
max_id: this.filteredMaxId,
|
||||||
|
limit: 40
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
let mids = res.data.map(n => n.id);
|
||||||
|
let max_id = Math.min(...mids);
|
||||||
|
if(max_id < this.max_id) {
|
||||||
|
this.max_id = max_id;
|
||||||
|
res.data.forEach(n => {
|
||||||
|
if(this.ids.indexOf(n.id) == -1) {
|
||||||
|
this.ids.push(n.id);
|
||||||
|
this.notifications.push(n);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.filteredIterations++;
|
||||||
|
if(this.filterPaginationTimeout && this.filterPaginationTimeout < 500) {
|
||||||
|
clearTimeout(this.filterPaginationTimeout);
|
||||||
|
}
|
||||||
|
if(!res.data || !res.data.length) {
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
}
|
||||||
|
if(!res.data.length) {
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
}
|
||||||
|
let ids = res.data.map(n => n.id);
|
||||||
|
this.filteredMaxId = Math.min(...ids);
|
||||||
|
let types = this.tabs[this.tabIndex].types;
|
||||||
|
let data = res.data.filter(n => types.includes(n.type));
|
||||||
|
this.filteredFeed.push(...data);
|
||||||
|
this.filteredIsIntersecting = false;
|
||||||
|
if(this.filteredFeed.length < 10) {
|
||||||
|
setTimeout(() => this.enterFilteredIntersect(), 500);
|
||||||
|
}
|
||||||
|
this.filterPaginationTimeout = setTimeout(() => {
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
}, 2000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.canLoadMoreFiltered = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchFollowRequests() {
|
||||||
|
axios.get('/account/follow-requests.json')
|
||||||
|
.then(res => {
|
||||||
|
if(this.followRequestsPage == 1) {
|
||||||
|
this.followRequests = res.data;
|
||||||
|
this.followRequestsChecked = true;
|
||||||
|
} else {
|
||||||
|
this.followRequests.accounts.push(...res.data.accounts);
|
||||||
|
}
|
||||||
|
this.followRequestsPage++;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showFollowRequests() {
|
||||||
|
this.tabSwitching = false;
|
||||||
|
this.filteredEmpty = false;
|
||||||
|
this.filteredIterations = 0;
|
||||||
|
this.tabIndex = 10;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFollowRequest(action, index) {
|
||||||
|
if(!window.confirm('Are you sure you want to ' + action + ' this follow request?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post('/account/follow-requests', {
|
||||||
|
action: action,
|
||||||
|
id: this.followRequests.accounts[index].rid
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.followRequests.count--;
|
||||||
|
this.followRequests.accounts.splice(index, 1);
|
||||||
|
this.toggleTab(0);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
truncate(str, len = 40) {
|
||||||
|
return _.truncate(str, { length: len });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.notification-metro-component {
|
||||||
|
.notification-filters {
|
||||||
|
.nav-link {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon:not(.active) {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.active) {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -37,6 +37,15 @@
|
||||||
<div v-for="(n, index) in feed" class="mb-2">
|
<div v-for="(n, index) in feed" class="mb-2">
|
||||||
<div class="media align-items-center">
|
<div class="media align-items-center">
|
||||||
<img
|
<img
|
||||||
|
v-if="n.type === 'autospam.warning'"
|
||||||
|
class="mr-2 rounded-circle shadow-sm p-1"
|
||||||
|
style="border: 2px solid var(--danger)"
|
||||||
|
src="/img/pixelfed-icon-color.svg"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
class="mr-2 rounded-circle shadow-sm"
|
class="mr-2 rounded-circle shadow-sm"
|
||||||
:src="n.account.avatar"
|
:src="n.account.avatar"
|
||||||
width="32"
|
width="32"
|
||||||
|
@ -58,6 +67,14 @@
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="n.type == 'autospam.warning'">
|
||||||
|
<p class="my-0">
|
||||||
|
Your recent <a :href="getPostUrl(n.status)" @click.prevent="goToPost(n.status)" class="font-weight-bold">post</a> has been unlisted.
|
||||||
|
</p>
|
||||||
|
<p class="mt-n1 mb-0">
|
||||||
|
<span class="small text-muted"><a href="#" class="font-weight-bold" @click.prevent="showAutospamInfo(n.status)">Click here</a> for more info.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div v-else-if="n.type == 'comment'">
|
<div v-else-if="n.type == 'comment'">
|
||||||
<p class="my-0">
|
<p class="my-0">
|
||||||
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.acct">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> commented on your <a class="font-weight-bold" :href="getPostUrl(n.status)" @click.prevent="goToPost(n.status)">post</a>.
|
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.acct">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> commented on your <a class="font-weight-bold" :href="getPostUrl(n.status)" @click.prevent="goToPost(n.status)">post</a>.
|
||||||
|
@ -383,6 +400,20 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showAutospamInfo(status) {
|
||||||
|
let el = document.createElement('p');
|
||||||
|
el.classList.add('text-left');
|
||||||
|
el.classList.add('mb-0');
|
||||||
|
el.innerHTML = '<p class="">We use automated systems to help detect potential abuse and spam. Your recent <a href="/i/web/post/' + status.id + '" class="font-weight-bold">post</a> was flagged for review. <br /> <p class=""><span class="font-weight-bold">Don\'t worry! Your post will be reviewed by a human</span>, and they will restore your post if they determine it appropriate.</p><p style="font-size:12px">Once a human approves your post, any posts you create after will not be marked as unlisted. If you delete this post and share more posts before a human can approve any of them, you will need to wait for at least one unlisted post to be reviewed by a human.';
|
||||||
|
let wrapper = document.createElement('div');
|
||||||
|
wrapper.appendChild(el);
|
||||||
|
swal({
|
||||||
|
title: 'Why was my post unlisted?',
|
||||||
|
content: wrapper,
|
||||||
|
icon: 'warning'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue