mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-23 05:23:17 +00:00
commit
b8ad9fe5e6
77 changed files with 2217 additions and 765 deletions
165
app/Console/Commands/Installer.php
Normal file
165
app/Console/Commands/Installer.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Redis;
|
||||
|
||||
class Installer extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'install';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'CLI Installer';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->welcome();
|
||||
}
|
||||
|
||||
protected function welcome()
|
||||
{
|
||||
$this->info(' ____ _ ______ __ ');
|
||||
$this->info(' / __ \(_) _____ / / __/__ ____/ / ');
|
||||
$this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ');
|
||||
$this->info(' / ____/ /> </ __/ / __/ __/ /_/ / ');
|
||||
$this->info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ ');
|
||||
$this->info(' ');
|
||||
$this->info(' Welcome to the Pixelfed Installer!');
|
||||
$this->info(' ');
|
||||
$this->info(' ');
|
||||
$this->info('Pixelfed version: ' . config('pixelfed.version'));
|
||||
$this->line(' ');
|
||||
$this->info('Scanning system...');
|
||||
$this->preflightCheck();
|
||||
}
|
||||
protected function preflightCheck()
|
||||
{
|
||||
$this->line(' ');
|
||||
$this->info('Checking for installed dependencies...');
|
||||
$redis = Redis::connection();
|
||||
if($redis->ping()) {
|
||||
$this->info('- Found redis!');
|
||||
} else {
|
||||
$this->error('- Redis not found, aborting installation');
|
||||
exit;
|
||||
}
|
||||
$this->checkPhpDependencies();
|
||||
$this->checkPermissions();
|
||||
$this->envCheck();
|
||||
}
|
||||
|
||||
protected function checkPhpDependencies()
|
||||
{
|
||||
$extensions = [
|
||||
'bcmath',
|
||||
'ctype',
|
||||
'curl',
|
||||
'json',
|
||||
'mbstring',
|
||||
'openssl'
|
||||
];
|
||||
$this->line('');
|
||||
$this->info('Checking for required php extensions...');
|
||||
foreach($extensions as $ext) {
|
||||
if(extension_loaded($ext) == false) {
|
||||
$this->error("- {$ext} extension not found, aborting installation");
|
||||
exit;
|
||||
} else {
|
||||
$this->info("- {$ext} extension found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkPermissions()
|
||||
{
|
||||
$this->line('');
|
||||
$this->info('Checking for proper filesystem permissions...');
|
||||
|
||||
$paths = [
|
||||
base_path('bootstrap'),
|
||||
base_path('storage')
|
||||
];
|
||||
|
||||
foreach($paths as $path) {
|
||||
if(is_writeable($path) == false) {
|
||||
$this->error("- Invalid permission found! Aborting installation.");
|
||||
$this->error(" Please make the following path writeable by the web server:");
|
||||
$this->error(" $path");
|
||||
exit;
|
||||
} else {
|
||||
$this->info("- Found valid permissions for {$path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function envCheck()
|
||||
{
|
||||
if(!file_exists(base_path('.env'))) {
|
||||
$this->line('');
|
||||
$this->info('No .env configuration file found. We will create one now!');
|
||||
$this->createEnv();
|
||||
} else {
|
||||
$confirm = $this->confirm('Found .env file, do you want to overwrite it?');
|
||||
if(!$confirm) {
|
||||
$this->info('Cancelling installation.');
|
||||
exit;
|
||||
}
|
||||
$confirm = $this->confirm('Are you really sure you want to overwrite it?');
|
||||
if(!$confirm) {
|
||||
$this->info('Cancelling installation.');
|
||||
exit;
|
||||
}
|
||||
$this->error('Warning ... if you did not backup your .env before its overwritten it will be permanently deleted.');
|
||||
$confirm = $this->confirm('The application may be installed already, are you really sure you want to overwrite it?');
|
||||
if(!$confirm) {
|
||||
$this->info('Cancelling installation.');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$this->postInstall();
|
||||
}
|
||||
|
||||
protected function createEnv()
|
||||
{
|
||||
$this->line('');
|
||||
// copy env
|
||||
$name = $this->ask('Site name [ex: Pixelfed]');
|
||||
$domain = $this->ask('Site Domain [ex: pixelfed.com]');
|
||||
$tls = $this->choice('Use HTTPS/TLS?', ['https', 'http'], 0);
|
||||
$dbDrive = $this->choice('Select database driver', ['mysql', 'pgsql'/*, 'sqlite', 'sqlsrv'*/], 0);
|
||||
$ws = $this->choice('Select cache driver', ["apc", "array", "database", "file", "memcached", "redis"], 5);
|
||||
|
||||
}
|
||||
|
||||
protected function postInstall()
|
||||
{
|
||||
$this->callSilent('config:cache');
|
||||
//$this->call('route:cache');
|
||||
$this->info('Pixelfed has been successfully installed!');
|
||||
}
|
||||
}
|
51
app/Events/NewMention.php
Normal file
51
app/Events/NewMention.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use App\User;
|
||||
|
||||
class NewMention implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
protected $user;
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user, $data)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function broadcastAs()
|
||||
{
|
||||
return 'notification.new.mention';
|
||||
}
|
||||
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new PrivateChannel('App.User.' . $this->user->id);
|
||||
}
|
||||
|
||||
public function broadcastWith()
|
||||
{
|
||||
return ['id' => $this->user->id];
|
||||
}
|
||||
|
||||
public function via()
|
||||
{
|
||||
return 'broadcast';
|
||||
}
|
||||
}
|
57
app/Events/Notification/NewPublicPost.php
Normal file
57
app/Events/Notification/NewPublicPost.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events\Notification;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use App\Status;
|
||||
use App\Transformer\Api\StatusTransformer;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
|
||||
class NewPublicPost implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Status $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function broadcastAs()
|
||||
{
|
||||
return 'status';
|
||||
}
|
||||
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new Channel('firehost.public');
|
||||
}
|
||||
|
||||
public function broadcastWith()
|
||||
{
|
||||
$resource = new Fractal\Resource\Item($this->status, new StatusTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return [
|
||||
'entity' => $res
|
||||
];
|
||||
}
|
||||
|
||||
public function via()
|
||||
{
|
||||
return 'broadcast';
|
||||
}
|
||||
}
|
|
@ -128,7 +128,7 @@ class AccountController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
public function fetchNotifications($id)
|
||||
public function fetchNotifications(int $id)
|
||||
{
|
||||
$key = config('cache.prefix').":user.{$id}.notifications";
|
||||
$redis = Redis::connection();
|
||||
|
@ -167,14 +167,14 @@ class AccountController extends Controller
|
|||
public function mute(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'type' => 'required|string',
|
||||
'type' => 'required|alpha_dash',
|
||||
'item' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$user = Auth::user()->profile;
|
||||
$type = $request->input('type');
|
||||
$item = $request->input('item');
|
||||
$action = "{$type}.mute";
|
||||
$action = $type . '.mute';
|
||||
|
||||
if (!in_array($action, $this->filters)) {
|
||||
return abort(406);
|
||||
|
@ -211,17 +211,71 @@ class AccountController extends Controller
|
|||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function block(Request $request)
|
||||
public function unmute(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'type' => 'required|string',
|
||||
'type' => 'required|alpha_dash',
|
||||
'item' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$user = Auth::user()->profile;
|
||||
$type = $request->input('type');
|
||||
$item = $request->input('item');
|
||||
$action = "{$type}.block";
|
||||
$action = $type . '.mute';
|
||||
|
||||
if (!in_array($action, $this->filters)) {
|
||||
return abort(406);
|
||||
}
|
||||
$filterable = [];
|
||||
switch ($type) {
|
||||
case 'user':
|
||||
$profile = Profile::findOrFail($item);
|
||||
if ($profile->id == $user->id) {
|
||||
return abort(403);
|
||||
}
|
||||
$class = get_class($profile);
|
||||
$filterable['id'] = $profile->id;
|
||||
$filterable['type'] = $class;
|
||||
break;
|
||||
|
||||
default:
|
||||
abort(400);
|
||||
break;
|
||||
}
|
||||
|
||||
$filter = UserFilter::whereUserId($user->id)
|
||||
->whereFilterableId($filterable['id'])
|
||||
->whereFilterableType($filterable['type'])
|
||||
->whereFilterType('mute')
|
||||
->first();
|
||||
|
||||
if($filter) {
|
||||
$filter->delete();
|
||||
}
|
||||
|
||||
$pid = $user->id;
|
||||
Cache::forget("user:filter:list:$pid");
|
||||
Cache::forget("feature:discover:people:$pid");
|
||||
Cache::forget("feature:discover:posts:$pid");
|
||||
|
||||
if($request->wantsJson()) {
|
||||
return response()->json([200]);
|
||||
} else {
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
public function block(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'type' => 'required|alpha_dash',
|
||||
'item' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$user = Auth::user()->profile;
|
||||
$type = $request->input('type');
|
||||
$item = $request->input('item');
|
||||
$action = $type.'.block';
|
||||
if (!in_array($action, $this->filters)) {
|
||||
return abort(406);
|
||||
}
|
||||
|
@ -259,6 +313,56 @@ class AccountController extends Controller
|
|||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
||||
public function unblock(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'type' => 'required|alpha_dash',
|
||||
'item' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$user = Auth::user()->profile;
|
||||
$type = $request->input('type');
|
||||
$item = $request->input('item');
|
||||
$action = $type . '.block';
|
||||
if (!in_array($action, $this->filters)) {
|
||||
return abort(406);
|
||||
}
|
||||
$filterable = [];
|
||||
switch ($type) {
|
||||
case 'user':
|
||||
$profile = Profile::findOrFail($item);
|
||||
if ($profile->id == $user->id) {
|
||||
return abort(403);
|
||||
}
|
||||
$class = get_class($profile);
|
||||
$filterable['id'] = $profile->id;
|
||||
$filterable['type'] = $class;
|
||||
break;
|
||||
|
||||
default:
|
||||
abort(400);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$filter = UserFilter::whereUserId($user->id)
|
||||
->whereFilterableId($filterable['id'])
|
||||
->whereFilterableType($filterable['type'])
|
||||
->whereFilterType('block')
|
||||
->first();
|
||||
|
||||
if($filter) {
|
||||
$filter->delete();
|
||||
}
|
||||
|
||||
$pid = $user->id;
|
||||
Cache::forget("user:filter:list:$pid");
|
||||
Cache::forget("feature:discover:people:$pid");
|
||||
Cache::forget("feature:discover:posts:$pid");
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function followRequests(Request $request)
|
||||
{
|
||||
$pid = Auth::user()->profile->id;
|
||||
|
|
|
@ -31,6 +31,10 @@ class ApiController extends BaseApiController
|
|||
|
||||
'media_types' => config('pixelfed.media_types'),
|
||||
'enforce_account_limit' => config('pixelfed.enforce_account_limit')
|
||||
],
|
||||
'activitypub' => [
|
||||
'enabled' => config('pixelfed.activitypub_enabled'),
|
||||
'remote_follow' => config('pixelfed.remote_follow_enabled')
|
||||
]
|
||||
];
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Cache;
|
||||
|
||||
use App\Comment;
|
||||
|
@ -58,14 +59,21 @@ class CommentController extends Controller
|
|||
|
||||
Cache::forget('transform:status:'.$status->url());
|
||||
|
||||
$autolink = Autolink::create()->autolink($comment);
|
||||
$reply = new Status();
|
||||
$reply->profile_id = $profile->id;
|
||||
$reply->caption = e($comment);
|
||||
$reply->rendered = $autolink;
|
||||
$reply->in_reply_to_id = $status->id;
|
||||
$reply->in_reply_to_profile_id = $status->profile_id;
|
||||
$reply->save();
|
||||
$reply = DB::transaction(function() use($comment, $status, $profile) {
|
||||
$autolink = Autolink::create()->autolink($comment);
|
||||
$reply = new Status();
|
||||
$reply->profile_id = $profile->id;
|
||||
$reply->caption = e($comment);
|
||||
$reply->rendered = $autolink;
|
||||
$reply->in_reply_to_id = $status->id;
|
||||
$reply->in_reply_to_profile_id = $status->profile_id;
|
||||
$reply->save();
|
||||
|
||||
$status->reply_count++;
|
||||
$status->save();
|
||||
|
||||
return $reply;
|
||||
});
|
||||
|
||||
NewStatusPipeline::dispatch($reply, false);
|
||||
CommentPipeline::dispatch($status, $reply);
|
||||
|
|
|
@ -82,9 +82,10 @@ trait Instagram
|
|||
->whereStage(1)
|
||||
->firstOrFail();
|
||||
|
||||
$limit = config('pixelfed.import.instagram.limits.posts');
|
||||
foreach ($media as $k => $v) {
|
||||
$original = $v->getClientOriginalName();
|
||||
if(strlen($original) < 32 || $k > 100) {
|
||||
if(strlen($original) < 32 || $k > $limit) {
|
||||
continue;
|
||||
}
|
||||
$storagePath = "import/{$job->uuid}";
|
||||
|
@ -105,7 +106,6 @@ trait Instagram
|
|||
$job->save();
|
||||
});
|
||||
return redirect($job->url());
|
||||
return view('settings.import.instagram.step-one', compact('profile', 'job'));
|
||||
}
|
||||
|
||||
public function instagramStepTwo(Request $request, $uuid)
|
||||
|
@ -148,6 +148,7 @@ trait Instagram
|
|||
{
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereService('instagram')
|
||||
->whereNull('completed_at')
|
||||
->whereUuid($uuid)
|
||||
->whereStage(3)
|
||||
|
@ -159,14 +160,21 @@ trait Instagram
|
|||
{
|
||||
$profile = Auth::user()->profile;
|
||||
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
|
||||
try {
|
||||
$import = ImportJob::whereProfileId($profile->id)
|
||||
->where('uuid', $uuid)
|
||||
->whereNotNull('media_json')
|
||||
->whereNull('completed_at')
|
||||
->whereUuid($uuid)
|
||||
->whereStage(3)
|
||||
->firstOrFail();
|
||||
ImportInstagram::dispatch($import);
|
||||
} catch (Exception $e) {
|
||||
\Log::info($e);
|
||||
}
|
||||
|
||||
ImportInstagram::dispatchNow($job);
|
||||
|
||||
return redirect($profile->url());
|
||||
return redirect(route('settings'))->with(['status' => [
|
||||
'Import successful! It may take a few minutes to finish.'
|
||||
]]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,7 @@ class PublicApiController extends Controller
|
|||
'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
||||
'limit' => 'nullable|integer|min:5|max:50'
|
||||
]);
|
||||
|
||||
$limit = $request->limit ?? 10;
|
||||
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
|
||||
$status = Status::whereProfileId($profile->id)->whereCommentsDisabled(false)->findOrFail($postId);
|
||||
|
@ -116,7 +117,7 @@ class PublicApiController extends Controller
|
|||
if($request->filled('min_id')) {
|
||||
$replies = $status->comments()
|
||||
->whereNull('reblog_of_id')
|
||||
->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'created_at')
|
||||
->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||
->where('id', '>=', $request->min_id)
|
||||
->orderBy('id', 'desc')
|
||||
->paginate($limit);
|
||||
|
@ -124,7 +125,7 @@ class PublicApiController extends Controller
|
|||
if($request->filled('max_id')) {
|
||||
$replies = $status->comments()
|
||||
->whereNull('reblog_of_id')
|
||||
->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'created_at')
|
||||
->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||
->where('id', '<=', $request->max_id)
|
||||
->orderBy('id', 'desc')
|
||||
->paginate($limit);
|
||||
|
@ -132,7 +133,7 @@ class PublicApiController extends Controller
|
|||
} else {
|
||||
$replies = $status->comments()
|
||||
->whereNull('reblog_of_id')
|
||||
->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'created_at')
|
||||
->select('id', 'caption', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||
->orderBy('id', 'desc')
|
||||
->paginate($limit);
|
||||
}
|
||||
|
@ -180,8 +181,8 @@ class PublicApiController extends Controller
|
|||
if(!$user) {
|
||||
abort(403);
|
||||
} else {
|
||||
$follows = $profile->followedBy(Auth::user()->profile);
|
||||
if($follows == false && $profile->id !== $user->profile->id) {
|
||||
$follows = $profile->followedBy($user->profile);
|
||||
if($follows == false && $profile->id !== $user->profile->id && $user->is_admin == false) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
@ -357,8 +358,6 @@ class PublicApiController extends Controller
|
|||
'created_at',
|
||||
'updated_at'
|
||||
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
||||
->whereLocal(true)
|
||||
->whereNull('uri')
|
||||
->where('id', $dir, $id)
|
||||
->whereIn('profile_id', $following)
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
|
@ -386,8 +385,6 @@ class PublicApiController extends Controller
|
|||
'created_at',
|
||||
'updated_at'
|
||||
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
||||
->whereLocal(true)
|
||||
->whereNull('uri')
|
||||
->whereIn('profile_id', $following)
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
->whereNull('in_reply_to_id')
|
||||
|
@ -453,14 +450,18 @@ class PublicApiController extends Controller
|
|||
'is_nsfw',
|
||||
'scope',
|
||||
'local',
|
||||
'reply_count',
|
||||
'comments_disabled',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
)->where('id', $dir, $id)
|
||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
->whereNotNull('uri')
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
->whereVisibility('public')
|
||||
->orderBy('created_at', 'desc')
|
||||
->latest()
|
||||
->limit($limit)
|
||||
->get();
|
||||
} else {
|
||||
|
@ -476,14 +477,17 @@ class PublicApiController extends Controller
|
|||
'is_nsfw',
|
||||
'scope',
|
||||
'local',
|
||||
'reply_count',
|
||||
'comments_disabled',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
)->whereIn('type', ['photo', 'photo:album', 'video', 'video:album'])
|
||||
->whereNotIn('profile_id', $filtered)
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
->whereNotNull('uri')
|
||||
->whereVisibility('public')
|
||||
->orderBy('created_at', 'desc')
|
||||
->latest()
|
||||
->simplePaginate($limit);
|
||||
}
|
||||
|
||||
|
@ -524,8 +528,8 @@ class PublicApiController extends Controller
|
|||
{
|
||||
abort_unless(Auth::check(), 403);
|
||||
$profile = Profile::with('user')->whereNull('status')->whereNull('domain')->findOrFail($id);
|
||||
if($profile->is_private || !$profile->user->settings->show_profile_followers) {
|
||||
return [];
|
||||
if(Auth::id() != $profile->user_id && $profile->is_private || !$profile->user->settings->show_profile_followers) {
|
||||
return response()->json([]);
|
||||
}
|
||||
$followers = $profile->followers()->orderByDesc('followers.created_at')->paginate(10);
|
||||
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
|
||||
|
@ -538,8 +542,8 @@ class PublicApiController extends Controller
|
|||
{
|
||||
abort_unless(Auth::check(), 403);
|
||||
$profile = Profile::with('user')->whereNull('status')->whereNull('domain')->findOrFail($id);
|
||||
if($profile->is_private || !$profile->user->settings->show_profile_following) {
|
||||
return [];
|
||||
if(Auth::id() != $profile->user_id && $profile->is_private || !$profile->user->settings->show_profile_following) {
|
||||
return response()->json([]);
|
||||
}
|
||||
$following = $profile->following()->orderByDesc('followers.created_at')->paginate(10);
|
||||
$resource = new Fractal\Resource\Collection($following, new AccountTransformer());
|
||||
|
|
|
@ -9,6 +9,7 @@ use App\Status;
|
|||
use Illuminate\Http\Request;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
HashtagTransformer,
|
||||
|
@ -22,17 +23,20 @@ class SearchController extends Controller
|
|||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function searchAPI(Request $request, $tag)
|
||||
public function searchAPI(Request $request)
|
||||
{
|
||||
if(mb_strlen($tag) < 3) {
|
||||
return;
|
||||
}
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:3|max:120',
|
||||
'src' => 'required|string|in:metro',
|
||||
'v' => 'required|integer|in:1'
|
||||
]);
|
||||
$tag = $request->input('q');
|
||||
$tag = e(urldecode($tag));
|
||||
|
||||
$hash = hash('sha256', $tag);
|
||||
$tokens = Cache::remember('api:search:tag:'.$hash, now()->addMinutes(5), function () use ($tag) {
|
||||
$tokens = [];
|
||||
if(Helpers::validateUrl($tag) != false) {
|
||||
if(Helpers::validateUrl($tag) != false && config('pixelfed.activitypub_enabled') == true && config('pixelfed.remote_follow_enabled') == true) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if(isset($remote['type']) && in_array($remote['type'], ['Create', 'Person']) == true) {
|
||||
$type = $remote['type'];
|
||||
|
@ -65,7 +69,12 @@ class SearchController extends Controller
|
|||
}
|
||||
}
|
||||
}
|
||||
$hashtags = Hashtag::select('id', 'name', 'slug')->where('slug', 'like', '%'.$tag.'%')->whereHas('posts')->limit(20)->get();
|
||||
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
||||
$hashtags = Hashtag::select('id', 'name', 'slug')
|
||||
->where('slug', 'like', '%'.$htag.'%')
|
||||
->whereHas('posts')
|
||||
->limit(20)
|
||||
->get();
|
||||
if($hashtags->count() > 0) {
|
||||
$tags = $hashtags->map(function ($item, $key) {
|
||||
return [
|
||||
|
@ -83,9 +92,9 @@ class SearchController extends Controller
|
|||
});
|
||||
$users = Profile::select('username', 'name', 'id')
|
||||
->whereNull('status')
|
||||
->whereNull('domain')
|
||||
->where('id', '!=', Auth::user()->profile->id)
|
||||
->where('username', 'like', '%'.$tag.'%')
|
||||
->whereNull('domain')
|
||||
//->orWhere('remote_url', $tag)
|
||||
->limit(20)
|
||||
->get();
|
||||
|
@ -120,7 +129,6 @@ class SearchController extends Controller
|
|||
->whereNull('reblog_of_id')
|
||||
->whereProfileId(Auth::user()->profile->id)
|
||||
->where('caption', 'like', '%'.$tag.'%')
|
||||
->orWhere('uri', $tag)
|
||||
->latest()
|
||||
->limit(10)
|
||||
->get();
|
||||
|
|
|
@ -47,6 +47,10 @@ trait HomeSettings
|
|||
$email = $request->input('email');
|
||||
$user = Auth::user();
|
||||
$profile = $user->profile;
|
||||
$layout = $request->input('profile_layout');
|
||||
if($layout) {
|
||||
$layout = !in_array($layout, ['metro', 'moment']) ? 'metro' : $layout;
|
||||
}
|
||||
|
||||
$validate = config('pixelfed.enforce_email_verification');
|
||||
|
||||
|
@ -89,6 +93,11 @@ trait HomeSettings
|
|||
$changes = true;
|
||||
$profile->bio = $bio;
|
||||
}
|
||||
|
||||
if ($profile->profile_layout != $layout) {
|
||||
$changes = true;
|
||||
$profile->profile_layout = $layout;
|
||||
}
|
||||
}
|
||||
|
||||
if ($changes === true) {
|
||||
|
|
|
@ -8,6 +8,7 @@ use App\Media;
|
|||
use App\Profile;
|
||||
use App\User;
|
||||
use App\UserFilter;
|
||||
use App\UserDevice;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
use Auth;
|
||||
use DB;
|
||||
|
@ -20,19 +21,19 @@ trait SecuritySettings
|
|||
|
||||
public function security()
|
||||
{
|
||||
$sessions = DB::table('sessions')
|
||||
->whereUserId(Auth::id())
|
||||
->limit(20)
|
||||
->get();
|
||||
$user = Auth::user();
|
||||
|
||||
$activity = AccountLog::whereUserId(Auth::id())
|
||||
$activity = AccountLog::whereUserId($user->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
$user = Auth::user();
|
||||
$devices = UserDevice::whereUserId($user->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
return view('settings.security', compact('sessions', 'activity', 'user'));
|
||||
return view('settings.security', compact('activity', 'user', 'devices'));
|
||||
}
|
||||
|
||||
public function securityTwoFactorSetup(Request $request)
|
||||
|
|
|
@ -42,11 +42,11 @@ class StatusController extends Controller
|
|||
|
||||
if($status->visibility == 'private' || $user->is_private) {
|
||||
if(!Auth::check()) {
|
||||
abort(403);
|
||||
abort(404);
|
||||
}
|
||||
$pid = Auth::user()->profile;
|
||||
if($user->followedBy($pid) == false && $user->id !== $pid->id) {
|
||||
abort(403);
|
||||
if($user->followedBy($pid) == false && $user->id !== $pid->id && Auth::user()->is_admin == false) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,21 @@ class AuthServiceProvider extends ServiceProvider
|
|||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
// Passport::routes();
|
||||
// Passport::tokensExpireIn(now()->addDays(15));
|
||||
// Passport::refreshTokensExpireIn(now()->addDays(30));
|
||||
if(config('pixelfed.oauth_enabled')) {
|
||||
Passport::routes();
|
||||
Passport::tokensExpireIn(now()->addDays(15));
|
||||
Passport::refreshTokensExpireIn(now()->addDays(30));
|
||||
Passport::enableImplicitGrant();
|
||||
|
||||
Passport::setDefaultScope([
|
||||
'user:read',
|
||||
'user:write'
|
||||
]);
|
||||
|
||||
Passport::tokensCan([
|
||||
'user:read' => 'Read a user’s profile info and media',
|
||||
'user:write' => 'This scope lets an app "Change your profile information"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,11 @@ class CreateNote extends Fractal\TransformerAbstract
|
|||
'Hashtag' => 'as:Hashtag',
|
||||
'sensitive' => 'as:sensitive',
|
||||
'commentsEnabled' => 'sc:Boolean',
|
||||
'capabilities' => [
|
||||
'announce' => ['@type' => '@id'],
|
||||
'like' => ['@type' => '@id'],
|
||||
'reply' => ['@type' => '@id']
|
||||
]
|
||||
]
|
||||
],
|
||||
'id' => $status->permalink(),
|
||||
|
@ -65,6 +70,11 @@ class CreateNote extends Fractal\TransformerAbstract
|
|||
})->toArray(),
|
||||
'tag' => $tags,
|
||||
'commentsEnabled' => (bool) !$status->comments_disabled,
|
||||
'capabilities' => [
|
||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'reply' => $status->comments_disabled == true ? null : 'https://www.w3.org/ns/activitystreams#Public'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -35,6 +35,11 @@ class Note extends Fractal\TransformerAbstract
|
|||
'Hashtag' => 'as:Hashtag',
|
||||
'sensitive' => 'as:sensitive',
|
||||
'commentsEnabled' => 'sc:Boolean',
|
||||
'capabilities' => [
|
||||
'announce' => ['@type' => '@id'],
|
||||
'like' => ['@type' => '@id'],
|
||||
'reply' => ['@type' => '@id'],
|
||||
]
|
||||
]
|
||||
],
|
||||
'id' => $status->url(),
|
||||
|
@ -58,6 +63,11 @@ class Note extends Fractal\TransformerAbstract
|
|||
})->toArray(),
|
||||
'tag' => $tags,
|
||||
'commentsEnabled' => (bool) !$status->comments_disabled,
|
||||
'capabilities' => [
|
||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||
'reply' => $status->comments_disabled == true ? null : 'https://www.w3.org/ns/activitystreams#Public'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ class RelationshipTransformer extends Fractal\TransformerAbstract
|
|||
'id' => (string) $profile->id,
|
||||
'following' => $user->follows($profile),
|
||||
'followed_by' => $user->followedBy($profile),
|
||||
'blocking' => null,
|
||||
'muting' => null,
|
||||
'blocking' => $user->blockedIds()->contains($profile->id),
|
||||
'muting' => $user->mutedIds()->contains($profile->id),
|
||||
'muting_notifications' => null,
|
||||
'requested' => null,
|
||||
'domain_blocking' => null,
|
||||
|
|
|
@ -23,7 +23,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
'url' => $status->url(),
|
||||
'in_reply_to_id' => $status->in_reply_to_id,
|
||||
'in_reply_to_account_id' => $status->in_reply_to_profile_id,
|
||||
'reblog' => $status->reblog_of_id || $status->in_reply_to_id ? $this->transform($status->parent()) : null,
|
||||
'reblog' => null,
|
||||
'content' => $status->rendered ?? $status->caption,
|
||||
'created_at' => $status->created_at->format('c'),
|
||||
'emojis' => [],
|
||||
|
@ -42,9 +42,11 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
'language' => null,
|
||||
'pinned' => null,
|
||||
|
||||
'pf_type' => $status->type ?? $status->setType(),
|
||||
'reply_count' => $status->reply_count,
|
||||
'comments_disabled' => $status->comments_disabled ? true : false
|
||||
'pf_type' => $status->type ?? $status->setType(),
|
||||
'reply_count' => (int) $status->reply_count,
|
||||
'comments_disabled' => $status->comments_disabled ? true : false,
|
||||
'thread' => false,
|
||||
'replies' => []
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Jenssegers\Agent\Agent;
|
||||
|
||||
class UserDevice extends Model
|
||||
{
|
||||
|
@ -20,4 +21,14 @@ class UserDevice extends Model
|
|||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function getUserAgent()
|
||||
{
|
||||
if(!$this->user_agent) {
|
||||
return 'Unknown';
|
||||
}
|
||||
$agent = new Agent();
|
||||
$agent->setUserAgent($this->user_agent);
|
||||
return $agent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ use App\Jobs\AvatarPipeline\CreateAvatar;
|
|||
use App\Jobs\RemoteFollowPipeline\RemoteFollowImportRecent;
|
||||
use App\Jobs\ImageOptimizePipeline\{ImageOptimize,ImageThumbnail};
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Util\HttpSignatures\{GuzzleHttpSignatures, KeyStore, Context, Verifier};
|
||||
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
@ -30,7 +28,7 @@ class Helpers {
|
|||
|
||||
public static function validateObject($data)
|
||||
{
|
||||
$verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo'];
|
||||
$verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo', 'Tombstone'];
|
||||
|
||||
$valid = Validator::make($data, [
|
||||
'type' => [
|
||||
|
@ -38,11 +36,11 @@ class Helpers {
|
|||
Rule::in($verbs)
|
||||
],
|
||||
'id' => 'required|string',
|
||||
'actor' => 'required|string',
|
||||
'actor' => 'required|string|url',
|
||||
'object' => 'required',
|
||||
'object.type' => 'required_if:type,Create',
|
||||
'object.attachment' => 'required_if:type,Create',
|
||||
'object.attributedTo' => 'required_if:type,Create',
|
||||
'object.attributedTo' => 'required_if:type,Create|url',
|
||||
'published' => 'required_if:type,Create|date'
|
||||
])->passes();
|
||||
|
||||
|
@ -71,7 +69,7 @@ class Helpers {
|
|||
'string',
|
||||
Rule::in($mediaTypes)
|
||||
],
|
||||
'*.url' => 'required|max:255',
|
||||
'*.url' => 'required|url|max:255',
|
||||
'*.mediaType' => [
|
||||
'required',
|
||||
'string',
|
||||
|
@ -193,6 +191,7 @@ class Helpers {
|
|||
$res = Zttp::withHeaders(self::zttpUserAgent())->get($url);
|
||||
$res = json_decode($res->body(), true, 8);
|
||||
if(json_last_error() == JSON_ERROR_NONE) {
|
||||
abort_if(!self::validateObject($res), 422);
|
||||
return $res;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -238,14 +237,26 @@ class Helpers {
|
|||
}
|
||||
|
||||
$scope = 'private';
|
||||
|
||||
$cw = isset($activity['sensitive']) ? (bool) $activity['sensitive'] : false;
|
||||
|
||||
if(isset($res['to']) == true && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
|
||||
$scope = 'public';
|
||||
if(isset($res['to']) == true) {
|
||||
if(is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
|
||||
$scope = 'public';
|
||||
}
|
||||
if(is_string($res['to']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['to']) {
|
||||
$scope = 'public';
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($res['cc']) == true && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
|
||||
if(isset($res['cc']) == true) {
|
||||
$scope = 'unlisted';
|
||||
if(is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
if(is_string($res['cc']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['cc']) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
}
|
||||
|
||||
if(config('costar.enabled') == true) {
|
||||
|
@ -309,7 +320,7 @@ class Helpers {
|
|||
$status->scope = $scope;
|
||||
$status->visibility = $scope;
|
||||
$status->save();
|
||||
self::importNoteAttachment($res, $status);
|
||||
// self::importNoteAttachment($res, $status);
|
||||
return $status;
|
||||
});
|
||||
|
||||
|
@ -320,6 +331,8 @@ class Helpers {
|
|||
|
||||
public static function importNoteAttachment($data, Status $status)
|
||||
{
|
||||
return;
|
||||
|
||||
if(self::verifyAttachments($data) == false) {
|
||||
return;
|
||||
}
|
||||
|
@ -336,28 +349,28 @@ class Helpers {
|
|||
if(in_array($type, $allowed) == false || $valid == false) {
|
||||
continue;
|
||||
}
|
||||
$info = pathinfo($url);
|
||||
// $info = pathinfo($url);
|
||||
|
||||
// pleroma attachment fix
|
||||
$url = str_replace(' ', '%20', $url);
|
||||
// // pleroma attachment fix
|
||||
// $url = str_replace(' ', '%20', $url);
|
||||
|
||||
$img = file_get_contents($url, false, stream_context_create(['ssl' => ["verify_peer"=>true,"verify_peer_name"=>true]]));
|
||||
$file = '/tmp/'.str_random(32);
|
||||
file_put_contents($file, $img);
|
||||
$fdata = new File($file);
|
||||
$path = Storage::putFile($storagePath, $fdata, 'public');
|
||||
$media = new Media();
|
||||
$media->status_id = $status->id;
|
||||
$media->profile_id = $status->profile_id;
|
||||
$media->user_id = null;
|
||||
$media->media_path = $path;
|
||||
$media->size = $fdata->getSize();
|
||||
$media->mime = $fdata->getMimeType();
|
||||
$media->save();
|
||||
// $img = file_get_contents($url, false, stream_context_create(['ssl' => ["verify_peer"=>true,"verify_peer_name"=>true]]));
|
||||
// $file = '/tmp/'.str_random(32);
|
||||
// file_put_contents($file, $img);
|
||||
// $fdata = new File($file);
|
||||
// $path = Storage::putFile($storagePath, $fdata, 'public');
|
||||
// $media = new Media();
|
||||
// $media->status_id = $status->id;
|
||||
// $media->profile_id = $status->profile_id;
|
||||
// $media->user_id = null;
|
||||
// $media->media_path = $path;
|
||||
// $media->size = $fdata->getSize();
|
||||
// $media->mime = $fdata->getMimeType();
|
||||
// $media->save();
|
||||
|
||||
ImageThumbnail::dispatch($media);
|
||||
ImageOptimize::dispatch($media);
|
||||
unlink($file);
|
||||
// ImageThumbnail::dispatch($media);
|
||||
// ImageOptimize::dispatch($media);
|
||||
// unlink($file);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -380,15 +393,19 @@ class Helpers {
|
|||
return;
|
||||
}
|
||||
$domain = parse_url($res['id'], PHP_URL_HOST);
|
||||
$username = $res['preferredUsername'];
|
||||
$username = Purify::clean($res['preferredUsername']);
|
||||
$remoteUsername = "@{$username}@{$domain}";
|
||||
|
||||
abort_if(!self::validateUrl($res['inbox']), 400);
|
||||
abort_if(!self::validateUrl($res['outbox']), 400);
|
||||
abort_if(!self::validateUrl($res['id']), 400);
|
||||
|
||||
$profile = Profile::whereRemoteUrl($res['id'])->first();
|
||||
if(!$profile) {
|
||||
$profile = new Profile;
|
||||
$profile->domain = $domain;
|
||||
$profile->username = $remoteUsername;
|
||||
$profile->name = strip_tags($res['name']);
|
||||
$profile->username = Purify::clean($remoteUsername);
|
||||
$profile->name = Purify::clean($res['name']) ?? 'user';
|
||||
$profile->bio = Purify::clean($res['summary']);
|
||||
$profile->sharedInbox = isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null;
|
||||
$profile->inbox_url = $res['inbox'];
|
||||
|
@ -407,6 +424,8 @@ class Helpers {
|
|||
|
||||
public static function sendSignedObject($senderProfile, $url, $body)
|
||||
{
|
||||
abort_if(!self::validateUrl($url), 400);
|
||||
|
||||
$payload = json_encode($body);
|
||||
$headers = HttpSignature::sign($senderProfile, $url, $body);
|
||||
|
||||
|
@ -418,42 +437,4 @@ class Helpers {
|
|||
$response = curl_exec($ch);
|
||||
return;
|
||||
}
|
||||
|
||||
private static function _headersToSigningString($headers) {
|
||||
}
|
||||
|
||||
public static function validateSignature($request, $payload = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static function fetchPublicKey()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
$is_url = $this->is_url;
|
||||
$valid = $this->validateUrl();
|
||||
if (!$valid) {
|
||||
throw new \Exception('Invalid URL provided');
|
||||
}
|
||||
if ($is_url && isset($profile->public_key) && $profile->public_key) {
|
||||
return $profile->public_key;
|
||||
}
|
||||
|
||||
try {
|
||||
$url = $this->profile;
|
||||
$res = Zttp::timeout(30)->withHeaders([
|
||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => 'PixelFedBot v0.1 - https://pixelfed.org',
|
||||
])->get($url);
|
||||
$actor = json_decode($res->getBody(), true);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Unable to fetch public key');
|
||||
}
|
||||
if($actor['publicKey']['owner'] != $profile) {
|
||||
throw new Exception('Invalid key match');
|
||||
}
|
||||
$this->public_key = $actor['publicKey']['publicKeyPem'];
|
||||
$this->key_id = $actor['publicKey']['id'];
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ class Inbox
|
|||
|
||||
public function handle()
|
||||
{
|
||||
abort_if(!Helpers::validateObject($this->payload), 400);
|
||||
$this->handleVerb();
|
||||
}
|
||||
|
||||
|
@ -135,6 +136,8 @@ class Inbox
|
|||
|
||||
public function handleNoteCreate()
|
||||
{
|
||||
return;
|
||||
|
||||
$activity = $this->payload['object'];
|
||||
$actor = $this->actorFirstOrCreate($this->payload['actor']);
|
||||
if(!$actor || $actor->domain == null) {
|
||||
|
@ -259,24 +262,24 @@ class Inbox
|
|||
{
|
||||
$actor = $this->payload['actor'];
|
||||
$obj = $this->payload['object'];
|
||||
abort_if(!Helpers::validateUrl($obj), 400);
|
||||
if(is_string($obj) && Helpers::validateUrl($obj)) {
|
||||
// actor object detected
|
||||
// todo delete actor
|
||||
} else if (Helpers::validateUrl($obj['id']) && is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
|
||||
// tombstone detected
|
||||
$status = Status::whereLocal(false)->whereUri($obj['id'])->firstOrFail();
|
||||
$status->forceDelete();
|
||||
} else if (Helpers::validateUrl($obj['id']) && Helpers::validateObject($obj) && $obj['type'] == 'Tombstone') {
|
||||
// todo delete status or object
|
||||
}
|
||||
}
|
||||
|
||||
public function handleLikeActivity()
|
||||
{
|
||||
$actor = $this->payload['actor'];
|
||||
|
||||
abort_if(!Helpers::validateUrl($actor), 400);
|
||||
|
||||
$profile = self::actorFirstOrCreate($actor);
|
||||
$obj = $this->payload['object'];
|
||||
if(Helpers::validateLocalUrl($obj) == false) {
|
||||
return;
|
||||
}
|
||||
abort_if(!Helpers::validateLocalUrl($obj), 400);
|
||||
$status = Helpers::statusFirstOrFetch($obj);
|
||||
if(!$status || !$profile) {
|
||||
return;
|
||||
|
@ -286,10 +289,11 @@ class Inbox
|
|||
'status_id' => $status->id
|
||||
]);
|
||||
|
||||
if($like->wasRecentlyCreated == false) {
|
||||
return;
|
||||
if($like->wasRecentlyCreated == true) {
|
||||
LikePipeline::dispatch($like);
|
||||
}
|
||||
LikePipeline::dispatch($like);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"fideloper/proxy": "^4.0",
|
||||
"greggilbert/recaptcha": "dev-master",
|
||||
"intervention/image": "^2.4",
|
||||
"jenssegers/agent": "^2.6",
|
||||
"laravel/framework": "5.8.*",
|
||||
"laravel/horizon": "^3.0",
|
||||
"laravel/passport": "^7.0",
|
||||
|
|
172
composer.lock
generated
172
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "188c87638a863fd575f41213e72976f5",
|
||||
"content-hash": "702a3ed0b8499d50323723eb4fb41965",
|
||||
"packages": [
|
||||
{
|
||||
"name": "alchemy/binary-driver",
|
||||
|
@ -1558,6 +1558,124 @@
|
|||
"description": "Highlight PHP code in terminal",
|
||||
"time": "2018-09-29T18:48:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jaybizzle/crawler-detect",
|
||||
"version": "v1.2.80",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
|
||||
"reference": "af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847",
|
||||
"reference": "af6a36e6d69670df3f0a3ed8e21d4b8cc67a7847",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8|^5.5|^6.5",
|
||||
"satooshi/php-coveralls": "1.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jaybizzle\\CrawlerDetect\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Beech",
|
||||
"email": "m@rkbee.ch",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent",
|
||||
"homepage": "https://github.com/JayBizzle/Crawler-Detect/",
|
||||
"keywords": [
|
||||
"crawler",
|
||||
"crawler detect",
|
||||
"crawler detector",
|
||||
"crawlerdetect",
|
||||
"php crawler detect"
|
||||
],
|
||||
"time": "2019-04-05T19:52:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jenssegers/agent",
|
||||
"version": "v2.6.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jenssegers/agent.git",
|
||||
"reference": "bcb895395e460478e101f41cdab139c48dc721ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jenssegers/agent/zipball/bcb895395e460478e101f41cdab139c48dc721ce",
|
||||
"reference": "bcb895395e460478e101f41cdab139c48dc721ce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"jaybizzle/crawler-detect": "^1.2",
|
||||
"mobiledetect/mobiledetectlib": "^2.7.6",
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5.0|^6.0|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/support": "^4.0|^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Jenssegers\\Agent\\AgentServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Agent": "Jenssegers\\Agent\\Facades\\Agent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jenssegers\\Agent\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jens Segers",
|
||||
"homepage": "https://jenssegers.com"
|
||||
}
|
||||
],
|
||||
"description": "Desktop/mobile user agent parser with support for Laravel, based on Mobiledetect",
|
||||
"homepage": "https://github.com/jenssegers/agent",
|
||||
"keywords": [
|
||||
"Agent",
|
||||
"browser",
|
||||
"desktop",
|
||||
"laravel",
|
||||
"mobile",
|
||||
"platform",
|
||||
"user agent",
|
||||
"useragent"
|
||||
],
|
||||
"time": "2019-01-19T21:32:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v5.8.10",
|
||||
|
@ -2270,6 +2388,58 @@
|
|||
],
|
||||
"time": "2019-03-29T18:19:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mobiledetect/mobiledetectlib",
|
||||
"version": "2.8.33",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/serbanghita/Mobile-Detect.git",
|
||||
"reference": "cd385290f9a0d609d2eddd165a1e44ec1bf12102"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/cd385290f9a0d609d2eddd165a1e44ec1bf12102",
|
||||
"reference": "cd385290f9a0d609d2eddd165a1e44ec1bf12102",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8.35||~5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"Mobile_Detect.php"
|
||||
],
|
||||
"psr-0": {
|
||||
"Detection": "namespaced/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Serban Ghita",
|
||||
"email": "serbanghita@gmail.com",
|
||||
"homepage": "http://mobiledetect.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.",
|
||||
"homepage": "https://github.com/serbanghita/Mobile-Detect",
|
||||
"keywords": [
|
||||
"detect mobile devices",
|
||||
"mobile",
|
||||
"mobile detect",
|
||||
"mobile detector",
|
||||
"php mobile detect"
|
||||
],
|
||||
"time": "2018-09-01T15:05:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "1.24.0",
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your PixelFed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.8.6',
|
||||
'version' => '0.9.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -46,7 +46,7 @@ return [
|
|||
| default memory_limit php.ini is used for the rest of the app.
|
||||
|
|
||||
*/
|
||||
'memory_limit' => '1024M',
|
||||
'memory_limit' => env('MEMORY_LIMIT', '1024M'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -259,7 +259,9 @@ return [
|
|||
|
||||
|
||||
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
|
||||
|
||||
'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
|
||||
|
||||
'ap_inbox' => env('ACTIVITYPUB_INBOX', false),
|
||||
'ap_shared' => env('ACTIVITYPUB_SHAREDINBOX', false),
|
||||
'ap_delivery_timeout' => env('ACTIVITYPUB_DELIVERY_TIMEOUT', 2.0),
|
||||
|
@ -267,11 +269,13 @@ return [
|
|||
|
||||
'import' => [
|
||||
'instagram' => [
|
||||
'enabled' => env('IMPORT_INSTAGRAM_ENABLED', false),
|
||||
'enabled' => false,
|
||||
'limits' => [
|
||||
'posts' => (int) env('IMPORT_INSTAGRAM_POST_LIMIT', 100),
|
||||
'size' => (int) env('IMPORT_INSTAGRAM_SIZE_LIMIT', 250)
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
'oauth_enabled' => env('OAUTH_ENABLED', false),
|
||||
];
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddLayoutToProfilesTable extends Migration
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('profiles', function (Blueprint $table) {
|
||||
$table->string('profile_layout')->nullable()->after('website');
|
||||
$table->string('post_layout')->nullable()->after('profile_layout');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('profiles', function (Blueprint $table) {
|
||||
$table->dropColumn('profile_layout');
|
||||
$table->dropColumn('post_layout');
|
||||
});
|
||||
}
|
||||
}
|
BIN
public/js/components.js
vendored
BIN
public/js/components.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/developers.js
vendored
Normal file
BIN
public/js/developers.js
vendored
Normal file
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.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/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
8
resources/assets/js/components.js
vendored
8
resources/assets/js/components.js
vendored
|
@ -18,10 +18,10 @@ pixelfed.readmore = () => {
|
|||
return;
|
||||
}
|
||||
el.readmore({
|
||||
collapsedHeight: 44,
|
||||
heightMargin: 20,
|
||||
moreLink: '<a href="#" class="font-weight-bold small">Read more</a>',
|
||||
lessLink: '<a href="#" class="font-weight-bold small">Hide</a>',
|
||||
collapsedHeight: 45,
|
||||
heightMargin: 48,
|
||||
moreLink: '<a href="#" class="d-block font-weight-lighter small text-dark text-center">Read more ...</a>',
|
||||
lessLink: '<a href="#" class="d-block font-weight-lighter small text-dark text-center">Hide</a>',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -25,43 +25,56 @@
|
|||
</div>
|
||||
|
||||
<div class="postPresenterContainer">
|
||||
<div v-if="ids.length == 0" class="w-100 h-100 bg-light py-5 cursor-pointer" style="border-bottom: 1px solid #f1f1f1" v-on:click="addMedia()">
|
||||
<p class="text-center mb-0 font-weight-bold p-5">Click here to add photos.</p>
|
||||
<div v-if="uploading">
|
||||
<div class="w-100 h-100 bg-light py-5" style="border-bottom: 1px solid #f1f1f1">
|
||||
<div class="p-5">
|
||||
<b-progress :value="uploadProgress" :max="100" striped :animated="true"></b-progress>
|
||||
<p class="text-center mb-0 font-weight-bold">Uploading ... ({{uploadProgress}}%)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="ids.length > 0">
|
||||
|
||||
<b-carousel id="p-carousel"
|
||||
style="text-shadow: 1px 1px 2px #333;"
|
||||
controls
|
||||
indicators
|
||||
background="#ffffff"
|
||||
:interval="0"
|
||||
v-model="carouselCursor"
|
||||
>
|
||||
<b-carousel-slide v-if="ids.length > 0" v-for="(preview, index) in media" :key="'preview_media_'+index">
|
||||
<div slot="img" :class="[media[index].filter_class?media[index].filter_class + ' cursor-pointer':' cursor-pointer']" v-on:click="addMedia()">
|
||||
<img class="d-block img-fluid w-100" :src="preview.url" :alt="preview.description" :title="preview.description">
|
||||
</div>
|
||||
</b-carousel-slide>
|
||||
</b-carousel>
|
||||
</div>
|
||||
<div v-if="mediaDrawer" class="bg-dark align-items-center">
|
||||
<ul class="nav media-drawer-filters text-center">
|
||||
<li class="nav-item">
|
||||
<div class="p-1 pt-3">
|
||||
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
|
||||
</div>
|
||||
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-white active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
|
||||
</li>
|
||||
<li class="nav-item" v-for="(filter, index) in filters">
|
||||
<div class="p-1 pt-3">
|
||||
<div :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
|
||||
<img :src="media[carouselCursor].url" width="100px" height="60px" class="">
|
||||
<div v-else>
|
||||
<div v-if="ids.length > 0 && ids.length != config.uploader.album_limit" class="card-header py-2 bg-primary m-2 rounded cursor-pointer" v-on:click="addMedia()">
|
||||
<p class="text-center mb-0 font-weight-bold text-white"><i class="fas fa-plus mr-1"></i> Add Photo</p>
|
||||
</div>
|
||||
<div v-if="ids.length == 0" class="w-100 h-100 bg-light py-5 cursor-pointer" style="border-bottom: 1px solid #f1f1f1" v-on:click="addMedia()">
|
||||
<p class="text-center mb-0 font-weight-bold p-5">Click here to add photos</p>
|
||||
</div>
|
||||
<div v-if="ids.length > 0">
|
||||
|
||||
<b-carousel id="p-carousel"
|
||||
style="text-shadow: 1px 1px 2px #333;"
|
||||
controls
|
||||
indicators
|
||||
background="#ffffff"
|
||||
:interval="0"
|
||||
v-model="carouselCursor"
|
||||
>
|
||||
<b-carousel-slide v-if="ids.length > 0" v-for="(preview, index) in media" :key="'preview_media_'+index">
|
||||
<div slot="img" :class="[media[index].filter_class?media[index].filter_class:'']" style="display:flex;min-height: 320px;align-items: center;">
|
||||
<img class="d-block img-fluid w-100" :src="preview.url" :alt="preview.description" :title="preview.description">
|
||||
</div>
|
||||
</div>
|
||||
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-white active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</b-carousel-slide>
|
||||
</b-carousel>
|
||||
</div>
|
||||
<div v-if="ids.length > 0" class="bg-dark align-items-center">
|
||||
<ul class="nav media-drawer-filters text-center">
|
||||
<li class="nav-item">
|
||||
<div class="p-1 pt-3">
|
||||
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
|
||||
</div>
|
||||
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-white active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
|
||||
</li>
|
||||
<li class="nav-item" v-for="(filter, index) in filters">
|
||||
<div class="p-1 pt-3">
|
||||
<div :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
|
||||
<img :src="media[carouselCursor].url" width="100px" height="60px" class="">
|
||||
</div>
|
||||
</div>
|
||||
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-white active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="mediaDrawer" class="bg-lighter p-2 row">
|
||||
<div class="col-12">
|
||||
|
@ -84,24 +97,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[mediaDrawer?'d-none':'card-body']">
|
||||
<div class="card-body p-0">
|
||||
<div class="caption">
|
||||
<p class="mb-2">
|
||||
<textarea class="form-control d-inline-block" rows="3" placeholder="Add an optional caption" v-model="composeText"></textarea>
|
||||
</p>
|
||||
</div>
|
||||
<div class="comments">
|
||||
</div>
|
||||
<div class="timestamp pt-1">
|
||||
<p class="small text-uppercase mb-0">
|
||||
<span class="text-muted">
|
||||
Draft
|
||||
</span>
|
||||
</p>
|
||||
<textarea class="form-control mb-0 border-0 rounded-0" rows="3" placeholder="Add an optional caption" v-model="composeText"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[mediaDrawer?'d-none':'card-footer']">
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="custom-control custom-switch d-inline mr-3">
|
||||
|
@ -135,7 +137,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a :class="[visibility=='private'?'dropdown-item active':'dropdown-item']" href="#" data-id="private" data-title="Followers Only" v-on:click.prevent="visibility = 'unlisted'">
|
||||
<a :class="[visibility=='unlisted'?'dropdown-item active':'dropdown-item']" href="#" data-id="private" data-title="Unlisted" v-on:click.prevent="visibility = 'unlisted'">
|
||||
<div class="row">
|
||||
<div class="d-none d-block-sm col-sm-2 px-0 text-center">
|
||||
<i class="fas fa-lock"></i>
|
||||
|
@ -192,6 +194,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer py-1">
|
||||
<p class="text-center mb-0 font-weight-bold text-muted small">Having issues? You can also use the <a href="/i/compose">Classic Compose UI</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -234,7 +239,9 @@ export default {
|
|||
carouselCursor: 0,
|
||||
visibility: 'public',
|
||||
mediaDrawer: false,
|
||||
composeState: 'publish'
|
||||
composeState: 'publish',
|
||||
uploading: false,
|
||||
uploadProgress: 0
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -301,6 +308,9 @@ export default {
|
|||
fetchProfile() {
|
||||
axios.get('/api/v1/accounts/verify_credentials').then(res => {
|
||||
this.profile = res.data;
|
||||
if(res.data.locked == true) {
|
||||
this.visibility = 'private';
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
});
|
||||
|
@ -320,6 +330,7 @@ export default {
|
|||
$(document).on('change', '.file-input', function(e) {
|
||||
let io = document.querySelector('.file-input');
|
||||
Array.prototype.forEach.call(io.files, function(io, i) {
|
||||
self.uploading = true;
|
||||
if(self.media && self.media.length + i >= self.config.uploader.album_limit) {
|
||||
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
|
||||
return;
|
||||
|
@ -338,20 +349,25 @@ export default {
|
|||
let xhrConfig = {
|
||||
onUploadProgress: function(e) {
|
||||
let progress = Math.round( (e.loaded * 100) / e.total );
|
||||
self.uploadProgress = progress;
|
||||
}
|
||||
};
|
||||
|
||||
axios.post('/api/v1/media', form, xhrConfig)
|
||||
.then(function(e) {
|
||||
self.uploadProgress = 100;
|
||||
self.ids.push(e.data.id);
|
||||
self.media.push(e.data);
|
||||
setTimeout(function() {
|
||||
self.mediaDrawer = true;
|
||||
self.uploading = false;
|
||||
}, 1000);
|
||||
}).catch(function(e) {
|
||||
self.uploading = false;
|
||||
io.value = null;
|
||||
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||
});
|
||||
io.value = null;
|
||||
self.uploadProgress = 0;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,195 +1,285 @@
|
|||
<template>
|
||||
<div class="postComponent d-none">
|
||||
<div class="container px-0">
|
||||
<div class="card card-md-rounded-0 status-container orientation-unknown">
|
||||
<div class="row px-0 mx-0">
|
||||
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
|
||||
<a :href="statusProfileUrl" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
||||
<div class="status-avatar mr-2">
|
||||
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;">
|
||||
</div>
|
||||
<div class="username">
|
||||
<span class="username-link font-weight-bold text-dark">{{ statusUsername }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div v-if="user != false" class="float-right">
|
||||
<div class="post-actions">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||
<span class="fas fa-ellipsis-v text-muted"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<div v-if="!owner()">
|
||||
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile()">Mute Profile</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile()">Block Profile</a>
|
||||
</div>
|
||||
<div v-if="ownerOrAdmin()">
|
||||
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
|
||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="loaded && warning" class="bg-white pt-3 border-bottom">
|
||||
<div class="container">
|
||||
<p class="text-center font-weight-bold">You are blocking this account</p>
|
||||
<p class="text-center font-weight-bold">Click <a href="#" class="cursor-pointer" @click.prevent="warning = false; fetchData()">here</a> to view this status</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loaded && warning == false" class="postComponent">
|
||||
<div v-if="profileLayout == 'metro'" class="container px-0">
|
||||
<div class="card card-md-rounded-0 status-container orientation-unknown">
|
||||
<div class="row px-0 mx-0">
|
||||
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
|
||||
<a :href="statusProfileUrl" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
||||
<div class="status-avatar mr-2">
|
||||
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 px-0 mx-0">
|
||||
<div class="postPresenterLoader text-center">
|
||||
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
<div class="username">
|
||||
<span class="username-link font-weight-bold text-dark">{{ statusUsername }}</span>
|
||||
</div>
|
||||
<div class="postPresenterContainer d-none d-flex justify-content-center align-items-center">
|
||||
<div v-if="status.pf_type === 'photo'" class="w-100">
|
||||
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video'" class="w-100">
|
||||
<video-presenter :status="status"></video-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:album'" class="w-100">
|
||||
<photo-album-presenter :status="status" v-on:lightbox="lightbox"></photo-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video:album'" class="w-100">
|
||||
<video-album-presenter :status="status"></video-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
|
||||
<mixed-album-presenter :status="status" v-on:lightbox="lightbox"></mixed-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else class="w-100">
|
||||
<p class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
|
||||
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
|
||||
<a :href="statusProfileUrl" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
||||
<div class="status-avatar mr-2">
|
||||
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;">
|
||||
</div>
|
||||
<div class="username">
|
||||
<span class="username-link font-weight-bold text-dark">{{ statusUsername }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="float-right">
|
||||
<div class="post-actions">
|
||||
<div v-if="user != false" class="dropdown">
|
||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||
<span class="fas fa-ellipsis-v text-muted"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<span v-if="!owner()">
|
||||
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile">Mute Profile</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile">Block Profile</a>
|
||||
</span>
|
||||
<span v-if="ownerOrAdmin()">
|
||||
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
|
||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost">Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<div v-if="user != false" class="float-right">
|
||||
<div class="post-actions">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||
<span class="fas fa-ellipsis-v text-muted"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<div v-if="!owner()">
|
||||
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile()">Mute Profile</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile()">Block Profile</a>
|
||||
</div>
|
||||
<div v-if="ownerOrAdmin()">
|
||||
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
|
||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-md-column flex-column-reverse h-100">
|
||||
<div class="card-body status-comments pb-5">
|
||||
<div class="status-comment">
|
||||
<p class="mb-1 read-more" style="overflow: hidden;">
|
||||
<span class="font-weight-bold pr-1">{{statusUsername}}</span>
|
||||
<span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 px-0 mx-0">
|
||||
<div class="postPresenterLoader text-center">
|
||||
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div class="postPresenterContainer d-none d-flex justify-content-center align-items-center">
|
||||
<div v-if="status.pf_type === 'photo'" class="w-100">
|
||||
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
|
||||
</div>
|
||||
|
||||
<div v-if="showComments">
|
||||
<div class="postCommentsLoader text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postCommentsContainer d-none pt-3">
|
||||
<p class="mb-1 text-center load-more-link d-none"><a href="#" class="text-muted" v-on:click="loadMore">Load more comments</a></p>
|
||||
<div class="comments" data-min-id="0" data-max-id="0">
|
||||
<div v-for="(reply, index) in results" class="pb-3">
|
||||
<p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
|
||||
<span>
|
||||
<a class="text-dark font-weight-bold mr-1" :href="reply.account.url" v-bind:title="reply.account.username">{{truncate(reply.account.username,15)}}</a>
|
||||
<span class="text-break" v-html="reply.content"></span>
|
||||
<div v-else-if="status.pf_type === 'video'" class="w-100">
|
||||
<video-presenter :status="status"></video-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:album'" class="w-100">
|
||||
<photo-album-presenter :status="status" v-on:lightbox="lightbox"></photo-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video:album'" class="w-100">
|
||||
<video-album-presenter :status="status"></video-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
|
||||
<mixed-album-presenter :status="status" v-on:lightbox="lightbox"></mixed-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else class="w-100">
|
||||
<p class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
|
||||
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
|
||||
<a :href="statusProfileUrl" class="d-flex align-items-center status-username text-truncate" data-toggle="tooltip" data-placement="bottom" :title="statusUsername">
|
||||
<div class="status-avatar mr-2">
|
||||
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;">
|
||||
</div>
|
||||
<div class="username">
|
||||
<span class="username-link font-weight-bold text-dark">{{ statusUsername }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="float-right">
|
||||
<div class="post-actions">
|
||||
<div v-if="user != false" class="dropdown">
|
||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||
<span class="fas fa-ellipsis-v text-muted"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<span v-if="!owner()">
|
||||
<a class="dropdown-item font-weight-bold" :href="reportUrl()">Report</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile">Mute Profile</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile">Block Profile</a>
|
||||
</span>
|
||||
<span class="pl-2" style="min-width:38px">
|
||||
<span v-on:click="likeReply(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
|
||||
<post-menu :status="reply" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteComment(reply.id, index)"></post-menu>
|
||||
<span v-if="ownerOrAdmin()">
|
||||
<a class="dropdown-item font-weight-bold" href="#" v-on:click.prevent="toggleCommentVisibility">{{ showComments ? 'Disable' : 'Enable'}} Comments</a>
|
||||
<a class="dropdown-item font-weight-bold" :href="editUrl()">Edit</a>
|
||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost">Delete</a>
|
||||
</span>
|
||||
</p>
|
||||
<p class="">
|
||||
<span class="text-muted mr-3" style="width: 20px;" v-text="timeAgo(reply.created_at)"></span>
|
||||
<span v-if="reply.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{reply.favourites_count == 1 ? '1 like' : reply.favourites_count + ' likes'}}</span>
|
||||
<span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply)">Reply</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-md-column flex-column-reverse h-100">
|
||||
<div class="card-body status-comments pb-5">
|
||||
<div class="status-comment">
|
||||
<p class="mb-1 read-more" style="overflow: hidden;">
|
||||
<span class="font-weight-bold pr-1">{{statusUsername}}</span>
|
||||
<span class="comment-text" :id="status.id + '-status-readmore'" v-html="status.content"></span>
|
||||
</p>
|
||||
|
||||
<div v-if="showComments">
|
||||
<div class="postCommentsLoader text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postCommentsContainer d-none pt-3">
|
||||
<p v-if="status.reply_count > 10"class="mb-1 text-center load-more-link d-none"><a href="#" class="text-muted" v-on:click="loadMore">Load more comments</a></p>
|
||||
<div class="comments" data-min-id="0" data-max-id="0">
|
||||
<div v-for="(reply, index) in results" class="pb-3">
|
||||
<p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
|
||||
<span>
|
||||
<a class="text-dark font-weight-bold mr-1" :href="reply.account.url" v-bind:title="reply.account.username">{{truncate(reply.account.username,15)}}</a>
|
||||
<span class="text-break" v-html="reply.content"></span>
|
||||
</span>
|
||||
<span class="pl-2" style="min-width:38px">
|
||||
<span v-on:click="likeReply(reply, $event)"><i v-bind:class="[reply.favourited ? 'fas fa-heart fa-sm text-danger':'far fa-heart fa-sm text-lighter']"></i></span>
|
||||
<post-menu :status="reply" :profile="user" :size="'sm'" :modal="'true'" class="d-inline-block pl-2" v-on:deletePost="deleteComment(reply.id, index)"></post-menu>
|
||||
</span>
|
||||
</p>
|
||||
<p class="">
|
||||
<span class="text-muted mr-3" style="width: 20px;" v-text="timeAgo(reply.created_at)"></span>
|
||||
<span v-if="reply.favourites_count" class="text-muted comment-reaction font-weight-bold mr-3">{{reply.favourites_count == 1 ? '1 like' : reply.favourites_count + ' likes'}}</span>
|
||||
<span class="text-muted comment-reaction font-weight-bold cursor-pointer" v-on:click="replyFocus(reply)">Reply</span>
|
||||
</p>
|
||||
<div v-if="reply.reply_count > 0" class="cursor-pointer" style="margin-left:30px;" v-on:click="toggleReplies(reply)">
|
||||
<span class="show-reply-bar"></span>
|
||||
<span class="comment-reaction font-weight-bold text-muted">{{reply.thread ? 'Hide' : 'View'}} Replies ({{reply.reply_count}})</span>
|
||||
</div>
|
||||
<div v-if="reply.thread == true" class="comment-thread">
|
||||
<p class="d-flex justify-content-between align-items-top read-more pb-3" style="overflow-y: hidden;" v-for="(s, index) in reply.replies">
|
||||
<span>
|
||||
<a class="text-dark font-weight-bold mr-1" :href="s.account.url" :title="s.account.username">{{s.account.username}}</a>
|
||||
<span class="text-break" v-html="s.content"></span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body flex-grow-0 py-1">
|
||||
<div class="reactions my-1">
|
||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
||||
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
<h3 v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
||||
</div>
|
||||
<div class="reaction-counts font-weight-bold mb-0">
|
||||
<span style="cursor:pointer;" v-on:click="likesModal">
|
||||
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
||||
</span>
|
||||
<span class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
||||
</span>
|
||||
</div>
|
||||
<div class="timestamp pt-2 d-flex align-items-bottom justify-content-between">
|
||||
<a v-bind:href="statusUrl" class="small text-muted">
|
||||
{{timestampFormat()}}
|
||||
</a>
|
||||
<span class="small text-muted text-capitalize cursor-pointer" v-on:click="visibilityModal">{{status.visibility}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body flex-grow-0 py-1">
|
||||
<div class="reactions my-1">
|
||||
<div v-if="showComments && user.length !== 0" class="card-footer bg-white px-2 py-0">
|
||||
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
||||
<li class="nav-item" v-on:click="emojiReaction">😂</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">💯</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">❤️</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">🙌</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">👏</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😍</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😯</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😢</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😅</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😁</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">🙂</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😎</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="showComments" class="card-footer bg-white sticky-md-bottom p-0">
|
||||
<div v-if="user.length == 0" class="comment-form-guest p-3">
|
||||
<a href="/login">Login</a> to like or comment.
|
||||
</div>
|
||||
<form v-else class="border-0 rounded-0 align-middle" method="post" action="/i/comment" :data-id="statusId" data-truncate="false">
|
||||
<textarea class="form-control border-0 rounded-0" name="comment" placeholder="Add a comment…" autocomplete="off" autocorrect="off" style="height:56px;line-height: 18px;max-height:80px;resize: none; padding-right:4.2rem;" v-model="replyText"></textarea>
|
||||
<input type="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="postReply"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="profileLayout == 'moment'" class="momentui">
|
||||
<div class="bg-dark mt-md-n4">
|
||||
<div class="container">
|
||||
<div class="postPresenterContainer d-none d-flex justify-content-center align-items-center">
|
||||
<div v-if="status.pf_type === 'photo'" class="w-100">
|
||||
<photo-presenter :status="status" v-on:lightbox="lightbox"></photo-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video'" class="w-100">
|
||||
<video-presenter :status="status"></video-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:album'" class="w-100">
|
||||
<photo-album-presenter :status="status" v-on:lightbox="lightbox"></photo-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video:album'" class="w-100">
|
||||
<video-album-presenter :status="status"></video-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
|
||||
<mixed-album-presenter :status="status" v-on:lightbox="lightbox"></mixed-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else class="w-100">
|
||||
<p class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white">
|
||||
<div class="container">
|
||||
<div class="row py-5">
|
||||
<div class="col-12 col-md-8">
|
||||
<div class="reactions py-2">
|
||||
<h3 v-bind:class="[reactions.liked ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus"></h3>
|
||||
<h3 v-if="!status.comments_disabled" class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="replyFocus(status)"></h3>
|
||||
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
<h3 v-bind:class="[reactions.bookmarked ? 'fas fa-bookmark text-warning m-0 float-right cursor-pointer' : 'far fa-bookmark m-0 float-right cursor-pointer']" title="Bookmark" v-on:click="bookmarkStatus"></h3>
|
||||
<h3 v-bind:class="[reactions.shared ? 'far fa-share-square pr-3 m-0 text-primary float-right cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn float-right cursor-pointer']" title="Share" v-on:click="shareStatus"></h3>
|
||||
</div>
|
||||
<div class="reaction-counts font-weight-bold mb-0">
|
||||
<span style="cursor:pointer;" v-on:click="likesModal">
|
||||
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
||||
</span>
|
||||
<span class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
||||
</span>
|
||||
<div class="reaction-counts font-weight-bold mb-0">
|
||||
<span style="cursor:pointer;" v-on:click="likesModal">
|
||||
<span class="like-count">{{status.favourites_count || 0}}</span> likes
|
||||
</span>
|
||||
<span class="float-right" style="cursor:pointer;" v-on:click="sharesModal">
|
||||
<span class="share-count pl-4">{{status.reblogs_count || 0}}</span> shares
|
||||
</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="media align-items-center">
|
||||
<img :src="statusAvatar" class="rounded-circle shadow-lg mr-3" alt="avatar" width="72px" height="72px">
|
||||
<div class="media-body lead">
|
||||
by <a href="#">{{statusUsername}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timestamp">
|
||||
<a v-bind:href="statusUrl" class="small text-muted">
|
||||
{{timestampFormat()}}
|
||||
</a>
|
||||
<hr>
|
||||
<div>
|
||||
<p class="lead"><i class="far fa-clock"></i> {{timestampFormat()}}</p>
|
||||
<div class="lead" v-html="status.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showComments && user.length !== 0" class="card-footer bg-white px-2 py-0">
|
||||
<ul class="nav align-items-center emoji-reactions" style="overflow-x: scroll;flex-wrap: unset;">
|
||||
<li class="nav-item" v-on:click="emojiReaction">😂</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">💯</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">❤️</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">🙌</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">👏</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😍</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😯</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😢</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😅</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😁</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">🙂</li>
|
||||
<li class="nav-item" v-on:click="emojiReaction">😎</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="showComments" class="card-footer bg-white sticky-md-bottom p-0">
|
||||
<div v-if="user.length == 0" class="comment-form-guest p-3">
|
||||
<a href="/login">Login</a> to like or comment.
|
||||
<div class="col-12 col-md-4">
|
||||
<div v-if="status.comments_disabled" class="bg-light p-5 text-center lead">
|
||||
<p class="mb-0">Comments have been disabled on this post.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form v-else class="border-0 rounded-0 align-middle" method="post" action="/i/comment" :data-id="statusId" data-truncate="false">
|
||||
<textarea class="form-control border-0 rounded-0" name="comment" placeholder="Add a comment…" autocomplete="off" autocorrect="off" style="height:56px;line-height: 18px;max-height:80px;resize: none; padding-right:4.2rem;" v-model="replyText"></textarea>
|
||||
<input type="button" value="Post" class="d-inline-block btn btn-link font-weight-bold reply-btn text-decoration-none" v-on:click.prevent="postReply"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-modal ref="likesModal"
|
||||
id="l-modal"
|
||||
hide-footer
|
||||
|
@ -323,7 +413,7 @@
|
|||
}
|
||||
.emoji-reactions .nav-item {
|
||||
font-size: 1.2rem;
|
||||
padding: 7px;
|
||||
padding: 9px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.emoji-reactions::-webkit-scrollbar {
|
||||
|
@ -332,13 +422,31 @@
|
|||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
.momentui .bg-dark {
|
||||
background: #000 !important;
|
||||
}
|
||||
.momentui .carousel.slide,
|
||||
.momentui .carousel-item {
|
||||
background: #000 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
pixelfed.postComponent = {};
|
||||
|
||||
export default {
|
||||
props: ['status-id', 'status-username', 'status-template', 'status-url', 'status-profile-url', 'status-avatar'],
|
||||
props: [
|
||||
'status-id',
|
||||
'status-username',
|
||||
'status-template',
|
||||
'status-url',
|
||||
'status-profile-url',
|
||||
'status-avatar',
|
||||
'status-profile-id',
|
||||
'profile-layout'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
status: false,
|
||||
|
@ -354,20 +462,24 @@ export default {
|
|||
sharesPage: 1,
|
||||
lightboxMedia: false,
|
||||
replyText: '',
|
||||
|
||||
relationship: {},
|
||||
results: [],
|
||||
pagination: {},
|
||||
min_id: 0,
|
||||
max_id: 0,
|
||||
reply_to_profile_id: 0,
|
||||
thread: false,
|
||||
showComments: false
|
||||
showComments: false,
|
||||
warning: false,
|
||||
loaded: false,
|
||||
loading: null,
|
||||
replyingToId: this.statusId,
|
||||
emoji: ['😀','😁','😂','🤣','😃','😄','😅','😆','😉','😊','😋','😎','😍','😘','😗','😙','😚','☺️','🙂','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😯','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','☹️','🙁','😖','😞','😟','😤','😢','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🙌','👏','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👌','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'],
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
this.authCheck();
|
||||
this.fetchRelationships();
|
||||
let token = $('meta[name="csrf-token"]').attr('content');
|
||||
$('input[name="_token"]').each(function(k, v) {
|
||||
let el = $(v);
|
||||
|
@ -395,14 +507,6 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
authCheck() {
|
||||
let authed = $('body').hasClass('loggedIn');
|
||||
if(authed == true) {
|
||||
$('.comment-form-guest').addClass('d-none');
|
||||
$('.comment-form').removeClass('d-none');
|
||||
}
|
||||
},
|
||||
|
||||
showMuteBlock() {
|
||||
let sid = this.status.account.id;
|
||||
let uid = this.user.id;
|
||||
|
@ -427,10 +531,6 @@ export default {
|
|||
},
|
||||
|
||||
fetchData() {
|
||||
let loader = this.$loading.show({
|
||||
'opacity': 0,
|
||||
'background-color': '#f5f8fa'
|
||||
});
|
||||
axios.get('/api/v2/profile/'+this.statusUsername+'/status/'+this.statusId)
|
||||
.then(response => {
|
||||
let self = this;
|
||||
|
@ -444,7 +544,6 @@ export default {
|
|||
self.sharesPage = 2;
|
||||
//this.buildPresenter();
|
||||
this.showMuteBlock();
|
||||
loader.hide();
|
||||
pixelfed.readmore();
|
||||
if(self.status.comments_disabled == false) {
|
||||
self.showComments = true;
|
||||
|
@ -671,16 +770,20 @@ export default {
|
|||
return;
|
||||
}
|
||||
let data = {
|
||||
item: this.statusId,
|
||||
item: this.replyingToId,
|
||||
comment: this.replyText
|
||||
}
|
||||
axios.post('/i/comment', data)
|
||||
.then(function(res) {
|
||||
let entity = res.data.entity;
|
||||
self.results.push(entity);
|
||||
if(entity.in_reply_to_id == self.status.id) {
|
||||
self.results.push(entity);
|
||||
let elem = $('.status-comments')[0];
|
||||
elem.scrollTop = elem.clientHeight;
|
||||
} else {
|
||||
|
||||
}
|
||||
self.replyText = '';
|
||||
let elem = $('.status-comments')[0];
|
||||
elem.scrollTop = elem.clientHeight;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -694,16 +797,20 @@ export default {
|
|||
swal('Something went wrong!', 'Please try again later', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
l(e) {
|
||||
let len = e.length;
|
||||
if(len < 10) { return e; }
|
||||
return e.substr(0, 10)+'...';
|
||||
},
|
||||
|
||||
replyFocus(e) {
|
||||
this.replyingToId = e.id;
|
||||
this.reply_to_profile_id = e.account.id;
|
||||
this.replyText = '@' + e.account.username + ' ';
|
||||
$('textarea[name="comment"]').focus();
|
||||
},
|
||||
|
||||
fetchComments() {
|
||||
let url = '/api/v2/comments/'+this.statusUsername+'/status/'+this.statusId;
|
||||
axios.get(url)
|
||||
|
@ -741,6 +848,7 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadMore(e) {
|
||||
e.preventDefault();
|
||||
if(this.pagination.total_pages == 1 || this.pagination.current_page == this.pagination.total_pages) {
|
||||
|
@ -760,6 +868,7 @@ export default {
|
|||
this.pagination = response.data.meta.pagination;
|
||||
});
|
||||
},
|
||||
|
||||
likeReply(status, $event) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
|
@ -778,11 +887,13 @@ export default {
|
|||
swal('Error', 'Something went wrong, please try again later.', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
truncate(str,lim) {
|
||||
return _.truncate(str,{
|
||||
length: lim
|
||||
});
|
||||
},
|
||||
|
||||
timeAgo(ts) {
|
||||
let date = Date.parse(ts);
|
||||
let seconds = Math.floor((new Date() - date) / 1000);
|
||||
|
@ -852,6 +963,77 @@ export default {
|
|||
return;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetchRelationships() {
|
||||
let loader = this.$loading.show({
|
||||
'opacity': 0,
|
||||
'background-color': '#f5f8fa'
|
||||
});
|
||||
if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == false) {
|
||||
this.loaded = true;
|
||||
loader.hide();
|
||||
this.fetchData();
|
||||
return;
|
||||
} else {
|
||||
axios.get('/api/v1/accounts/relationships', {
|
||||
params: {
|
||||
'id[]': this.statusProfileId
|
||||
}
|
||||
}).then(res => {
|
||||
if(res.data[0] == null) {
|
||||
this.loaded = true;
|
||||
loader.hide();
|
||||
this.fetchData();
|
||||
return;
|
||||
}
|
||||
this.relationship = res.data[0];
|
||||
if(res.data[0].blocking == true) {
|
||||
this.loaded = true;
|
||||
loader.hide();
|
||||
this.warning = true;
|
||||
return;
|
||||
} else {
|
||||
this.loaded = true;
|
||||
loader.hide();
|
||||
this.fetchData();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
visibilityModal() {
|
||||
switch(this.status.visibility) {
|
||||
case 'public':
|
||||
swal('Public Post', 'This post is visible to everyone.', 'info');
|
||||
break;
|
||||
|
||||
case 'unlisted':
|
||||
swal('Unlisted Post', 'This post is visible on profiles and with a direct links. It is not displayed on timelines.', 'info');
|
||||
break;
|
||||
|
||||
case 'private':
|
||||
swal('Private Post', 'This post is only visible to followers.', 'info');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
toggleReplies(reply) {
|
||||
if(reply.thread) {
|
||||
reply.thread = false;
|
||||
} else {
|
||||
if(reply.replies.length > 0) {
|
||||
reply.thread = true;
|
||||
return;
|
||||
}
|
||||
let url = '/api/v2/comments/'+reply.account.username+'/status/'+reply.id;
|
||||
axios.get(url)
|
||||
.then(response => {
|
||||
reply.replies = _.reverse(response.data.data);
|
||||
reply.thread = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
|
|
@ -1,278 +1,347 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-center py-5 my-5" v-if="loading">
|
||||
<div v-if="relationship && relationship.blocking && warning" class="bg-white pt-3 border-bottom">
|
||||
<div class="container">
|
||||
<p class="text-center font-weight-bold">You are blocking this account</p>
|
||||
<p class="text-center font-weight-bold">Click <a href="#" class="cursor-pointer" @click.prevent="warning = false;">here</a> to view profile</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading" class="d-flex justify-content-center py-5 my-5">
|
||||
<img src="/img/pixelfed-icon-grey.svg" class="">
|
||||
</div>
|
||||
<div v-if="!loading">
|
||||
<div class="bg-white py-5 border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 d-flex">
|
||||
<div class="profile-avatar mx-md-auto">
|
||||
<div class="d-block d-md-none">
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<img class="rounded-circle box-shadow mr-5" :src="profile.avatar" width="77px" height="77px">
|
||||
</div>
|
||||
<div class="col-7 pl-2">
|
||||
<p class="font-weight-ultralight h3 mb-0">{{profile.username}}</p>
|
||||
<p v-if="profile.id == user.id && user.hasOwnProperty('id')">
|
||||
<a class="btn btn-outline-dark py-0 px-4 mt-3" href="/settings/home">Edit Profile</a>
|
||||
</p>
|
||||
<div v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||
<p class="mt-3 mb-0" v-if="relationship.following == true">
|
||||
<button type="button" class="btn btn-outline-dark font-weight-bold px-4 py-0" v-on:click="followProfile()" data-toggle="tooltip" title="Unfollow">Unfollow</button>
|
||||
<div v-if="!loading && !warning">
|
||||
<div v-if="profileLayout == 'metro'">
|
||||
<div class="bg-white py-5 border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 d-flex">
|
||||
<div class="profile-avatar mx-md-auto">
|
||||
<div class="d-block d-md-none">
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<img class="rounded-circle box-shadow mr-5" :src="profile.avatar" width="77px" height="77px">
|
||||
</div>
|
||||
<div class="col-7 pl-2">
|
||||
<p class="align-middle">
|
||||
|
||||
<span class="font-weight-ultralight h3 mb-0">{{profile.username}}</span>
|
||||
<span class="float-right mb-0" v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||
<a class="fas fa-cog fa-lg text-muted text-decoration-none" href="#" @click.prevent="visitorMenu"></a>
|
||||
</span>
|
||||
</p>
|
||||
<p class="mt-3 mb-0" v-if="!relationship.following">
|
||||
<button type="button" class="btn btn-outline-dark font-weight-bold px-4 py-0" v-on:click="followProfile()" data-toggle="tooltip" title="Follow">Follow</button>
|
||||
<p v-if="profile.id == user.id && user.hasOwnProperty('id')">
|
||||
<a class="btn btn-outline-dark py-0 px-4 mt-3" href="/settings/home">Edit Profile</a>
|
||||
</p>
|
||||
<div v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||
<p class="mt-3 mb-0" v-if="relationship.following == true">
|
||||
<button type="button" class="btn btn-outline-dark font-weight-bold px-4 py-0" v-on:click="followProfile()" data-toggle="tooltip" title="Unfollow">Unfollow</button>
|
||||
</p>
|
||||
<p class="mt-3 mb-0" v-if="!relationship.following">
|
||||
<button type="button" class="btn btn-outline-dark font-weight-bold px-4 py-0" v-on:click="followProfile()" data-toggle="tooltip" title="Follow">Follow</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-md-block">
|
||||
<img class="rounded-circle box-shadow" :src="profile.avatar" width="172px" height="172px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-md-block">
|
||||
<img class="rounded-circle box-shadow" :src="profile.avatar" width="172px" height="172px">
|
||||
</div>
|
||||
<div class="col-12 col-md-8 d-flex align-items-center">
|
||||
<div class="profile-details">
|
||||
<div class="d-none d-md-flex username-bar pb-2 align-items-center">
|
||||
<span class="font-weight-ultralight h3">{{profile.username}}</span>
|
||||
<span class="pl-4" v-if="profile.is_admin">
|
||||
<span class="btn btn-outline-secondary font-weight-bold py-0">ADMIN</span>
|
||||
</span>
|
||||
<span class="pl-4">
|
||||
<a :href="'/users/'+profile.username+'.atom'" class="fas fa-rss fa-lg text-muted text-decoration-none"></a>
|
||||
</span>
|
||||
<span class="pl-4" v-if="owner">
|
||||
<a class="fas fa-cog fa-lg text-muted text-decoration-none" href="/settings/home"></a>
|
||||
</span>
|
||||
<span class="pl-4" v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||
<a class="fas fa-cog fa-lg text-muted text-decoration-none" href="#" @click.prevent="visitorMenu"></a>
|
||||
</span>
|
||||
<span v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||
<span class="pl-4" v-if="relationship.following == true">
|
||||
<button type="button" class="btn btn-outline-secondary font-weight-bold btn-sm" v-on:click="followProfile()" data-toggle="tooltip" title="Unfollow"><i class="fas fa-user-minus"></i></button>
|
||||
</span>
|
||||
<span class="pl-4" v-if="!relationship.following">
|
||||
<button type="button" class="btn btn-primary font-weight-bold btn-sm" v-on:click="followProfile()" data-toggle="tooltip" title="Follow"><i class="fas fa-user-plus"></i></button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-none d-md-inline-flex profile-stats pb-3 lead">
|
||||
<div class="font-weight-light pr-5">
|
||||
<a class="text-dark" :href="profile.url">
|
||||
<span class="font-weight-bold">{{profile.statuses_count}}</span>
|
||||
Posts
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="profileSettings.followers.count" class="font-weight-light pr-5">
|
||||
<a class="text-dark cursor-pointer" v-on:click="followersModal()">
|
||||
<span class="font-weight-bold">{{profile.followers_count}}</span>
|
||||
Followers
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="profileSettings.following.count" class="font-weight-light">
|
||||
<a class="text-dark cursor-pointer" v-on:click="followingModal()">
|
||||
<span class="font-weight-bold">{{profile.following_count}}</span>
|
||||
Following
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="lead mb-0 d-flex align-items-center pt-3">
|
||||
<span class="font-weight-bold pr-3">{{profile.display_name}}</span>
|
||||
</p>
|
||||
<div v-if="profile.note" class="mb-0 lead" v-html="profile.note"></div>
|
||||
<p v-if="profile.website" class="mb-0"><a :href="profile.website" class="font-weight-bold" rel="me external nofollow noopener" target="_blank">{{profile.website}}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 d-flex align-items-center">
|
||||
<div class="profile-details">
|
||||
<div class="d-none d-md-flex username-bar pb-2 align-items-center">
|
||||
<span class="font-weight-ultralight h3">{{profile.username}}</span>
|
||||
<span class="pl-4" v-if="profile.is_admin">
|
||||
<span class="btn btn-outline-secondary font-weight-bold py-0">ADMIN</span>
|
||||
</span>
|
||||
<span class="pl-4">
|
||||
<a :href="'/users/'+profile.username+'.atom'" class="fas fa-rss fa-lg text-muted text-decoration-none"></a>
|
||||
</span>
|
||||
<span class="pl-4" v-if="owner">
|
||||
<a class="fas fa-cog fa-lg text-muted text-decoration-none" href="/settings/home"></a>
|
||||
</span>
|
||||
<span v-if="profile.id != user.id && user.hasOwnProperty('id')">
|
||||
<span class="pl-4" v-if="relationship.following == true">
|
||||
<button type="button" class="btn btn-outline-secondary font-weight-bold btn-sm" v-on:click="followProfile()" data-toggle="tooltip" title="Unfollow"><i class="fas fa-user-minus"></i></button>
|
||||
</span>
|
||||
<span class="pl-4" v-if="!relationship.following">
|
||||
<button type="button" class="btn btn-primary font-weight-bold btn-sm" v-on:click="followProfile()" data-toggle="tooltip" title="Follow"><i class="fas fa-user-plus"></i></button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-block d-md-none bg-white my-0 py-2 border-bottom">
|
||||
<ul class="nav d-flex justify-content-center">
|
||||
<li class="nav-item">
|
||||
<div class="font-weight-light">
|
||||
<span class="text-dark text-center">
|
||||
<p class="font-weight-bold mb-0">{{profile.statuses_count}}</p>
|
||||
<p class="text-muted mb-0">Posts</p>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item px-5">
|
||||
<div v-if="profileSettings.followers.count" class="font-weight-light">
|
||||
<a class="text-dark cursor-pointer text-center" v-on:click="followersModal()">
|
||||
<p class="font-weight-bold mb-0">{{profile.followers_count}}</p>
|
||||
<p class="text-muted mb-0">Followers</p>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<div v-if="profileSettings.following.count" class="font-weight-light">
|
||||
<a class="text-dark cursor-pointer text-center" v-on:click="followingModal()">
|
||||
<p class="font-weight-bold mb-0">{{profile.following_count}}</p>
|
||||
<p class="text-muted mb-0">Following</p>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white">
|
||||
<ul class="nav nav-topbar d-flex justify-content-center border-0">
|
||||
<!-- <li class="nav-item">
|
||||
<a class="nav-link active font-weight-bold text-uppercase" :href="profile.url">Posts</a>
|
||||
</li>
|
||||
-->
|
||||
<li class="nav-item">
|
||||
<a :class="this.mode == 'grid' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('grid')"><i class="fas fa-th fa-lg"></i></a>
|
||||
</li>
|
||||
|
||||
<!-- <li class="nav-item">
|
||||
<a :class="this.mode == 'masonry' ? 'nav-link font-weight-bold text-uppercase active' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('masonry')"><i class="fas fa-th-large"></i></a>
|
||||
</li> -->
|
||||
|
||||
<li class="nav-item px-3">
|
||||
<a :class="this.mode == 'list' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('list')"><i class="fas fa-th-list fa-lg"></i></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" v-if="owner">
|
||||
<a class="nav-link font-weight-bold text-uppercase" :href="profile.url + '/saved'">Saved</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="profile-timeline mt-md-4">
|
||||
<div class="row" v-if="mode == 'grid'">
|
||||
<div class="col-4 p-0 p-sm-2 p-md-3" v-for="(s, index) in timeline">
|
||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||
<div class="square">
|
||||
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
||||
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
||||
<div class="square-content" v-bind:style="previewBackground(s)">
|
||||
</div>
|
||||
<div class="info-overlay-text">
|
||||
<h5 class="text-white m-auto font-weight-bold">
|
||||
<span>
|
||||
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
|
||||
<span class="d-flex-inline">{{s.favourites_count}}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span>
|
||||
<span class="d-flex-inline">{{s.reblogs_count}}</span>
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="mode == 'list'">
|
||||
<div class="col-md-8 col-lg-8 offset-md-2 px-0 mb-3 timeline">
|
||||
<div class="card status-card card-md-rounded-0 my-sm-2 my-md-3 my-lg-4" :data-status-id="status.id" v-for="(status, index) in timeline" :key="status.id">
|
||||
|
||||
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
|
||||
<a class="username font-weight-bold pl-2 text-dark" v-bind:href="status.account.url">
|
||||
{{status.account.username}}
|
||||
</a>
|
||||
<div v-if="user.hasOwnProperty('id')" class="text-right" style="flex-grow:1;">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||
<span class="fas fa-ellipsis-v fa-lg text-muted"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item font-weight-bold" :href="status.url">Go to post</a>
|
||||
<span v-if="status.account.id != user.id">
|
||||
<a class="dropdown-item font-weight-bold" :href="reportUrl(status)">Report</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile(status)">Mute Profile</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile(status)">Block Profile</a>
|
||||
</span>
|
||||
<span v-if="status.account.id == user.id || user.is_admin == true">
|
||||
<a class="dropdown-item font-weight-bold" :href="editUrl(status)">Edit</a>
|
||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="postPresenterContainer">
|
||||
<div v-if="status.pf_type === 'photo'" class="w-100">
|
||||
<photo-presenter :status="status"></photo-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video'" class="w-100">
|
||||
<video-presenter :status="status"></video-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:album'" class="w-100">
|
||||
<photo-album-presenter :status="status"></photo-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video:album'" class="w-100">
|
||||
<video-album-presenter :status="status"></video-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
|
||||
<mixed-album-presenter :status="status"></mixed-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else class="w-100">
|
||||
<p class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="reactions my-1" v-if="user.hasOwnProperty('id')">
|
||||
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
|
||||
<h3 class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
|
||||
<h3 v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||
</div>
|
||||
|
||||
<div class="likes font-weight-bold">
|
||||
<span class="like-count">{{status.favourites_count}}</span> {{status.favourites_count == 1 ? 'like' : 'likes'}}
|
||||
</div>
|
||||
<div class="caption">
|
||||
<p class="mb-2 read-more" style="overflow: hidden;">
|
||||
<span class="username font-weight-bold">
|
||||
<bdi><a class="text-dark" :href="status.account.url">{{status.account.username}}</a></bdi>
|
||||
</span>
|
||||
<span v-html="status.content"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="comments">
|
||||
</div>
|
||||
<div class="timestamp pt-1">
|
||||
<p class="small text-uppercase mb-0">
|
||||
<a :href="status.url" class="text-muted">
|
||||
<timeago :datetime="status.created_at" :auto-update="60" :converter-options="{includeSeconds:true}" :title="timestampFormat(status.created_at)" v-b-tooltip.hover.bottom></timeago>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-white d-none">
|
||||
<form class="" v-on:submit.prevent="commentSubmit(status, $event)">
|
||||
<input type="hidden" name="item" value="">
|
||||
<input class="form-control status-reply-input" name="comment" placeholder="Add a comment…" autocomplete="off">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-md-inline-flex profile-stats pb-3 lead">
|
||||
<div class="font-weight-light pr-5">
|
||||
<a class="text-dark" :href="profile.url">
|
||||
</div>
|
||||
</div>
|
||||
<div class="masonry-grid" v-if="mode == 'masonry'">
|
||||
<div class="d-inline p-0 p-sm-2 p-md-3 masonry-item" v-for="(status, index) in timeline">
|
||||
<a class="" v-on:click.prevent="statusModal(status)" :href="status.url">
|
||||
<img :src="previewUrl(status)" :class="'o-'+masonryOrientation(status)">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="timeline.length">
|
||||
<infinite-loading @infinite="infiniteTimeline">
|
||||
<div slot="no-more"></div>
|
||||
<div slot="no-results"></div>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="profileLayout == 'moment'">
|
||||
<div class="w-100 h-100 mt-n3 bg-pixelfed" style="width:100%;min-height:274px;">
|
||||
</div>
|
||||
<div class="bg-white border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex justify-content-center">
|
||||
<img class="rounded-circle box-shadow" :src="profile.avatar" width="172px" height="172px" style="margin-top:-90px; border: 5px solid #fff">
|
||||
</div>
|
||||
|
||||
<div class="col-12 text-center">
|
||||
<div class="profile-details my-3">
|
||||
<p class="font-weight-ultralight h2 text-center">{{profile.username}}</p>
|
||||
<div v-if="profile.note" class="text-center text-muted p-3" v-html="profile.note"></div>
|
||||
<div class="pb-3 text-muted text-center">
|
||||
<a class="text-lighter" :href="profile.url">
|
||||
<span class="font-weight-bold">{{profile.statuses_count}}</span>
|
||||
Posts
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="profileSettings.followers.count" class="font-weight-light pr-5">
|
||||
<a class="text-dark cursor-pointer" v-on:click="followersModal()">
|
||||
<a v-if="profileSettings.followers.count" class="text-lighter cursor-pointer px-3" v-on:click="followersModal()">
|
||||
<span class="font-weight-bold">{{profile.followers_count}}</span>
|
||||
Followers
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="profileSettings.following.count" class="font-weight-light">
|
||||
<a class="text-dark cursor-pointer" v-on:click="followingModal()">
|
||||
<a v-if="profileSettings.following.count" class="text-lighter cursor-pointer" v-on:click="followingModal()">
|
||||
<span class="font-weight-bold">{{profile.following_count}}</span>
|
||||
Following
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="lead mb-0 d-flex align-items-center pt-3">
|
||||
<span class="font-weight-bold pr-3">{{profile.display_name}}</span>
|
||||
</p>
|
||||
<div v-if="profile.note" class="mb-0 lead" v-html="profile.note"></div>
|
||||
<p v-if="profile.website" class="mb-0"><a :href="profile.website" class="font-weight-bold" rel="me external nofollow noopener" target="_blank">{{profile.website}}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-block d-md-none bg-white my-0 py-2 border-bottom">
|
||||
<ul class="nav d-flex justify-content-center">
|
||||
<li class="nav-item">
|
||||
<div class="font-weight-light">
|
||||
<span class="text-dark text-center">
|
||||
<p class="font-weight-bold mb-0">{{profile.statuses_count}}</p>
|
||||
<p class="text-muted mb-0">Posts</p>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item px-5">
|
||||
<div v-if="profileSettings.followers.count" class="font-weight-light">
|
||||
<a class="text-dark cursor-pointer text-center" v-on:click="followersModal()">
|
||||
<p class="font-weight-bold mb-0">{{profile.followers_count}}</p>
|
||||
<p class="text-muted mb-0">Followers</p>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<div v-if="profileSettings.following.count" class="font-weight-light">
|
||||
<a class="text-dark cursor-pointer text-center" v-on:click="followingModal()">
|
||||
<p class="font-weight-bold mb-0">{{profile.following_count}}</p>
|
||||
<p class="text-muted mb-0">Following</p>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white">
|
||||
<ul class="nav nav-topbar d-flex justify-content-center border-0">
|
||||
<!-- <li class="nav-item">
|
||||
<a class="nav-link active font-weight-bold text-uppercase" :href="profile.url">Posts</a>
|
||||
</li>
|
||||
-->
|
||||
<li class="nav-item">
|
||||
<a :class="this.mode == 'grid' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('grid')"><i class="fas fa-th fa-lg"></i></a>
|
||||
</li>
|
||||
|
||||
<!-- <li class="nav-item">
|
||||
<a :class="this.mode == 'masonry' ? 'nav-link font-weight-bold text-uppercase active' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('masonry')"><i class="fas fa-th-large"></i></a>
|
||||
</li> -->
|
||||
|
||||
<li class="nav-item px-3">
|
||||
<a :class="this.mode == 'list' ? 'nav-link font-weight-bold text-uppercase text-primary' : 'nav-link font-weight-bold text-uppercase'" href="#" v-on:click.prevent="switchMode('list')"><i class="fas fa-th-list fa-lg"></i></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" v-if="owner">
|
||||
<a class="nav-link font-weight-bold text-uppercase" :href="profile.url + '/saved'">Saved</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="profile-timeline mt-md-4">
|
||||
<div class="row" v-if="mode == 'grid'">
|
||||
<div class="col-4 p-0 p-sm-2 p-md-3" v-for="(s, index) in timeline">
|
||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||
<div class="square">
|
||||
<span v-if="s.pf_type == 'photo:album'" class="float-right mr-3 post-icon"><i class="fas fa-images fa-2x"></i></span>
|
||||
<span v-if="s.pf_type == 'video'" class="float-right mr-3 post-icon"><i class="fas fa-video fa-2x"></i></span>
|
||||
<span v-if="s.pf_type == 'video:album'" class="float-right mr-3 post-icon"><i class="fas fa-film fa-2x"></i></span>
|
||||
<div class="square-content" v-bind:style="previewBackground(s)">
|
||||
</div>
|
||||
<div class="info-overlay-text">
|
||||
<h5 class="text-white m-auto font-weight-bold">
|
||||
<span>
|
||||
<span class="far fa-heart fa-lg p-2 d-flex-inline"></span>
|
||||
<span class="d-flex-inline">{{s.favourites_count}}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="fas fa-retweet fa-lg p-2 d-flex-inline"></span>
|
||||
<span class="d-flex-inline">{{s.reblogs_count}}</span>
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="mode == 'list'">
|
||||
<div class="col-md-8 col-lg-8 offset-md-2 px-0 mb-3 timeline">
|
||||
<div class="card status-card card-md-rounded-0 my-sm-2 my-md-3 my-lg-4" :data-status-id="status.id" v-for="(status, index) in timeline" :key="status.id">
|
||||
|
||||
<div class="card-header d-inline-flex align-items-center bg-white">
|
||||
<img v-bind:src="status.account.avatar" width="32px" height="32px" style="border-radius: 32px;">
|
||||
<a class="username font-weight-bold pl-2 text-dark" v-bind:href="status.account.url">
|
||||
{{status.account.username}}
|
||||
</a>
|
||||
<div class="text-right" style="flex-grow:1;">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link text-dark no-caret dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Post options">
|
||||
<span class="fas fa-ellipsis-v fa-lg text-muted"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item font-weight-bold" :href="status.url">Go to post</a>
|
||||
<span v-bind:class="[statusOwner(status) ? 'd-none' : '']">
|
||||
<a class="dropdown-item font-weight-bold" :href="reportUrl(status)">Report</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="muteProfile(status)">Mute Profile</a>
|
||||
<a class="dropdown-item font-weight-bold" v-on:click="blockProfile(status)">Block Profile</a>
|
||||
</span>
|
||||
<span v-bind:class="[statusOwner(status) ? '' : 'd-none']">
|
||||
<a class="dropdown-item font-weight-bold" :href="editUrl(status)">Edit</a>
|
||||
<a class="dropdown-item font-weight-bold text-danger" v-on:click="deletePost(status)">Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="postPresenterContainer">
|
||||
<div v-if="status.pf_type === 'photo'" class="w-100">
|
||||
<photo-presenter :status="status"></photo-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video'" class="w-100">
|
||||
<video-presenter :status="status"></video-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:album'" class="w-100">
|
||||
<photo-album-presenter :status="status"></photo-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'video:album'" class="w-100">
|
||||
<video-album-presenter :status="status"></video-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status.pf_type === 'photo:video:album'" class="w-100">
|
||||
<mixed-album-presenter :status="status"></mixed-album-presenter>
|
||||
</div>
|
||||
|
||||
<div v-else class="w-100">
|
||||
<p class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="reactions my-1">
|
||||
<h3 v-bind:class="[status.favourited ? 'fas fa-heart text-danger pr-3 m-0 cursor-pointer' : 'far fa-heart pr-3 m-0 like-btn cursor-pointer']" title="Like" v-on:click="likeStatus(status, $event)"></h3>
|
||||
<h3 class="far fa-comment pr-3 m-0 cursor-pointer" title="Comment" v-on:click="commentFocus(status, $event)"></h3>
|
||||
<h3 v-bind:class="[status.reblogged ? 'far fa-share-square pr-3 m-0 text-primary cursor-pointer' : 'far fa-share-square pr-3 m-0 share-btn cursor-pointer']" title="Share" v-on:click="shareStatus(status, $event)"></h3>
|
||||
</div>
|
||||
|
||||
<div class="likes font-weight-bold">
|
||||
<span class="like-count">{{status.favourites_count}}</span> {{status.favourites_count == 1 ? 'like' : 'likes'}}
|
||||
</div>
|
||||
<div class="caption">
|
||||
<p class="mb-2 read-more" style="overflow: hidden;">
|
||||
<span class="username font-weight-bold">
|
||||
<bdi><a class="text-dark" :href="status.account.url">{{status.account.username}}</a></bdi>
|
||||
</span>
|
||||
<span v-html="status.content"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="comments">
|
||||
</div>
|
||||
<div class="timestamp pt-1">
|
||||
<p class="small text-uppercase mb-0">
|
||||
<a :href="status.url" class="text-muted">
|
||||
<timeago :datetime="status.created_at" :auto-update="60" :converter-options="{includeSeconds:true}" :title="timestampFormat(status.created_at)" v-b-tooltip.hover.bottom></timeago>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-white d-none">
|
||||
<form class="" v-on:submit.prevent="commentSubmit(status, $event)">
|
||||
<input type="hidden" name="item" value="">
|
||||
<input class="form-control status-reply-input" name="comment" placeholder="Add a comment…" autocomplete="off">
|
||||
</form>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="profile-timeline mt-md-4">
|
||||
<div class="card-columns" v-if="mode == 'grid'">
|
||||
<div class="p-sm-2 p-md-3" v-for="(s, index) in timeline">
|
||||
<a class="card info-overlay card-md-border-0" :href="s.url">
|
||||
<img :src="s.media_attachments[0].url" class="img-fluid">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="masonry-grid" v-if="mode == 'masonry'">
|
||||
<div class="d-inline p-0 p-sm-2 p-md-3 masonry-item" v-for="(status, index) in timeline">
|
||||
<a class="" v-on:click.prevent="statusModal(status)" :href="status.url">
|
||||
<img :src="previewUrl(status)" :class="'o-'+masonryOrientation(status)">
|
||||
</a>
|
||||
<div v-if="timeline.length">
|
||||
<infinite-loading @infinite="infiniteTimeline">
|
||||
<div slot="no-more"></div>
|
||||
<div slot="no-results"></div>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="timeline.length">
|
||||
<infinite-loading @infinite="infiniteTimeline">
|
||||
<div slot="no-more"></div>
|
||||
<div slot="no-results"></div>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -289,7 +358,7 @@
|
|||
<div class="list-group-item border-0" v-for="(user, index) in following" :key="'following_'+index">
|
||||
<div class="media">
|
||||
<a :href="user.url">
|
||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px">
|
||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy">
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<p class="mb-0" style="font-size: 14px">
|
||||
|
@ -318,7 +387,7 @@
|
|||
<div class="list-group-item border-0" v-for="(user, index) in followers" :key="'follower_'+index">
|
||||
<div class="media">
|
||||
<a :href="user.url">
|
||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px">
|
||||
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + '’s avatar'" width="30px" loading="lazy">
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<p class="mb-0" style="font-size: 14px">
|
||||
|
@ -337,6 +406,40 @@
|
|||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
<b-modal ref="visitorContextMenu"
|
||||
id="visitor-context-menu"
|
||||
hide-footer
|
||||
hide-header
|
||||
centered
|
||||
size="sm"
|
||||
body-class="list-group-flush p-0">
|
||||
<div class="list-group" v-if="relationship">
|
||||
<div v-if="!owner && !relationship.following" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded text-primary" @click="followProfile">
|
||||
Follow
|
||||
</div>
|
||||
<div v-if="!owner && relationship.following" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded" @click="followProfile">
|
||||
Unfollow
|
||||
</div>
|
||||
<div v-if="!owner && !relationship.muting" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded" @click="muteProfile">
|
||||
Mute
|
||||
</div>
|
||||
<div v-if="!owner && relationship.muting" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded" @click="unmuteProfile">
|
||||
Unmute
|
||||
</div>
|
||||
<div v-if="!owner" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded text-danger" @click="reportProfile">
|
||||
Report User
|
||||
</div>
|
||||
<div v-if="!owner && !relationship.blocking" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded text-danger" @click="blockProfile">
|
||||
Block
|
||||
</div>
|
||||
<div v-if="!owner && relationship.blocking" class="list-group-item cursor-pointer text-center font-weight-bold lead rounded text-danger" @click="unblockProfile">
|
||||
Unblock
|
||||
</div>
|
||||
<div class="list-group-item cursor-pointer text-center font-weight-bold lead rounded text-muted" @click="$refs.visitorContextMenu.hide()">
|
||||
Close
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
<!-- <style type="text/css" scoped="">
|
||||
|
@ -373,7 +476,8 @@
|
|||
export default {
|
||||
props: [
|
||||
'profile-id',
|
||||
'profile-settings'
|
||||
'profile-settings',
|
||||
'profile-layout'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
|
@ -394,7 +498,8 @@ export default {
|
|||
followerMore: true,
|
||||
following: [],
|
||||
followingCursor: 1,
|
||||
followingMore: true
|
||||
followingMore: true,
|
||||
warning: false
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
@ -412,16 +517,12 @@ export default {
|
|||
axios.get('/api/v1/accounts/' + this.profileId).then(res => {
|
||||
this.profile = res.data;
|
||||
});
|
||||
axios.get('/api/v1/accounts/verify_credentials').then(res => {
|
||||
this.user = res.data;
|
||||
});
|
||||
axios.get('/api/v1/accounts/relationships', {
|
||||
params: {
|
||||
'id[]': this.profileId
|
||||
}
|
||||
}).then(res => {
|
||||
this.relationship = res.data[0];
|
||||
});
|
||||
if($('body').hasClass('loggedIn') == true) {
|
||||
axios.get('/api/v1/accounts/verify_credentials').then(res => {
|
||||
this.user = res.data;
|
||||
});
|
||||
this.fetchRelationships();
|
||||
}
|
||||
let apiUrl = '/api/v1/accounts/' + this.profileId + '/statuses';
|
||||
axios.get(apiUrl, {
|
||||
params: {
|
||||
|
@ -491,6 +592,11 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
reportProfile() {
|
||||
let id = this.profile.id;
|
||||
window.location.href = '/i/report?type=user&id=' + id;
|
||||
},
|
||||
|
||||
reportUrl(status) {
|
||||
let type = status.in_reply_to ? 'comment' : 'post';
|
||||
let id = status.id;
|
||||
|
@ -617,32 +723,90 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
muteProfile(status) {
|
||||
fetchRelationships() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
axios.get('/api/v1/accounts/relationships', {
|
||||
params: {
|
||||
'id[]': this.profileId
|
||||
}
|
||||
}).then(res => {
|
||||
if(res.length) {
|
||||
this.relationship = res.data[0];
|
||||
if(res.data[0].blocking == true) {
|
||||
this.warning = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
muteProfile(status = null) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
let id = this.profileId;
|
||||
axios.post('/i/mute', {
|
||||
type: 'user',
|
||||
item: status.account.id
|
||||
item: id
|
||||
}).then(res => {
|
||||
this.feed = this.feed.filter(s => s.account.id !== status.account.id);
|
||||
swal('Success', 'You have successfully muted ' + status.account.acct, 'success');
|
||||
this.fetchRelationships();
|
||||
this.$refs.visitorContextMenu.hide();
|
||||
swal('Success', 'You have successfully muted ' + this.profile.acct, 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
blockProfile(status) {
|
||||
|
||||
unmuteProfile(status = null) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
let id = this.profileId;
|
||||
axios.post('/i/unmute', {
|
||||
type: 'user',
|
||||
item: id
|
||||
}).then(res => {
|
||||
this.fetchRelationships();
|
||||
this.$refs.visitorContextMenu.hide();
|
||||
swal('Success', 'You have successfully unmuted ' + this.profile.acct, 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
blockProfile(status = null) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
let id = this.profileId;
|
||||
axios.post('/i/block', {
|
||||
type: 'user',
|
||||
item: status.account.id
|
||||
item: id
|
||||
}).then(res => {
|
||||
this.feed = this.feed.filter(s => s.account.id !== status.account.id);
|
||||
swal('Success', 'You have successfully blocked ' + status.account.acct, 'success');
|
||||
this.warning = true;
|
||||
this.fetchRelationships();
|
||||
this.$refs.visitorContextMenu.hide();
|
||||
swal('Success', 'You have successfully blocked ' + this.profile.acct, 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
unblockProfile(status = null) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
let id = this.profileId;
|
||||
axios.post('/i/unblock', {
|
||||
type: 'user',
|
||||
item: id
|
||||
}).then(res => {
|
||||
this.fetchRelationships();
|
||||
this.$refs.visitorContextMenu.hide();
|
||||
swal('Success', 'You have successfully unblocked ' + this.profile.acct, 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
});
|
||||
|
@ -657,7 +821,7 @@ export default {
|
|||
type: 'status',
|
||||
item: status.id
|
||||
}).then(res => {
|
||||
this.feed.splice(index,1);
|
||||
this.timeline.splice(index,1);
|
||||
swal('Success', 'You have successfully deleted this post', 'success');
|
||||
}).catch(err => {
|
||||
swal('Error', 'Something went wrong. Please try again later.', 'error');
|
||||
|
@ -665,6 +829,9 @@ export default {
|
|||
},
|
||||
|
||||
commentSubmit(status, $event) {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
let id = status.id;
|
||||
let form = $event.target;
|
||||
let input = $(form).find('input[name="comment"]');
|
||||
|
@ -710,9 +877,13 @@ export default {
|
|||
},
|
||||
|
||||
followProfile() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
axios.post('/i/follow', {
|
||||
item: this.profileId
|
||||
}).then(res => {
|
||||
this.$refs.visitorContextMenu.hide();
|
||||
if(this.relationship.following) {
|
||||
this.profile.followers_count--;
|
||||
if(this.profile.locked == true) {
|
||||
|
@ -726,6 +897,10 @@ export default {
|
|||
},
|
||||
|
||||
followingModal() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
||||
return;
|
||||
}
|
||||
if(this.profileSettings.following.list == false) {
|
||||
return;
|
||||
}
|
||||
|
@ -749,6 +924,10 @@ export default {
|
|||
},
|
||||
|
||||
followersModal() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
||||
return;
|
||||
}
|
||||
if(this.profileSettings.followers.list == false) {
|
||||
return;
|
||||
}
|
||||
|
@ -772,6 +951,10 @@ export default {
|
|||
},
|
||||
|
||||
followingLoadMore() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
window.location.href = encodeURI('/login?next=/' + this.profile.username + '/');
|
||||
return;
|
||||
}
|
||||
axios.get('/api/v1/accounts/'+this.profile.id+'/following', {
|
||||
params: {
|
||||
page: this.followingCursor
|
||||
|
@ -790,6 +973,9 @@ export default {
|
|||
|
||||
|
||||
followersLoadMore() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
axios.get('/api/v1/accounts/'+this.profile.id+'/followers', {
|
||||
params: {
|
||||
page: this.followerCursor
|
||||
|
@ -804,6 +990,13 @@ export default {
|
|||
this.followerMore = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
visitorMenu() {
|
||||
if($('body').hasClass('loggedIn') == false) {
|
||||
return;
|
||||
}
|
||||
this.$refs.visitorContextMenu.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
<div v-if="!loading && !networkError" class="mt-5 row">
|
||||
|
||||
<div class="col-12 col-md-3 mb-4">
|
||||
<div>
|
||||
<div v-if="results.hashtags || results.profiles || results.statuses">
|
||||
<p class="font-weight-bold">Filters</p>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="filter1" v-model="filters.hashtags">
|
||||
<label class="custom-control-label text-muted" for="filter1">Show Hashtags</label>
|
||||
<label class="custom-control-label text-muted font-weight-light" for="filter1">Show Hashtags</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="filter2" v-model="filters.profiles">
|
||||
<label class="custom-control-label text-muted" for="filter2">Show Profiles</label>
|
||||
<label class="custom-control-label text-muted font-weight-light" for="filter2">Show Profiles</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="filter3" v-model="filters.statuses">
|
||||
<label class="custom-control-label text-muted" for="filter3">Show Statuses</label>
|
||||
<label class="custom-control-label text-muted font-weight-light" for="filter3">Show Statuses</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,11 +56,11 @@
|
|||
<p class="font-weight-bold text-truncate text-dark">
|
||||
{{profile.value}}
|
||||
</p>
|
||||
<!-- <p class="mb-0 text-center">
|
||||
<p class="mb-0 text-center">
|
||||
<button :class="[profile.entity.following ? 'btn btn-secondary btn-sm py-1 font-weight-bold' : 'btn btn-primary btn-sm py-1 font-weight-bold']" v-on:click="followProfile(profile.entity.id)">
|
||||
{{profile.entity.following ? 'Unfollow' : 'Follow'}}
|
||||
</button>
|
||||
</p> -->
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -124,27 +124,31 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
fetchSearchResults() {
|
||||
axios.get('/api/search/' + encodeURI(this.query))
|
||||
.then(res => {
|
||||
let results = res.data;
|
||||
this.results.hashtags = results.hashtags;
|
||||
this.results.profiles = results.profiles;
|
||||
this.results.statuses = results.posts;
|
||||
this.loading = false;
|
||||
}).catch(err => {
|
||||
this.loading = false;
|
||||
// this.networkError = true;
|
||||
})
|
||||
axios.get('/api/search', {
|
||||
params: {
|
||||
'q': this.query,
|
||||
'src': 'metro',
|
||||
'v': 1
|
||||
}
|
||||
}).then(res => {
|
||||
let results = res.data;
|
||||
this.results.hashtags = results.hashtags;
|
||||
this.results.profiles = results.profiles;
|
||||
this.results.statuses = results.posts;
|
||||
this.loading = false;
|
||||
}).catch(err => {
|
||||
this.loading = false;
|
||||
// this.networkError = true;
|
||||
})
|
||||
},
|
||||
|
||||
followProfile(id) {
|
||||
// todo: finish AP Accept handling to enable remote follows
|
||||
return;
|
||||
// axios.post('/i/follow', {
|
||||
// item: id
|
||||
// }).then(res => {
|
||||
// window.location.href = window.location.href;
|
||||
// });
|
||||
axios.post('/i/follow', {
|
||||
item: id
|
||||
}).then(res => {
|
||||
window.location.href = window.location.href;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</video>
|
||||
|
||||
<div v-else-if="media.type == 'Image'" slot="img" :class="media.filter_class" v-on:click="$emit('lightbox', media)">
|
||||
<img class="d-block img-fluid w-100" :src="media.url" :alt="media.description" :title="media.description">
|
||||
<img class="d-block img-fluid w-100" :src="media.url" :alt="media.description" :title="media.description" loading="lazy">
|
||||
</div>
|
||||
|
||||
<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
|
@ -43,7 +43,7 @@
|
|||
</video>
|
||||
|
||||
<div v-else-if="media.type == 'Image'" slot="img" :class="media.filter_class" v-on:click="$emit('lightbox', media)">
|
||||
<img class="d-block img-fluid w-100" :src="media.url" :alt="media.description" :title="media.description">
|
||||
<img class="d-block img-fluid w-100" :src="media.url" :alt="media.description" :title="media.description" loading="lazy">
|
||||
</div>
|
||||
|
||||
<p v-else class="text-center p-0 font-weight-bold text-white">Error: Problem rendering preview.</p>
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
</summary>
|
||||
<b-carousel :id="status.id + '-carousel'"
|
||||
v-model="cursor"
|
||||
style="text-shadow: 1px 1px 2px #333;"
|
||||
style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;"
|
||||
controls
|
||||
background="#ffffff"
|
||||
:interval="0"
|
||||
>
|
||||
<b-carousel-slide v-for="(img, index) in status.media_attachments" :key="img.id">
|
||||
<div slot="img" :class="img.filter_class + ' d-block mx-auto text-center'" style="max-height: 600px;" v-on:click="$emit('lightbox', img)">
|
||||
<img class="img-fluid" style="max-height: 600px;" :src="img.url" :alt="img.description" :title="img.description">
|
||||
<div slot="img" :class="img.filter_class + ' d-block mx-auto text-center'" style="max-height: 600px;" v-on:click="$emit('lightbox', status.media_attachments[index])">
|
||||
<img class="img-fluid" style="max-height: 600px;" :src="img.url" :alt="img.description" :title="img.description" loading="lazy" v-on:click="$emit('lightbox', status.media_attachments[index])">
|
||||
</div>
|
||||
</b-carousel-slide>
|
||||
<span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;">
|
||||
|
@ -26,14 +26,14 @@
|
|||
<div v-else>
|
||||
<b-carousel :id="status.id + '-carousel'"
|
||||
v-model="cursor"
|
||||
style="text-shadow: 1px 1px 2px #333;"
|
||||
style="text-shadow: 1px 1px 2px #333;min-height: 330px;display: flex;align-items: center;"
|
||||
controls
|
||||
background="#ffffff"
|
||||
:interval="0"
|
||||
>
|
||||
<b-carousel-slide v-for="(img, index) in status.media_attachments" :key="img.id" :alt="img.description" :title="img.description">
|
||||
<div slot="img" :class="img.filter_class + ' d-block mx-auto text-center'" style="max-height: 600px;" v-on:click="$emit('lightbox', img)">
|
||||
<img class="img-fluid" style="max-height: 600px;" :src="img.url">
|
||||
<div slot="img" :class="img.filter_class + ' d-block mx-auto text-center'" style="max-height: 600px;" v-on:click="$emit('lightbox', status.media_attachments[index])">
|
||||
<img class="img-fluid" style="max-height: 600px;" :src="img.url" loading="lazy">
|
||||
</div>
|
||||
</b-carousel-slide>
|
||||
<span class="badge badge-dark box-shadow" style="position: absolute;top:10px;right:10px;">
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
<p class="font-weight-light">(click to show)</p>
|
||||
</summary>
|
||||
<div class="max-hide-overflow" v-on:click="$emit('lightbox', status.media_attachments[0])" :class="status.media_attachments[0].filter_class" :alt="status.media_attachments[0].description" :title="status.media_attachments[0].description">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url" loading="lazy">
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div :class="status.media_attachments[0].filter_class" v-on:click="$emit('lightbox', status.media_attachments[0])" :alt="status.media_attachments[0].description" :title="status.media_attachments[0].description">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url">
|
||||
<img class="card-img-top" :src="status.media_attachments[0].url" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
14
resources/assets/js/developers.js
vendored
Normal file
14
resources/assets/js/developers.js
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
Vue.component(
|
||||
'passport-clients',
|
||||
require('./components/passport/Clients.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'passport-authorized-clients',
|
||||
require('./components/passport/AuthorizedClients.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'passport-personal-access-tokens',
|
||||
require('./components/passport/PersonalAccessTokens.vue').default
|
||||
);
|
|
@ -6,13 +6,8 @@
|
|||
<div class="card mt-3">
|
||||
<div class="card-body p-0">
|
||||
<ul class="nav nav-pills d-flex text-center">
|
||||
|
||||
{{-- <li class="nav-item flex-fill">
|
||||
<a class="nav-link font-weight-bold text-uppercase" href="#">Following</a>
|
||||
</li> --}}
|
||||
|
||||
<li class="nav-item flex-fill">
|
||||
<a class="nav-link font-weight-bold text-uppercase active" href="{{route('notifications')}}">My Notifications</a>
|
||||
<a class="nav-link font-weight-bold text-uppercase active" href="{{route('notifications')}}">Notifications</a>
|
||||
</li>
|
||||
<li class="nav-item flex-fill">
|
||||
<a class="nav-link font-weight-bold text-uppercase" href="{{route('follow-requests')}}">Follow Requests</a>
|
||||
|
@ -93,7 +88,7 @@
|
|||
<span class="text-muted notification-timestamp pl-1">{{$notification->created_at->diffForHumans(null, true, true, true)}}</span>
|
||||
</span>
|
||||
<span class="float-right notification-action">
|
||||
@if($notification->item_id)
|
||||
@if(false == true && $notification->item_id && $notification->item_type == 'App\Status')
|
||||
<a href="{{$notification->status->parent()->url()}}">
|
||||
<div class="notification-image" style="background-image: url('{{$notification->status->parent()->thumb()}}')"></div>
|
||||
</a>
|
||||
|
@ -142,5 +137,10 @@
|
|||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{mix('js/activity.js')}}"></script>
|
||||
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
new Vue({
|
||||
el: '#content'
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
22
resources/views/admin/settings/config/cache.blade.php
Normal file
22
resources/views/admin/settings/config/cache.blade.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<div class="alert alert-info">Cache information is read only, to make changes please edit the .env</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="app_name" class="col-sm-3 col-form-label font-weight-bold text-right">Driver</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" disabled>
|
||||
<option {{config('cache.default') == 'apc' ? 'selected=""':''}}>APC</option>
|
||||
<option {{config('cache.default') == 'array' ? 'selected=""':''}}>Array</option>
|
||||
<option {{config('cache.default') == 'database' ? 'selected=""':''}}>Database</option>
|
||||
<option {{config('cache.default') == 'file' ? 'selected=""':''}}>File</option>
|
||||
<option {{config('cache.default') == 'memcached' ? 'selected=""':''}}>Memcached</option>
|
||||
<option {{config('cache.default') == 'redis' ? 'selected=""':''}}>Redis</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="db_host" class="col-sm-3 col-form-label font-weight-bold text-right">Cache Prefix</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" disabled value="{{config('cache.prefix')}}">
|
||||
</div>
|
||||
</div>
|
37
resources/views/admin/settings/config/database.blade.php
Normal file
37
resources/views/admin/settings/config/database.blade.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<div class="alert alert-info">Database information is read only, to make changes please edit the .env</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="app_name" class="col-sm-3 col-form-label font-weight-bold text-right">Driver</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" disabled>
|
||||
<option {{config('database.default') == 'mysql' ? 'selected=""':''}}>MySQL</option>
|
||||
<option {{config('database.default') == 'pgsql' ? 'selected=""':''}}>Postgres</option>
|
||||
<option {{config('database.default') == 'sqlite' ? 'selected=""':''}}>SQLite</option>
|
||||
<option {{config('database.default') == 'sqlsrv' ? 'selected=""':''}}>MSSQL</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="db_host" class="col-sm-3 col-form-label font-weight-bold text-right">Host</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="" name="db_host" disabled value="{{config('database.connections.mysql.host')}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="db_port" class="col-sm-3 col-form-label font-weight-bold text-right">Port</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="db_port" name="db_port" disabled value="{{config('database.connections.mysql.port')}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="db_database" class="col-sm-3 col-form-label font-weight-bold text-right">Database</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="db_database" name="db_database" disabled value="{{config('database.connections.mysql.database')}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="db_username" class="col-sm-3 col-form-label font-weight-bold text-right">Username</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" id="db_username" name="db_username" disabled value="{{config('database.connections.mysql.username')}}">
|
||||
</div>
|
||||
</div>
|
12
resources/views/admin/settings/config/filesystem.blade.php
Normal file
12
resources/views/admin/settings/config/filesystem.blade.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div class="alert alert-info">Filesystems information is read only, to make changes please edit the .env</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="app_name" class="col-sm-3 col-form-label font-weight-bold text-right">Driver</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" disabled>
|
||||
<option {{config('filesystems.default') == 'local' ? 'selected=""':''}}>Local</option>
|
||||
<option {{config('filesystems.default') == 's3' ? 'selected=""':''}}>S3</option>
|
||||
<option {{config('filesystems.default') == 'spaces' ? 'selected=""':''}}>Digital Ocean Spaces</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
103
resources/views/admin/settings/config/general.blade.php
Normal file
103
resources/views/admin/settings/config/general.blade.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<form method="post">
|
||||
@csrf
|
||||
<div class="form-group row">
|
||||
<label for="app_url" class="col-sm-3 col-form-label font-weight-bold text-right">Registration</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-check pb-2">
|
||||
<input class="form-check-input" type="checkbox" id="open_registration" name="open_registration" {{config('pixelfed.open_registration') === true ? 'checked=""' : '' }}>
|
||||
<label class="form-check-label font-weight-bold" for="open_registration">
|
||||
{{config('pixelfed.open_registration') === true ? 'Open' : 'Closed' }}
|
||||
</label>
|
||||
<p class="text-muted small help-text font-weight-bold">When this option is enabled, new user registration is open.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="app_url" class="col-sm-3 col-form-label font-weight-bold text-right">Email Validation</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-check pb-2">
|
||||
<input class="form-check-input" type="checkbox" id="enforce_email_verification" name="enforce_email_verification" {{config('pixelfed.enforce_email_verification') === true ? 'checked=""' : '' }}>
|
||||
<label class="form-check-label font-weight-bold" for="open_registration">
|
||||
{{config('pixelfed.enforce_email_verification') == true ? 'Enabled' : 'Disabled' }}
|
||||
</label>
|
||||
<p class="text-muted small help-text font-weight-bold">Enforce email validation for new user registration.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="app_url" class="col-sm-3 col-form-label font-weight-bold text-right">Recaptcha</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-check pb-2">
|
||||
<input class="form-check-input" type="checkbox" id="recaptcha" name="recaptcha" {{config('pixelfed.recaptcha') === true ? 'checked=""' : '' }}>
|
||||
<label class="form-check-label font-weight-bold" for="open_registration">
|
||||
{{config('pixelfed.recaptcha') == true ? 'Enabled' : 'Disabled' }}
|
||||
</label>
|
||||
<p class="text-muted small help-text font-weight-bold">When this option is enabled, new user registration is open.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="app_url" class="col-sm-3 col-form-label font-weight-bold text-right">ActivityPub</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-check pb-2">
|
||||
<input class="form-check-input" type="checkbox" id="activitypub_enabled" name="activitypub_enabled" {{config('pixelfed.activitypub_enabled') === true ? 'checked=""' : '' }}>
|
||||
<label class="form-check-label font-weight-bold" for="activitypub_enabled">
|
||||
{{config('pixelfed.activitypub_enabled') === true ? 'Enabled' : 'Disabled' }}
|
||||
</label>
|
||||
<p class="text-muted small help-text font-weight-bold">Enable for federation support.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label font-weight-bold text-right">Account Size</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" placeholder="1000000" name="max_account_size" value="{{config('pixelfed.max_account_size')}}">
|
||||
<span class="help-text font-weight-bold text-muted small">
|
||||
Max account size for users, in KB.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label font-weight-bold text-right">Max Upload Size</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" placeholder="15000" name="max_photo_size" value="{{config('pixelfed.max_photo_size')}}">
|
||||
<span class="help-text font-weight-bold text-muted small">
|
||||
Max file size for uploads, in KB.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label font-weight-bold text-right">Caption Length</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" placeholder="500" name="caption_limit" value="{{config('pixelfed.max_caption_length')}}">
|
||||
<span class="help-text font-weight-bold text-muted small">
|
||||
Character limit for captions and comments.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label font-weight-bold text-right">Max Album Size</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" placeholder="3" name="album_limit" value="{{config('pixelfed.max_album_length')}}">
|
||||
<span class="help-text font-weight-bold text-muted small">
|
||||
Limit # of media per post.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label font-weight-bold text-right">Image Quality</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" placeholder="80" name="image_quality" value="{{config('pixelfed.image_quality')}}">
|
||||
<span class="help-text font-weight-bold text-muted small">
|
||||
Image quality. Must be a value between 1 (worst) - 100 (best).
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group row mb-0">
|
||||
<div class="col-12 text-right">
|
||||
<button type="submit" class="btn btn-primary font-weight-bold">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -45,8 +45,8 @@
|
|||
</li>
|
||||
|
||||
{{-- <li class="pr-2">
|
||||
<a class="nav-link font-weight-bold" href="/" title="Home">
|
||||
{{ __('Network') }}
|
||||
<a class="nav-link font-weight-bold {{request()->is('timeline/network') ?'text-primary':''}}" href="{{route('timeline.network')}}" title="Network Timeline">
|
||||
<i class="fas fa-globe fa-lg"></i>
|
||||
</a>
|
||||
</li> --}}
|
||||
<div class="d-none d-md-block">
|
||||
|
@ -87,7 +87,11 @@
|
|||
<span class="far fa-map pr-1"></span>
|
||||
{{__('navmenu.publicTimeline')}}
|
||||
</a>
|
||||
|
||||
{{-- <a class="dropdown-item font-weight-bold" href="{{route('timeline.network')}}">
|
||||
<span class="fas fa-globe pr-1"></span>
|
||||
Network Timeline
|
||||
</a> --}}
|
||||
<div class="d-block d-md-none dropdown-divider"></div>
|
||||
<a class="d-block d-md-none dropdown-item font-weight-bold" href="{{route('discover')}}">
|
||||
<span class="far fa-compass pr-1"></span>
|
||||
{{__('navmenu.discover')}}
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
<profile profile-id="{{$profile->id}}" :profile-settings="{{json_encode($settings)}}"></profile>
|
||||
|
||||
<profile profile-id="{{$profile->id}}" :profile-settings="{{json_encode($settings)}}" profile-layout="{{$profile->profile_layout ?? 'metro'}}"></profile>
|
||||
@if($profile->website)
|
||||
<a class="d-none" href="{{$profile->website}}" rel="me">{{$profile->website}}</a>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@push('meta')<meta property="og:description" content="{{$profile->bio}}">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Abusive/Harmful Comment
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Abusive/Harmful Post
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Abusive/Harmful Profile
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
|
|
|
@ -2,21 +2,21 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
I'm not interested in this content
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="p-5 text-center">
|
||||
<p class="lead">You can <b class="font-weight-bold">unfollow</b> or <b class="font-weight-bold">mute</b> a user or hashtag from appearing in your timeline. Unless the content violates our terms of service, there is nothing we can do to remove it.</p>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
{{-- <div class="col-12 col-md-8 offset-md-2">
|
||||
<p><a class="font-weight-bold" href="#">
|
||||
Learn more
|
||||
</a> about our reporting guidelines and policy.</p>
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Sensitive Comment
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Sensitive Post
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Sensitive Profile
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Comment Spam
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Post Spam
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container mt-4 mb-5 pb-5">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0 col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header lead font-weight-bold">
|
||||
<div class="card-header lead font-weight-bold bg-white">
|
||||
Report Profile Spam
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{mix('js/developers.js')}}"></script>
|
||||
<script type="text/javascript">
|
||||
new Vue({
|
||||
el: '#content'
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{mix('js/developers.js')}}"></script>
|
||||
<script type="text/javascript">
|
||||
new Vue({
|
||||
el: '#content'
|
||||
|
|
|
@ -98,6 +98,22 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-5">
|
||||
<p class="font-weight-bold text-muted text-center">Layout</p>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="email" class="col-sm-3 col-form-label font-weight-bold text-right">Profile Layout</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="profileLayout1" name="profile_layout" class="custom-control-input" {{Auth::user()->profile->profile_layout != 'moment' ? 'checked':''}} value="metro">
|
||||
<label class="custom-control-label" for="profileLayout1">Metro</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="profileLayout2" name="profile_layout" class="custom-control-input" {{Auth::user()->profile->profile_layout == 'moment' ? 'checked':''}} value="moment">
|
||||
<label class="custom-control-label" for="profileLayout2">Moment</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group row">
|
||||
<div class="col-12 d-flex align-items-center justify-content-between">
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
@endif
|
||||
</div>
|
||||
|
||||
@include('settings.security.2fa.partial.log-panel')
|
||||
@include('settings.security.log-panel')
|
||||
|
||||
@include('settings.security.device-panel')
|
||||
</section>
|
||||
|
||||
@endsection
|
47
resources/views/settings/security/device-panel.blade.php
Normal file
47
resources/views/settings/security/device-panel.blade.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<div class="mb-4 pb-4">
|
||||
<h4 class="font-weight-bold">Devices</h4>
|
||||
<hr>
|
||||
<ul class="list-group">
|
||||
@foreach($devices as $device)
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-center p-3">
|
||||
<div>
|
||||
@if($device->getUserAgent()->isMobile())
|
||||
<i class="fas fa-mobile fa-5x text-muted"></i>
|
||||
@else
|
||||
<i class="fas fa-desktop fa-5x text-muted"></i>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0 font-weight-bold">
|
||||
<span class="text-muted">IP:</span>
|
||||
<span class="text-truncate">{{$device->ip}}</span>
|
||||
</p>
|
||||
<p class="mb-0 font-weight-bold">
|
||||
<span class="text-muted">Device:</span>
|
||||
<span>{{$device->getUserAgent()->device()}}</span>
|
||||
</p>
|
||||
<p class="mb-0 font-weight-bold">
|
||||
<span class="text-muted">Browser:</span>
|
||||
<span>{{$device->getUserAgent()->browser()}}</span>
|
||||
</p>
|
||||
{{-- <p class="mb-0 font-weight-bold">
|
||||
<span class="text-muted">Country:</span>
|
||||
<span>Canada</span>
|
||||
</p> --}}
|
||||
<p class="mb-0 font-weight-bold">
|
||||
<span class="text-muted">Last Login:</span>
|
||||
<span>{{$device->updated_at->diffForHumans()}}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="btn-group">
|
||||
{{-- <a class="btn btn-success font-weight-bold py-0 btn-sm" href="#">Trust</a>
|
||||
<a class="btn btn-outline-secondary font-weight-bold py-0 btn-sm" href="#">Remove Device</a> --}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
|
@ -1,12 +1,12 @@
|
|||
<div class="mb-4 pb-4">
|
||||
<h4 class="font-weight-bold">Account Log</h4>
|
||||
<hr>
|
||||
<ul class="list-group" style="max-height: 400px;overflow-y: scroll;">
|
||||
<ul class="list-group border" style="max-height: 400px;overflow-y: auto;">
|
||||
@if($activity->count() == 0)
|
||||
<p class="alert alert-info font-weight-bold">No activity logs found!</p>
|
||||
@endif
|
||||
@foreach($activity as $log)
|
||||
<li class="list-group-item">
|
||||
<li class="list-group-item rounded-0 border-0">
|
||||
<div class="media">
|
||||
<div class="media-body">
|
||||
<span class="my-0 font-weight-bold text-muted">
|
|
@ -31,4 +31,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
new Vue({
|
||||
el: '#content'
|
||||
});
|
||||
</script>
|
||||
@endpush
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container">
|
||||
<div class="col-12">
|
||||
<div class="card mt-5">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0">
|
||||
<div class="card mt-md-5 px-0 mx-md-3">
|
||||
<div class="card-header font-weight-bold text-muted bg-white py-4">
|
||||
<a href="{{route('site.help')}}" class="text-muted">{{__('helpcenter.helpcenter')}}</a>
|
||||
<span class="px-2 font-weight-light">—</span>
|
||||
{{ $breadcrumb ?? ''}}
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="row">
|
||||
<div class="row px-0">
|
||||
@include('site.help.partial.sidebar')
|
||||
<div class="col-12 col-md-9 p-5">
|
||||
@if (session('status'))
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<div class="volume"></div>
|
||||
<div class="camera"></div>
|
||||
<div class="screen">
|
||||
<img src="/img/landing/android_1.jpg" class="img-fluid">
|
||||
<img src="/img/landing/android_1.jpg" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div class="marvel-device iphone-x" style="position: absolute;z-index: 20;margin: 99px 0 0 151px;">
|
||||
|
@ -63,10 +63,10 @@
|
|||
<div class="inner-shadow"></div>
|
||||
<div class="screen">
|
||||
<div id="iosDevice">
|
||||
<img v-if="!loading" src="/img/landing/ios_4.jpg" class="img-fluid">
|
||||
<img v-if="!loading" src="/img/landing/ios_3.jpg" class="img-fluid">
|
||||
<img v-if="!loading" src="/img/landing/ios_2.jpg" class="img-fluid">
|
||||
<img src="/img/landing/ios_1.jpg" class="img-fluid">
|
||||
<img src="/img/landing/ios_4.jpg" class="img-fluid" loading="lazy">
|
||||
<img src="/img/landing/ios_3.jpg" class="img-fluid" loading="lazy">
|
||||
<img src="/img/landing/ios_2.jpg" class="img-fluid" loading="lazy">
|
||||
<img src="/img/landing/ios_1.jpg" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div class="container">
|
||||
<div class="col-12">
|
||||
<div class="card mt-5">
|
||||
<div class="container px-0 mt-0 mt-md-4 mb-md-5 pb-md-5">
|
||||
<div class="col-12 px-0">
|
||||
<div class="card mt-md-5">
|
||||
<div class="card-body p-0">
|
||||
<div class="row">
|
||||
<div class="row px-0">
|
||||
@include('site.partial.sidebar')
|
||||
<div class="col-12 col-md-9 p-5">
|
||||
@if (session('status'))
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
</noscript>
|
||||
<div class="mt-md-4"></div>
|
||||
<post-component status-template="{{$status->viewType()}}" status-id="{{$status->id}}" status-username="{{$status->profile->username}}" status-url="{{$status->url()}}" status-profile-url="{{$status->profile->url()}}" status-avatar="{{$status->profile->avatarUrl()}}"></post-component>
|
||||
<post-component status-template="{{$status->viewType()}}" status-id="{{$status->id}}" status-username="{{$status->profile->username}}" status-url="{{$status->url()}}" status-profile-url="{{$status->profile->url()}}" status-avatar="{{$status->profile->avatarUrl()}}" status-profile-id="{{$status->profile_id}}" profile-layout="{{$status->profile->profile_layout ?? 'metro'}}"></post-component>
|
||||
|
||||
|
||||
@endsection
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
@push('scripts')
|
||||
<script type="text/javascript" src="{{ mix('js/timeline.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ mix('js/compose.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
new Vue({
|
||||
el: '#content'
|
||||
|
|
|
@ -34,9 +34,15 @@
|
|||
<label class="font-weight-bold text-muted small">Visibility</label>
|
||||
<div class="switch switch-sm">
|
||||
<select class="form-control" name="visibility">
|
||||
<option value="public" selected="">Public</option>
|
||||
<option value="unlisted">Unlisted (hidden from public timelines)</option>
|
||||
<option value="private">Followers Only</option>
|
||||
@if(Auth::user()->profile->is_private)
|
||||
<option value="public">Public</option>
|
||||
<option value="unlisted">Unlisted (hidden from public timelines)</option>
|
||||
<option value="private" selected="">Followers Only</option>
|
||||
@else
|
||||
<option value="public" selected="">Public</option>
|
||||
<option value="unlisted">Unlisted (hidden from public timelines)</option>
|
||||
<option value="private">Followers Only</option>
|
||||
@endif
|
||||
</select>
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
|
|
94
resources/views/vendor/passport/authorize.blade.php
vendored
Normal file
94
resources/views/vendor/passport/authorize.blade.php
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>{{ config('app.name') }} - Authorization</title>
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ mix('/css/app.css') }}" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
.passport-authorize .container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.passport-authorize .scopes {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.passport-authorize .buttons {
|
||||
margin-top: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.passport-authorize .btn {
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
.passport-authorize .btn-approve {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.passport-authorize form {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="passport-authorize">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="text-center mb-5">
|
||||
<img src="/img/pixelfed-icon-grey.svg">
|
||||
</div>
|
||||
<div class="card card-default">
|
||||
<div class="card-header text-center font-weight-bold bg-white">
|
||||
Authorization Request
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Introduction -->
|
||||
<p><strong>{{ $client->name }}</strong> is requesting permission to access your account.</p>
|
||||
|
||||
<!-- Scope List -->
|
||||
@if (count($scopes) > 0)
|
||||
<div class="scopes">
|
||||
<p><strong>This application will be able to:</strong></p>
|
||||
|
||||
<ul>
|
||||
@foreach ($scopes as $scope)
|
||||
<li><b class="pr-3">{{$scope->id}}</b> {{ $scope->description }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="buttons">
|
||||
<!-- Authorize Button -->
|
||||
<form method="post" action="{{ route('passport.authorizations.approve') }}">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<input type="hidden" name="state" value="{{ $request->state }}">
|
||||
<input type="hidden" name="client_id" value="{{ $client->id }}">
|
||||
<button type="submit" class="btn btn-success font-weight-bold btn-approve">Authorize</button>
|
||||
</form>
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<form method="post" action="{{ route('passport.authorizations.deny') }}">
|
||||
{{ csrf_field() }}
|
||||
{{ method_field('DELETE') }}
|
||||
|
||||
<input type="hidden" name="state" value="{{ $request->state }}">
|
||||
<input type="hidden" name="client_id" value="{{ $client->id }}">
|
||||
<button class="btn btn-outline-danger font-weight-bold">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -64,9 +64,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('discover', 'DiscoverController@home')->name('discover');
|
||||
|
||||
Route::group(['prefix' => 'api'], function () {
|
||||
Route::get('search/{tag}', 'SearchController@searchAPI')
|
||||
//->where('tag', '.*');
|
||||
->where('tag', '[A-Za-z0-9]+');
|
||||
Route::get('search', 'SearchController@searchAPI');
|
||||
Route::get('nodeinfo/2.0.json', 'FederationController@nodeinfo');
|
||||
|
||||
Route::group(['prefix' => 'v1'], function () {
|
||||
|
@ -83,6 +81,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('notifications', 'ApiController@notifications');
|
||||
Route::get('timelines/public', 'PublicApiController@publicTimelineApi');
|
||||
Route::get('timelines/home', 'PublicApiController@homeTimelineApi');
|
||||
// Route::get('timelines/network', 'PublicApiController@homeTimelineApi');
|
||||
});
|
||||
Route::group(['prefix' => 'v2'], function() {
|
||||
Route::get('config', 'ApiController@siteConfiguration');
|
||||
|
@ -111,7 +110,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::post('comment', 'CommentController@store');
|
||||
Route::post('delete', 'StatusController@delete');
|
||||
Route::post('mute', 'AccountController@mute');
|
||||
Route::post('unmute', 'AccountController@unmute');
|
||||
Route::post('block', 'AccountController@block');
|
||||
Route::post('unblock', 'AccountController@unblock');
|
||||
Route::post('like', 'LikeController@store');
|
||||
Route::post('share', 'StatusController@storeShare');
|
||||
Route::post('follow', 'FollowerController@store');
|
||||
|
@ -266,6 +267,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::redirect('/', '/');
|
||||
Route::get('public', 'TimelineController@local')->name('timeline.public');
|
||||
Route::post('public', 'StatusController@store');
|
||||
// Route::get('network', 'TimelineController@network')->name('timeline.network');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'users'], function () {
|
||||
|
|
|
@ -22,12 +22,6 @@ class NoteAttachmentTest extends TestCase
|
|||
$this->invalidMime = json_decode('{"id":"https://mastodon.social/users/dansup/statuses/100889802384218791/activity","type":"Create","actor":"https://mastodon.social/users/dansup","published":"2018-10-13T18:43:33Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/dansup/followers"],"object":{"id":"https://mastodon.social/users/dansup/statuses/100889802384218791","type":"Note","summary":null,"inReplyTo":null,"published":"2018-10-13T18:43:33Z","url":"https://mastodon.social/@dansup/100889802384218791","attributedTo":"https://mastodon.social/users/dansup","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mastodon.social/users/dansup/followers"],"sensitive":false,"atomUri":"https://mastodon.social/users/dansup/statuses/100889802384218791","inReplyToAtomUri":null,"conversation":"tag:mastodon.social,2018-10-13:objectId=59103420:objectType=Conversation","content":"<p>Good Morning! <a href=\"https://mastodon.social/tags/coffee\" class=\"mention hashtag\" rel=\"tag\">#<span>coffee</span></a></p>","contentMap":{"en":"<p>Good Morning! <a href=\"https://mastodon.social/tags/coffee\" class=\"mention hashtag\" rel=\"tag\">#<span>coffee</span></a></p>"},"attachment":[{"type":"Document","mediaType":"image/webp","url":"https://files.mastodon.social/media_attachments/files/007/110/573/original/96a196885a77c9a4.jpg","name":null}],"tag":[{"type":"Hashtag","href":"https://mastodon.social/tags/coffee","name":"#coffee"}]}}', true, 9);
|
||||
}
|
||||
|
||||
public function testPleroma()
|
||||
{
|
||||
$valid = Helpers::verifyAttachments($this->pleroma);
|
||||
$this->assertTrue($valid);
|
||||
}
|
||||
|
||||
public function testMastodon()
|
||||
{
|
||||
$valid = Helpers::verifyAttachments($this->mastodon);
|
||||
|
|
23
tests/Unit/PurifierTest.php
Normal file
23
tests/Unit/PurifierTest.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Purify;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
class PurifierTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function puckTest()
|
||||
{
|
||||
$actual = Purify::clean("<span class=\"fa-spin fa\">catgirl spinning around in the interblag</span>");
|
||||
$expected = 'catgirl spinning around in the interblag';
|
||||
$this->assertEquals($expected, $actual);
|
||||
|
||||
$actual = Purify::clean("<p class=\"fa-spin fa\">catgirl spinning around in the interblag</p>");
|
||||
$expected = '<p>catgirl spinning around in the interblag</p>';
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
3
webpack.mix.js
vendored
3
webpack.mix.js
vendored
|
@ -34,6 +34,9 @@ mix.js('resources/assets/js/app.js', 'public/js')
|
|||
// SearchResults component
|
||||
.js('resources/assets/js/search.js', 'public/js')
|
||||
|
||||
// Developer Components
|
||||
.js('resources/assets/js/developers.js', 'public/js')
|
||||
|
||||
.sass('resources/assets/sass/app.scss', 'public/css', {
|
||||
implementation: require('node-sass')
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue