mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-29 09:43:16 +00:00
commit
4c2661b8dd
9 changed files with 128 additions and 167 deletions
|
@ -21,6 +21,9 @@
|
||||||
- Updated MediaTransformers, add default blurhash attribute. ([3f14a4c4](https://github.com/pixelfed/pixelfed/commit/3f14a4c4))
|
- Updated MediaTransformers, add default blurhash attribute. ([3f14a4c4](https://github.com/pixelfed/pixelfed/commit/3f14a4c4))
|
||||||
- Updated Timeline.vue, fix hashtag status previews. ([7768e844](https://github.com/pixelfed/pixelfed/commit/7768e844))
|
- Updated Timeline.vue, fix hashtag status previews. ([7768e844](https://github.com/pixelfed/pixelfed/commit/7768e844))
|
||||||
- Updated AP helpers, fix statusFetch 404s. ([3419379a](https://github.com/pixelfed/pixelfed/commit/3419379a))
|
- Updated AP helpers, fix statusFetch 404s. ([3419379a](https://github.com/pixelfed/pixelfed/commit/3419379a))
|
||||||
|
- Updated InternalApiController, update discoverPosts method to improve performance. ([9862a855](https://github.com/pixelfed/pixelfed/commit/9862a855))
|
||||||
|
- Updated DiscoverComponent, add blurhash and like/comment counts. ([a8ebdd2e](https://github.com/pixelfed/pixelfed/commit/a8ebdd2e))
|
||||||
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10)
|
## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10)
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class AccountLog extends Model
|
class AccountLog extends Model
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected $fillable = ['*'];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
|
|
|
@ -35,6 +35,8 @@ use Illuminate\Support\Str;
|
||||||
use App\Services\MediaTagService;
|
use App\Services\MediaTagService;
|
||||||
use App\Services\ModLogService;
|
use App\Services\ModLogService;
|
||||||
use App\Services\PublicTimelineService;
|
use App\Services\PublicTimelineService;
|
||||||
|
use App\Services\SnowflakeService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
class InternalApiController extends Controller
|
class InternalApiController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -82,37 +84,27 @@ class InternalApiController extends Controller
|
||||||
$following = array_merge($following, $filters);
|
$following = array_merge($following, $filters);
|
||||||
|
|
||||||
$sql = config('database.default') !== 'pgsql';
|
$sql = config('database.default') !== 'pgsql';
|
||||||
|
$min_id = SnowflakeService::byDate(now()->subMonths(3));
|
||||||
$posts = Status::select(
|
$posts = Status::select(
|
||||||
'id',
|
'id',
|
||||||
'caption',
|
|
||||||
'is_nsfw',
|
'is_nsfw',
|
||||||
'profile_id',
|
'profile_id',
|
||||||
'type',
|
'type',
|
||||||
'uri',
|
'uri',
|
||||||
'created_at'
|
|
||||||
)
|
)
|
||||||
->whereNull('uri')
|
->whereNull('uri')
|
||||||
->whereIn('type', ['photo','photo:album', 'video'])
|
->whereIn('type', ['photo','photo:album', 'video'])
|
||||||
->whereIsNsfw(false)
|
->whereIsNsfw(false)
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
->whereNotIn('profile_id', $following)
|
->whereNotIn('profile_id', $following)
|
||||||
->when($sql, function($q, $s) {
|
->where('id', '>', $min_id)
|
||||||
return $q->where('created_at', '>', now()->subMonths(3));
|
|
||||||
})
|
|
||||||
->with('media')
|
|
||||||
->inRandomOrder()
|
->inRandomOrder()
|
||||||
->latest()
|
|
||||||
->take(39)
|
->take(39)
|
||||||
->get();
|
->pluck('id');
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
'posts' => $posts->map(function($post) {
|
'posts' => $posts->map(function($post) {
|
||||||
return [
|
return StatusService::get($post);
|
||||||
'type' => $post->type,
|
|
||||||
'url' => $post->url(),
|
|
||||||
'thumb' => $post->thumb(),
|
|
||||||
];
|
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
@ -323,117 +315,7 @@ class InternalApiController extends Controller
|
||||||
|
|
||||||
public function composePost(Request $request)
|
public function composePost(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
abort(400, 'Endpoint deprecated');
|
||||||
'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
|
|
||||||
'media.*' => 'required',
|
|
||||||
'media.*.id' => 'required|integer|min:1',
|
|
||||||
'media.*.filter_class' => 'nullable|alpha_dash|max:30',
|
|
||||||
'media.*.license' => 'nullable|string|max:140',
|
|
||||||
'media.*.alt' => 'nullable|string|max:140',
|
|
||||||
'cw' => 'nullable|boolean',
|
|
||||||
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
|
|
||||||
'place' => 'nullable',
|
|
||||||
'comments_disabled' => 'nullable',
|
|
||||||
'tagged' => 'nullable'
|
|
||||||
]);
|
|
||||||
|
|
||||||
if(config('costar.enabled') == true) {
|
|
||||||
$blockedKeywords = config('costar.keyword.block');
|
|
||||||
if($blockedKeywords !== null && $request->caption) {
|
|
||||||
$keywords = config('costar.keyword.block');
|
|
||||||
foreach($keywords as $kw) {
|
|
||||||
if(Str::contains($request->caption, $kw) == true) {
|
|
||||||
abort(400, 'Invalid object');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = Auth::user();
|
|
||||||
$profile = $user->profile;
|
|
||||||
$visibility = $request->input('visibility');
|
|
||||||
$medias = $request->input('media');
|
|
||||||
$attachments = [];
|
|
||||||
$status = new Status;
|
|
||||||
$mimes = [];
|
|
||||||
$place = $request->input('place');
|
|
||||||
$cw = $request->input('cw');
|
|
||||||
$tagged = $request->input('tagged');
|
|
||||||
|
|
||||||
foreach($medias as $k => $media) {
|
|
||||||
if($k + 1 > config('pixelfed.max_album_length')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$m = Media::findOrFail($media['id']);
|
|
||||||
if($m->profile_id !== $profile->id || $m->status_id) {
|
|
||||||
abort(403, 'Invalid media id');
|
|
||||||
}
|
|
||||||
$m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null;
|
|
||||||
$m->license = $media['license'];
|
|
||||||
$m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null;
|
|
||||||
$m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
|
|
||||||
if($cw == true || $profile->cw == true) {
|
|
||||||
$m->is_nsfw = $cw;
|
|
||||||
$status->is_nsfw = $cw;
|
|
||||||
}
|
|
||||||
$m->save();
|
|
||||||
$attachments[] = $m;
|
|
||||||
array_push($mimes, $m->mime);
|
|
||||||
}
|
|
||||||
|
|
||||||
$mediaType = StatusController::mimeTypeCheck($mimes);
|
|
||||||
|
|
||||||
if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) {
|
|
||||||
abort(400, __('exception.compose.invalid.album'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if($place && is_array($place)) {
|
|
||||||
$status->place_id = $place['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if($request->filled('comments_disabled')) {
|
|
||||||
$status->comments_disabled = (bool) $request->input('comments_disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
$status->caption = strip_tags($request->caption);
|
|
||||||
$status->scope = 'draft';
|
|
||||||
$status->profile_id = $profile->id;
|
|
||||||
$status->save();
|
|
||||||
|
|
||||||
foreach($attachments as $media) {
|
|
||||||
$media->status_id = $status->id;
|
|
||||||
$media->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
|
||||||
$cw = $profile->cw == true ? true : $cw;
|
|
||||||
$status->is_nsfw = $cw;
|
|
||||||
$status->visibility = $visibility;
|
|
||||||
$status->scope = $visibility;
|
|
||||||
$status->type = $mediaType;
|
|
||||||
$status->save();
|
|
||||||
|
|
||||||
foreach($tagged as $tg) {
|
|
||||||
$mt = new MediaTag;
|
|
||||||
$mt->status_id = $status->id;
|
|
||||||
$mt->media_id = $status->media->first()->id;
|
|
||||||
$mt->profile_id = $tg['id'];
|
|
||||||
$mt->tagged_username = $tg['name'];
|
|
||||||
$mt->is_public = true; // (bool) $tg['privacy'] ?? 1;
|
|
||||||
$mt->metadata = json_encode([
|
|
||||||
'_v' => 1,
|
|
||||||
]);
|
|
||||||
$mt->save();
|
|
||||||
MediaTagService::set($mt->status_id, $mt->profile_id);
|
|
||||||
MediaTagService::sendNotification($mt);
|
|
||||||
}
|
|
||||||
|
|
||||||
NewStatusPipeline::dispatch($status);
|
|
||||||
Cache::forget('user:account:id:'.$profile->user_id);
|
|
||||||
Cache::forget('_api:statuses:recent_9:'.$profile->id);
|
|
||||||
Cache::forget('profile:status_count:'.$profile->id);
|
|
||||||
Cache::forget($user->storageUsedKey());
|
|
||||||
return $status->url();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bookmarks(Request $request)
|
public function bookmarks(Request $request)
|
||||||
|
|
|
@ -219,20 +219,21 @@ class SeasonalController extends Controller
|
||||||
{
|
{
|
||||||
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
|
abort_if(now()->gt('2021-03-01 00:00:00'), 404);
|
||||||
abort_if(config('database.default') != 'mysql', 404);
|
abort_if(config('database.default') != 'mysql', 404);
|
||||||
$this->validate($request, [
|
|
||||||
'profile_id' => 'required',
|
|
||||||
'type' => 'required|string|in:view,hide'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
$log = new AccountLog();
|
$log = AccountLog::firstOrCreate([
|
||||||
$log->user_id = $user->id;
|
[
|
||||||
$log->item_type = 'App\User';
|
'item_type' => 'App\User',
|
||||||
$log->item_id = $user->id;
|
'item_id' => $user->id,
|
||||||
$log->action = $request->input('type') == 'view' ? 'seasonal.my2020.view' : 'seasonal.my2020.hide';
|
'user_id' => $user->id,
|
||||||
$log->ip_address = $request->ip();
|
'action' => 'seasonal.my2020.view'
|
||||||
$log->user_agent = $request->user_agent();
|
],
|
||||||
$log->save();
|
[
|
||||||
|
'ip_address' => $request->ip(),
|
||||||
|
'user_agent' => $request->userAgent()
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
return response()->json(200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
public/js/discover.js
vendored
BIN
public/js/discover.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -39,13 +39,46 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row p-0" style="display: flex;">
|
<div class="row p-0" style="display: flex;">
|
||||||
<div v-for="(post, index) in trending.slice(0, 12)" class="col-4 p-1 p-sm-2 p-md-3 pt-0">
|
<div v-for="(s, index) in trending.slice(0, 12)" class="col-4 p-1 p-sm-2 p-md-3 pt-0">
|
||||||
<a class="card info-overlay card-md-border-0" :href="post.url">
|
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||||
<div class="square">
|
<div class="square">
|
||||||
<span v-if="post.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
<div v-if="s.sensitive" class="square-content">
|
||||||
<span v-if="post.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
<div class="info-overlay-text-label">
|
||||||
<span v-if="post.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
<h5 class="text-white m-auto font-weight-bold">
|
||||||
<div class="square-content" v-bind:style="{ 'background-image': 'url(' + post.media_attachments[0].preview_url + ')' }">
|
<span>
|
||||||
|
<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<blur-hash-canvas
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:hash="s.media_attachments[0].blurhash"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="square-content">
|
||||||
|
|
||||||
|
<blur-hash-image
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:hash="s.media_attachments[0].blurhash"
|
||||||
|
:src="s.media_attachments[0].preview_url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
||||||
|
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||||
|
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
||||||
|
<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">{{formatCount(s.favourites_count)}}</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
|
||||||
|
<span class="d-flex-inline">{{formatCount(s.reply_count)}}</span>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -126,13 +159,46 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row p-0" style="display: flex;">
|
<div class="row p-0" style="display: flex;">
|
||||||
<div v-for="(post, index) in posts" class="col-4 p-1 p-sm-2 p-md-3 pt-0">
|
<div v-for="(s, index) in posts" class="col-4 p-1 p-sm-2 p-md-3 pt-0">
|
||||||
<a class="card info-overlay card-md-border-0" :href="post.url">
|
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||||
<div class="square">
|
<div class="square">
|
||||||
<span v-if="post.type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
<div v-if="s.sensitive" class="square-content">
|
||||||
<span v-if="post.type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
<div class="info-overlay-text-label">
|
||||||
<span v-if="post.type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
<h5 class="text-white m-auto font-weight-bold">
|
||||||
<div class="square-content" v-bind:style="{ 'background-image': 'url(' + post.thumb + ')' }">
|
<span>
|
||||||
|
<span class="far fa-eye-slash fa-lg p-2 d-flex-inline"></span>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<blur-hash-canvas
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:hash="s.media_attachments[0].blurhash"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else class="square-content">
|
||||||
|
|
||||||
|
<blur-hash-image
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:hash="s.media_attachments[0].blurhash"
|
||||||
|
:src="s.media_attachments[0].preview_url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
||||||
|
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||||
|
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
||||||
|
<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">{{formatCount(s.favourites_count)}}</span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<span class="far fa-comment fa-lg p-2 d-flex-inline"></span>
|
||||||
|
<span class="d-flex-inline">{{formatCount(s.reply_count)}}</span>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -314,6 +380,10 @@
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.places = res.data;
|
this.places = res.data;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
formatCount(s) {
|
||||||
|
return App.util.format.count(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
@if(config('instance.restricted.enabled') == false)
|
@if(config('instance.restricted.enabled') == false)
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<p class="d-flex flex-wrap justify-content-center mb-0 text-uppercase font-weight-bold small text-justify">
|
<p class="text-center text-uppercase font-weight-bold small text-justify">
|
||||||
<a href="{{route('site.about')}}" class="text-primary p-2">{{__('site.about')}}</a>
|
<a href="{{route('site.about')}}" class="text-dark p-2">{{__('site.about')}}</a>
|
||||||
@if(config('instance.contact.enabled') || config('instance.email'))
|
<a href="{{route('site.help')}}" class="text-dark p-2">{{__('site.help')}}</a>
|
||||||
<a href="{{route('site.contact')}}" class="text-primary p-2">{{__('site.contact-us')}}</a>
|
<a href="{{route('site.terms')}}" class="text-dark p-2">{{__('site.terms')}}</a>
|
||||||
@endif
|
<a href="{{route('site.privacy')}}" class="text-dark p-2">{{__('site.privacy')}}</a>
|
||||||
<a href="{{route('site.help')}}" class="text-primary p-2">{{__('site.help')}}</a>
|
<a href="{{route('site.language')}}" class="text-dark p-2">{{__('site.language')}}</a>
|
||||||
<a href="{{route('site.terms')}}" class="text-primary p-2">{{__('site.terms')}}</a>
|
</p>
|
||||||
<a href="{{route('site.privacy')}}" class="text-primary p-2">{{__('site.privacy')}}</a>
|
<p class="text-center text-muted small mb-0">
|
||||||
<a href="{{route('discover.places')}}" class="text-primary p-2">{{__('site.places')}}</a>
|
<span class="text-muted">© {{date('Y')}} {{config('pixelfed.domain.app')}}</span>
|
||||||
<a href="{{route('site.language')}}" class="text-primary p-2">{{__('site.language')}}</a>
|
<span class="mx-2">·</span>
|
||||||
<a href="https://pixelfed.org" class="text-muted p-2 ml-md-auto" rel="noopener" title="version {{config('pixelfed.version')}}" data-toggle="tooltip">Powered by Pixelfed</a>
|
<a href="https://pixelfed.org" class="text-muted font-weight-bold" rel="noopener">Powered by Pixelfed</a>
|
||||||
|
<span class="mx-2">·</span>
|
||||||
|
<span class="text-muted">v{{config('pixelfed.version')}}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
|
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link font-weight-bold text-primary" href="{{ route('login') }}" title="Login">
|
<a class="nav-link font-weight-bold text-dark" href="{{ route('login') }}" title="Login">
|
||||||
{{ __('Login') }}
|
{{ __('Login') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@if(config('pixelfed.open_registration') && config('instance.restricted.enabled') == false)
|
@if(config('pixelfed.open_registration') && config('instance.restricted.enabled') == false)
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link font-weight-bold" href="{{ route('register') }}" title="Register">
|
<a class="ml-3 nav-link font-weight-bold text-dark" href="{{ route('register') }}" title="Register">
|
||||||
{{ __('Register') }}
|
{{ __('Register') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-md-2 d-none d-md-block">
|
<li class="nav-item px-md-2 d-none d-md-block">
|
||||||
<a class="nav-link font-weight-bold text-dark" href="/account/activity" title="Notifications" data-toggle="tooltip" data-placement="bottom">
|
<a class="nav-link font-weight-bold text-dark" href="/account/activity" title="Notifications" data-toggle="tooltip" data-placement="bottom">
|
||||||
<i class="far fa-bell fa-lg" style="vertical-align: middle;"></i>
|
<i class="far fa-bell fa-lg"></i>
|
||||||
<span class="sr-only">Notifications</span>
|
<span class="sr-only">Notifications</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in a new issue