mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-26 16:23:16 +00:00
commit
f13ba6d08f
60 changed files with 1492 additions and 21 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -19,6 +19,8 @@
|
|||
- Add Password change email notification ([de1cca4f](https://github.com/pixelfed/pixelfed/commit/de1cca4f))
|
||||
- Add shared inbox ([4733ca9f](https://github.com/pixelfed/pixelfed/commit/4733ca9f))
|
||||
- Add federated photo filters ([0a5a0e86](https://github.com/pixelfed/pixelfed/commit/0a5a0e86))
|
||||
- Add AccountInterstitial model and controller ([8766ccfe](https://github.com/pixelfed/pixelfed/commit/8766ccfe))
|
||||
- Add Blurhash encoder ([fad102bf](https://github.com/pixelfed/pixelfed/commit/fad102bf))
|
||||
|
||||
### Updated
|
||||
- Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc))
|
||||
|
@ -106,6 +108,20 @@
|
|||
- Updated federation config, make sharedInbox enabled by default. ([6e3522c0](https://github.com/pixelfed/pixelfed/commit/6e3522c0))
|
||||
- Updated PostComponent, change timestamp format. ([e51665f6](https://github.com/pixelfed/pixelfed/commit/e51665f6))
|
||||
- Updated PostComponent, use proper username context for reply mentions. Fixes ([#2421](https://github.com/pixelfed/pixelfed/issues/2421)). ([dac06088](https://github.com/pixelfed/pixelfed/commit/dac06088))
|
||||
- Updated Navbar, added profile avatar. ([19abf1b4](https://github.com/pixelfed/pixelfed/commit/19abf1b4))
|
||||
- Updated package.json, add blurhash. ([cc1b081a](https://github.com/pixelfed/pixelfed/commit/cc1b081a))
|
||||
- Updated Status model, fix thumb nsfw caching. ([327ef138](https://github.com/pixelfed/pixelfed/commit/327ef138))
|
||||
- Updated User model, add interstitial relation. ([bd321a72](https://github.com/pixelfed/pixelfed/commit/bd321a72))
|
||||
- Updated StatusStatelessTransformer, add missing attributes. ([4d22426d](https://github.com/pixelfed/pixelfed/commit/4d22426d))
|
||||
- Updated media pipeline, add blurhash support. ([473e0495](https://github.com/pixelfed/pixelfed/commit/473e0495))
|
||||
- Updated DeleteAccountPipeline, add AccountInterstitial and DirectMessage purging. ([b3078f27](https://github.com/pixelfed/pixelfed/commit/b3078f27))
|
||||
- Updated ComposeModal.vue component, reuse sharedData. ([e28d022f](https://github.com/pixelfed/pixelfed/commit/e28d022f))
|
||||
- Updated ApiController, return status object after deletion. ([0718711d](https://github.com/pixelfed/pixelfed/commit/0718711d))
|
||||
- Updated InternalApiController, add interstitial logic. ([20681bcf](https://github.com/pixelfed/pixelfed/commit/20681bcf))
|
||||
- Updated PublicApiController, improve stateless object caching. ([342e7a50](https://github.com/pixelfed/pixelfed/commit/342e7a50))
|
||||
- Updated StatusController, add interstitial logic. ([003caf7e](https://github.com/pixelfed/pixelfed/commit/003caf7e))
|
||||
- Updated middleware, add AccountInterstitial support. ([19d6e7df](https://github.com/pixelfed/pixelfed/commit/19d6e7df))
|
||||
- Updated BaseApiController, add favourites method. ([76353ca9](https://github.com/pixelfed/pixelfed/commit/76353ca9))
|
||||
|
||||
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
|
||||
### Added
|
||||
|
|
30
app/AccountInterstitial.php
Normal file
30
app/AccountInterstitial.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountInterstitial extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['read_at', 'appeal_requested_at'];
|
||||
|
||||
public const JSON_MESSAGE = 'Please use web browser to proceed.';
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
if($this->item_type != 'App\Status') {
|
||||
return;
|
||||
}
|
||||
return $this->hasOne(Status::class, 'id', 'item_id');
|
||||
}
|
||||
}
|
76
app/Http/Controllers/AccountInterstitialController.php
Normal file
76
app/Http/Controllers/AccountInterstitialController.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Status;
|
||||
use App\AccountInterstitial;
|
||||
|
||||
class AccountInterstitialController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function get(Request $request)
|
||||
{
|
||||
$interstitial = $request->user()
|
||||
->interstitials()
|
||||
->whereNull('read_at')
|
||||
->first();
|
||||
if(!$interstitial) {
|
||||
$user = $request->user();
|
||||
$user->has_interstitial = false;
|
||||
$user->save();
|
||||
return redirect('/');
|
||||
}
|
||||
$meta = json_decode($interstitial->meta);
|
||||
$view = $interstitial->view;
|
||||
return view($view, compact('interstitial', 'meta'));
|
||||
}
|
||||
|
||||
public function read(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'id' => 'required',
|
||||
'type' => 'required|in:post.cw,post.removed,post.unlist',
|
||||
'action' => 'required|in:appeal,confirm',
|
||||
'appeal_message' => 'nullable|max:500'
|
||||
]);
|
||||
|
||||
$redirect = '/';
|
||||
|
||||
$id = decrypt($request->input('id'));
|
||||
$action = $request->input('action');
|
||||
$user = $request->user();
|
||||
|
||||
$ai = AccountInterstitial::whereUserId($user->id)
|
||||
->whereType($request->input('type'))
|
||||
->findOrFail($id);
|
||||
|
||||
if($action == 'appeal') {
|
||||
$ai->appeal_requested_at = now();
|
||||
$ai->appeal_message = $request->input('appeal_message');
|
||||
}
|
||||
|
||||
$ai->read_at = now();
|
||||
$ai->save();
|
||||
|
||||
$more = AccountInterstitial::whereUserId($user->id)
|
||||
->whereNull('read_at')
|
||||
->exists();
|
||||
|
||||
if(!$more) {
|
||||
$user->has_interstitial = false;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
if(in_array($ai->type, ['post.cw', 'post.unlist'])) {
|
||||
$redirect = Status::findOrFail($ai->item_id)->url();
|
||||
}
|
||||
|
||||
return redirect($redirect);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\{
|
||||
AccountInterstitial,
|
||||
Contact,
|
||||
Hashtag,
|
||||
Newsroom,
|
||||
|
@ -85,6 +86,67 @@ class AdminController extends Controller
|
|||
return view('admin.reports.show', compact('report'));
|
||||
}
|
||||
|
||||
public function appeals(Request $request)
|
||||
{
|
||||
$appeals = AccountInterstitial::whereNotNull('appeal_requested_at')
|
||||
->whereNull('appeal_handled_at')
|
||||
->latest()
|
||||
->paginate(6);
|
||||
return view('admin.reports.appeals', compact('appeals'));
|
||||
}
|
||||
|
||||
public function showAppeal(Request $request, $id)
|
||||
{
|
||||
$appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
|
||||
->whereNull('appeal_handled_at')
|
||||
->findOrFail($id);
|
||||
$meta = json_decode($appeal->meta);
|
||||
return view('admin.reports.show_appeal', compact('appeal', 'meta'));
|
||||
}
|
||||
|
||||
public function updateAppeal(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'action' => 'required|in:dismiss,approve'
|
||||
]);
|
||||
|
||||
$action = $request->input('action');
|
||||
$appeal = AccountInterstitial::whereNotNull('appeal_requested_at')
|
||||
->whereNull('appeal_handled_at')
|
||||
->findOrFail($id);
|
||||
|
||||
if($action == 'dismiss') {
|
||||
$appeal->appeal_handled_at = now();
|
||||
$appeal->save();
|
||||
|
||||
return redirect('/i/admin/reports/appeals');
|
||||
}
|
||||
|
||||
switch ($appeal->type) {
|
||||
case 'post.cw':
|
||||
$status = $appeal->status;
|
||||
$status->is_nsfw = false;
|
||||
$status->save();
|
||||
break;
|
||||
|
||||
case 'post.unlist':
|
||||
$status = $appeal->status;
|
||||
$status->scope = 'public';
|
||||
$status->visibility = 'public';
|
||||
$status->save();
|
||||
break;
|
||||
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
|
||||
$appeal->appeal_handled_at = now();
|
||||
$appeal->save();
|
||||
|
||||
return redirect('/i/admin/reports/appeals');
|
||||
}
|
||||
|
||||
public function profiles(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
|
|
|
@ -1761,6 +1761,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
NewStatusPipeline::dispatch($status);
|
||||
Cache::forget('user:account:id:'.$user->id);
|
||||
Cache::forget('_api:statuses:recent_9:'.$user->profile_id);
|
||||
Cache::forget('profile:status_count:'.$user->profile_id);
|
||||
Cache::forget($user->storageUsedKey());
|
||||
|
||||
|
@ -1783,10 +1784,15 @@ class ApiV1Controller extends Controller
|
|||
$status = Status::whereProfileId($request->user()->profile->id)
|
||||
->findOrFail($id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||
|
||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||
StatusDelete::dispatch($status);
|
||||
|
||||
return response()->json(['Status successfully deleted.']);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
$res['text'] = $res['content'];
|
||||
unset($res['content']);
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,8 +11,9 @@ use Auth, Cache, Storage, URL;
|
|||
use Carbon\Carbon;
|
||||
use App\{
|
||||
Avatar,
|
||||
Notification,
|
||||
Like,
|
||||
Media,
|
||||
Notification,
|
||||
Profile,
|
||||
Status
|
||||
};
|
||||
|
@ -21,7 +22,8 @@ use App\Transformer\Api\{
|
|||
NotificationTransformer,
|
||||
MediaTransformer,
|
||||
MediaDraftTransformer,
|
||||
StatusTransformer
|
||||
StatusTransformer,
|
||||
StatusStatelessTransformer
|
||||
};
|
||||
use League\Fractal;
|
||||
use App\Util\Media\Filter;
|
||||
|
@ -338,4 +340,29 @@ class BaseApiController extends Controller
|
|||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function accountLikes(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$limit = 10;
|
||||
$page = (int) $request->input('page', 1);
|
||||
|
||||
if($page > 20) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$favourites = $user->profile->likes()
|
||||
->latest()
|
||||
->simplePaginate($limit)
|
||||
->pluck('status_id');
|
||||
|
||||
$statuses = Status::find($favourites)->reverse();
|
||||
|
||||
$resource = new Fractal\Resource\Collection($statuses, new StatusStatelessTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use App\{
|
||||
AccountInterstitial,
|
||||
DirectMessage,
|
||||
DiscoverCategory,
|
||||
Hashtag,
|
||||
|
@ -213,6 +214,35 @@ class InternalApiController extends Controller
|
|||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
|
||||
|
||||
if($status->uri == null) {
|
||||
$media = $status->media;
|
||||
$ai = new AccountInterstitial;
|
||||
$ai->user_id = $status->profile->user_id;
|
||||
$ai->type = 'post.cw';
|
||||
$ai->view = 'account.moderation.post.cw';
|
||||
$ai->item_type = 'App\Status';
|
||||
$ai->item_id = $status->id;
|
||||
$ai->has_media = (bool) $media->count();
|
||||
$ai->blurhash = $media->count() ? $media->first()->blurhash : null;
|
||||
$ai->meta = json_encode([
|
||||
'caption' => $status->caption,
|
||||
'created_at' => $status->created_at,
|
||||
'type' => $status->type,
|
||||
'url' => $status->url(),
|
||||
'is_nsfw' => $status->is_nsfw,
|
||||
'scope' => $status->scope,
|
||||
'reblog' => $status->reblog_of_id,
|
||||
'likes_count' => $status->likes_count,
|
||||
'reblogs_count' => $status->reblogs_count,
|
||||
]);
|
||||
$ai->save();
|
||||
|
||||
$u = $status->profile->user;
|
||||
$u->has_interstitial = true;
|
||||
$u->save();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'remcw':
|
||||
|
@ -231,6 +261,14 @@ class InternalApiController extends Controller
|
|||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
if($status->uri == null) {
|
||||
$ai = AccountInterstitial::whereUserId($status->profile->user_id)
|
||||
->whereType('post.cw')
|
||||
->whereItemId($status->id)
|
||||
->whereItemType('App\Status')
|
||||
->first();
|
||||
$ai->delete();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'unlist':
|
||||
|
@ -250,6 +288,34 @@ class InternalApiController extends Controller
|
|||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
|
||||
if($status->uri == null) {
|
||||
$media = $status->media;
|
||||
$ai = new AccountInterstitial;
|
||||
$ai->user_id = $status->profile->user_id;
|
||||
$ai->type = 'post.unlist';
|
||||
$ai->view = 'account.moderation.post.unlist';
|
||||
$ai->item_type = 'App\Status';
|
||||
$ai->item_id = $status->id;
|
||||
$ai->has_media = (bool) $media->count();
|
||||
$ai->blurhash = $media->count() ? $media->first()->blurhash : null;
|
||||
$ai->meta = json_encode([
|
||||
'caption' => $status->caption,
|
||||
'created_at' => $status->created_at,
|
||||
'type' => $status->type,
|
||||
'url' => $status->url(),
|
||||
'is_nsfw' => $status->is_nsfw,
|
||||
'scope' => $status->scope,
|
||||
'reblog' => $status->reblog_of_id,
|
||||
'likes_count' => $status->likes_count,
|
||||
'reblogs_count' => $status->reblogs_count,
|
||||
]);
|
||||
$ai->save();
|
||||
|
||||
$u = $status->profile->user;
|
||||
$u->has_interstitial = true;
|
||||
$u->save();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return ['msg' => 200];
|
||||
|
@ -364,6 +430,7 @@ class InternalApiController extends Controller
|
|||
|
||||
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();
|
||||
|
|
|
@ -66,7 +66,9 @@ class ProfileController extends Controller
|
|||
'list' => $settings->show_profile_followers
|
||||
]
|
||||
];
|
||||
return view('profile.show', compact('profile', 'settings'));
|
||||
$ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show';
|
||||
|
||||
return view($ui, compact('profile', 'settings'));
|
||||
} else {
|
||||
$key = 'profile:settings:' . $user->id;
|
||||
$ttl = now()->addHours(6);
|
||||
|
@ -103,7 +105,8 @@ class ProfileController extends Controller
|
|||
'list' => $settings->show_profile_followers
|
||||
]
|
||||
];
|
||||
return view('profile.show', compact('profile', 'settings'));
|
||||
$ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show';
|
||||
return view($ui, compact('profile', 'settings'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ use League\Fractal;
|
|||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
RelationshipTransformer,
|
||||
StatusTransformer
|
||||
StatusTransformer,
|
||||
StatusStatelessTransformer
|
||||
};
|
||||
use App\Services\{
|
||||
AccountService,
|
||||
|
@ -86,6 +87,24 @@ class PublicApiController extends Controller
|
|||
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
|
||||
$status = Status::whereProfileId($profile->id)->findOrFail($postid);
|
||||
$this->scopeCheck($profile, $status);
|
||||
if(!Auth::check()) {
|
||||
$res = Cache::remember('wapi:v1:status:stateless_byid:' . $status->id, now()->addMinutes(30), function() use($status) {
|
||||
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
||||
$res = [
|
||||
'status' => $this->fractal->createData($item)->toArray(),
|
||||
'user' => [],
|
||||
'likes' => [],
|
||||
'shares' => [],
|
||||
'reactions' => [
|
||||
'liked' => false,
|
||||
'shared' => false,
|
||||
'bookmarked' => false,
|
||||
],
|
||||
];
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
});
|
||||
return $res;
|
||||
}
|
||||
$item = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||
$res = [
|
||||
'status' => $this->fractal->createData($item)->toArray(),
|
||||
|
@ -419,7 +438,6 @@ class PublicApiController extends Controller
|
|||
|
||||
}
|
||||
|
||||
|
||||
public function networkTimelineApi(Request $request)
|
||||
{
|
||||
return response()->json([]);
|
||||
|
@ -543,6 +561,50 @@ class PublicApiController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$tag = in_array('private', $visibility) ? 'private' : 'public';
|
||||
if($min_id == 1 && $limit == 9 && $tag == 'public') {
|
||||
$limit = 9;
|
||||
$scope = ['photo', 'photo:album', 'video', 'video:album'];
|
||||
$key = '_api:statuses:recent_9:'.$profile->id;
|
||||
$res = Cache::remember($key, now()->addHours(24), function() use($profile, $scope, $visibility, $limit) {
|
||||
$dir = '>';
|
||||
$id = 1;
|
||||
$timeline = Status::select(
|
||||
'id',
|
||||
'uri',
|
||||
'caption',
|
||||
'rendered',
|
||||
'profile_id',
|
||||
'type',
|
||||
'in_reply_to_id',
|
||||
'reblog_of_id',
|
||||
'is_nsfw',
|
||||
'likes_count',
|
||||
'reblogs_count',
|
||||
'scope',
|
||||
'visibility',
|
||||
'local',
|
||||
'place_id',
|
||||
'comments_disabled',
|
||||
'cw_summary',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
)->whereProfileId($profile->id)
|
||||
->whereIn('type', $scope)
|
||||
->where('id', $dir, $id)
|
||||
->whereIn('visibility', $visibility)
|
||||
->limit($limit)
|
||||
->orderByDesc('id')
|
||||
->get();
|
||||
|
||||
$resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
});
|
||||
return $res;
|
||||
}
|
||||
|
||||
$dir = $min_id ? '>' : '<';
|
||||
$id = $min_id ?? $max_id;
|
||||
$timeline = Status::select(
|
||||
|
@ -560,6 +622,8 @@ class PublicApiController extends Controller
|
|||
'scope',
|
||||
'visibility',
|
||||
'local',
|
||||
'place_id',
|
||||
'comments_disabled',
|
||||
'cw_summary',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
|||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Jobs\StatusPipeline\StatusDelete;
|
||||
use App\Jobs\SharePipeline\SharePipeline;
|
||||
use App\AccountInterstitial;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
|
@ -162,14 +163,49 @@ class StatusController extends Controller
|
|||
|
||||
$status = Status::findOrFail($request->input('item'));
|
||||
|
||||
if ($status->profile_id === Auth::user()->profile->id || Auth::user()->is_admin == true) {
|
||||
$user = Auth::user();
|
||||
|
||||
if($status->profile_id != $user->profile->id &&
|
||||
$user->is_admin == true &&
|
||||
$status->uri == null
|
||||
) {
|
||||
$media = $status->media;
|
||||
|
||||
$ai = new AccountInterstitial;
|
||||
$ai->user_id = $status->profile->user_id;
|
||||
$ai->type = 'post.removed';
|
||||
$ai->view = 'account.moderation.post.removed';
|
||||
$ai->item_type = 'App\Status';
|
||||
$ai->item_id = $status->id;
|
||||
$ai->has_media = (bool) $media->count();
|
||||
$ai->blurhash = $media->count() ? $media->first()->blurhash : null;
|
||||
$ai->meta = json_encode([
|
||||
'caption' => $status->caption,
|
||||
'created_at' => $status->created_at,
|
||||
'type' => $status->type,
|
||||
'url' => $status->url(),
|
||||
'is_nsfw' => $status->is_nsfw,
|
||||
'scope' => $status->scope,
|
||||
'reblog' => $status->reblog_of_id,
|
||||
'likes_count' => $status->likes_count,
|
||||
'reblogs_count' => $status->reblogs_count,
|
||||
]);
|
||||
$ai->save();
|
||||
|
||||
$u = $status->profile->user;
|
||||
$u->has_interstitial = true;
|
||||
$u->save();
|
||||
}
|
||||
|
||||
if ($status->profile_id == $user->profile->id || $user->is_admin == true) {
|
||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||
StatusDelete::dispatch($status);
|
||||
}
|
||||
|
||||
if($request->wantsJson()) {
|
||||
return response()->json(['Status successfully deleted.']);
|
||||
} else {
|
||||
return redirect(Auth::user()->url());
|
||||
return redirect($user->url());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ class Kernel extends HttpKernel
|
|||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'twofactor' => \App\Http\Middleware\TwoFactorAuth::class,
|
||||
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
|
||||
'interstitial' => \App\Http\Middleware\AccountInterstitial::class,
|
||||
// 'restricted' => \App\Http\Middleware\RestrictedAccess::class,
|
||||
];
|
||||
}
|
||||
|
|
48
app/Http/Middleware/AccountInterstitial.php
Normal file
48
app/Http/Middleware/AccountInterstitial.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Auth;
|
||||
use App\User;
|
||||
|
||||
class AccountInterstitial
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$ar = [
|
||||
'login',
|
||||
'logout',
|
||||
'password*',
|
||||
'loginAs*',
|
||||
'i/warning*',
|
||||
'i/auth/checkpoint',
|
||||
'i/auth/sudo',
|
||||
'site/privacy',
|
||||
'site/terms',
|
||||
'site/kb/community-guidelines',
|
||||
];
|
||||
|
||||
if(Auth::check() && !$request->is($ar)) {
|
||||
if($request->user()->has_interstitial) {
|
||||
if($request->wantsJson()) {
|
||||
$res = ['_refresh'=>true,'error' => 403, 'message' => \App\AccountInterstitial::JSON_MESSAGE];
|
||||
return response()->json($res, 403);
|
||||
} else {
|
||||
return redirect('/i/warning');
|
||||
}
|
||||
} else {
|
||||
return $next($request);
|
||||
}
|
||||
} else {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||
use DB;
|
||||
use Illuminate\Support\Str;
|
||||
use App\{
|
||||
AccountInterstitial,
|
||||
AccountLog,
|
||||
Activity,
|
||||
Avatar,
|
||||
|
@ -68,6 +69,10 @@ class DeleteAccountPipeline implements ShouldQueue
|
|||
});
|
||||
});
|
||||
|
||||
DB::transaction(function() use ($user) {
|
||||
AccountInterstitial::whereUserId($user->id)->delete();
|
||||
});
|
||||
|
||||
DB::transaction(function() use ($user) {
|
||||
if($user->profile) {
|
||||
$avatar = $user->profile->avatar;
|
||||
|
@ -79,6 +84,7 @@ class DeleteAccountPipeline implements ShouldQueue
|
|||
Bookmark::whereProfileId($user->profile_id)->forceDelete();
|
||||
EmailVerification::whereUserId($user->id)->forceDelete();
|
||||
StatusHashtag::whereProfileId($id)->delete();
|
||||
DirectMessage::whereFromId($user->profile_id)->delete();
|
||||
FollowRequest::whereFollowingId($id)
|
||||
->orWhere('follower_id', $id)
|
||||
->forceDelete();
|
||||
|
|
|
@ -89,7 +89,8 @@ class Status extends Model
|
|||
|
||||
public function thumb($showNsfw = false)
|
||||
{
|
||||
return Cache::remember('status:thumb:'.$this->id, now()->addMinutes(15), function() use ($showNsfw) {
|
||||
$key = $showNsfw ? 'status:thumb:nsfw1'.$this->id : 'status:thumb:nsfw0'.$this->id;
|
||||
return Cache::remember($key, now()->addMinutes(15), function() use ($showNsfw) {
|
||||
$type = $this->type ?? $this->setType();
|
||||
$is_nsfw = !$showNsfw ? $this->is_nsfw : false;
|
||||
if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) {
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace App\Transformer\Api;
|
|||
use App\Status;
|
||||
use League\Fractal;
|
||||
use Cache;
|
||||
use App\Services\HashidService;
|
||||
use App\Services\MediaTagService;
|
||||
|
||||
class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
|
@ -17,8 +19,11 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
|||
|
||||
public function transform(Status $status)
|
||||
{
|
||||
$taggedPeople = MediaTagService::get($status->id);
|
||||
|
||||
return [
|
||||
'id' => (string) $status->id,
|
||||
'shortcode' => HashidService::encode($status->id),
|
||||
'uri' => $status->url(),
|
||||
'url' => $status->url(),
|
||||
'in_reply_to_id' => $status->in_reply_to_id,
|
||||
|
@ -42,13 +47,17 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
|||
'language' => null,
|
||||
'pinned' => null,
|
||||
|
||||
'mentions' => [],
|
||||
'tags' => [],
|
||||
'pf_type' => $status->type ?? $status->setType(),
|
||||
'reply_count' => (int) $status->reply_count,
|
||||
'comments_disabled' => $status->comments_disabled ? true : false,
|
||||
'thread' => false,
|
||||
'replies' => [],
|
||||
'parent' => $status->parent() ? $this->transform($status->parent()) : [],
|
||||
'parent' => [],
|
||||
'place' => $status->place,
|
||||
'local' => (bool) $status->local,
|
||||
'taggedPeople' => $taggedPeople
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -88,4 +88,9 @@ class User extends Authenticatable
|
|||
return $this->hasMany(AccountLog::class);
|
||||
}
|
||||
|
||||
public function interstitials()
|
||||
{
|
||||
return $this->hasMany(AccountInterstitial::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
34
app/Util/Blurhash/AC.php
Normal file
34
app/Util/Blurhash/AC.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\Blurhash;
|
||||
|
||||
final class AC {
|
||||
|
||||
public static function encode(array $value, float $max_value): float {
|
||||
$quant_r = static::quantise($value[0] / $max_value);
|
||||
$quant_g = static::quantise($value[1] / $max_value);
|
||||
$quant_b = static::quantise($value[2] / $max_value);
|
||||
return $quant_r * 19 * 19 + $quant_g * 19 + $quant_b;
|
||||
}
|
||||
|
||||
public static function decode(int $value, float $max_value): array {
|
||||
$quant_r = floor($value / (19 * 19));
|
||||
$quant_g = floor($value / 19) % 19;
|
||||
$quant_b = $value % 19;
|
||||
|
||||
return [
|
||||
static::signPow(($quant_r - 9) / 9, 2) * $max_value,
|
||||
static::signPow(($quant_g - 9) / 9, 2) * $max_value,
|
||||
static::signPow(($quant_b - 9) / 9, 2) * $max_value
|
||||
];
|
||||
}
|
||||
|
||||
private static function quantise(float $value): float {
|
||||
return floor(max(0, min(18, floor(static::signPow($value, 0.5) * 9 + 9.5))));
|
||||
}
|
||||
|
||||
private static function signPow(float $base, float $exp): float {
|
||||
$sign = $base <=> 0;
|
||||
return $sign * pow(abs($base), $exp);
|
||||
}
|
||||
}
|
39
app/Util/Blurhash/Base83.php
Normal file
39
app/Util/Blurhash/Base83.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\Blurhash;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Base83 {
|
||||
private const ALPHABET = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
|
||||
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
|
||||
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',', '-', '.',
|
||||
':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~'
|
||||
];
|
||||
|
||||
private const BASE = 83;
|
||||
|
||||
public static function encode(int $value, int $length): string {
|
||||
if (floor($value / (self::BASE ** $length)) != 0) {
|
||||
throw new InvalidArgumentException('Specified length is too short to encode given value.');
|
||||
}
|
||||
|
||||
$result = '';
|
||||
for ($i = 1; $i <= $length; $i++) {
|
||||
$digit = floor($value / (self::BASE ** ($length - $i))) % self::BASE;
|
||||
$result .= self::ALPHABET[$digit];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function decode(string $hash): int {
|
||||
$result = 0;
|
||||
foreach (str_split($hash) as $char) {
|
||||
$result = $result * self::BASE + (int) array_search($char, self::ALPHABET, true);
|
||||
}
|
||||
return (int) $result;
|
||||
}
|
||||
}
|
139
app/Util/Blurhash/Blurhash.php
Normal file
139
app/Util/Blurhash/Blurhash.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\Blurhash;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Blurhash {
|
||||
|
||||
public static function encode(array $image, int $components_x = 4, int $components_y = 4, bool $linear = false): string {
|
||||
if (($components_x < 1 || $components_x > 9) || ($components_y < 1 || $components_y > 9)) {
|
||||
throw new InvalidArgumentException("x and y component counts must be between 1 and 9 inclusive.");
|
||||
}
|
||||
$height = count($image);
|
||||
$width = count($image[0]);
|
||||
|
||||
$image_linear = $image;
|
||||
if (!$linear) {
|
||||
$image_linear = [];
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$line = [];
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
$pixel = $image[$y][$x];
|
||||
$line[] = [
|
||||
Color::toLinear($pixel[0]),
|
||||
Color::toLinear($pixel[1]),
|
||||
Color::toLinear($pixel[2])
|
||||
];
|
||||
}
|
||||
$image_linear[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
$components = [];
|
||||
$scale = 1 / ($width * $height);
|
||||
for ($y = 0; $y < $components_y; $y++) {
|
||||
for ($x = 0; $x < $components_x; $x++) {
|
||||
$normalisation = $x == 0 && $y == 0 ? 1 : 2;
|
||||
$r = $g = $b = 0;
|
||||
for ($i = 0; $i < $width; $i++) {
|
||||
for ($j = 0; $j < $height; $j++) {
|
||||
$color = $image_linear[$j][$i];
|
||||
$basis = $normalisation
|
||||
* cos(M_PI * $i * $x / $width)
|
||||
* cos(M_PI * $j * $y / $height);
|
||||
|
||||
$r += $basis * $color[0];
|
||||
$g += $basis * $color[1];
|
||||
$b += $basis * $color[2];
|
||||
}
|
||||
}
|
||||
|
||||
$components[] = [
|
||||
$r * $scale,
|
||||
$g * $scale,
|
||||
$b * $scale
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$dc_value = DC::encode(array_shift($components) ?: []);
|
||||
|
||||
$max_ac_component = 0;
|
||||
foreach ($components as $component) {
|
||||
$component[] = $max_ac_component;
|
||||
$max_ac_component = max ($component);
|
||||
}
|
||||
|
||||
$quant_max_ac_component = (int) max(0, min(82, floor($max_ac_component * 166 - 0.5)));
|
||||
$ac_component_norm_factor = ($quant_max_ac_component + 1) / 166;
|
||||
|
||||
$ac_values = [];
|
||||
foreach ($components as $component) {
|
||||
$ac_values[] = AC::encode($component, $ac_component_norm_factor);
|
||||
}
|
||||
|
||||
$blurhash = Base83::encode($components_x - 1 + ($components_y - 1) * 9, 1);
|
||||
$blurhash .= Base83::encode($quant_max_ac_component, 1);
|
||||
$blurhash .= Base83::encode($dc_value, 4);
|
||||
foreach ($ac_values as $ac_value) {
|
||||
$blurhash .= Base83::encode((int) $ac_value, 2);
|
||||
}
|
||||
|
||||
return $blurhash;
|
||||
}
|
||||
|
||||
public static function decode (string $blurhash, int $width, int $height, float $punch = 1.0, bool $linear = false): array {
|
||||
if (empty($blurhash) || strlen($blurhash) < 6) {
|
||||
throw new InvalidArgumentException("Blurhash string must be at least 6 characters");
|
||||
}
|
||||
|
||||
$size_info = Base83::decode($blurhash[0]);
|
||||
$size_y = floor($size_info / 9) + 1;
|
||||
$size_x = ($size_info % 9) + 1;
|
||||
|
||||
$length = (int) strlen($blurhash);
|
||||
$expected_length = (int) (4 + (2 * $size_y * $size_x));
|
||||
if ($length !== $expected_length) {
|
||||
throw new InvalidArgumentException("Blurhash length mismatch: length is {$length} but it should be {$expected_length}");
|
||||
}
|
||||
|
||||
$colors = [DC::decode(Base83::decode(substr($blurhash, 2, 4)))];
|
||||
|
||||
$quant_max_ac_component = Base83::decode($blurhash[1]);
|
||||
$max_value = ($quant_max_ac_component + 1) / 166;
|
||||
for ($i = 1; $i < $size_x * $size_y; $i++) {
|
||||
$value = Base83::decode(substr($blurhash, 4 + $i * 2, 2));
|
||||
$colors[$i] = AC::decode($value, $max_value * $punch);
|
||||
}
|
||||
|
||||
$pixels = [];
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$row = [];
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
$r = $g = $b = 0;
|
||||
for ($j = 0; $j < $size_y; $j++) {
|
||||
for ($i = 0; $i < $size_x; $i++) {
|
||||
$color = $colors[$i + $j * $size_x];
|
||||
$basis =
|
||||
cos((M_PI * $x * $i) / $width) *
|
||||
cos((M_PI * $y * $j) / $height);
|
||||
|
||||
$r += $color[0] * $basis;
|
||||
$g += $color[1] * $basis;
|
||||
$b += $color[2] * $basis;
|
||||
}
|
||||
}
|
||||
|
||||
$row[] = $linear ? [$r, $g, $b] : [
|
||||
Color::toSRGB($r),
|
||||
Color::toSRGB($g),
|
||||
Color::toSRGB($b)
|
||||
];
|
||||
}
|
||||
$pixels[] = $row;
|
||||
}
|
||||
|
||||
return $pixels;
|
||||
}
|
||||
}
|
19
app/Util/Blurhash/Color.php
Normal file
19
app/Util/Blurhash/Color.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\Blurhash;
|
||||
|
||||
final class Color {
|
||||
public static function toLinear(int $value): float {
|
||||
$value = $value / 255;
|
||||
return ($value <= 0.04045)
|
||||
? $value / 12.92
|
||||
: pow(($value + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
public static function tosRGB(float $value): int {
|
||||
$normalized = max(0, min(1, $value));
|
||||
return ($normalized <= 0.0031308)
|
||||
? (int) round($normalized * 12.92 * 255 + 0.5)
|
||||
: (int) round((1.055 * pow($normalized, 1 / 2.4) - 0.055) * 255 + 0.5);
|
||||
}
|
||||
}
|
24
app/Util/Blurhash/DC.php
Normal file
24
app/Util/Blurhash/DC.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\Blurhash;
|
||||
|
||||
final class DC {
|
||||
|
||||
public static function encode(array $value): int {
|
||||
$rounded_r = Color::tosRGB($value[0]);
|
||||
$rounded_g = Color::tosRGB($value[1]);
|
||||
$rounded_b = Color::tosRGB($value[2]);
|
||||
return ($rounded_r << 16) + ($rounded_g << 8) + $rounded_b;
|
||||
}
|
||||
|
||||
public static function decode(int $value): array {
|
||||
$r = $value >> 16;
|
||||
$g = ($value >> 8) & 255;
|
||||
$b = $value & 255;
|
||||
return [
|
||||
Color::toLinear($r),
|
||||
Color::toLinear($g),
|
||||
Color::toLinear($b)
|
||||
];
|
||||
}
|
||||
}
|
47
app/Util/Media/Blurhash.php
Normal file
47
app/Util/Media/Blurhash.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\Media;
|
||||
|
||||
use App\Util\Blurhash\Blurhash as BlurhashEngine;
|
||||
use App\Media;
|
||||
|
||||
class Blurhash {
|
||||
|
||||
public static function generate(Media $media)
|
||||
{
|
||||
if(!in_array($media->mime, ['image/png', 'image/jpeg'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = storage_path('app/' . $media->thumbnail_path);
|
||||
|
||||
if(!is_file($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$image = imagecreatefromstring(file_get_contents($file));
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
|
||||
$pixels = [];
|
||||
for ($y = 0; $y < $height; ++$y) {
|
||||
$row = [];
|
||||
for ($x = 0; $x < $width; ++$x) {
|
||||
$index = imagecolorat($image, $x, $y);
|
||||
$colors = imagecolorsforindex($image, $index);
|
||||
|
||||
$row[] = [$colors['red'], $colors['green'], $colors['blue']];
|
||||
}
|
||||
$pixels[] = $row;
|
||||
}
|
||||
|
||||
$components_x = 4;
|
||||
$components_y = 4;
|
||||
$blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y);
|
||||
if(strlen($blurhash) > 191) {
|
||||
return;
|
||||
}
|
||||
return $blurhash;
|
||||
}
|
||||
|
||||
}
|
|
@ -182,6 +182,10 @@ class Image
|
|||
|
||||
|
||||
$media->save();
|
||||
|
||||
if($thumbnail) {
|
||||
$this->generateBlurhash($media);
|
||||
}
|
||||
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||
Cache::forget('status:thumb:'.$media->status_id);
|
||||
} catch (Exception $e) {
|
||||
|
@ -198,4 +202,13 @@ class Image
|
|||
|
||||
return ['path' => $basePath, 'png' => $png];
|
||||
}
|
||||
|
||||
protected function generateBlurhash($media)
|
||||
{
|
||||
$blurhash = Blurhash::generate($media);
|
||||
if($blurhash) {
|
||||
$media->blurhash = $blurhash;
|
||||
$media->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIndexesToLikesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('likes', function (Blueprint $table) {
|
||||
$table->index('profile_id', 'likes_profile_id_index');
|
||||
$table->index('status_id', 'likes_status_id_index');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('likes', function (Blueprint $table) {
|
||||
$table->dropIndex('likes_profile_id_index');
|
||||
$table->dropIndex('likes_status_id_index');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateAccountInterstitialsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('account_interstitials', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('user_id')->nullable()->index();
|
||||
$table->string('type')->nullable();
|
||||
$table->string('view')->nullable();
|
||||
$table->bigInteger('item_id')->unsigned()->nullable();
|
||||
$table->string('item_type')->nullable();
|
||||
$table->boolean('has_media')->default(false)->nullable();
|
||||
$table->string('blurhash')->nullable();
|
||||
$table->text('message')->nullable();
|
||||
$table->text('violation_header')->nullable();
|
||||
$table->text('violation_body')->nullable();
|
||||
$table->json('meta')->nullable();
|
||||
$table->text('appeal_message')->nullable();
|
||||
$table->timestamp('appeal_requested_at')->nullable()->index();
|
||||
$table->timestamp('appeal_handled_at')->nullable()->index();
|
||||
$table->timestamp('read_at')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('users', function(Blueprint $table) {
|
||||
$table->boolean('has_interstitial')->default(false)->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('account_interstitials');
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('has_interstitial');
|
||||
});
|
||||
}
|
||||
}
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -1649,6 +1649,11 @@
|
|||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
|
||||
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg=="
|
||||
},
|
||||
"blurhash": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-1.1.3.tgz",
|
||||
"integrity": "sha512-yUhPJvXexbqbyijCIE/T2NCXcj9iNPhWmOKbPTuR/cm7Q5snXYIfnVnz6m7MWOXxODMz/Cr3UcVkRdHiuDVRDw=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"dependencies": {
|
||||
"@trevoreyre/autocomplete-vue": "^2.2.0",
|
||||
"animate.css": "^4.1.0",
|
||||
"blurhash": "^1.1.3",
|
||||
"bootstrap-vue": "^2.16.0",
|
||||
"filesize": "^3.6.1",
|
||||
"howler": "^2.2.0",
|
||||
|
|
BIN
public/js/activity.js
vendored
BIN
public/js/activity.js
vendored
Binary file not shown.
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
BIN
public/js/collectioncompose.js
vendored
BIN
public/js/collectioncompose.js
vendored
Binary file not shown.
BIN
public/js/collections.js
vendored
BIN
public/js/collections.js
vendored
Binary file not shown.
BIN
public/js/compose.js
vendored
BIN
public/js/compose.js
vendored
Binary file not shown.
BIN
public/js/direct.js
vendored
BIN
public/js/direct.js
vendored
Binary file not shown.
BIN
public/js/discover.js
vendored
BIN
public/js/discover.js
vendored
Binary file not shown.
BIN
public/js/hashtag.js
vendored
BIN
public/js/hashtag.js
vendored
Binary file not shown.
BIN
public/js/memoryprofile.js
vendored
Normal file
BIN
public/js/memoryprofile.js
vendored
Normal file
Binary file not shown.
BIN
public/js/mode-dot.js
vendored
BIN
public/js/mode-dot.js
vendored
Binary file not shown.
BIN
public/js/profile-directory.js
vendored
BIN
public/js/profile-directory.js
vendored
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/quill.js
vendored
BIN
public/js/quill.js
vendored
Binary file not shown.
BIN
public/js/rempos.js
vendored
BIN
public/js/rempos.js
vendored
Binary file not shown.
BIN
public/js/rempro.js
vendored
BIN
public/js/rempro.js
vendored
Binary file not shown.
BIN
public/js/search.js
vendored
BIN
public/js/search.js
vendored
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/story-compose.js
vendored
BIN
public/js/story-compose.js
vendored
Binary file not shown.
BIN
public/js/theme-monokai.js
vendored
BIN
public/js/theme-monokai.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
BIN
public/js/vendor.js
vendored
BIN
public/js/vendor.js
vendored
Binary file not shown.
Binary file not shown.
1
resources/assets/js/app.js
vendored
1
resources/assets/js/app.js
vendored
|
@ -6,6 +6,7 @@ require('bootstrap');
|
|||
window.axios = require('axios');
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
require('readmore-js');
|
||||
window.blurhash = require("blurhash");
|
||||
|
||||
let token = document.head.querySelector('meta[name="csrf-token"]');
|
||||
if (token) {
|
||||
|
|
|
@ -635,15 +635,23 @@ export default {
|
|||
methods: {
|
||||
fetchProfile() {
|
||||
let self = this;
|
||||
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
||||
self.profile = res.data;
|
||||
window.pixelfed.currentUser = res.data;
|
||||
if(res.data.locked == true) {
|
||||
self.visibility = 'private';
|
||||
self.visibilityTag = 'Followers Only';
|
||||
if(window._sharedData.curUser) {
|
||||
self.profile = window._sharedData.curUser;
|
||||
if(self.profile.locked == true) {
|
||||
self.visibility = 'private';
|
||||
self.visibilityTag = 'Followers Only';
|
||||
}
|
||||
}).catch(err => {
|
||||
});
|
||||
} else {
|
||||
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
||||
self.profile = res.data;
|
||||
window.pixelfed.currentUser = res.data;
|
||||
if(res.data.locked == true) {
|
||||
self.visibility = 'private';
|
||||
self.visibilityTag = 'Followers Only';
|
||||
}
|
||||
}).catch(err => {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
addMedia(event) {
|
||||
|
|
130
resources/views/account/moderation/post/cw.blade.php
Normal file
130
resources/views/account/moderation/post/cw.blade.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 offset-md-3 text-center">
|
||||
<p class="h1 pb-2" style="font-weight: 200">Your Post Contains Sensitive or Offensive Material</p>
|
||||
<p class="lead py-3">We applied a Content Warning to your post because it doesn't follow our <a class="font-weight-bold text-dark" href="{{route('help.community-guidelines')}}">Community Guidelines</a>.</p>
|
||||
<p class="font-weight-bold alert alert-danger text-left">To continue you must click the "I Understand" button or "REQUEST APPEAL" button at the bottom of this page.</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3">
|
||||
<hr>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 mt-3">
|
||||
<p class="h4 font-weight-bold">Post Details</p>
|
||||
@if($interstitial->has_media)
|
||||
<div class="py-4 align-items-center">
|
||||
<div class="d-block text-center text-truncate">
|
||||
@if($interstitial->blurhash)
|
||||
<canvas id="mblur" width="400" height="400" class="rounded shadow"></canvas>
|
||||
@else
|
||||
<img src="/storage/no-preview.png" class="mr-3 img-fluid" alt="No preview available">
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-2 border rounded p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
Caption: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="mb-0" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="py-4 align-items-center">
|
||||
<div class="mt-2 border rounded p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
Comment: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="mb-0" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="appealButton" class="col-12 col-md-6 offset-md-3 mt-3">
|
||||
<button type="button" class="btn btn-outline-primary btn-block font-weight-bold" onclick="requestAppeal()">REQUEST APPEAL</button>
|
||||
</div>
|
||||
<div id="appealForm" class="col-12 col-md-6 offset-md-3 d-none mt-3">
|
||||
<form method="post" action="/i/warning">
|
||||
@csrf
|
||||
<p class="h4 font-weight-bold">Request Appeal</p>
|
||||
<p class="pt-4">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="4" placeholder="Write your appeal request message here" name="appeal_message"></textarea>
|
||||
</div>
|
||||
</p>
|
||||
{{-- <p class="lead"><span class="font-weight-bold">Learn more</span> about what we remove.</p> --}}
|
||||
<input type="hidden" name="id" value="{{encrypt($interstitial->id)}}">
|
||||
<input type="hidden" name="type" value="{{$interstitial->type}}">
|
||||
<input type="hidden" name="action" value="appeal">
|
||||
<button type="submit" class="btn btn-outline-primary btn-block font-weight-bold">REQUEST APPEAL</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-4">
|
||||
|
||||
<form method="post" action="/i/warning">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="id" value="{{encrypt($interstitial->id)}}">
|
||||
<input type="hidden" name="type" value="{{$interstitial->type}}">
|
||||
<input type="hidden" name="action" value="confirm">
|
||||
<button type="submit" class="btn btn-primary btn-block font-weight-bold">I Understand</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
function requestAppeal() {
|
||||
$('#appealButton').addClass('d-none');
|
||||
$('#appealForm').removeClass('d-none');
|
||||
}
|
||||
</script>
|
||||
@if($interstitial->blurhash)
|
||||
<script type="text/javascript">
|
||||
const pixels = window.blurhash.decode("{{$interstitial->blurhash}}", 400, 400);
|
||||
const canvas = document.getElementById("mblur");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imageData = ctx.createImageData(400, 400);
|
||||
imageData.data.set(pixels);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
99
resources/views/account/moderation/post/removed.blade.php
Normal file
99
resources/views/account/moderation/post/removed.blade.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 offset-md-3 text-center">
|
||||
<p class="h1 pb-2" style="font-weight: 200">Your Post Has Been Deleted</p>
|
||||
<p class="lead py-1">We removed your post because it doesn't follow our <a class="font-weight-bold text-dark" href="{{route('help.community-guidelines')}}">Community Guidelines</a>. If you violate our guidelines again, your account may be restricted or disabled.</p>
|
||||
<p class="font-weight-bold alert alert-danger text-left">To continue you must click the "I Understand" button at the bottom of this page.</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3">
|
||||
<hr>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 mt-3">
|
||||
<p class="h4 font-weight-bold">Post Details</p>
|
||||
@if($interstitial->has_media)
|
||||
<div class="py-4 align-items-center">
|
||||
<div class="d-block text-center text-truncate">
|
||||
@if($interstitial->blurhash)
|
||||
<canvas id="mblur" width="400" height="400" class="rounded shadow"></canvas>
|
||||
@else
|
||||
<img src="/storage/no-preview.png" class="mr-3 img-fluid" alt="No preview available">
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-2 border rounded p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
Caption: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="mb-0" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="media py-4 align-items-center">
|
||||
<div class="media-body ml-2">
|
||||
<p class="">
|
||||
Comment: <span class="lead text-break font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
<p class="mb-0 small">
|
||||
Posted on {{$meta->created_at}}
|
||||
</p>
|
||||
<p class="mb-0 font-weight-bold text-primary">
|
||||
{{$meta->url}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-5">
|
||||
<form method="post" action="/i/warning">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="id" value="{{encrypt($interstitial->id)}}">
|
||||
<input type="hidden" name="type" value="{{$interstitial->type}}">
|
||||
<input type="hidden" name="action" value="confirm">
|
||||
<button type="submit" class="btn btn-primary btn-block font-weight-bold">I Understand</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
function requestAppeal() {
|
||||
$('#appealButton').addClass('d-none');
|
||||
$('#appealForm').removeClass('d-none');
|
||||
}
|
||||
</script>
|
||||
@if($interstitial->blurhash)
|
||||
<script type="text/javascript">
|
||||
const pixels = window.blurhash.decode("{{$interstitial->blurhash}}", 400, 400);
|
||||
const canvas = document.getElementById("mblur");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imageData = ctx.createImageData(400, 400);
|
||||
imageData.data.set(pixels);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
128
resources/views/account/moderation/post/unlist.blade.php
Normal file
128
resources/views/account/moderation/post/unlist.blade.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 offset-md-3 text-center">
|
||||
<p class="h1 pb-2" style="font-weight: 200">Your Post Was Unlisted</p>
|
||||
<p class="lead py-3">We removed your post from public timelines because it doesn't follow our <a class="font-weight-bold text-dark" href="{{route('help.community-guidelines')}}">Community Guidelines</a>.</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3">
|
||||
<hr>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3">
|
||||
<p class="h4 font-weight-bold">Post Details</p>
|
||||
@if($interstitial->has_media)
|
||||
<div class="py-4 align-items-center">
|
||||
<div class="d-block text-center text-truncate">
|
||||
@if($interstitial->blurhash)
|
||||
<canvas id="mblur" width="400" height="400" class="rounded shadow"></canvas>
|
||||
@else
|
||||
<img src="/storage/no-preview.png" class="mr-3 img-fluid" alt="No preview available">
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-2 border rounded p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
Caption: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="mb-0" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="py-4 align-items-center">
|
||||
<div class="mt-2 border rounded p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
Comment: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="mb-0" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="appealButton" class="col-12 col-md-6 offset-md-3">
|
||||
<button type="button" class="btn btn-outline-primary btn-block font-weight-bold" onclick="requestAppeal()">REQUEST APPEAL</button>
|
||||
</div>
|
||||
<div id="appealForm" class="col-12 col-md-6 offset-md-3 d-none">
|
||||
<form method="post" action="/i/warning">
|
||||
@csrf
|
||||
|
||||
<p class="h4 font-weight-bold">Request Appeal</p>
|
||||
<p class="pt-4">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="4" placeholder="Write your appeal request message here" name="appeal_message"></textarea>
|
||||
</div>
|
||||
</p>
|
||||
<input type="hidden" name="id" value="{{encrypt($interstitial->id)}}">
|
||||
<input type="hidden" name="type" value="{{$interstitial->type}}">
|
||||
<input type="hidden" name="action" value="appeal">
|
||||
<button type="submit" class="btn btn-outline-primary btn-block font-weight-bold">REQUEST APPEAL</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-4">
|
||||
<form method="post" action="/i/warning">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="id" value="{{encrypt($interstitial->id)}}">
|
||||
<input type="hidden" name="type" value="{{$interstitial->type}}">
|
||||
<input type="hidden" name="action" value="confirm">
|
||||
<button type="submit" class="btn btn-primary btn-block font-weight-bold">I Understand</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
function requestAppeal() {
|
||||
$('#appealButton').addClass('d-none');
|
||||
$('#appealForm').removeClass('d-none');
|
||||
}
|
||||
</script>
|
||||
@if($interstitial->blurhash)
|
||||
<script type="text/javascript">
|
||||
const pixels = window.blurhash.decode("{{$interstitial->blurhash}}", 400, 400);
|
||||
const canvas = document.getElementById("mblur");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imageData = ctx.createImageData(400, 400);
|
||||
imageData.data.set(pixels);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
63
resources/views/admin/reports/appeals.blade.php
Normal file
63
resources/views/admin/reports/appeals.blade.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
@extends('admin.partial.template-full')
|
||||
|
||||
@section('section')
|
||||
<div class="title mb-3">
|
||||
<h3 class="font-weight-bold d-inline-block">Appeals</h3>
|
||||
<span class="float-right">
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card border bg-primary text-white rounded-pill shadow">
|
||||
<div class="card-body pl-4 ml-3">
|
||||
<p class="h1 font-weight-bold mb-1" style="font-weight: 700">{{App\AccountInterstitial::whereNull('appeal_handled_at')->whereNotNull('appeal_requested_at')->count()}}</p>
|
||||
<p class="lead mb-0 font-weight-lighter">active appeals</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 card border bg-warning text-dark rounded-pill shadow">
|
||||
<div class="card-body pl-4 ml-3">
|
||||
<p class="h1 font-weight-bold mb-1" style="font-weight: 700">{{App\AccountInterstitial::whereNotNull('appeal_handled_at')->whereNotNull('appeal_requested_at')->count()}}</p>
|
||||
<p class="lead mb-0 font-weight-lighter">closed appeals</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 offset-md-1">
|
||||
<ul class="list-group">
|
||||
@if($appeals->count() == 0)
|
||||
<li class="list-group-item text-center py-5">
|
||||
<p class="mb-0 py-5 font-weight-bold">No appeals found!</p>
|
||||
</li>
|
||||
@endif
|
||||
@foreach($appeals as $appeal)
|
||||
<a class="list-group-item text-decoration-none text-dark" href="/i/admin/reports/appeal/{{$appeal->id}}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="{{$appeal->has_media ? $appeal->status->thumb(true) : '/storage/no-preview.png'}}" width="64" height="64" class="rounded border">
|
||||
<div class="ml-2">
|
||||
<span class="d-inline-block text-truncate">
|
||||
<p class="mb-0 small font-weight-bold text-primary">{{$appeal->type}}</p>
|
||||
@if($appeal->item_type)
|
||||
<p class="mb-0 font-weight-bold">{{starts_with($appeal->item_type, 'App\\') ? explode('\\',$appeal->item_type)[1] : $appeal->item_type}}</p>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-block">
|
||||
<p class="mb-0 font-weight-bold">@{{$appeal->user->username}}</p>
|
||||
<p class="mb-0 small text-muted font-weight-bold">{{$appeal->created_at->diffForHumans(null, null, true)}}</p>
|
||||
</div>
|
||||
<div class="d-inline-block">
|
||||
<p class="mb-0 small">
|
||||
<i class="fas fa-chevron-right fa-2x text-lighter"></i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</ul>
|
||||
<p>{!!$appeals->render()!!}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
|
@ -15,6 +15,15 @@
|
|||
</a>
|
||||
</span>
|
||||
</div>
|
||||
@php($ai = App\AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count())
|
||||
@if($ai)
|
||||
<div class="mb-4">
|
||||
<a class="btn btn-outline-primary px-5 py-3" href="/i/admin/reports/appeals">
|
||||
<p class="font-weight-bold h4 mb-0">{{$ai}}</p>
|
||||
Appeal {{$ai == 1 ? 'Request' : 'Requests'}}
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
@if($reports->count())
|
||||
<div class="card shadow-none border">
|
||||
<div class="list-group list-group-flush">
|
||||
|
|
125
resources/views/admin/reports/show_appeal.blade.php
Normal file
125
resources/views/admin/reports/show_appeal.blade.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
@extends('admin.partial.template-full')
|
||||
|
||||
@section('section')
|
||||
<div class="d-flex justify-content-between title mb-3">
|
||||
<div>
|
||||
<p class="font-weight-bold h3">Moderation Appeal</p>
|
||||
<p class="text-muted mb-0 lead">From <a href="{{$appeal->user->url()}}" class="text-muted font-weight-bold">@{{$appeal->user->username}}</a> about {{$appeal->appeal_requested_at->diffForHumans()}}.</p>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 mt-3">
|
||||
@if($appeal->type == 'post.cw')
|
||||
<div class="card shadow-none border">
|
||||
<div class="card-header bg-light h5 font-weight-bold py-4">Content Warning applied to {{$appeal->has_media ? 'Post' : 'Comment'}}</div>
|
||||
@if($appeal->has_media)
|
||||
<img class="card-img-top border-bottom" src="{{$appeal->status->thumb(true)}}">
|
||||
@endif
|
||||
<div class="card-body">
|
||||
<div class="mt-2 p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
{{$appeal->has_media ? 'Caption' : 'Comment'}}: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary"><a href="{{$meta->url}}">{{$meta->url}}</a></span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Message: <span class="font-weight-bold">{{$appeal->appeal_message}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($appeal->type == 'post.unlist')
|
||||
<div class="card shadow-none border">
|
||||
<div class="card-header bg-light h5 font-weight-bold py-4">{{$appeal->has_media ? 'Post' : 'Comment'}} was unlisted from timelines</div>
|
||||
@if($appeal->has_media)
|
||||
<img class="card-img-top border-bottom" src="{{$appeal->status->thumb(true)}}">
|
||||
@endif
|
||||
<div class="card-body">
|
||||
<div class="mt-2 p-3">
|
||||
@if($meta->caption)
|
||||
<p class="text-break">
|
||||
{{$appeal->has_media ? 'Caption' : 'Comment'}}: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||
</p>
|
||||
@endif
|
||||
<p class="mb-0">
|
||||
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||
</p>
|
||||
<p class="" style="word-break: break-all !important;">
|
||||
URL: <span class="font-weight-bold text-primary"><a href="{{$meta->url}}">{{$meta->url}}</a></span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Message: <span class="font-weight-bold">{{$appeal->appeal_message}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-12 col-md-4 mt-3">
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="dismiss">
|
||||
<button type="submit" class="btn btn-primary btn-block font-weight-bold mb-3">Dismiss Appeal Request</button>
|
||||
</form>
|
||||
<button type="button" class="btn btn-light border btn-block font-weight-bold mb-3" onclick="approveWarning()">Approve Appeal</button>
|
||||
<div class="card shadow-none border mt-5">
|
||||
<div class="card-header text-center font-weight-bold bg-light">
|
||||
@{{$appeal->user->username}} stats
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="">
|
||||
Open Appeals: <span class="font-weight-bold">{{App\AccountInterstitial::whereUserId($appeal->user_id)->whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count()}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Total Appeals: <span class="font-weight-bold">{{App\AccountInterstitial::whereUserId($appeal->user_id)->whereNotNull('appeal_requested_at')->count()}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Total Warnings: <span class="font-weight-bold">{{App\AccountInterstitial::whereUserId($appeal->user_id)->count()}}</span>
|
||||
</p>
|
||||
<p class="">
|
||||
Status Count: <span class="font-weight-bold">{{$appeal->user->statuses()->count()}}</span>
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Joined: <span class="font-weight-bold">{{$appeal->user->created_at->diffForHumans(null, null, false)}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
function approveWarning() {
|
||||
if(window.confirm('Are you sure you want to approve this appeal?') == true) {
|
||||
axios.post(window.location.href, {
|
||||
action: 'approve'
|
||||
}).then(res => {
|
||||
window.location.href = '/i/admin/reports/appeals';
|
||||
}).catch(err => {
|
||||
swal('Oops!', 'An error occured, please try again later.', 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endpush
|
|
@ -8,6 +8,5 @@
|
|||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/discover.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
||||
<script type="text/javascript">App.boot();</script>
|
||||
@endpush
|
35
resources/views/profile/memory.blade.php
Normal file
35
resources/views/profile/memory.blade.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
@extends('layouts.app',['title' => $profile->username . " on " . config('app.name')])
|
||||
|
||||
@section('content')
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger text-center font-weight-bold mb-0">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<memory-profile profile-id="{{$profile->id}}" profile-username="{{$profile->username}}" :profile-settings="{{json_encode($settings)}}" profile-layout="{{$profile->profile_layout ?? 'metro'}}"></memory-profile>
|
||||
@if($profile->website)
|
||||
<a class="d-none" href="{{$profile->website}}" rel="me">{{$profile->website}}</a>
|
||||
@endif
|
||||
|
||||
<noscript>
|
||||
<div class="container">
|
||||
<p class="pt-5 text-center lead">Please enable javascript to view this content.</p>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
@endsection
|
||||
|
||||
@push('meta')<meta property="og:description" content="{{$profile->bio}}">
|
||||
@if(false == $settings['crawlable'] || $profile->remote_url)
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
@else <meta property="og:image" content="{{$profile->avatarUrl()}}">
|
||||
<link href="{{$profile->permalink('.atom')}}" rel="alternate" title="{{$profile->username}} on Pixelfed" type="application/atom+xml">
|
||||
<link href='{{$profile->permalink()}}' rel='alternate' type='application/activity+json'>
|
||||
@endif
|
||||
@endpush
|
||||
|
||||
@push('scripts')<script type="text/javascript" src="{{ mix('js/memoryprofile.js') }}"></script>
|
||||
<script type="text/javascript" defer>App.boot();</script>
|
||||
|
||||
@endpush
|
|
@ -8,6 +8,9 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
Route::get('reports/show/{id}', 'AdminController@showReport');
|
||||
Route::post('reports/show/{id}', 'AdminController@updateReport');
|
||||
Route::post('reports/bulk', 'AdminController@bulkUpdateReport');
|
||||
Route::get('reports/appeals', 'AdminController@appeals');
|
||||
Route::get('reports/appeal/{id}', 'AdminController@showAppeal');
|
||||
Route::post('reports/appeal/{id}', 'AdminController@updateAppeal');
|
||||
Route::redirect('statuses', '/statuses/list');
|
||||
Route::get('statuses/list', 'AdminController@statuses')->name('admin.statuses');
|
||||
Route::get('statuses/show/{id}', 'AdminController@showStatus');
|
||||
|
@ -73,7 +76,7 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
Route::post('newsroom/create', 'AdminController@newsroomStore');
|
||||
});
|
||||
|
||||
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
|
||||
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization','interstitial'])->group(function () {
|
||||
Route::get('/', 'SiteController@home')->name('timeline.personal');
|
||||
Route::post('/', 'StatusController@store');
|
||||
|
||||
|
@ -125,6 +128,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('discover/tag', 'DiscoverController@getHashtags');
|
||||
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'pixelfed'], function() {
|
||||
Route::group(['prefix' => 'v1'], function() {
|
||||
Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
|
||||
|
@ -146,6 +150,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('timelines/home', 'PublicApiController@homeTimelineApi');
|
||||
Route::get('newsroom/timeline', 'NewsroomController@timelineApi');
|
||||
Route::post('newsroom/markasread', 'NewsroomController@markAsRead');
|
||||
Route::get('favourites', 'Api\BaseApiController@accountLikes');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'v2'], function() {
|
||||
|
@ -169,6 +174,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('discover/posts/places', 'DiscoverController@trendingPlaces');
|
||||
});
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'local'], function () {
|
||||
// Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
|
||||
// Route::get('accounts/relationships', 'PublicApiController@relationships');
|
||||
|
@ -295,6 +301,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('redirect', 'SiteController@redirectUrl');
|
||||
Route::post('admin/media/block/add', 'MediaBlocklistController@add');
|
||||
Route::post('admin/media/block/delete', 'MediaBlocklistController@delete');
|
||||
|
||||
Route::get('warning', 'AccountInterstitialController@get');
|
||||
Route::post('warning', 'AccountInterstitialController@read');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'account'], function () {
|
||||
|
|
Loading…
Reference in a new issue