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 Password change email notification ([de1cca4f](https://github.com/pixelfed/pixelfed/commit/de1cca4f))
|
||||||
- Add shared inbox ([4733ca9f](https://github.com/pixelfed/pixelfed/commit/4733ca9f))
|
- Add shared inbox ([4733ca9f](https://github.com/pixelfed/pixelfed/commit/4733ca9f))
|
||||||
- Add federated photo filters ([0a5a0e86](https://github.com/pixelfed/pixelfed/commit/0a5a0e86))
|
- 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
|
||||||
- Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc))
|
- 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 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, 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 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)
|
## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9)
|
||||||
### Added
|
### 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;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\{
|
use App\{
|
||||||
|
AccountInterstitial,
|
||||||
Contact,
|
Contact,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
Newsroom,
|
Newsroom,
|
||||||
|
@ -85,6 +86,67 @@ class AdminController extends Controller
|
||||||
return view('admin.reports.show', compact('report'));
|
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)
|
public function profiles(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
|
|
@ -1761,6 +1761,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
NewStatusPipeline::dispatch($status);
|
NewStatusPipeline::dispatch($status);
|
||||||
Cache::forget('user:account:id:'.$user->id);
|
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('profile:status_count:'.$user->profile_id);
|
||||||
Cache::forget($user->storageUsedKey());
|
Cache::forget($user->storageUsedKey());
|
||||||
|
|
||||||
|
@ -1783,10 +1784,15 @@ class ApiV1Controller extends Controller
|
||||||
$status = Status::whereProfileId($request->user()->profile->id)
|
$status = Status::whereProfileId($request->user()->profile->id)
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||||
|
|
||||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
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 Carbon\Carbon;
|
||||||
use App\{
|
use App\{
|
||||||
Avatar,
|
Avatar,
|
||||||
Notification,
|
Like,
|
||||||
Media,
|
Media,
|
||||||
|
Notification,
|
||||||
Profile,
|
Profile,
|
||||||
Status
|
Status
|
||||||
};
|
};
|
||||||
|
@ -21,7 +22,8 @@ use App\Transformer\Api\{
|
||||||
NotificationTransformer,
|
NotificationTransformer,
|
||||||
MediaTransformer,
|
MediaTransformer,
|
||||||
MediaDraftTransformer,
|
MediaDraftTransformer,
|
||||||
StatusTransformer
|
StatusTransformer,
|
||||||
|
StatusStatelessTransformer
|
||||||
};
|
};
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use App\Util\Media\Filter;
|
use App\Util\Media\Filter;
|
||||||
|
@ -338,4 +340,29 @@ class BaseApiController extends Controller
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
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 Illuminate\Http\Request;
|
||||||
use App\{
|
use App\{
|
||||||
|
AccountInterstitial,
|
||||||
DirectMessage,
|
DirectMessage,
|
||||||
DiscoverCategory,
|
DiscoverCategory,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
|
@ -213,6 +214,35 @@ class InternalApiController extends Controller
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->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;
|
break;
|
||||||
|
|
||||||
case 'remcw':
|
case 'remcw':
|
||||||
|
@ -231,6 +261,14 @@ class InternalApiController extends Controller
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->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;
|
break;
|
||||||
|
|
||||||
case 'unlist':
|
case 'unlist':
|
||||||
|
@ -250,6 +288,34 @@ class InternalApiController extends Controller
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->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;
|
break;
|
||||||
}
|
}
|
||||||
return ['msg' => 200];
|
return ['msg' => 200];
|
||||||
|
@ -364,6 +430,7 @@ class InternalApiController extends Controller
|
||||||
|
|
||||||
NewStatusPipeline::dispatch($status);
|
NewStatusPipeline::dispatch($status);
|
||||||
Cache::forget('user:account:id:'.$profile->user_id);
|
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('profile:status_count:'.$profile->id);
|
||||||
Cache::forget($user->storageUsedKey());
|
Cache::forget($user->storageUsedKey());
|
||||||
return $status->url();
|
return $status->url();
|
||||||
|
|
|
@ -66,7 +66,9 @@ class ProfileController extends Controller
|
||||||
'list' => $settings->show_profile_followers
|
'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 {
|
} else {
|
||||||
$key = 'profile:settings:' . $user->id;
|
$key = 'profile:settings:' . $user->id;
|
||||||
$ttl = now()->addHours(6);
|
$ttl = now()->addHours(6);
|
||||||
|
@ -103,7 +105,8 @@ class ProfileController extends Controller
|
||||||
'list' => $settings->show_profile_followers
|
'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\{
|
use App\Transformer\Api\{
|
||||||
AccountTransformer,
|
AccountTransformer,
|
||||||
RelationshipTransformer,
|
RelationshipTransformer,
|
||||||
StatusTransformer
|
StatusTransformer,
|
||||||
|
StatusStatelessTransformer
|
||||||
};
|
};
|
||||||
use App\Services\{
|
use App\Services\{
|
||||||
AccountService,
|
AccountService,
|
||||||
|
@ -86,6 +87,24 @@ class PublicApiController extends Controller
|
||||||
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
|
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
|
||||||
$status = Status::whereProfileId($profile->id)->findOrFail($postid);
|
$status = Status::whereProfileId($profile->id)->findOrFail($postid);
|
||||||
$this->scopeCheck($profile, $status);
|
$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());
|
$item = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||||
$res = [
|
$res = [
|
||||||
'status' => $this->fractal->createData($item)->toArray(),
|
'status' => $this->fractal->createData($item)->toArray(),
|
||||||
|
@ -419,7 +438,6 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function networkTimelineApi(Request $request)
|
public function networkTimelineApi(Request $request)
|
||||||
{
|
{
|
||||||
return response()->json([]);
|
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 ? '>' : '<';
|
$dir = $min_id ? '>' : '<';
|
||||||
$id = $min_id ?? $max_id;
|
$id = $min_id ?? $max_id;
|
||||||
$timeline = Status::select(
|
$timeline = Status::select(
|
||||||
|
@ -560,6 +622,8 @@ class PublicApiController extends Controller
|
||||||
'scope',
|
'scope',
|
||||||
'visibility',
|
'visibility',
|
||||||
'local',
|
'local',
|
||||||
|
'place_id',
|
||||||
|
'comments_disabled',
|
||||||
'cw_summary',
|
'cw_summary',
|
||||||
'created_at',
|
'created_at',
|
||||||
'updated_at'
|
'updated_at'
|
||||||
|
|
|
@ -6,6 +6,7 @@ use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
use App\Jobs\SharePipeline\SharePipeline;
|
use App\Jobs\SharePipeline\SharePipeline;
|
||||||
|
use App\AccountInterstitial;
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
|
@ -162,14 +163,49 @@ class StatusController extends Controller
|
||||||
|
|
||||||
$status = Status::findOrFail($request->input('item'));
|
$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);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
StatusDelete::dispatch($status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($request->wantsJson()) {
|
if($request->wantsJson()) {
|
||||||
return response()->json(['Status successfully deleted.']);
|
return response()->json(['Status successfully deleted.']);
|
||||||
} else {
|
} else {
|
||||||
return redirect(Auth::user()->url());
|
return redirect($user->url());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ class Kernel extends HttpKernel
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'twofactor' => \App\Http\Middleware\TwoFactorAuth::class,
|
'twofactor' => \App\Http\Middleware\TwoFactorAuth::class,
|
||||||
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
|
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
|
||||||
|
'interstitial' => \App\Http\Middleware\AccountInterstitial::class,
|
||||||
// 'restricted' => \App\Http\Middleware\RestrictedAccess::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 DB;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\{
|
use App\{
|
||||||
|
AccountInterstitial,
|
||||||
AccountLog,
|
AccountLog,
|
||||||
Activity,
|
Activity,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
@ -68,6 +69,10 @@ class DeleteAccountPipeline implements ShouldQueue
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DB::transaction(function() use ($user) {
|
||||||
|
AccountInterstitial::whereUserId($user->id)->delete();
|
||||||
|
});
|
||||||
|
|
||||||
DB::transaction(function() use ($user) {
|
DB::transaction(function() use ($user) {
|
||||||
if($user->profile) {
|
if($user->profile) {
|
||||||
$avatar = $user->profile->avatar;
|
$avatar = $user->profile->avatar;
|
||||||
|
@ -79,6 +84,7 @@ class DeleteAccountPipeline implements ShouldQueue
|
||||||
Bookmark::whereProfileId($user->profile_id)->forceDelete();
|
Bookmark::whereProfileId($user->profile_id)->forceDelete();
|
||||||
EmailVerification::whereUserId($user->id)->forceDelete();
|
EmailVerification::whereUserId($user->id)->forceDelete();
|
||||||
StatusHashtag::whereProfileId($id)->delete();
|
StatusHashtag::whereProfileId($id)->delete();
|
||||||
|
DirectMessage::whereFromId($user->profile_id)->delete();
|
||||||
FollowRequest::whereFollowingId($id)
|
FollowRequest::whereFollowingId($id)
|
||||||
->orWhere('follower_id', $id)
|
->orWhere('follower_id', $id)
|
||||||
->forceDelete();
|
->forceDelete();
|
||||||
|
|
|
@ -89,7 +89,8 @@ class Status extends Model
|
||||||
|
|
||||||
public function thumb($showNsfw = false)
|
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();
|
$type = $this->type ?? $this->setType();
|
||||||
$is_nsfw = !$showNsfw ? $this->is_nsfw : false;
|
$is_nsfw = !$showNsfw ? $this->is_nsfw : false;
|
||||||
if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) {
|
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 App\Status;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use App\Services\HashidService;
|
||||||
|
use App\Services\MediaTagService;
|
||||||
|
|
||||||
class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -17,8 +19,11 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
|
|
||||||
public function transform(Status $status)
|
public function transform(Status $status)
|
||||||
{
|
{
|
||||||
|
$taggedPeople = MediaTagService::get($status->id);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (string) $status->id,
|
'id' => (string) $status->id,
|
||||||
|
'shortcode' => HashidService::encode($status->id),
|
||||||
'uri' => $status->url(),
|
'uri' => $status->url(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
'in_reply_to_id' => $status->in_reply_to_id,
|
'in_reply_to_id' => $status->in_reply_to_id,
|
||||||
|
@ -42,13 +47,17 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
'language' => null,
|
'language' => null,
|
||||||
'pinned' => null,
|
'pinned' => null,
|
||||||
|
|
||||||
|
'mentions' => [],
|
||||||
|
'tags' => [],
|
||||||
'pf_type' => $status->type ?? $status->setType(),
|
'pf_type' => $status->type ?? $status->setType(),
|
||||||
'reply_count' => (int) $status->reply_count,
|
'reply_count' => (int) $status->reply_count,
|
||||||
'comments_disabled' => $status->comments_disabled ? true : false,
|
'comments_disabled' => $status->comments_disabled ? true : false,
|
||||||
'thread' => false,
|
'thread' => false,
|
||||||
'replies' => [],
|
'replies' => [],
|
||||||
'parent' => $status->parent() ? $this->transform($status->parent()) : [],
|
'parent' => [],
|
||||||
|
'place' => $status->place,
|
||||||
'local' => (bool) $status->local,
|
'local' => (bool) $status->local,
|
||||||
|
'taggedPeople' => $taggedPeople
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,4 +88,9 @@ class User extends Authenticatable
|
||||||
return $this->hasMany(AccountLog::class);
|
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();
|
$media->save();
|
||||||
|
|
||||||
|
if($thumbnail) {
|
||||||
|
$this->generateBlurhash($media);
|
||||||
|
}
|
||||||
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||||
Cache::forget('status:thumb:'.$media->status_id);
|
Cache::forget('status:thumb:'.$media->status_id);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
@ -198,4 +202,13 @@ class Image
|
||||||
|
|
||||||
return ['path' => $basePath, 'png' => $png];
|
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",
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
|
||||||
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg=="
|
"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": {
|
"bn.js": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz",
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trevoreyre/autocomplete-vue": "^2.2.0",
|
"@trevoreyre/autocomplete-vue": "^2.2.0",
|
||||||
"animate.css": "^4.1.0",
|
"animate.css": "^4.1.0",
|
||||||
|
"blurhash": "^1.1.3",
|
||||||
"bootstrap-vue": "^2.16.0",
|
"bootstrap-vue": "^2.16.0",
|
||||||
"filesize": "^3.6.1",
|
"filesize": "^3.6.1",
|
||||||
"howler": "^2.2.0",
|
"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 = require('axios');
|
||||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
require('readmore-js');
|
require('readmore-js');
|
||||||
|
window.blurhash = require("blurhash");
|
||||||
|
|
||||||
let token = document.head.querySelector('meta[name="csrf-token"]');
|
let token = document.head.querySelector('meta[name="csrf-token"]');
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|
|
@ -635,15 +635,23 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
fetchProfile() {
|
fetchProfile() {
|
||||||
let self = this;
|
let self = this;
|
||||||
axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
|
if(window._sharedData.curUser) {
|
||||||
self.profile = res.data;
|
self.profile = window._sharedData.curUser;
|
||||||
window.pixelfed.currentUser = res.data;
|
if(self.profile.locked == true) {
|
||||||
if(res.data.locked == true) {
|
self.visibility = 'private';
|
||||||
self.visibility = 'private';
|
self.visibilityTag = 'Followers Only';
|
||||||
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) {
|
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>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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())
|
@if($reports->count())
|
||||||
<div class="card shadow-none border">
|
<div class="card shadow-none border">
|
||||||
<div class="list-group list-group-flush">
|
<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')
|
@push('scripts')
|
||||||
<script type="text/javascript" src="{{ mix('js/discover.js') }}"></script>
|
<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>
|
<script type="text/javascript">App.boot();</script>
|
||||||
@endpush
|
@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::get('reports/show/{id}', 'AdminController@showReport');
|
||||||
Route::post('reports/show/{id}', 'AdminController@updateReport');
|
Route::post('reports/show/{id}', 'AdminController@updateReport');
|
||||||
Route::post('reports/bulk', 'AdminController@bulkUpdateReport');
|
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::redirect('statuses', '/statuses/list');
|
||||||
Route::get('statuses/list', 'AdminController@statuses')->name('admin.statuses');
|
Route::get('statuses/list', 'AdminController@statuses')->name('admin.statuses');
|
||||||
Route::get('statuses/show/{id}', 'AdminController@showStatus');
|
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::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::get('/', 'SiteController@home')->name('timeline.personal');
|
||||||
Route::post('/', 'StatusController@store');
|
Route::post('/', 'StatusController@store');
|
||||||
|
|
||||||
|
@ -125,6 +128,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
||||||
Route::get('discover/tag', 'DiscoverController@getHashtags');
|
Route::get('discover/tag', 'DiscoverController@getHashtags');
|
||||||
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
|
Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'pixelfed'], function() {
|
Route::group(['prefix' => 'pixelfed'], function() {
|
||||||
Route::group(['prefix' => 'v1'], function() {
|
Route::group(['prefix' => 'v1'], function() {
|
||||||
Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
|
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('timelines/home', 'PublicApiController@homeTimelineApi');
|
||||||
Route::get('newsroom/timeline', 'NewsroomController@timelineApi');
|
Route::get('newsroom/timeline', 'NewsroomController@timelineApi');
|
||||||
Route::post('newsroom/markasread', 'NewsroomController@markAsRead');
|
Route::post('newsroom/markasread', 'NewsroomController@markAsRead');
|
||||||
|
Route::get('favourites', 'Api\BaseApiController@accountLikes');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'v2'], function() {
|
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::get('discover/posts/places', 'DiscoverController@trendingPlaces');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'local'], function () {
|
Route::group(['prefix' => 'local'], function () {
|
||||||
// Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
|
// Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials');
|
||||||
// Route::get('accounts/relationships', 'PublicApiController@relationships');
|
// 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::get('redirect', 'SiteController@redirectUrl');
|
||||||
Route::post('admin/media/block/add', 'MediaBlocklistController@add');
|
Route::post('admin/media/block/add', 'MediaBlocklistController@add');
|
||||||
Route::post('admin/media/block/delete', 'MediaBlocklistController@delete');
|
Route::post('admin/media/block/delete', 'MediaBlocklistController@delete');
|
||||||
|
|
||||||
|
Route::get('warning', 'AccountInterstitialController@get');
|
||||||
|
Route::post('warning', 'AccountInterstitialController@read');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'account'], function () {
|
Route::group(['prefix' => 'account'], function () {
|
||||||
|
|
Loading…
Reference in a new issue