mirror of
https://github.com/YunoHost-Apps/pixelfed_ynh.git
synced 2024-09-03 20:06:04 +02:00
Merge branch 'frontend-ui-refactor' into gdpr-privacy-policy
This commit is contained in:
commit
921ff1ae61
115 changed files with 5032 additions and 1037 deletions
34
.env.example
34
.env.example
|
@ -1,46 +1,52 @@
|
|||
APP_NAME=Laravel
|
||||
APP_NAME="PixelFed Test"
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
ADMIN_DOMAIN="localhost"
|
||||
APP_DOMAIN="localhost"
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=homestead
|
||||
DB_USERNAME=homestead
|
||||
DB_PASSWORD=secret
|
||||
DB_DATABASE=
|
||||
DB_USERNAME=
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
CACHE_DRIVER=redis
|
||||
SESSION_DRIVER=redis
|
||||
SESSION_LIFETIME=120
|
||||
QUEUE_DRIVER=sync
|
||||
QUEUE_DRIVER=redis
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_DRIVER=log
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="pixelfed@example.com
|
||||
MAIL_FROM_NAME="Pixelfed"
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
SESSION_DOMAIN=".pixelfed.dev"
|
||||
SESSION_DOMAIN="${APP_DOMAIN}"
|
||||
SESSION_SECURE_COOKIE=true
|
||||
API_BASE="/api/1/"
|
||||
API_SEARCH="/api/search"
|
||||
|
||||
OPEN_REGISTRATION=true
|
||||
RECAPTCHA_ENABLED=false
|
||||
ENFORCE_EMAIL_VERIFICATION=true
|
||||
|
||||
MAX_PHOTO_SIZE=15000
|
||||
MAX_CAPTION_LENGTH=150
|
||||
MAX_ALBUM_LENGTH=4
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
|
78
README.md
78
README.md
|
@ -1,4 +1,76 @@
|
|||
# PixelFed
|
||||
Federated Image Sharing
|
||||
# PixelFed: Federated Image Sharing
|
||||
|
||||
> This project is still in active development and not yet ready for use.
|
||||
PixelFed is a federated social image sharing platform, similar to instagram.
|
||||
Federation is done using the [ActivityPub](https://activitypub.rocks/) protocol,
|
||||
which is used by [Mastodon](http://joinmastodon.org/), [PeerTube](https://joinpeertube.org/en/),
|
||||
[Pleroma](https://pleroma.social/), and more. Through ActivityPub PixelFed can share
|
||||
and interact with these platforms, as well as other instances of PixelFed.
|
||||
|
||||
**_Please note this is alpha software, not recommended for production use,
|
||||
and federation is not supported yet._**
|
||||
|
||||
PixelFed is very early into the development stage. If you would like to have a
|
||||
permanent instance with minimal breakage, **do not use this software until
|
||||
there is a stable release**. The following setup instructions are intended for
|
||||
testing and development.
|
||||
|
||||
## Requirements
|
||||
- PHP >= 7.1.3 (7.2+ recommended for stable version)
|
||||
- MySQL, Postgres (MariaDB and sqlite are not supported yet)
|
||||
- Redis
|
||||
- Composer
|
||||
- GD or ImageMagick
|
||||
- OpenSSL PHP Extension
|
||||
- PDO PHP Extension
|
||||
- Mbstring PHP Extension
|
||||
- Tokenizer PHP Extension
|
||||
- XML PHP Extension
|
||||
- Ctype PHP Extension
|
||||
- JSON PHP Extension
|
||||
- JpegOptim
|
||||
- Optipng
|
||||
- Pngquant 2
|
||||
- SVGO
|
||||
- Gifsicle
|
||||
|
||||
## Installation
|
||||
|
||||
This guide assumes you have NGINX/Apache installed, along with the dependencies.
|
||||
Those will not be covered in these early docs.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dansup/pixelfed.git
|
||||
cd pixelfed
|
||||
composer install
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
**Edit .env file with proper values**
|
||||
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
```bash
|
||||
php artisan storage:link
|
||||
php artisan migrate
|
||||
php artisan horizon
|
||||
php artisan serve --host=localhost --port=80
|
||||
```
|
||||
|
||||
Check your browser at http://localhost
|
||||
|
||||
## Communication
|
||||
|
||||
The ways you can communicate on the project are below. Before interacting, please
|
||||
read through the [Code Of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
* IRC: #pixelfed on irc.freenode.net ([#freenode_#pixelfed:matrix.org through
|
||||
Matrix](https://matrix.to/#/#freenode_#pixelfed:matrix.org)
|
||||
* Project on Mastodon: [@pixelfed@mastodon.social](https://mastodon.social/@pixelfed)
|
||||
* E-mail: [hello@pixelfed.org](mailto:hello@pixelfed.org)
|
||||
|
||||
## Support
|
||||
|
||||
The lead maintainer is on Patreon! You can become a Patron at
|
||||
https://www.patreon.com/dansup
|
10
app/AccountLog.php
Normal file
10
app/AccountLog.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountLog extends Model
|
||||
{
|
||||
//
|
||||
}
|
|
@ -3,8 +3,16 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Avatar extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
}
|
||||
|
|
15
app/EmailVerification.php
Normal file
15
app/EmailVerification.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EmailVerification extends Model
|
||||
{
|
||||
public function url()
|
||||
{
|
||||
$base = config('app.url');
|
||||
$path = '/i/confirm-email/' . $this->user_token . '/' . $this->random_token;
|
||||
return "{$base}{$path}";
|
||||
}
|
||||
}
|
36
app/Events/AuthLoginEvent.php
Normal file
36
app/Events/AuthLoginEvent.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?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\ShouldBroadcast;
|
||||
use App\{User, UserSetting};
|
||||
|
||||
class AuthLoginEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function handle(User $user)
|
||||
{
|
||||
if(empty($user->settings)) {
|
||||
$settings = new UserSetting;
|
||||
$settings->user_id = $user->id;
|
||||
$settings->save();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Hashtag extends Model
|
||||
{
|
||||
protected $fillable = ['name','slug'];
|
||||
public $fillable = ['name','slug'];
|
||||
|
||||
public function posts()
|
||||
{
|
||||
|
|
|
@ -4,8 +4,9 @@ namespace App\Http\Controllers;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
use Auth, Cache, Redis;
|
||||
use App\{Notification, Profile, User};
|
||||
use App\Mail\ConfirmEmail;
|
||||
use Auth, DB, Cache, Mail, Redis;
|
||||
use App\{EmailVerification, Notification, Profile, User};
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
|
@ -17,19 +18,78 @@ class AccountController extends Controller
|
|||
public function notifications(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'page' => 'nullable|min:1|max:3'
|
||||
'page' => 'nullable|min:1|max:3',
|
||||
'a' => 'nullable|alpha_dash',
|
||||
]);
|
||||
$profile = Auth::user()->profile;
|
||||
$action = $request->input('a');
|
||||
$timeago = Carbon::now()->subMonths(6);
|
||||
$notifications = Notification::whereProfileId($profile->id)
|
||||
->whereDate('created_at', '>', $timeago)
|
||||
->orderBy('id','desc')
|
||||
->take(30)
|
||||
->simplePaginate();
|
||||
if($action && in_array($action, ['comment', 'follow', 'mention'])) {
|
||||
$notifications = Notification::whereProfileId($profile->id)
|
||||
->whereAction($action)
|
||||
->whereDate('created_at', '>', $timeago)
|
||||
->orderBy('id','desc')
|
||||
->simplePaginate(30);
|
||||
} else {
|
||||
$notifications = Notification::whereProfileId($profile->id)
|
||||
->whereDate('created_at', '>', $timeago)
|
||||
->orderBy('id','desc')
|
||||
->simplePaginate(30);
|
||||
}
|
||||
|
||||
return view('account.activity', compact('profile', 'notifications'));
|
||||
}
|
||||
|
||||
public function verifyEmail(Request $request)
|
||||
{
|
||||
return view('account.verify_email');
|
||||
}
|
||||
|
||||
public function sendVerifyEmail(Request $request)
|
||||
{
|
||||
$timeLimit = Carbon::now()->subDays(1)->toDateTimeString();
|
||||
$recentAttempt = EmailVerification::whereUserId(Auth::id())
|
||||
->where('created_at', '>', $timeLimit)->count();
|
||||
$exists = EmailVerification::whereUserId(Auth::id())->count();
|
||||
|
||||
if($recentAttempt == 1 && $exists == 1) {
|
||||
return redirect()->back()->with('error', 'A verification email has already been sent recently. Please check your email, or try again later.');
|
||||
} elseif ($recentAttempt == 0 && $exists !== 0) {
|
||||
// Delete old verification and send new one.
|
||||
EmailVerification::whereUserId(Auth::id())->delete();
|
||||
}
|
||||
|
||||
|
||||
$user = User::whereNull('email_verified_at')->find(Auth::id());
|
||||
$utoken = hash('sha512', $user->id);
|
||||
$rtoken = str_random(40);
|
||||
|
||||
$verify = new EmailVerification;
|
||||
$verify->user_id = $user->id;
|
||||
$verify->email = $user->email;
|
||||
$verify->user_token = $utoken;
|
||||
$verify->random_token = $rtoken;
|
||||
$verify->save();
|
||||
|
||||
Mail::to($user->email)->send(new ConfirmEmail($verify));
|
||||
|
||||
return redirect()->back()->with('status', 'Email verification email sent!');
|
||||
}
|
||||
|
||||
public function confirmVerifyEmail(Request $request, $userToken, $randomToken)
|
||||
{
|
||||
$verify = EmailVerification::where('user_token', $userToken)
|
||||
->where('random_token', $randomToken)
|
||||
->firstOrFail();
|
||||
|
||||
if(Auth::id() === $verify->user_id) {
|
||||
$user = User::find(Auth::id());
|
||||
$user->email_verified_at = Carbon::now();
|
||||
$user->save();
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
|
||||
public function fetchNotifications($id)
|
||||
{
|
||||
$key = config('cache.prefix') . ":user.{$id}.notifications";
|
||||
|
@ -54,4 +114,5 @@ class AccountController extends Controller
|
|||
}
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
71
app/Http/Controllers/Api/BaseApiController.php
Normal file
71
app/Http/Controllers/Api/BaseApiController.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Auth;
|
||||
use App\{Like, Profile, Status};
|
||||
use League\Fractal;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Util\Webfinger\Webfinger;
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
StatusTransformer
|
||||
};
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
|
||||
class BaseApiController extends Controller
|
||||
{
|
||||
protected $fractal;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
$this->fractal = new Fractal\Manager();
|
||||
$this->fractal->setSerializer(new ArraySerializer());
|
||||
}
|
||||
|
||||
public function accounts(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::findOrFail($id);
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function accountFollowers(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::findOrFail($id);
|
||||
$followers = $profile->followers;
|
||||
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function accountFollowing(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::findOrFail($id);
|
||||
$following = $profile->following;
|
||||
$resource = new Fractal\Resource\Collection($following, new AccountTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function accountStatuses(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::findOrFail($id);
|
||||
$statuses = $profile->statuses()->orderBy('id', 'desc')->paginate(20);
|
||||
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
|
||||
public function followSuggestions(Request $request)
|
||||
{
|
||||
$followers = Auth::user()->profile->recommendFollowers();
|
||||
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer);
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\{AccountLog, User};
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
|
||||
|
@ -25,7 +26,7 @@ class LoginController extends Controller
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/home';
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
|
@ -56,4 +57,25 @@ class LoginController extends Controller
|
|||
|
||||
$this->validate($request, $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has been authenticated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param mixed $user
|
||||
* @return mixed
|
||||
*/
|
||||
protected function authenticated($request, $user)
|
||||
{
|
||||
$log = new AccountLog;
|
||||
$log->user_id = $user->id;
|
||||
$log->item_id = $user->id;
|
||||
$log->item_type = 'App\User';
|
||||
$log->action = 'auth.login';
|
||||
$log->message = 'Account Login';
|
||||
$log->link = null;
|
||||
$log->ip_address = $request->ip();
|
||||
$log->user_agent = $request->userAgent();
|
||||
$log->save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,23 +16,27 @@ class BookmarkController extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'item' => 'required|integer|min:1'
|
||||
'item' => 'required|integer|min:1'
|
||||
]);
|
||||
|
||||
$profile = Auth::user()->profile;
|
||||
$status = Status::findOrFail($request->input('item'));
|
||||
|
||||
$bookmark = Bookmark::firstOrCreate(
|
||||
['status_id' => $status->id], ['profile_id' => $profile->id]
|
||||
['status_id' => $status->id], ['profile_id' => $profile->id]
|
||||
);
|
||||
|
||||
if(!$bookmark->wasRecentlyCreated) {
|
||||
$bookmark->delete();
|
||||
}
|
||||
|
||||
if($request->ajax()) {
|
||||
$response = ['code' => 200, 'msg' => 'Bookmark saved!'];
|
||||
} else {
|
||||
} else {
|
||||
$response = redirect()->back();
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,8 +34,8 @@ class CommentController extends Controller
|
|||
|
||||
$reply = new Status();
|
||||
$reply->profile_id = $profile->id;
|
||||
$reply->caption = $comment;
|
||||
$reply->rendered = e($comment);
|
||||
$reply->caption = e($comment);
|
||||
$reply->rendered = $comment;
|
||||
$reply->in_reply_to_id = $status->id;
|
||||
$reply->in_reply_to_profile_id = $status->profile_id;
|
||||
$reply->save();
|
||||
|
@ -44,7 +44,7 @@ class CommentController extends Controller
|
|||
CommentPipeline::dispatch($status, $reply);
|
||||
|
||||
if($request->ajax()) {
|
||||
$response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url()];
|
||||
$response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption];
|
||||
} else {
|
||||
$response = redirect($status->url());
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Auth, Cache;
|
||||
use App\Profile;
|
||||
use Carbon\Carbon;
|
||||
use League\Fractal;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Util\Lexer\Nickname;
|
||||
|
@ -13,15 +14,26 @@ use App\Transformer\ActivityPub\{
|
|||
ProfileTransformer
|
||||
};
|
||||
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
|
||||
use App\Jobs\InboxPipeline\InboxWorker;
|
||||
|
||||
class FederationController extends Controller
|
||||
{
|
||||
public function authCheck()
|
||||
{
|
||||
if(!Auth::check()) {
|
||||
abort(403);
|
||||
return abort(403);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public function authorizeFollow(Request $request)
|
||||
{
|
||||
$this->authCheck();
|
||||
$this->validate($request, [
|
||||
'acct' => 'required|string|min:3|max:255'
|
||||
]);
|
||||
$acct = $request->input('acct');
|
||||
$nickname = Nickname::normalizeProfileUrl($acct);
|
||||
return view('federation.authorizefollow', compact('acct', 'nickname'));
|
||||
}
|
||||
|
||||
public function remoteFollow()
|
||||
|
@ -64,61 +76,58 @@ class FederationController extends Controller
|
|||
|
||||
public function nodeinfo()
|
||||
{
|
||||
$res = [
|
||||
'metadata' => [
|
||||
'nodeName' => config('app.name'),
|
||||
'software' => [
|
||||
'homepage' => 'https://pixelfed.org',
|
||||
'github' => 'https://github.com/pixelfed',
|
||||
'follow' => 'https://mastodon.social/@pixelfed'
|
||||
],
|
||||
/*
|
||||
TODO: Custom Features for Trending
|
||||
'customFeatures' => [
|
||||
'trending' => [
|
||||
'description' => 'Trending API for federated discovery',
|
||||
'api' => [
|
||||
'url' => null,
|
||||
'docs' => null
|
||||
],
|
||||
$res = Cache::remember('api:nodeinfo', 60, function() {
|
||||
return [
|
||||
'metadata' => [
|
||||
'nodeName' => config('app.name'),
|
||||
'software' => [
|
||||
'homepage' => 'https://pixelfed.org',
|
||||
'github' => 'https://github.com/pixelfed',
|
||||
'follow' => 'https://mastodon.social/@pixelfed'
|
||||
],
|
||||
],
|
||||
*/
|
||||
],
|
||||
'openRegistrations' => config('pixelfed.open_registration'),
|
||||
'protocols' => [
|
||||
'activitypub'
|
||||
],
|
||||
'services' => [
|
||||
'inbound' => [],
|
||||
'outbound' => []
|
||||
],
|
||||
'software' => [
|
||||
'name' => 'pixelfed',
|
||||
'version' => config('pixelfed.version')
|
||||
],
|
||||
'usage' => [
|
||||
'localPosts' => \App\Status::whereLocal(true)->count(),
|
||||
'users' => [
|
||||
'total' => \App\User::count()
|
||||
]
|
||||
],
|
||||
'version' => '2.0'
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
'openRegistrations' => config('pixelfed.open_registration'),
|
||||
'protocols' => [
|
||||
'activitypub'
|
||||
],
|
||||
'services' => [
|
||||
'inbound' => [],
|
||||
'outbound' => []
|
||||
],
|
||||
'software' => [
|
||||
'name' => 'pixelfed',
|
||||
'version' => config('pixelfed.version')
|
||||
],
|
||||
'usage' => [
|
||||
'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(),
|
||||
'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(),
|
||||
'users' => [
|
||||
'total' => \App\User::count(),
|
||||
'activeHalfyear' => \App\User::where('updated_at', '>', Carbon::now()->subMonths(6)->toDateTimeString())->count(),
|
||||
'activeMonth' => \App\User::where('updated_at', '>', Carbon::now()->subMonths(1)->toDateTimeString())->count(),
|
||||
]
|
||||
],
|
||||
'version' => '2.0'
|
||||
];
|
||||
});
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
|
||||
public function webfinger(Request $request)
|
||||
{
|
||||
$this->validate($request, ['resource'=>'required']);
|
||||
$resource = $request->input('resource');
|
||||
$parsed = Nickname::normalizeProfileUrl($resource);
|
||||
$username = $parsed['username'];
|
||||
$user = Profile::whereUsername($username)->firstOrFail();
|
||||
$webfinger = (new Webfinger($user))->generate();
|
||||
return response()->json($webfinger);
|
||||
$this->validate($request, ['resource'=>'required|string|min:3|max:255']);
|
||||
|
||||
$hash = hash('sha512', $request->input('resource'));
|
||||
|
||||
$webfinger = Cache::remember('api:webfinger:'.$hash, 1440, function() use($request) {
|
||||
$resource = $request->input('resource');
|
||||
$parsed = Nickname::normalizeProfileUrl($resource);
|
||||
$username = $parsed['username'];
|
||||
$user = Profile::whereUsername($username)->firstOrFail();
|
||||
return (new Webfinger($user))->generate();
|
||||
});
|
||||
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function userOutbox(Request $request, $username)
|
||||
|
@ -135,4 +144,20 @@ class FederationController extends Controller
|
|||
return response()->json($res['data']);
|
||||
}
|
||||
|
||||
public function userInbox(Request $request, $username)
|
||||
{
|
||||
if(config('pixelfed.activitypub_enabled') == false) {
|
||||
abort(403);
|
||||
}
|
||||
$mimes = [
|
||||
'application/activity+json',
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
];
|
||||
if(!in_array($request->header('Content-Type'), $mimes)) {
|
||||
abort(500, 'Invalid request');
|
||||
}
|
||||
$profile = Profile::whereUsername($username)->firstOrFail();
|
||||
InboxWorker::dispatch($request, $profile, $request->all());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ImportDataController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Auth, Hashids;
|
||||
use Auth, Cache, Hashids;
|
||||
use App\{Like, Profile, Status, User};
|
||||
use App\Jobs\LikePipeline\LikePipeline;
|
||||
|
||||
|
@ -27,7 +27,7 @@ class LikeController extends Controller
|
|||
|
||||
if($status->likes()->whereProfileId($profile->id)->count() !== 0) {
|
||||
$like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail();
|
||||
$like->delete();
|
||||
$like->forceDelete();
|
||||
$count--;
|
||||
} else {
|
||||
$like = new Like;
|
||||
|
@ -35,9 +35,15 @@ class LikeController extends Controller
|
|||
$like->status_id = $status->id;
|
||||
$like->save();
|
||||
$count++;
|
||||
LikePipeline::dispatch($like);
|
||||
}
|
||||
|
||||
LikePipeline::dispatch($like);
|
||||
$likes = Like::whereProfileId($profile->id)
|
||||
->orderBy('id', 'desc')
|
||||
->take(1000)
|
||||
->pluck('status_id');
|
||||
|
||||
Cache::put('api:like-ids:user:'.$profile->id, $likes, 1440);
|
||||
|
||||
if($request->ajax()) {
|
||||
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
|
||||
|
|
|
@ -18,17 +18,22 @@ class ProfileController extends Controller
|
|||
public function show(Request $request, $username)
|
||||
{
|
||||
$user = Profile::whereUsername($username)->firstOrFail();
|
||||
$settings = User::whereUsername($username)->firstOrFail()->settings;
|
||||
|
||||
$mimes = [
|
||||
'application/activity+json',
|
||||
'application/ld+json',
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
];
|
||||
|
||||
if(in_array($request->header('accept'), $mimes) && config('pixelfed.activitypub_enabled')) {
|
||||
return $this->showActivityPub($request, $user);
|
||||
}
|
||||
|
||||
if($user->is_private == true) {
|
||||
$can_access = $this->privateProfileCheck($user);
|
||||
if($can_access !== true) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
// TODO: refactor this mess
|
||||
$owner = Auth::check() && Auth::id() === $user->user_id;
|
||||
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
|
||||
|
@ -36,11 +41,26 @@ class ProfileController extends Controller
|
|||
$timeline = $user->statuses()
|
||||
->whereHas('media')
|
||||
->whereNull('in_reply_to_id')
|
||||
->orderBy('id','desc')
|
||||
->orderBy('created_at','desc')
|
||||
->withCount(['comments', 'likes'])
|
||||
->simplePaginate(21);
|
||||
|
||||
return view('profile.show', compact('user', 'owner', 'is_following', 'is_admin', 'timeline'));
|
||||
return view('profile.show', compact('user', 'settings', 'owner', 'is_following', 'is_admin', 'timeline'));
|
||||
}
|
||||
|
||||
protected function privateProfileCheck(Profile $profile)
|
||||
{
|
||||
if(Auth::check() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$follower_ids = (array) $profile->followers()->pluck('followers.profile_id');
|
||||
$pid = Auth::user()->profile->id;
|
||||
if(!in_array($pid, $follower_ids) && $pid !== $profile->id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function showActivityPub(Request $request, $user)
|
||||
|
@ -89,11 +109,12 @@ class ProfileController extends Controller
|
|||
abort(403);
|
||||
}
|
||||
$user = Auth::user()->profile;
|
||||
$settings = User::whereUsername($username)->firstOrFail()->settings;
|
||||
$owner = true;
|
||||
$following = false;
|
||||
$timeline = $user->bookmarks()->withCount(['likes','comments'])->orderBy('created_at','desc')->simplePaginate(10);
|
||||
$is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
|
||||
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
||||
return view('profile.show', compact('user', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
|
||||
return view('profile.show', compact('user', 'settings', 'owner', 'following', 'timeline', 'is_following', 'is_admin'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\{Profile, User};
|
||||
use Auth;
|
||||
use App\{AccountLog, Profile, User};
|
||||
use Auth, DB;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
|
@ -89,6 +89,34 @@ class SettingsController extends Controller
|
|||
return view('settings.avatar');
|
||||
}
|
||||
|
||||
public function accessibility()
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
return view('settings.accessibility', compact('settings'));
|
||||
}
|
||||
|
||||
public function accessibilityStore(Request $request)
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
$fields = [
|
||||
'compose_media_descriptions',
|
||||
'reduce_motion',
|
||||
'optimize_screen_reader',
|
||||
'high_contrast_mode',
|
||||
'video_autoplay'
|
||||
];
|
||||
foreach($fields as $field) {
|
||||
$form = $request->input($field);
|
||||
if($form == 'on') {
|
||||
$settings->{$field} = true;
|
||||
} else {
|
||||
$settings->{$field} = false;
|
||||
}
|
||||
$settings->save();
|
||||
}
|
||||
return redirect(route('settings.accessibility'))->with('status', 'Settings successfully updated!');
|
||||
}
|
||||
|
||||
public function notifications()
|
||||
{
|
||||
return view('settings.notifications');
|
||||
|
@ -96,12 +124,61 @@ class SettingsController extends Controller
|
|||
|
||||
public function privacy()
|
||||
{
|
||||
return view('settings.privacy');
|
||||
$settings = Auth::user()->settings;
|
||||
$is_private = Auth::user()->profile->is_private;
|
||||
$settings['is_private'] = (bool) $is_private;
|
||||
return view('settings.privacy', compact('settings'));
|
||||
}
|
||||
|
||||
public function privacyStore(Request $request)
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
$profile = Auth::user()->profile;
|
||||
$fields = [
|
||||
'is_private',
|
||||
'crawlable',
|
||||
];
|
||||
foreach($fields as $field) {
|
||||
$form = $request->input($field);
|
||||
if($field == 'is_private') {
|
||||
if($form == 'on') {
|
||||
$profile->{$field} = true;
|
||||
$settings->show_guests = false;
|
||||
$settings->show_discover = false;
|
||||
$profile->save();
|
||||
} else {
|
||||
$profile->{$field} = false;
|
||||
$profile->save();
|
||||
}
|
||||
} elseif($field == 'crawlable') {
|
||||
if($form == 'on') {
|
||||
$settings->{$field} = false;
|
||||
} else {
|
||||
$settings->{$field} = true;
|
||||
}
|
||||
} else {
|
||||
if($form == 'on') {
|
||||
$settings->{$field} = true;
|
||||
} else {
|
||||
$settings->{$field} = false;
|
||||
}
|
||||
}
|
||||
$settings->save();
|
||||
}
|
||||
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
|
||||
}
|
||||
|
||||
public function security()
|
||||
{
|
||||
return view('settings.security');
|
||||
$sessions = DB::table('sessions')
|
||||
->whereUserId(Auth::id())
|
||||
->limit(20)
|
||||
->get();
|
||||
$activity = AccountLog::whereUserId(Auth::id())
|
||||
->orderBy('created_at','desc')
|
||||
->limit(50)
|
||||
->get();
|
||||
return view('settings.security', compact('sessions', 'activity'));
|
||||
}
|
||||
|
||||
public function applications()
|
||||
|
@ -121,7 +198,7 @@ class SettingsController extends Controller
|
|||
|
||||
public function dataImportInstagram()
|
||||
{
|
||||
return view('settings.import.ig');
|
||||
return view('settings.import.instagram.home');
|
||||
}
|
||||
|
||||
public function developers()
|
||||
|
|
|
@ -2,11 +2,39 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use App, Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use App\{Follower, Status, User};
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
|
||||
public function home()
|
||||
{
|
||||
if(Auth::check()) {
|
||||
return $this->homeTimeline();
|
||||
} else {
|
||||
return $this->homeGuest();
|
||||
}
|
||||
}
|
||||
|
||||
public function homeGuest()
|
||||
{
|
||||
return view('site.index');
|
||||
}
|
||||
|
||||
public function homeTimeline()
|
||||
{
|
||||
// TODO: Use redis for timelines
|
||||
$following = Follower::whereProfileId(Auth::user()->profile->id)->pluck('following_id');
|
||||
$following->push(Auth::user()->profile->id);
|
||||
$timeline = Status::whereIn('profile_id', $following)
|
||||
->orderBy('id','desc')
|
||||
->withCount(['comments', 'likes', 'shares'])
|
||||
->simplePaginate(10);
|
||||
return view('timeline.template', compact('timeline'));
|
||||
}
|
||||
|
||||
public function changeLocale(Request $request, $locale)
|
||||
{
|
||||
if(!App::isLocale($locale)) {
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
|||
|
||||
use Auth, Cache;
|
||||
use App\Jobs\StatusPipeline\{NewStatusPipeline, StatusDelete};
|
||||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||
use Illuminate\Http\Request;
|
||||
use App\{Media, Profile, Status, User};
|
||||
use Vinkla\Hashids\Facades\Hashids;
|
||||
|
@ -14,7 +15,7 @@ class StatusController extends Controller
|
|||
{
|
||||
$user = Profile::whereUsername($username)->firstOrFail();
|
||||
$status = Status::whereProfileId($user->id)
|
||||
->withCount(['likes', 'comments'])
|
||||
->withCount(['likes', 'comments', 'media'])
|
||||
->findOrFail($id);
|
||||
if(!$status->media_path && $status->in_reply_to_id) {
|
||||
return redirect($status->url());
|
||||
|
@ -32,36 +33,51 @@ class StatusController extends Controller
|
|||
$user = Auth::user();
|
||||
|
||||
$this->validate($request, [
|
||||
'photo' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
|
||||
'photo.*' => 'required|mimes:jpeg,png,bmp,gif|max:' . config('pixelfed.max_photo_size'),
|
||||
'caption' => 'string|max:' . config('pixelfed.max_caption_length'),
|
||||
'cw' => 'nullable|string'
|
||||
'cw' => 'nullable|string',
|
||||
'filter_class' => 'nullable|string',
|
||||
'filter_name' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if(count($request->file('photo')) > config('pixelfed.max_album_length')) {
|
||||
return redirect()->back()->with('error', 'Too many files, max limit per post: ' . config('pixelfed.max_album_length'));
|
||||
}
|
||||
|
||||
$cw = $request->filled('cw') && $request->cw == 'on' ? true : false;
|
||||
$monthHash = hash('sha1', date('Y') . date('m'));
|
||||
$userHash = hash('sha1', $user->id . (string) $user->created_at);
|
||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
||||
$path = $request->photo->store($storagePath);
|
||||
$profile = $user->profile;
|
||||
|
||||
$status = new Status;
|
||||
$status->profile_id = $profile->id;
|
||||
$status->caption = $request->caption;
|
||||
$status->caption = strip_tags($request->caption);
|
||||
$status->is_nsfw = $cw;
|
||||
|
||||
$status->save();
|
||||
|
||||
$media = new Media;
|
||||
$media->status_id = $status->id;
|
||||
$media->profile_id = $profile->id;
|
||||
$media->user_id = $user->id;
|
||||
$media->media_path = $path;
|
||||
$media->size = $request->file('photo')->getClientSize();
|
||||
$media->mime = $request->file('photo')->getClientMimeType();
|
||||
$media->save();
|
||||
NewStatusPipeline::dispatch($status, $media);
|
||||
$photos = $request->file('photo');
|
||||
$order = 1;
|
||||
foreach ($photos as $k => $v) {
|
||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
||||
$path = $v->store($storagePath);
|
||||
$media = new Media;
|
||||
$media->status_id = $status->id;
|
||||
$media->profile_id = $profile->id;
|
||||
$media->user_id = $user->id;
|
||||
$media->media_path = $path;
|
||||
$media->size = $v->getClientSize();
|
||||
$media->mime = $v->getClientMimeType();
|
||||
$media->filter_class = $request->input('filter_class');
|
||||
$media->filter_name = $request->input('filter_name');
|
||||
$media->order = $order;
|
||||
$media->save();
|
||||
ImageOptimize::dispatch($media);
|
||||
$order++;
|
||||
}
|
||||
|
||||
NewStatusPipeline::dispatch($status);
|
||||
|
||||
// TODO: Parse Caption
|
||||
// TODO: Send to subscribers
|
||||
|
||||
return redirect($status->url());
|
||||
|
|
|
@ -60,5 +60,6 @@ class Kernel extends HttpKernel
|
|||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
|
||||
];
|
||||
}
|
||||
|
|
28
app/Http/Middleware/EmailVerificationCheck.php
Normal file
28
app/Http/Middleware/EmailVerificationCheck.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Auth, Closure;
|
||||
|
||||
class EmailVerificationCheck
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if($request->user() &&
|
||||
config('pixelfed.enforce_email_verification') &&
|
||||
is_null($request->user()->email_verified_at) &&
|
||||
!$request->is('i/verify-email') && !$request->is('log*') &&
|
||||
!$request->is('i/confirm-email/*')
|
||||
) {
|
||||
return redirect('/i/verify-email');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace App;
|
|||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Report extends Model
|
||||
class ImportJob extends Model
|
||||
{
|
||||
//
|
||||
}
|
|
@ -38,7 +38,10 @@ class ImageUpdate implements ShouldQueue
|
|||
$thumb = storage_path('app/'. $media->thumbnail_path);
|
||||
try {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
ImageOptimizer::optimize($path);
|
||||
if($media->mime !== 'image/gif')
|
||||
{
|
||||
ImageOptimizer::optimize($path);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
|
43
app/Jobs/InboxPipeline/InboxWorker.php
Normal file
43
app/Jobs/InboxPipeline/InboxWorker.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\InboxPipeline;
|
||||
|
||||
use App\Profile;
|
||||
use App\Util\ActivityPub\Inbox;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class InboxWorker implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $request;
|
||||
protected $profile;
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($request, Profile $profile, $payload)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->profile = $profile;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
(new Inbox($this->request, $this->profile, $this->payload))->handle();
|
||||
}
|
||||
|
||||
}
|
41
app/Jobs/InboxPipeline/SharedInboxWorker.php
Normal file
41
app/Jobs/InboxPipeline/SharedInboxWorker.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\InboxPipeline;
|
||||
|
||||
use App\Profile;
|
||||
use App\Util\ActivityPub\Inbox;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class SharedInboxWorker implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $request;
|
||||
protected $profile;
|
||||
protected $payload;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($request, $payload)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
(new Inbox($this->request, null, $this->payload))->handleSharedInbox();
|
||||
}
|
||||
}
|
|
@ -37,6 +37,11 @@ class LikePipeline implements ShouldQueue
|
|||
$status = $this->like->status;
|
||||
$actor = $this->like->actor;
|
||||
|
||||
if($status->url !== null) {
|
||||
// Ignore notifications to remote statuses
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = Notification::whereProfileId($status->profile_id)
|
||||
->whereActorId($actor->id)
|
||||
->whereAction('like')
|
||||
|
|
|
@ -16,17 +16,15 @@ class NewStatusPipeline implements ShouldQueue
|
|||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Status $status, $media = false)
|
||||
public function __construct(Status $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,13 +35,10 @@ class NewStatusPipeline implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$media = $this->media;
|
||||
|
||||
StatusEntityLexer::dispatch($status);
|
||||
StatusActivityPubDeliver::dispatch($status);
|
||||
if($media) {
|
||||
ImageOptimize::dispatch($media);
|
||||
}
|
||||
//StatusActivityPubDeliver::dispatch($status);
|
||||
|
||||
Cache::forever('post.' . $status->id, $status);
|
||||
|
||||
$redis = Redis::connection();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Jobs\StatusPipeline;
|
||||
|
||||
use App\{Media, StatusHashtag, Status};
|
||||
use App\{Media, Notification, StatusHashtag, Status};
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
@ -58,8 +58,19 @@ class StatusDelete implements ShouldQueue
|
|||
|
||||
}
|
||||
}
|
||||
$comments = Status::where('in_reply_to_id', $status->id)->get();
|
||||
foreach($comments as $comment) {
|
||||
$comment->in_reply_to_id = null;
|
||||
$comment->save();
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($comment->id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
$status->likes()->delete();
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->delete();
|
||||
StatusHashtag::whereStatusId($status->id)->delete();
|
||||
$status->delete();
|
||||
|
||||
|
|
|
@ -2,25 +2,32 @@
|
|||
|
||||
namespace App\Jobs\StatusPipeline;
|
||||
|
||||
use Cache;
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
Hashtag,
|
||||
Media,
|
||||
Mention,
|
||||
Profile,
|
||||
Status,
|
||||
StatusHashtag
|
||||
};
|
||||
use App\Util\Lexer\Hashtag as HashtagLexer;
|
||||
use App\Util\Lexer\{Autolink, Extractor};
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use App\Jobs\MentionPipeline\MentionPipeline;
|
||||
|
||||
class StatusEntityLexer implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
protected $entities;
|
||||
protected $autolink;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
@ -39,36 +46,77 @@ class StatusEntityLexer implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$this->parseHashtags();
|
||||
$this->parseEntities();
|
||||
}
|
||||
|
||||
public function parseHashtags()
|
||||
public function parseEntities()
|
||||
{
|
||||
$this->extractEntities();
|
||||
}
|
||||
|
||||
public function extractEntities()
|
||||
{
|
||||
$this->entities = Extractor::create()->extract($this->status->caption);
|
||||
$this->autolinkStatus();
|
||||
}
|
||||
|
||||
public function autolinkStatus()
|
||||
{
|
||||
$this->autolink = Autolink::create()->autolink($this->status->caption);
|
||||
$this->storeEntities();
|
||||
}
|
||||
|
||||
public function storeEntities()
|
||||
{
|
||||
$this->storeHashtags();
|
||||
$this->storeMentions();
|
||||
DB::transaction(function () {
|
||||
$status = $this->status;
|
||||
$status->rendered = $this->autolink;
|
||||
$status->entities = json_encode($this->entities);
|
||||
$status->save();
|
||||
});
|
||||
}
|
||||
|
||||
public function storeHashtags()
|
||||
{
|
||||
$tags = array_unique($this->entities['hashtags']);
|
||||
$status = $this->status;
|
||||
$text = e($status->caption);
|
||||
$tags = HashtagLexer::getHashtags($text);
|
||||
$rendered = $text;
|
||||
if(count($tags) > 0) {
|
||||
$rendered = HashtagLexer::replaceHashtagsWithLinks($text);
|
||||
}
|
||||
$status->rendered = $rendered;
|
||||
$status->save();
|
||||
|
||||
Cache::forever('post.' . $status->id, $status);
|
||||
|
||||
foreach($tags as $tag) {
|
||||
$slug = str_slug($tag);
|
||||
|
||||
$htag = Hashtag::firstOrCreate(
|
||||
['name' => $tag],
|
||||
['slug' => $slug]
|
||||
);
|
||||
|
||||
$stag = new StatusHashtag;
|
||||
$stag->status_id = $status->id;
|
||||
$stag->hashtag_id = $htag->id;
|
||||
$stag->save();
|
||||
DB::transaction(function () use ($status, $tag) {
|
||||
$slug = str_slug($tag);
|
||||
$hashtag = Hashtag::firstOrCreate(
|
||||
['name' => $tag, 'slug' => $slug]
|
||||
);
|
||||
StatusHashtag::firstOrCreate(
|
||||
['status_id' => $status->id, 'hashtag_id' => $hashtag->id]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function storeMentions()
|
||||
{
|
||||
$mentions = array_unique($this->entities['mentions']);
|
||||
$status = $this->status;
|
||||
|
||||
foreach($mentions as $mention) {
|
||||
$mentioned = Profile::whereUsername($mention)->firstOrFail();
|
||||
|
||||
if(empty($mentioned) || !isset($mentioned->id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($status, $mentioned) {
|
||||
$m = new Mention;
|
||||
$m->status_id = $status->id;
|
||||
$m->profile_id = $mentioned->id;
|
||||
$m->save();
|
||||
});
|
||||
|
||||
MentionPipeline::dispatch($status, $m);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
10
app/Like.php
10
app/Like.php
|
@ -3,9 +3,19 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Like extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function actor()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'profile_id', 'id');
|
||||
|
|
34
app/Mail/ConfirmEmail.php
Normal file
34
app/Mail/ConfirmEmail.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\EmailVerification;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class ConfirmEmail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(EmailVerification $verify)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->markdown('emails.confirm_email')->with(['verify'=>$this->verify]);
|
||||
}
|
||||
}
|
|
@ -2,15 +2,32 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Storage;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Media extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function url()
|
||||
{
|
||||
$path = $this->media_path;
|
||||
$url = Storage::url($path);
|
||||
return url($url);
|
||||
}
|
||||
|
||||
public function thumbnailUrl()
|
||||
{
|
||||
$path = $this->thumbnail_path;
|
||||
$url = Storage::url($path);
|
||||
return url($url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,18 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Mention extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function profile()
|
||||
{
|
||||
|
|
|
@ -3,28 +3,37 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Notification extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
public function actor()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'actor_id', 'id');
|
||||
}
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function actor()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'actor_id', 'id');
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'profile_id', 'id');
|
||||
}
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'profile_id', 'id');
|
||||
}
|
||||
|
||||
public function item()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
public function item()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(Status::class, 'item_id', 'id');
|
||||
}
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(Status::class, 'item_id', 'id');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\{Profile, User};
|
||||
use App\{Profile, User, UserSetting};
|
||||
use App\Jobs\AvatarPipeline\CreateAvatar;
|
||||
|
||||
class UserObserver
|
||||
|
@ -36,6 +36,12 @@ class UserObserver
|
|||
|
||||
CreateAvatar::dispatch($profile);
|
||||
}
|
||||
|
||||
if(empty($user->settings)) {
|
||||
$settings = new UserSetting;
|
||||
$settings->user_id = $user->id;
|
||||
$settings->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,9 +5,18 @@ namespace App;
|
|||
use Storage;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Profile extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $hidden = [
|
||||
'private_key',
|
||||
];
|
||||
|
@ -20,6 +29,15 @@ class Profile extends Model
|
|||
}
|
||||
|
||||
public function url($suffix = '')
|
||||
{
|
||||
if($this->remote_url) {
|
||||
return $this->remote_url;
|
||||
} else {
|
||||
return url($this->username . $suffix);
|
||||
}
|
||||
}
|
||||
|
||||
public function localUrl($suffix = '')
|
||||
{
|
||||
return url($this->username . $suffix);
|
||||
}
|
||||
|
@ -115,4 +133,9 @@ class Profile extends Model
|
|||
$url = url(Storage::url($this->avatar->media_path ?? 'public/avatars/default.png'));
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function statusCount()
|
||||
{
|
||||
return $this->statuses()->whereHas('media')->count();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ class EventServiceProvider extends ServiceProvider
|
|||
'App\Events\Event' => [
|
||||
'App\Listeners\EventListener',
|
||||
],
|
||||
'auth.login' => [
|
||||
'App\Events\AuthLoginEvent',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
10
app/ReportComment.php
Normal file
10
app/ReportComment.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ReportComment extends Model
|
||||
{
|
||||
//
|
||||
}
|
10
app/ReportLog.php
Normal file
10
app/ReportLog.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ReportLog extends Model
|
||||
{
|
||||
//
|
||||
}
|
|
@ -2,12 +2,21 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use Auth, Storage;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Storage;
|
||||
use Vinkla\Hashids\Facades\Hashids;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Status extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
|
@ -25,7 +34,7 @@ class Status extends Model
|
|||
|
||||
public function thumb()
|
||||
{
|
||||
if($this->media->count() == 0) {
|
||||
if($this->media->count() == 0 || $this->is_nsfw) {
|
||||
return "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
|
||||
}
|
||||
return url(Storage::url($this->firstMedia()->thumbnail_path));
|
||||
|
@ -43,6 +52,11 @@ class Status extends Model
|
|||
return url($path);
|
||||
}
|
||||
|
||||
public function editUrl()
|
||||
{
|
||||
return $this->url() . '/edit';
|
||||
}
|
||||
|
||||
public function mediaUrl()
|
||||
{
|
||||
$media = $this->firstMedia();
|
||||
|
@ -57,15 +71,39 @@ class Status extends Model
|
|||
return $this->hasMany(Like::class);
|
||||
}
|
||||
|
||||
public function liked() : bool
|
||||
{
|
||||
$profile = Auth::user()->profile;
|
||||
return Like::whereProfileId($profile->id)->whereStatusId($this->id)->count();
|
||||
}
|
||||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany(Status::class, 'in_reply_to_id');
|
||||
}
|
||||
|
||||
public function bookmarked()
|
||||
{
|
||||
$profile = Auth::user()->profile;
|
||||
return Bookmark::whereProfileId($profile->id)->whereStatusId($this->id)->count();
|
||||
}
|
||||
|
||||
public function shares()
|
||||
{
|
||||
return $this->hasMany(Status::class, 'reblog_of_id');
|
||||
}
|
||||
|
||||
public function shared() : bool
|
||||
{
|
||||
$profile = Auth::user()->profile;
|
||||
return Status::whereProfileId($profile->id)->whereReblogOfId($this->id)->count();
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
if(!empty($this->in_reply_to_id)) {
|
||||
return Status::findOrFail($this->in_reply_to_id);
|
||||
$parent = $this->in_reply_to_id ?? $this->reblog_of_id;
|
||||
if(!empty($parent)) {
|
||||
return Status::findOrFail($parent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +124,23 @@ class Status extends Model
|
|||
);
|
||||
}
|
||||
|
||||
public function mentions()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Profile::class,
|
||||
Mention::class,
|
||||
'status_id',
|
||||
'id',
|
||||
'id',
|
||||
'profile_id'
|
||||
);
|
||||
}
|
||||
|
||||
public function reportUrl()
|
||||
{
|
||||
return route('report.form') . "?type=post&id={$this->id}";
|
||||
}
|
||||
|
||||
public function toActivityStream()
|
||||
{
|
||||
$media = $this->media;
|
||||
|
|
|
@ -6,5 +6,5 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class StatusHashtag extends Model
|
||||
{
|
||||
protected $fillable = ['status_id', 'hashtag_id'];
|
||||
public $fillable = ['status_id', 'hashtag_id'];
|
||||
}
|
||||
|
|
33
app/Transformer/Api/AccountTransformer.php
Normal file
33
app/Transformer/Api/AccountTransformer.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Profile;
|
||||
use League\Fractal;
|
||||
|
||||
class AccountTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Profile $profile)
|
||||
{
|
||||
return [
|
||||
'id' => $profile->id,
|
||||
'username' => $profile->username,
|
||||
'acct' => $profile->username,
|
||||
'display_name' => $profile->name,
|
||||
'locked' => (bool) $profile->is_private,
|
||||
'created_at' => $profile->created_at->format('c'),
|
||||
'followers_count' => $profile->followerCount(),
|
||||
'following_count' => $profile->followingCount(),
|
||||
'statuses_count' => $profile->statusCount(),
|
||||
'note' => $profile->bio,
|
||||
'url' => $profile->url(),
|
||||
'avatar' => $profile->avatarUrl(),
|
||||
'avatar_static' => $profile->avatarUrl(),
|
||||
'header' => '',
|
||||
'header_static' => '',
|
||||
'moved' => null,
|
||||
'fields' => null,
|
||||
'bot' => null
|
||||
];
|
||||
}
|
||||
}
|
16
app/Transformer/Api/ApplicationTransformer.php
Normal file
16
app/Transformer/Api/ApplicationTransformer.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class ApplicationTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform()
|
||||
{
|
||||
return [
|
||||
'name' => '',
|
||||
'website' => null
|
||||
];
|
||||
}
|
||||
}
|
18
app/Transformer/Api/HashtagTransformer.php
Normal file
18
app/Transformer/Api/HashtagTransformer.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Hashtag;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
|
||||
class HashtagTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Hashtag $hashtag)
|
||||
{
|
||||
return [
|
||||
'name' => $hashtag->name,
|
||||
'url' => $hashtag->url(),
|
||||
];
|
||||
}
|
||||
}
|
24
app/Transformer/Api/MediaTransformer.php
Normal file
24
app/Transformer/Api/MediaTransformer.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Media;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
|
||||
class MediaTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Media $media)
|
||||
{
|
||||
return [
|
||||
'id' => $media->id,
|
||||
'type' => 'image',
|
||||
'url' => $media->url(),
|
||||
'remote_url' => null,
|
||||
'preview_url' => $media->thumbnailUrl(),
|
||||
'text_url' => null,
|
||||
'meta' => null,
|
||||
'description' => null
|
||||
];
|
||||
}
|
||||
}
|
19
app/Transformer/Api/MentionTransformer.php
Normal file
19
app/Transformer/Api/MentionTransformer.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Profile;
|
||||
use League\Fractal;
|
||||
|
||||
class MentionTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
public function transform(Profile $profile)
|
||||
{
|
||||
return [
|
||||
'id' => $profile->id,
|
||||
'url' => $profile->url(),
|
||||
'username' => $profile->username,
|
||||
'acct' => $profile->username,
|
||||
];
|
||||
}
|
||||
}
|
69
app/Transformer/Api/StatusTransformer.php
Normal file
69
app/Transformer/Api/StatusTransformer.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Status;
|
||||
use League\Fractal;
|
||||
|
||||
class StatusTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
protected $defaultIncludes = [
|
||||
'account',
|
||||
'mentions',
|
||||
'media_attachments',
|
||||
'tags'
|
||||
];
|
||||
|
||||
public function transform(Status $status)
|
||||
{
|
||||
return [
|
||||
'id' => $status->id,
|
||||
'uri' => $status->url(),
|
||||
'url' => $status->url(),
|
||||
'in_reply_to_id' => $status->in_reply_to_id,
|
||||
'in_reply_to_account_id' => $status->in_reply_to_profile_id,
|
||||
|
||||
// TODO: fixme
|
||||
'reblog' => null,
|
||||
|
||||
'content' => "<p>$status->rendered</p>",
|
||||
'created_at' => $status->created_at->format('c'),
|
||||
'emojis' => [],
|
||||
'reblogs_count' => $status->shares()->count(),
|
||||
'favourites_count' => $status->likes()->count(),
|
||||
'reblogged' => $status->shared(),
|
||||
'favourited' => $status->liked(),
|
||||
'muted' => null,
|
||||
'sensitive' => (bool) $status->is_nsfw,
|
||||
'spoiler_text' => '',
|
||||
'visibility' => $status->visibility,
|
||||
'application' => null,
|
||||
'language' => null,
|
||||
'pinned' => null
|
||||
];
|
||||
}
|
||||
|
||||
public function includeAccount(Status $status)
|
||||
{
|
||||
$account = $status->profile;
|
||||
return $this->item($account, new AccountTransformer);
|
||||
}
|
||||
|
||||
public function includeMentions(Status $status)
|
||||
{
|
||||
$mentions = $status->mentions;
|
||||
return $this->collection($mentions, new MentionTransformer);
|
||||
}
|
||||
|
||||
public function includeMediaAttachments(Status $status)
|
||||
{
|
||||
$media = $status->media;
|
||||
return $this->collection($media, new MediaTransformer);
|
||||
}
|
||||
|
||||
public function includeTags(Status $status)
|
||||
{
|
||||
$tags = $status->hashtags;
|
||||
return $this->collection($tags, new HashtagTransformer);
|
||||
}
|
||||
}
|
15
app/User.php
15
app/User.php
|
@ -3,11 +3,19 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use Notifiable;
|
||||
use Notifiable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at', 'email_verified_at'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
@ -36,4 +44,9 @@ class User extends Authenticatable
|
|||
{
|
||||
return url(config('app.url') . '/' . $this->username);
|
||||
}
|
||||
|
||||
public function settings()
|
||||
{
|
||||
return $this->hasOne(UserSetting::class);
|
||||
}
|
||||
}
|
||||
|
|
10
app/UserFilter.php
Normal file
10
app/UserFilter.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserFilter extends Model
|
||||
{
|
||||
//
|
||||
}
|
10
app/UserSetting.php
Normal file
10
app/UserSetting.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserSetting extends Model
|
||||
{
|
||||
//
|
||||
}
|
|
@ -118,6 +118,7 @@ class RestrictedNames {
|
|||
|
||||
// Static Assets
|
||||
"assets",
|
||||
"storage",
|
||||
|
||||
// Laravel Horizon
|
||||
"horizon",
|
||||
|
@ -127,14 +128,19 @@ class RestrictedNames {
|
|||
"api",
|
||||
"auth",
|
||||
"i",
|
||||
"dashboard",
|
||||
"discover",
|
||||
"docs",
|
||||
"home",
|
||||
"login",
|
||||
"logout",
|
||||
"media",
|
||||
"p",
|
||||
"password",
|
||||
"reports",
|
||||
"search",
|
||||
"settings",
|
||||
"statuses",
|
||||
"site",
|
||||
"timeline",
|
||||
"user",
|
||||
|
|
|
@ -103,6 +103,10 @@ class Image {
|
|||
$ratio = $this->getAspectRatio($file, $thumbnail);
|
||||
$aspect = $ratio['dimensions'];
|
||||
$orientation = $ratio['orientation'];
|
||||
if($media->mime === 'image/gif' && !$thumbnail)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$img = Intervention::make($file)->orientate();
|
||||
|
|
|
@ -31,13 +31,9 @@ class Webfinger {
|
|||
|
||||
public function generateAliases()
|
||||
{
|
||||
$host = parse_url(config('app.url'), PHP_URL_HOST);
|
||||
$username = $this->user->username;
|
||||
$url = $this->user->url();
|
||||
|
||||
$this->aliases = [
|
||||
'acct:'.$username.'@'.$host,
|
||||
$url
|
||||
$this->user->url(),
|
||||
$this->user->permalink()
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
@ -55,24 +51,12 @@ class Webfinger {
|
|||
[
|
||||
'rel' => 'http://schemas.google.com/g/2010#updates-from',
|
||||
'type' => 'application/atom+xml',
|
||||
'href' => url("/users/{$user->username}.atom")
|
||||
'href' => $user->permalink('.atom')
|
||||
],
|
||||
[
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => $user->permalink()
|
||||
],
|
||||
[
|
||||
'rel' => 'magic-public-key',
|
||||
'href' => null//$user->public_key
|
||||
],
|
||||
[
|
||||
'rel' => 'salmon',
|
||||
'href' => $user->permalink('/salmon')
|
||||
],
|
||||
[
|
||||
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
|
||||
'href' => url('/main/ostatussub?profile={uri}')
|
||||
]
|
||||
];
|
||||
return $this;
|
||||
|
|
|
@ -6,18 +6,23 @@
|
|||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"99designs/http-signatures-guzzlehttp": "^2.0",
|
||||
"beyondcode/laravel-self-diagnosis": "^0.4.0",
|
||||
"bitverse/identicon": "^1.1",
|
||||
"doctrine/dbal": "^2.7",
|
||||
"fideloper/proxy": "^4.0",
|
||||
"greggilbert/recaptcha": "dev-master",
|
||||
"intervention/image": "^2.4",
|
||||
"kitetail/zttp": "^0.3.0",
|
||||
"pixelfed/zttp": "^0.4",
|
||||
"laravel/framework": "5.6.*",
|
||||
"laravel/horizon": "^1.2",
|
||||
"laravel/passport": "^6.0",
|
||||
"laravel/tinker": "^1.0",
|
||||
"league/fractal": "^0.17.0",
|
||||
"moontoast/math": "^1.1",
|
||||
"phpseclib/phpseclib": "~2.0",
|
||||
"pixelfed/dotenv-editor": "^2.0",
|
||||
"pixelfed/fractal": "^0.18.0",
|
||||
"pixelfed/google2fa-laravel": "^2.0",
|
||||
"pixelfed/http-signatures-guzzlehttp": "^4.0",
|
||||
"predis/predis": "^1.1",
|
||||
"spatie/laravel-backup": "^5.0.0",
|
||||
"spatie/laravel-image-optimizer": "^1.1",
|
||||
|
@ -25,6 +30,7 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.1",
|
||||
"beyondcode/laravel-er-diagram-generator": "^0.2.2",
|
||||
"filp/whoops": "^2.0",
|
||||
"fzaninotto/faker": "^1.4",
|
||||
"mockery/mockery": "^1.0",
|
||||
|
|
2148
composer.lock
generated
2148
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -65,7 +65,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -78,7 +78,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'locale' => 'en',
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -91,7 +91,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'fallback_locale' => 'en',
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -151,6 +151,7 @@ return [
|
|||
* Package Service Providers...
|
||||
*/
|
||||
Greggilbert\Recaptcha\RecaptchaServiceProvider::class,
|
||||
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
|
@ -211,6 +212,7 @@ return [
|
|||
'View' => Illuminate\Support\Facades\View::class,
|
||||
|
||||
'Recaptcha' => Greggilbert\Recaptcha\Facades\Recaptcha::class,
|
||||
'DotenvEditor' => Jackiedo\DotenvEditor\Facades\DotenvEditor::class,
|
||||
],
|
||||
|
||||
];
|
||||
|
|
27
config/dotenv-editor.php
Normal file
27
config/dotenv-editor.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
/*
|
||||
|----------------------------------------------------------------------
|
||||
| Auto backup mode
|
||||
|----------------------------------------------------------------------
|
||||
|
|
||||
| This value is used when you save your file content. If value is true,
|
||||
| the original file will be backed up before save.
|
||||
*/
|
||||
|
||||
'autoBackup' => true,
|
||||
|
||||
/*
|
||||
|----------------------------------------------------------------------
|
||||
| Backup location
|
||||
|----------------------------------------------------------------------
|
||||
|
|
||||
| This value is used when you backup your file. This value is the sub
|
||||
| path from root folder of project application.
|
||||
*/
|
||||
|
||||
'backupPath' => base_path('storage/dotenv-editor/backups/')
|
||||
|
||||
);
|
|
@ -49,5 +49,5 @@ return [
|
|||
* If set to `true` all output of the optimizer binaries will be appended to the default log.
|
||||
* You can also set this to a class that implements `Psr\Log\LoggerInterface`.
|
||||
*/
|
||||
'log_optimizer_activity' => true,
|
||||
'log_optimizer_activity' => false,
|
||||
];
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your PixelFed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.1.0',
|
||||
'version' => '0.1.2',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -77,6 +77,17 @@ return [
|
|||
|
||||
'activitypub_enabled' => env('ACTIVITY_PUB', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Account file size limit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Update the max account size, the per user limit of files in KB.
|
||||
|
|
||||
|
|
||||
*/
|
||||
'max_account_size' => env('MAX_ACCOUNT_SIZE', 100000),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Photo file size limit
|
||||
|
@ -96,5 +107,25 @@ return [
|
|||
|
|
||||
*/
|
||||
'max_caption_length' => env('MAX_CAPTION_LENGTH', 150),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Album size limit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The max number of photos allowed per post.
|
||||
|
|
||||
*/
|
||||
'max_album_length' => env('MAX_ALBUM_LENGTH', 4),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Email Verification
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Require email verification before a new user can do anything.
|
||||
|
|
||||
*/
|
||||
'enforce_email_verification' => env('ENFORCE_EMAIL_VERIFICATION', true),
|
||||
|
||||
];
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateWebSubsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('web_subs', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('follower_id')->unsigned()->index();
|
||||
$table->bigInteger('following_id')->unsigned()->index();
|
||||
$table->string('profile_url')->index();
|
||||
$table->timestamp('approved_at')->nullable();
|
||||
$table->unique(['follower_id', 'following_id', 'profile_url']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('web_subs');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateImportJobsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('import_jobs', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->bigInteger('profile_id')->unsigned();
|
||||
$table->string('service')->default('instagram');
|
||||
$table->string('uuid')->nullable();
|
||||
$table->string('storage_path')->nullable();
|
||||
$table->tinyInteger('stage')->unsigned()->default(0);
|
||||
$table->text('media_json')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('import_jobs');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddFiltersToMediaTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('media', function (Blueprint $table) {
|
||||
$table->string('filter_name')->nullable()->after('orientation');
|
||||
$table->string('filter_class')->nullable()->after('filter_name');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('media', function (Blueprint $table) {
|
||||
$table->dropColumn('filter_name');
|
||||
$table->dropColumn('filter_class');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddSoftDeletesToModels extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('avatars', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('likes', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('media', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('mentions', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('notifications', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('profiles', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('statuses', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('users', function ($table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateEmailVerificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('email_verifications', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('user_id')->unsigned();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('user_token')->index();
|
||||
$table->string('random_token')->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('email_verifications');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateReportCommentsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('report_comments', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->bigInteger('report_id')->unsigned()->index();
|
||||
$table->bigInteger('profile_id')->unsigned();
|
||||
$table->bigInteger('user_id')->unsigned();
|
||||
$table->text('comment');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('report_comments');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateReportLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('report_logs', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->bigInteger('profile_id')->unsigned();
|
||||
$table->bigInteger('item_id')->unsigned()->nullable();
|
||||
$table->string('item_type')->nullable();
|
||||
$table->string('action')->nullable();
|
||||
$table->boolean('system_message')->default(false);
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('report_logs');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateAccountLogsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('account_logs', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('user_id')->unsigned()->index();
|
||||
$table->bigInteger('item_id')->unsigned()->nullable();
|
||||
$table->string('item_type')->nullable();
|
||||
$table->string('action')->nullable();
|
||||
$table->string('message')->nullable();
|
||||
$table->string('link')->nullable();
|
||||
$table->string('ip_address')->nullable();
|
||||
$table->string('user_agent')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('account_logs');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateUserSettingsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_settings', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('user_id')->unsigned()->unique();
|
||||
$table->string('role')->default('user');
|
||||
$table->boolean('crawlable')->default(true);
|
||||
$table->boolean('show_guests')->default(true);
|
||||
$table->boolean('show_discover')->default(true);
|
||||
$table->boolean('public_dm')->default(false);
|
||||
$table->boolean('hide_cw_search')->default(true);
|
||||
$table->boolean('hide_blocked_search')->default(true);
|
||||
$table->boolean('always_show_cw')->default(false);
|
||||
$table->boolean('compose_media_descriptions')->default(false);
|
||||
$table->boolean('reduce_motion')->default(false);
|
||||
$table->boolean('optimize_screen_reader')->default(false);
|
||||
$table->boolean('high_contrast_mode')->default(false);
|
||||
$table->boolean('video_autoplay')->default(false);
|
||||
$table->boolean('send_email_new_follower')->default(false);
|
||||
$table->boolean('send_email_new_follower_request')->default(true);
|
||||
$table->boolean('send_email_on_share')->default(false);
|
||||
$table->boolean('send_email_on_like')->default(false);
|
||||
$table->boolean('send_email_on_mention')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_settings');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class Add2faToUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('2fa_enabled')->default(false);
|
||||
$table->string('2fa_secret')->nullable();
|
||||
$table->json('2fa_backup_codes')->nullable();
|
||||
$table->timestamp('2fa_setup_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('2fa_enabled');
|
||||
$table->dropColumn('2fa_secret');
|
||||
$table->dropColumn('2fa_backup_codes');
|
||||
$table->dropColumn('2fa_setup_at');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateUserFiltersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_filters', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('user_id')->unsigned()->index();
|
||||
$table->bigInteger('filterable_id')->unsigned();
|
||||
$table->string('filterable_type');
|
||||
$table->string('filter_type')->default('block')->index();
|
||||
$table->unique([
|
||||
'user_id',
|
||||
'filterable_id',
|
||||
'filterable_type',
|
||||
'filter_type'
|
||||
], 'filter_unique');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_filters');
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ services:
|
|||
- "mysql-data:/var/lib/mysql"
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
image: redis:4-alpine
|
||||
volumes:
|
||||
- "redis-data:/data"
|
||||
networks:
|
||||
|
|
4
public/css/app.css
vendored
4
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
12
public/img/pixelfed-icon-black.svg
Normal file
12
public/img/pixelfed-icon-black.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50px" height="50px" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>04/icon/black/svg/pixelfed-icon-black</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="icon-copy-6" fill="#000000">
|
||||
<path d="M26.1989752,16.1654164 C26.1191375,15.6717246 25.9970712,15.1834955 25.8327784,14.7064947 C25.4032698,13.4577487 24.7071549,12.3267513 23.7688167,11.3882121 L18.8200906,6.43842552 C18.6286052,6.24689918 18.4253978,6.0651186 18.2059577,5.88739016 C18.9457319,2.7779446 21.7493666,0.5 25.0056862,0.5 C28.4355286,0.5 31.3631553,3.02718742 31.9058524,6.39134335 C31.9709938,6.7962157 32.0040616,7.15536036 32.0040616,7.499875 L32.0040616,14.49975 C32.0040616,15.3777768 31.8350727,16.2367131 31.511582,17.0488633 C30.4438253,16.4852131 29.2225744,16.1654164 27.9252137,16.1654164 L26.1989752,16.1654164 Z M23.8073996,31.046185 C23.5834161,32.4635947 23.7071641,33.9247757 24.178594,35.2935053 C24.6081026,36.5422513 25.3042175,37.6732487 26.2425556,38.6117879 L31.1921829,43.5624744 C31.3833579,43.7531345 31.5862081,43.9343531 31.8055418,44.112075 C31.065962,47.2217942 28.2621925,49.5 25.0056862,49.5 C21.5757888,49.5 18.6481233,46.9727317 18.1054939,43.6084951 C18.0403874,43.2038799 18.0073108,42.8446855 18.0073108,42.500125 L18.0073108,35.50025 C18.0073108,35.4938387 18.0073198,35.4874284 18.0073378,35.4810192 L22.6056081,31.046185 L23.8073996,31.046185 Z M35.1468104,25.8777977 C35.1968412,25.8615612 35.2467562,25.8448643 35.2965488,25.8277069 C36.5465838,25.396568 37.6766493,24.7009751 38.6141205,23.763303 L43.5628467,18.8135164 C43.7543612,18.6219608 43.9361291,18.4186771 44.1138454,18.1991515 C47.2222774,18.9383789 49.5,21.7431301 49.5,25.0004375 C49.5,28.4310698 46.9732731,31.3593627 43.6097572,31.9021084 C43.2052287,31.9672288 42.8461113,32.0003125 42.5016246,32.0003125 L35.5032492,32.0003125 C34.4465015,32.0003125 33.4174129,31.75537 32.4642879,31.2907626 C31.9585449,31.0442436 31.4866999,30.7388098 31.0577027,30.3834131 C32.9917905,29.5211241 34.4916783,27.8849489 35.1468104,25.8777977 Z M32.9818651,18.0619641 C33.4869192,16.9416136 33.7536554,15.736333 33.7536554,14.49975 L33.7536554,7.499875 C33.7536554,7.22965928 33.7384248,6.95788037 33.7088174,6.67629632 C36.4300225,5.000963 40.0234106,5.37310623 42.325648,7.67579129 C44.7509518,10.1024315 45.0347176,13.9597464 43.0399586,16.7217074 C42.8019022,17.0521235 42.5708139,17.3300572 42.325697,17.5752267 L37.3769708,22.5250133 C36.8285828,23.0735188 36.1967915,23.5184213 35.5026804,23.8530318 C35.505411,23.7709507 35.506787,23.6885334 35.506787,23.6058007 C35.506787,21.4032822 34.5316098,19.4242869 32.9818651,18.0619641 Z M29.1180777,30.9546188 C29.8495788,31.7387598 30.723016,32.3887119 31.6978107,32.8638651 C32.8860975,33.4431037 34.176657,33.7502812 35.5032492,33.7502812 L42.5016246,33.7502812 C42.7718182,33.7502812 43.043575,33.7350433 43.3251385,33.7054219 C45.0005544,36.4268542 44.6282644,40.021099 42.325697,42.3241597 C39.9008053,44.7495711 36.0434832,45.0332243 33.2816352,43.0384785 C32.9514226,42.8004669 32.6735484,42.5693291 32.4284315,42.3241597 L27.4797053,37.3743731 C26.7330644,36.6275722 26.1784347,35.7261305 25.8326956,34.723272 C25.4247205,33.5387745 25.3418769,32.266466 25.5842066,31.046185 L27.9252137,31.046185 C28.3310733,31.046185 28.7294844,31.0148879 29.1180777,30.9546188 Z M18.0361092,17.005126 C16.9498565,16.5196511 15.7507833,16.2505937 14.4967508,16.2505937 L7.49837538,16.2505937 C7.232029,16.2505937 6.96418507,16.2653384 6.68632324,16.2940088 C5.0111787,13.5720997 5.38321091,9.97879809 7.68567533,7.67584029 C10.1105671,5.25042891 13.9678891,4.96677571 16.7297371,6.96152155 C17.0599498,7.19953306 17.337824,7.43067088 17.5829409,7.67584029 L22.5316124,12.6264472 C23.2782947,13.3732895 23.8329179,14.2747125 24.1786584,15.2775495 C24.2790442,15.5688604 24.3597451,15.8655119 24.4207608,16.1654164 L20.6352393,16.1654164 C19.6618729,16.1654164 18.7627665,16.4774398 18.0361092,17.005126 Z M16.2612547,35.2537259 C16.258899,35.3357513 16.257717,35.4179313 16.257717,35.50025 L16.257717,42.500125 C16.257717,42.7703407 16.2729476,43.0421196 16.3025549,43.3237037 C13.5813306,44.9990489 9.98791079,44.6268885 7.68567533,42.3241597 C5.26078356,39.8987483 4.97719112,36.0405997 6.97150962,33.2781599 C7.20947014,32.9478765 7.44055844,32.6699428 7.68567533,32.4247733 L12.6344015,27.4749867 C13.3810424,26.7281858 14.282291,26.1734373 15.2849346,25.8276241 C15.6048854,25.7173766 15.9312445,25.6308666 16.2612547,25.5680951 L16.2612547,35.2537259 Z M16.2612547,23.7931301 C15.7377606,23.8724201 15.2199939,23.9990983 14.7148236,24.1731681 C13.4647886,24.604307 12.334723,25.2998999 11.3972518,26.237572 L6.44852566,31.1873586 C6.25392808,31.3819979 6.06939348,31.588746 5.88894736,31.812338 C2.78032488,31.0741675 0.5,28.2622334 0.5,25.0004375 C0.5,21.5662743 3.03179045,18.6360332 6.40961211,18.0974198 C6.55156249,18.0732434 7.23969074,18.0005625 7.49837538,18.0005625 L14.4967508,18.0005625 C15.2999569,18.0005625 16.0753514,18.1390919 16.7989258,18.3943464 C16.4561913,19.0066903 16.2612547,19.7099771 16.2612547,20.4579458 L16.2612547,23.7931301 Z" id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.3 KiB |
26
public/img/pixelfed-icon-color.svg
Normal file
26
public/img/pixelfed-icon-color.svg
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="50px" height="50px" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>04/icon/color/svg/pixelfed-icon-color</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="icon-copy-8" fill-rule="nonzero">
|
||||
<g id="photos" transform="translate(0.500000, 0.500000)">
|
||||
<g id="Group-3">
|
||||
<g id="Group-2">
|
||||
<path d="M21.8377091,23.3816448 C21.8883023,23.7349539 21.9237176,24.0924792 21.9237176,24.4592798 C21.9237176,28.6332184 18.5086769,32.048259 14.3347384,32.048259 L7.58897916,32.048259 C3.41504062,32.048259 0,28.6332184 0,24.4592798 C0,20.6917733 2.78515535,17.5516224 6.39666621,16.9748599 C6.39666621,16.9748599 6.39750943,16.9757032 6.39750943,16.9757032 C6.39750943,16.9748599 6.39666621,16.9740167 6.39666621,16.9740167 C6.78623381,16.9116184 7.18254716,16.8703007 7.58897916,16.8703007 L14.3347384,16.8703007 C18.1418763,16.8703007 21.3090103,19.7144814 21.8377091,23.3816448 Z" id="Combined-Shape" fill="#BF3F8A"></path>
|
||||
<path d="M31.9479158,6.39160689 C31.3660941,2.78515535 28.2284728,0.00505931944 24.4643391,0.00505931944 C20.7002055,0.00505931944 17.5625842,2.78515535 16.9807625,6.39160689 C17.3037157,6.62433559 17.6157071,6.87730156 17.9057747,7.16736921 L22.6758697,11.9374642 C23.5115007,12.7730951 24.1025978,13.7621921 24.4643391,14.8136873 C25.0191778,16.4242373 25.0191778,18.1831941 24.4643391,19.7937441 C24.2147461,20.5172268 23.8538479,21.2078239 23.3808016,21.8419252 C23.7357971,21.8933617 24.095852,21.9296201 24.4643391,21.9296201 C24.8328262,21.9296201 25.1928811,21.8942049 25.5478767,21.8419252 C26.331228,21.7280906 27.074948,21.4953619 27.7630154,21.1597603 C29.2943028,20.4135107 30.5380521,19.1697614 31.2843018,17.638474 C31.7716829,16.6384152 32.0533183,15.521992 32.0533183,14.3397977 L32.0533183,7.59403848 C32.0533183,7.1842336 32.0111573,6.78454737 31.9479158,6.39160689 Z" id="Shape" fill="#ED8611"></path>
|
||||
<path d="M31.0229036,41.7503472 L26.2528086,36.9802522 C25.4171776,36.1446213 24.8260805,35.1555243 24.4643391,34.1040291 C23.9095005,32.4934791 23.9095005,30.7345224 24.4643391,29.1239724 C24.7139322,28.4004897 25.0748304,27.7098926 25.5478767,27.0757912 C25.1928811,27.0243548 24.8328262,26.9880963 24.4643391,26.9880963 C24.095852,26.9880963 23.7357971,27.0235116 23.3808016,27.0757912 C22.5974503,27.1896259 21.8537303,27.4223546 21.1656629,27.7579561 C19.6343755,28.5042057 18.3906262,29.7479551 17.6443765,31.2792424 C17.1569954,32.2793012 16.87536,33.3957244 16.87536,34.5779187 L16.87536,41.323678 C16.87536,41.7334828 16.917521,42.1331691 16.9807625,42.5261095 C17.5625842,46.1325611 20.7002055,48.9126571 24.4643391,48.9126571 C28.2284728,48.9126571 31.3660941,46.1325611 31.9479158,42.5261095 C31.6249626,42.2925376 31.3129712,42.0395717 31.0229036,41.7503472 Z" id="Shape" fill="#2E86DD"></path>
|
||||
<path d="M42.5311689,16.9757032 C42.2984402,17.2986564 42.0454742,17.6106477 41.7554066,17.9007154 L36.9853115,22.6708104 C36.1496806,23.5064413 35.1605837,24.0966953 34.1090884,24.4592798 C32.4985384,25.0141185 30.7395817,25.0141185 29.1290317,24.4592798 C28.405549,24.2096867 27.7149519,23.8487886 27.0808505,23.3757422 C27.0294141,23.7307378 26.9939989,24.0907927 26.9939989,24.4592798 C26.9939989,24.8277669 27.0294141,25.1878218 27.0808505,25.5428174 C27.1946852,26.3261687 27.4274139,27.0698887 27.7630154,27.7579561 C28.509265,29.2892435 29.7530144,30.5329928 31.2843018,31.2792424 C32.2843606,31.7666235 33.4007837,32.048259 34.582978,32.048259 L41.3287373,32.048259 C41.7385422,32.048259 42.1382284,32.006098 42.5311689,31.9428565 C46.1376204,31.3610348 48.9177164,28.2234135 48.9177164,24.4592798 C48.9177164,20.6951462 46.1376204,17.5566817 42.5311689,16.9757032 Z" id="Shape" fill="#9EE85D"></path>
|
||||
<path d="M41.7554066,7.16736921 C39.0942045,4.50616718 34.9093041,4.25320121 31.9479158,6.39160689 C32.0111573,6.78454737 32.0533183,7.1842336 32.0533183,7.59403848 L32.0533183,14.3397977 C32.0533183,15.521992 31.7716829,16.6384152 31.2843018,17.638474 C30.5380521,19.1697614 29.2943028,20.4135107 27.7630154,21.1597603 C27.074948,21.4953619 26.331228,21.7280906 25.5478767,21.8419252 C25.7628978,22.1294632 25.9922536,22.4094122 26.2528086,22.6699672 C26.5133635,22.9305221 26.7933125,23.159878 27.0808505,23.374899 C27.7149519,23.8479454 28.405549,24.2096867 29.1290317,24.4584366 C30.7395817,25.0132753 32.4985384,25.0132753 34.1090884,24.4584366 C35.1605837,24.095852 36.1496806,23.5055981 36.9853115,22.6699672 L41.7554066,17.8998722 C42.0454742,17.6098045 42.2984402,17.2978132 42.5311689,16.9748599 C44.6704178,14.0134716 44.4166086,9.82941445 41.7554066,7.16736921 Z" id="Shape" fill="#F0C51A"></path>
|
||||
<path d="M42.5311689,31.9428565 C42.1382284,32.006098 41.7385422,32.048259 41.3287373,32.048259 L34.582978,32.048259 C33.4007837,32.048259 32.2843606,31.7666235 31.2843018,31.2792424 C29.7530144,30.5329928 28.509265,29.2892435 27.7630154,27.7579561 C27.4274139,27.0698887 27.1946852,26.3261687 27.0808505,25.5428174 C26.7933125,25.7578385 26.5133635,25.9871943 26.2528086,26.2477493 C25.9922536,26.5083042 25.7628978,26.7882532 25.5478767,27.0757912 C25.0748304,27.7098926 24.713089,28.4004897 24.4643391,29.1239724 C23.9095005,30.7345224 23.9095005,32.4934791 24.4643391,34.1040291 C24.8269237,35.1555243 25.4171776,36.1446213 26.2528086,36.9802522 L31.0229036,41.7503472 C31.3129712,42.0404149 31.6249626,42.2933809 31.9479158,42.5261095 C34.9093041,44.6645152 39.0942045,44.4115493 41.7554066,41.7503472 C44.4166086,39.0891452 44.6704178,34.9042448 42.5311689,31.9428565 Z" id="Shape" fill="#49B85F"></path>
|
||||
<path d="M24.4643391,14.8145305 C24.1017546,13.7630353 23.5115007,12.7739384 22.6758697,11.9383074 L17.9057747,7.16736921 C17.6157071,6.87730156 17.3037157,6.62433559 16.9807625,6.39160689 C14.0193742,4.25320121 9.83447377,4.50616718 7.17327175,7.16736921 C4.51206972,9.82857123 4.25910375,14.0117852 6.39666621,16.9731735 C6.78623381,16.9116184 7.18254716,16.8703007 7.58897916,16.8703007 L14.3347384,16.8703007 C18.1418763,16.8703007 21.3090103,19.7144814 21.8377091,23.3816448 C21.841082,23.3791151 21.8444549,23.3774287 21.8478278,23.374899 C22.1353658,23.159878 22.4153148,22.9305221 22.6758697,22.6699672 C22.9364247,22.4094122 23.1657805,22.1294632 23.3808016,21.8419252 C23.8538479,21.2078239 24.2155893,20.5172268 24.4643391,19.7937441 C25.0191778,18.1831941 25.0191778,16.4242373 24.4643391,14.8145305 Z" id="Shape" fill="#ED5B47"></path>
|
||||
<path d="M22.6758697,26.2477493 C22.4153148,25.9871943 22.1353658,25.7578385 21.8478278,25.5428174 C21.2137264,25.069771 20.5231293,24.7080297 19.7996466,24.4592798 C18.1890966,23.9044411 16.4301399,23.9044411 14.8195899,24.4592798 C13.7680946,24.8218644 12.7789977,25.4121183 11.9433668,26.2477493 L7.17327175,31.0178443 C6.8832041,31.3079119 6.63023813,31.6199033 6.39750943,31.9428565 C4.25910375,34.9042448 4.51206972,39.0891452 7.17327175,41.7503472 C9.83447377,44.4115493 14.0193742,44.6645152 16.9807625,42.5261095 C16.917521,42.1331691 16.87536,41.7334828 16.87536,41.323678 L16.87536,34.5779187 C16.87536,33.3957244 17.1569954,32.2793012 17.6443765,31.2792424 C18.3906262,29.7479551 19.6343755,28.5042057 21.1656629,27.7579561 C21.8537303,27.4223546 22.5974503,27.1896259 23.3808016,27.0757912 C23.1657805,26.7882532 22.9364247,26.5083042 22.6758697,26.2477493 Z" id="Shape" fill="#8D59A8"></path>
|
||||
</g>
|
||||
<path d="M22.9797433,30.3617318 L27.2641103,30.3617318 C31.3001553,30.3617318 34.5720162,27.1514896 34.5720162,23.191455 C34.5720162,19.2314205 31.3001553,16.0211782 27.2641103,16.0211782 L21.0804977,16.0211782 C18.7520102,16.0211782 16.8643981,17.8732411 16.8643981,20.1578764 L16.8643981,36.258456 L22.9797433,30.3617318 Z" id="Path-6-Copy-2" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.1 KiB |
2
public/js/app.js
vendored
2
public/js/app.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"/js/app.js": "/js/app.js?id=3cc7b07981e8b77e0db0",
|
||||
"/css/app.css": "/css/app.css?id=d7fd6ff1b9b190f3a2eb",
|
||||
"/js/app.js": "/js/app.js?id=4c2440700c647b915b2e",
|
||||
"/css/app.css": "/css/app.css?id=a7c64d139bb04ef8e290",
|
||||
"/js/timeline.js": "/js/timeline.js?id=d9a3145c0cd21ca09172",
|
||||
"/js/activity.js": "/js/activity.js?id=723dfb98bbbc96a9d39f"
|
||||
}
|
1
resources/assets/js/bootstrap.js
vendored
1
resources/assets/js/bootstrap.js
vendored
|
@ -22,6 +22,7 @@ try {
|
|||
require('./components/commentform');
|
||||
require('./components/searchform');
|
||||
require('./components/bookmarkform');
|
||||
require('./components/statusform');
|
||||
} catch (e) {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,12 +14,14 @@ $(document).ready(function() {
|
|||
let commenttext = commentform.val();
|
||||
let item = {item: id, comment: commenttext};
|
||||
|
||||
commentform.prop('disabled', true);
|
||||
axios.post('/i/comment', item)
|
||||
.then(function (res) {
|
||||
|
||||
var username = res.data.username;
|
||||
var permalink = res.data.url;
|
||||
var profile = res.data.profile;
|
||||
var reply = res.data.comment;
|
||||
|
||||
if($('.status-container').length == 1) {
|
||||
var comments = el.parents().eq(3).find('.comments');
|
||||
|
@ -27,12 +29,13 @@ $(document).ready(function() {
|
|||
var comments = el.parents().eq(1).find('.comments');
|
||||
}
|
||||
|
||||
var comment = '<p class="mb-0"><span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="' + profile + '">' + username + '</a></bdi></span><span class="comment-text">'+ commenttext + '</span><span class="float-right"><a href="' + permalink + '" class="text-dark small font-weight-bold">1s</a></span></p>';
|
||||
var comment = '<p class="mb-0"><span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="' + profile + '">' + username + '</a></bdi></span><span class="comment-text">'+ reply + '</span><span class="float-right"><a href="' + permalink + '" class="text-dark small font-weight-bold">1s</a></span></p>';
|
||||
|
||||
comments.prepend(comment);
|
||||
|
||||
commentform.val('');
|
||||
commentform.blur();
|
||||
commentform.prop('disabled', false);
|
||||
|
||||
})
|
||||
.catch(function (res) {
|
||||
|
|
107
resources/assets/js/components/passport/AuthorizedClients.vue
Normal file
107
resources/assets/js/components/passport/AuthorizedClients.vue
Normal file
|
@ -0,0 +1,107 @@
|
|||
<style scoped>
|
||||
.action-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="tokens.length > 0">
|
||||
<div class="card card-default mb-4">
|
||||
<div class="card-header font-weight-bold bg-white">Authorized Applications</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Authorized Tokens -->
|
||||
<table class="table table-borderless mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Scopes</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="token in tokens">
|
||||
<!-- Client Name -->
|
||||
<td style="vertical-align: middle;">
|
||||
{{ token.client.name }}
|
||||
</td>
|
||||
|
||||
<!-- Scopes -->
|
||||
<td style="vertical-align: middle;">
|
||||
<span v-if="token.scopes.length > 0">
|
||||
{{ token.scopes.join(', ') }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Revoke Button -->
|
||||
<td style="vertical-align: middle;">
|
||||
<a class="action-link text-danger" @click="revoke(token)">
|
||||
Revoke
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
tokens: []
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 1.x).
|
||||
*/
|
||||
ready() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 2.x).
|
||||
*/
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Prepare the component (Vue 2.x).
|
||||
*/
|
||||
prepareComponent() {
|
||||
this.getTokens();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the authorized tokens for the user.
|
||||
*/
|
||||
getTokens() {
|
||||
axios.get('/oauth/tokens')
|
||||
.then(response => {
|
||||
this.tokens = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Revoke the given token.
|
||||
*/
|
||||
revoke(token) {
|
||||
axios.delete('/oauth/tokens/' + token.id)
|
||||
.then(response => {
|
||||
this.getTokens();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
350
resources/assets/js/components/passport/Clients.vue
Normal file
350
resources/assets/js/components/passport/Clients.vue
Normal file
|
@ -0,0 +1,350 @@
|
|||
<style scoped>
|
||||
.action-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="card card-default mb-4">
|
||||
<div class="card-header font-weight-bold bg-white">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>
|
||||
OAuth Clients
|
||||
</span>
|
||||
|
||||
<a class="action-link" tabindex="-1" @click="showCreateClientForm">
|
||||
Create New Client
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Current Clients -->
|
||||
<p class="mb-0" v-if="clients.length === 0">
|
||||
You have not created any OAuth clients.
|
||||
</p>
|
||||
|
||||
<table class="table table-borderless mb-0" v-if="clients.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client ID</th>
|
||||
<th>Name</th>
|
||||
<th>Secret</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="client in clients">
|
||||
<!-- ID -->
|
||||
<td style="vertical-align: middle;">
|
||||
{{ client.id }}
|
||||
</td>
|
||||
|
||||
<!-- Name -->
|
||||
<td style="vertical-align: middle;">
|
||||
{{ client.name }}
|
||||
</td>
|
||||
|
||||
<!-- Secret -->
|
||||
<td style="vertical-align: middle;">
|
||||
<code>{{ client.secret }}</code>
|
||||
</td>
|
||||
|
||||
<!-- Edit Button -->
|
||||
<td style="vertical-align: middle;">
|
||||
<a class="action-link" tabindex="-1" @click="edit(client)">
|
||||
Edit
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<td style="vertical-align: middle;">
|
||||
<a class="action-link text-danger" @click="destroy(client)">
|
||||
Delete
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Client Modal -->
|
||||
<div class="modal fade" id="modal-create-client" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
Create Client
|
||||
</h4>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Form Errors -->
|
||||
<div class="alert alert-danger" v-if="createForm.errors.length > 0">
|
||||
<p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
|
||||
<br>
|
||||
<ul>
|
||||
<li v-for="error in createForm.errors">
|
||||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Create Client Form -->
|
||||
<form role="form">
|
||||
<!-- Name -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label">Name</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<input id="create-client-name" type="text" class="form-control" autocomplete="off"
|
||||
@keyup.enter="store" v-model="createForm.name">
|
||||
|
||||
<span class="form-text text-muted">
|
||||
Something your users will recognize and trust.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Redirect URL -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label">Redirect URL</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" name="redirect"
|
||||
@keyup.enter="store" v-model="createForm.redirect">
|
||||
|
||||
<span class="form-text text-muted">
|
||||
Your application's authorization callback URL.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary font-weight-bold" data-dismiss="modal">Close</button>
|
||||
|
||||
<button type="button" class="btn btn-primary font-weight-bold" @click="store">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Client Modal -->
|
||||
<div class="modal fade" id="modal-edit-client" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
Edit Client
|
||||
</h4>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Form Errors -->
|
||||
<div class="alert alert-danger" v-if="editForm.errors.length > 0">
|
||||
<p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
|
||||
<br>
|
||||
<ul>
|
||||
<li v-for="error in editForm.errors">
|
||||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Edit Client Form -->
|
||||
<form role="form">
|
||||
<!-- Name -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label">Name</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<input id="edit-client-name" type="text" class="form-control"
|
||||
@keyup.enter="update" v-model="editForm.name">
|
||||
|
||||
<span class="form-text text-muted">
|
||||
Something your users will recognize and trust.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Redirect URL -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label">Redirect URL</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" name="redirect"
|
||||
@keyup.enter="update" v-model="editForm.redirect">
|
||||
|
||||
<span class="form-text text-muted">
|
||||
Your application's authorization callback URL.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
|
||||
<button type="button" class="btn btn-primary" @click="update">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
clients: [],
|
||||
|
||||
createForm: {
|
||||
errors: [],
|
||||
name: '',
|
||||
redirect: ''
|
||||
},
|
||||
|
||||
editForm: {
|
||||
errors: [],
|
||||
name: '',
|
||||
redirect: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 1.x).
|
||||
*/
|
||||
ready() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 2.x).
|
||||
*/
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Prepare the component.
|
||||
*/
|
||||
prepareComponent() {
|
||||
this.getClients();
|
||||
|
||||
$('#modal-create-client').on('shown.bs.modal', () => {
|
||||
$('#create-client-name').focus();
|
||||
});
|
||||
|
||||
$('#modal-edit-client').on('shown.bs.modal', () => {
|
||||
$('#edit-client-name').focus();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the OAuth clients for the user.
|
||||
*/
|
||||
getClients() {
|
||||
axios.get('/oauth/clients')
|
||||
.then(response => {
|
||||
this.clients = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the form for creating new clients.
|
||||
*/
|
||||
showCreateClientForm() {
|
||||
$('#modal-create-client').modal('show');
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new OAuth client for the user.
|
||||
*/
|
||||
store() {
|
||||
this.persistClient(
|
||||
'post', '/oauth/clients',
|
||||
this.createForm, '#modal-create-client'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the given client.
|
||||
*/
|
||||
edit(client) {
|
||||
this.editForm.id = client.id;
|
||||
this.editForm.name = client.name;
|
||||
this.editForm.redirect = client.redirect;
|
||||
|
||||
$('#modal-edit-client').modal('show');
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the client being edited.
|
||||
*/
|
||||
update() {
|
||||
this.persistClient(
|
||||
'put', '/oauth/clients/' + this.editForm.id,
|
||||
this.editForm, '#modal-edit-client'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Persist the client to storage using the given form.
|
||||
*/
|
||||
persistClient(method, uri, form, modal) {
|
||||
form.errors = [];
|
||||
|
||||
axios[method](uri, form)
|
||||
.then(response => {
|
||||
this.getClients();
|
||||
|
||||
form.name = '';
|
||||
form.redirect = '';
|
||||
form.errors = [];
|
||||
|
||||
$(modal).modal('hide');
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
form.errors = _.flatten(_.toArray(error.response.data.errors));
|
||||
} else {
|
||||
form.errors = ['Something went wrong. Please try again.'];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the given client.
|
||||
*/
|
||||
destroy(client) {
|
||||
axios.delete('/oauth/clients/' + client.id)
|
||||
.then(response => {
|
||||
this.getClients();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
298
resources/assets/js/components/passport/PersonalAccessTokens.vue
Normal file
298
resources/assets/js/components/passport/PersonalAccessTokens.vue
Normal file
|
@ -0,0 +1,298 @@
|
|||
<style scoped>
|
||||
.action-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<div class="card card-default mb-4">
|
||||
<div class="card-header font-weight-bold bg-white">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>
|
||||
Personal Access Tokens
|
||||
</span>
|
||||
|
||||
<a class="action-link" tabindex="-1" @click="showCreateTokenForm">
|
||||
Create New Token
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- No Tokens Notice -->
|
||||
<p class="mb-0" v-if="tokens.length === 0">
|
||||
You have not created any personal access tokens.
|
||||
</p>
|
||||
|
||||
<!-- Personal Access Tokens -->
|
||||
<table class="table table-borderless mb-0" v-if="tokens.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="token in tokens">
|
||||
<!-- Client Name -->
|
||||
<td style="vertical-align: middle;">
|
||||
{{ token.name }}
|
||||
</td>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<td style="vertical-align: middle;">
|
||||
<a class="action-link text-danger" @click="revoke(token)">
|
||||
Delete
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Token Modal -->
|
||||
<div class="modal fade" id="modal-create-token" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
Create Token
|
||||
</h4>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Form Errors -->
|
||||
<div class="alert alert-danger" v-if="form.errors.length > 0">
|
||||
<p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
|
||||
<br>
|
||||
<ul>
|
||||
<li v-for="error in form.errors">
|
||||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Create Token Form -->
|
||||
<form role="form" @submit.prevent="store">
|
||||
<!-- Name -->
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Name</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<input id="create-token-name" type="text" class="form-control" name="name" v-model="form.name" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scopes -->
|
||||
<div class="form-group row" v-if="scopes.length > 0">
|
||||
<label class="col-md-4 col-form-label">Scopes</label>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div v-for="scope in scopes">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
@click="toggleScope(scope.id)"
|
||||
:checked="scopeIsAssigned(scope.id)">
|
||||
|
||||
{{ scope.id }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary font-weight-bold" data-dismiss="modal">Close</button>
|
||||
|
||||
<button type="button" class="btn btn-primary font-weight-bold" @click="store">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Access Token Modal -->
|
||||
<div class="modal fade" id="modal-access-token" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
Personal Access Token
|
||||
</h4>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Here is your new personal access token. This is the only time it will be shown so don't lose it!
|
||||
You may now use this token to make API requests.
|
||||
</p>
|
||||
|
||||
<textarea class="form-control" rows="10">{{ accessToken }}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
/*
|
||||
* The component's data.
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
accessToken: null,
|
||||
|
||||
tokens: [],
|
||||
scopes: [],
|
||||
|
||||
form: {
|
||||
name: '',
|
||||
scopes: [],
|
||||
errors: []
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 1.x).
|
||||
*/
|
||||
ready() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare the component (Vue 2.x).
|
||||
*/
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Prepare the component.
|
||||
*/
|
||||
prepareComponent() {
|
||||
this.getTokens();
|
||||
this.getScopes();
|
||||
|
||||
$('#modal-create-token').on('shown.bs.modal', () => {
|
||||
$('#create-token-name').focus();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the personal access tokens for the user.
|
||||
*/
|
||||
getTokens() {
|
||||
axios.get('/oauth/personal-access-tokens')
|
||||
.then(response => {
|
||||
this.tokens = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the available scopes.
|
||||
*/
|
||||
getScopes() {
|
||||
axios.get('/oauth/scopes')
|
||||
.then(response => {
|
||||
this.scopes = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the form for creating new tokens.
|
||||
*/
|
||||
showCreateTokenForm() {
|
||||
$('#modal-create-token').modal('show');
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new personal access token.
|
||||
*/
|
||||
store() {
|
||||
this.accessToken = null;
|
||||
|
||||
this.form.errors = [];
|
||||
|
||||
axios.post('/oauth/personal-access-tokens', this.form)
|
||||
.then(response => {
|
||||
this.form.name = '';
|
||||
this.form.scopes = [];
|
||||
this.form.errors = [];
|
||||
|
||||
this.tokens.push(response.data.token);
|
||||
|
||||
this.showAccessToken(response.data.accessToken);
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
this.form.errors = _.flatten(_.toArray(error.response.data.errors));
|
||||
} else {
|
||||
this.form.errors = ['Something went wrong. Please try again.'];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the given scope in the list of assigned scopes.
|
||||
*/
|
||||
toggleScope(scope) {
|
||||
if (this.scopeIsAssigned(scope)) {
|
||||
this.form.scopes = _.reject(this.form.scopes, s => s == scope);
|
||||
} else {
|
||||
this.form.scopes.push(scope);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if the given scope has been assigned to the token.
|
||||
*/
|
||||
scopeIsAssigned(scope) {
|
||||
return _.indexOf(this.form.scopes, scope) >= 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the given access token to the user.
|
||||
*/
|
||||
showAccessToken(accessToken) {
|
||||
$('#modal-create-token').modal('hide');
|
||||
|
||||
this.accessToken = accessToken;
|
||||
|
||||
$('#modal-access-token').modal('show');
|
||||
},
|
||||
|
||||
/**
|
||||
* Revoke the given token.
|
||||
*/
|
||||
revoke(token) {
|
||||
axios.delete('/oauth/personal-access-tokens/' + token.id)
|
||||
.then(response => {
|
||||
this.getTokens();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
110
resources/assets/js/components/statusform.js
vendored
Normal file
110
resources/assets/js/components/statusform.js
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
$(document).ready(function() {
|
||||
|
||||
$('#statusForm .btn-filter-select').on('click', function(e) {
|
||||
let el = $(this);
|
||||
});
|
||||
|
||||
pixelfed.create = {};
|
||||
pixelfed.filters = {};
|
||||
pixelfed.create.hasGeneratedSelect = false;
|
||||
pixelfed.create.selectedFilter = false;
|
||||
pixelfed.create.currentFilterName = false;
|
||||
pixelfed.create.currentFilterClass = false;
|
||||
|
||||
pixelfed.filters.list = [
|
||||
['1977','filter-1977'],
|
||||
['Aden','filter-aden'],
|
||||
['Amaro','filter-amaro'],
|
||||
['Ashby','filter-ashby'],
|
||||
['Brannan','filter-brannan'],
|
||||
['Brooklyn','filter-brooklyn'],
|
||||
['Charmes','filter-charmes'],
|
||||
['Clarendon','filter-clarendon'],
|
||||
['Crema','filter-crema'],
|
||||
['Dogpatch','filter-dogpatch'],
|
||||
['Earlybird','filter-earlybird'],
|
||||
['Gingham','filter-gingham'],
|
||||
['Ginza','filter-ginza'],
|
||||
['Hefe','filter-hefe'],
|
||||
['Helena','filter-helena'],
|
||||
['Hudson','filter-hudson'],
|
||||
['Inkwell','filter-inkwell'],
|
||||
['Kelvin','filter-kelvin'],
|
||||
['Kuno','filter-juno'],
|
||||
['Lark','filter-lark'],
|
||||
['Lo-Fi','filter-lofi'],
|
||||
['Ludwig','filter-ludwig'],
|
||||
['Maven','filter-maven'],
|
||||
['Mayfair','filter-mayfair'],
|
||||
['Moon','filter-moon'],
|
||||
['Nashville','filter-nashville'],
|
||||
['Perpetua','filter-perpetua'],
|
||||
['Poprocket','filter-poprocket'],
|
||||
['Reyes','filter-reyes'],
|
||||
['Rise','filter-rise'],
|
||||
['Sierra','filter-sierra'],
|
||||
['Skyline','filter-skyline'],
|
||||
['Slumber','filter-slumber'],
|
||||
['Stinson','filter-stinson'],
|
||||
['Sutro','filter-sutro'],
|
||||
['Toaster','filter-toaster'],
|
||||
['Valencia','filter-valencia'],
|
||||
['Vesper','filter-vesper'],
|
||||
['Walden','filter-walden'],
|
||||
['Willow','filter-willow'],
|
||||
['X-Pro II','filter-xpro-ii']
|
||||
];
|
||||
|
||||
function previewImage(input) {
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
$('.filterPreview').attr('src', e.target.result);
|
||||
}
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function generateFilterSelect() {
|
||||
let filters = pixelfed.filters.list;
|
||||
for(var i = 0, len = filters.length; i < len; i++) {
|
||||
let filter = filters[i];
|
||||
let name = filter[0];
|
||||
let className = filter[1];
|
||||
let select = $('#filterSelectDropdown');
|
||||
var template = '<option value="' + className + '">' + name + '</option>';
|
||||
select.append(template);
|
||||
}
|
||||
pixelfed.create.hasGeneratedSelect = true;
|
||||
}
|
||||
|
||||
$('#fileInput').on('change', function() {
|
||||
previewImage(this);
|
||||
$('#statusForm .form-filters.d-none').removeClass('d-none');
|
||||
$('#statusForm .form-preview.d-none').removeClass('d-none');
|
||||
$('#statusForm #collapsePreview').collapse('show');
|
||||
if(!pixelfed.create.hasGeneratedSelect) {
|
||||
generateFilterSelect();
|
||||
}
|
||||
});
|
||||
|
||||
$('#filterSelectDropdown').on('change', function() {
|
||||
let el = $(this);
|
||||
let filter = el.val();
|
||||
let oldFilter = pixelfed.create.currentFilterClass;
|
||||
if(filter == 'none') {
|
||||
$('.filterContainer').removeClass(oldFilter);
|
||||
pixelfed.create.currentFilterClass = false;
|
||||
pixelfed.create.currentFilterName = 'None';
|
||||
$('.form-group.form-preview .form-text').text('Current Filter: No filter selected');
|
||||
return;
|
||||
}
|
||||
$('.filterContainer').removeClass(oldFilter).addClass(filter);
|
||||
pixelfed.create.currentFilterClass = filter;
|
||||
pixelfed.create.currentFilterName = el.find(':selected').text();
|
||||
$('.form-group.form-preview .form-text').text('Current Filter: ' + pixelfed.create.currentFilterName);
|
||||
$('input[name=filter_class]').val(pixelfed.create.currentFilterClass);
|
||||
$('input[name=filter_name]').val(pixelfed.create.currentFilterName);
|
||||
});
|
||||
|
||||
});
|
2
resources/assets/sass/app.scss
vendored
2
resources/assets/sass/app.scss
vendored
|
@ -11,6 +11,8 @@
|
|||
|
||||
@import "custom";
|
||||
|
||||
@import "components/filters";
|
||||
|
||||
@import "components/typeahead";
|
||||
|
||||
@import "components/notifications";
|
||||
|
|
445
resources/assets/sass/components/filters.scss
vendored
Normal file
445
resources/assets/sass/components/filters.scss
vendored
Normal file
|
@ -0,0 +1,445 @@
|
|||
/*! Instagram.css v0.1.3 | MIT License | github.com/picturepan2/instagram.css */
|
||||
[class*="filter"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[class*="filter"]::before {
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.filter-1977 {
|
||||
-webkit-filter: sepia(.5) hue-rotate(-30deg) saturate(1.4);
|
||||
filter: sepia(.5) hue-rotate(-30deg) saturate(1.4);
|
||||
}
|
||||
|
||||
.filter-aden {
|
||||
-webkit-filter: sepia(.2) brightness(1.15) saturate(1.4);
|
||||
filter: sepia(.2) brightness(1.15) saturate(1.4);
|
||||
}
|
||||
|
||||
.filter-aden::before {
|
||||
background: rgba(125, 105, 24, .1);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-amaro {
|
||||
-webkit-filter: sepia(.35) contrast(1.1) brightness(1.2) saturate(1.3);
|
||||
filter: sepia(.35) contrast(1.1) brightness(1.2) saturate(1.3);
|
||||
}
|
||||
|
||||
.filter-amaro::before {
|
||||
background: rgba(125, 105, 24, .2);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-ashby {
|
||||
-webkit-filter: sepia(.5) contrast(1.2) saturate(1.8);
|
||||
filter: sepia(.5) contrast(1.2) saturate(1.8);
|
||||
}
|
||||
|
||||
.filter-ashby::before {
|
||||
background: rgba(125, 105, 24, .35);
|
||||
content: "";
|
||||
mix-blend-mode: lighten;
|
||||
}
|
||||
|
||||
.filter-brannan {
|
||||
-webkit-filter: sepia(.4) contrast(1.25) brightness(1.1) saturate(.9) hue-rotate(-2deg);
|
||||
filter: sepia(.4) contrast(1.25) brightness(1.1) saturate(.9) hue-rotate(-2deg);
|
||||
}
|
||||
|
||||
.filter-brooklyn {
|
||||
-webkit-filter: sepia(.25) contrast(1.25) brightness(1.25) hue-rotate(5deg);
|
||||
filter: sepia(.25) contrast(1.25) brightness(1.25) hue-rotate(5deg);
|
||||
}
|
||||
|
||||
.filter-brooklyn::before {
|
||||
background: rgba(127, 187, 227, .2);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-charmes {
|
||||
-webkit-filter: sepia(.25) contrast(1.25) brightness(1.25) saturate(1.35) hue-rotate(-5deg);
|
||||
filter: sepia(.25) contrast(1.25) brightness(1.25) saturate(1.35) hue-rotate(-5deg);
|
||||
}
|
||||
|
||||
.filter-charmes::before {
|
||||
background: rgba(125, 105, 24, .25);
|
||||
content: "";
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
.filter-clarendon {
|
||||
-webkit-filter: sepia(.15) contrast(1.25) brightness(1.25) hue-rotate(5deg);
|
||||
filter: sepia(.15) contrast(1.25) brightness(1.25) hue-rotate(5deg);
|
||||
}
|
||||
|
||||
.filter-clarendon::before {
|
||||
background: rgba(127, 187, 227, .4);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-crema {
|
||||
-webkit-filter: sepia(.5) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-2deg);
|
||||
filter: sepia(.5) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-2deg);
|
||||
}
|
||||
|
||||
.filter-crema::before {
|
||||
background: rgba(125, 105, 24, .2);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-dogpatch {
|
||||
-webkit-filter: sepia(.35) saturate(1.1) contrast(1.5);
|
||||
filter: sepia(.35) saturate(1.1) contrast(1.5);
|
||||
}
|
||||
|
||||
.filter-earlybird {
|
||||
-webkit-filter: sepia(.25) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-5deg);
|
||||
filter: sepia(.25) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-5deg);
|
||||
}
|
||||
|
||||
.filter-earlybird::before {
|
||||
background: radial-gradient(circle closest-corner, transparent 0, rgba(125, 105, 24, .2) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, transparent 0, rgba(125, 105, 24, .2) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, transparent 0, rgba(125, 105, 24, .2) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, transparent 0, rgba(125, 105, 24, .2) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-gingham {
|
||||
-webkit-filter: contrast(1.1) brightness(1.1);
|
||||
filter: contrast(1.1) brightness(1.1);
|
||||
}
|
||||
|
||||
.filter-gingham::before {
|
||||
background: #e6e6e6;
|
||||
content: "";
|
||||
mix-blend-mode: soft-light;
|
||||
}
|
||||
|
||||
.filter-ginza {
|
||||
-webkit-filter: sepia(.25) contrast(1.15) brightness(1.2) saturate(1.35) hue-rotate(-5deg);
|
||||
filter: sepia(.25) contrast(1.15) brightness(1.2) saturate(1.35) hue-rotate(-5deg);
|
||||
}
|
||||
|
||||
.filter-ginza::before {
|
||||
background: rgba(125, 105, 24, .15);
|
||||
content: "";
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
.filter-hefe {
|
||||
-webkit-filter: sepia(.4) contrast(1.5) brightness(1.2) saturate(1.4) hue-rotate(-10deg);
|
||||
filter: sepia(.4) contrast(1.5) brightness(1.2) saturate(1.4) hue-rotate(-10deg);
|
||||
}
|
||||
|
||||
.filter-hefe::before {
|
||||
background: radial-gradient(circle closest-corner, transparent 0, rgba(0, 0, 0, .25) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, transparent 0, rgba(0, 0, 0, .25) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, transparent 0, rgba(0, 0, 0, .25) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, transparent 0, rgba(0, 0, 0, .25) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-helena {
|
||||
-webkit-filter: sepia(.5) contrast(1.05) brightness(1.05) saturate(1.35);
|
||||
filter: sepia(.5) contrast(1.05) brightness(1.05) saturate(1.35);
|
||||
}
|
||||
|
||||
.filter-helena::before {
|
||||
background: rgba(158, 175, 30, .25);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-hudson {
|
||||
-webkit-filter: sepia(.25) contrast(1.2) brightness(1.2) saturate(1.05) hue-rotate(-15deg);
|
||||
filter: sepia(.25) contrast(1.2) brightness(1.2) saturate(1.05) hue-rotate(-15deg);
|
||||
}
|
||||
|
||||
.filter-hudson::before {
|
||||
background: radial-gradient(circle closest-corner, transparent 25%, rgba(25, 62, 167, .25) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, transparent 25%, rgba(25, 62, 167, .25) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, transparent 25%, rgba(25, 62, 167, .25) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, transparent 25%, rgba(25, 62, 167, .25) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-inkwell {
|
||||
-webkit-filter: brightness(1.25) contrast(.85) grayscale(1);
|
||||
filter: brightness(1.25) contrast(.85) grayscale(1);
|
||||
}
|
||||
|
||||
.filter-juno {
|
||||
-webkit-filter: sepia(.35) contrast(1.15) brightness(1.15) saturate(1.8);
|
||||
filter: sepia(.35) contrast(1.15) brightness(1.15) saturate(1.8);
|
||||
}
|
||||
|
||||
.filter-juno::before {
|
||||
background: rgba(127, 187, 227, .2);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-kelvin {
|
||||
-webkit-filter: sepia(.15) contrast(1.5) brightness(1.1) hue-rotate(-10deg);
|
||||
filter: sepia(.15) contrast(1.5) brightness(1.1) hue-rotate(-10deg);
|
||||
}
|
||||
|
||||
.filter-kelvin::before {
|
||||
background: radial-gradient(circle closest-corner, rgba(128, 78, 15, .25) 0, rgba(128, 78, 15, .5) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, rgba(128, 78, 15, .25) 0, rgba(128, 78, 15, .5) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, rgba(128, 78, 15, .25) 0, rgba(128, 78, 15, .5) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, rgba(128, 78, 15, .25) 0, rgba(128, 78, 15, .5) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-lark {
|
||||
-webkit-filter: sepia(.25) contrast(1.2) brightness(1.3) saturate(1.25);
|
||||
filter: sepia(.25) contrast(1.2) brightness(1.3) saturate(1.25);
|
||||
}
|
||||
|
||||
.filter-lofi {
|
||||
-webkit-filter: saturate(1.1) contrast(1.5);
|
||||
filter: saturate(1.1) contrast(1.5);
|
||||
}
|
||||
|
||||
.filter-ludwig {
|
||||
-webkit-filter: sepia(.25) contrast(1.05) brightness(1.05) saturate(2);
|
||||
filter: sepia(.25) contrast(1.05) brightness(1.05) saturate(2);
|
||||
}
|
||||
|
||||
.filter-ludwig::before {
|
||||
background: rgba(125, 105, 24, .1);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-maven {
|
||||
-webkit-filter: sepia(.35) contrast(1.05) brightness(1.05) saturate(1.75);
|
||||
filter: sepia(.35) contrast(1.05) brightness(1.05) saturate(1.75);
|
||||
}
|
||||
|
||||
.filter-maven::before {
|
||||
background: rgba(158, 175, 30, .25);
|
||||
content: "";
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
.filter-mayfair {
|
||||
-webkit-filter: contrast(1.1) brightness(1.15) saturate(1.1);
|
||||
filter: contrast(1.1) brightness(1.15) saturate(1.1);
|
||||
}
|
||||
|
||||
.filter-mayfair::before {
|
||||
background: radial-gradient(circle closest-corner, transparent 0, rgba(175, 105, 24, .4) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, transparent 0, rgba(175, 105, 24, .4) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, transparent 0, rgba(175, 105, 24, .4) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, transparent 0, rgba(175, 105, 24, .4) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-moon {
|
||||
-webkit-filter: brightness(1.4) contrast(.95) saturate(0) sepia(.35);
|
||||
filter: brightness(1.4) contrast(.95) saturate(0) sepia(.35);
|
||||
}
|
||||
|
||||
.filter-nashville {
|
||||
-webkit-filter: sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg);
|
||||
filter: sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg);
|
||||
}
|
||||
|
||||
.filter-nashville::before {
|
||||
background: radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(128, 78, 15, .65) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(128, 78, 15, .65) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(128, 78, 15, .65) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(128, 78, 15, .65) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.filter-perpetua {
|
||||
-webkit-filter: contrast(1.1) brightness(1.25) saturate(1.1);
|
||||
filter: contrast(1.1) brightness(1.25) saturate(1.1);
|
||||
}
|
||||
|
||||
.filter-perpetua::before {
|
||||
background: linear-gradient(to bottom, rgba(0, 91, 154, .25), rgba(230, 193, 61, .25));
|
||||
background: -o-linear-gradient(top, rgba(0, 91, 154, .25), rgba(230, 193, 61, .25));
|
||||
background: -moz-linear-gradient(top, rgba(0, 91, 154, .25), rgba(230, 193, 61, .25));
|
||||
background: -webkit-linear-gradient(top, rgba(0, 91, 154, .25), rgba(230, 193, 61, .25));
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 91, 154, .25)), to(rgba(230, 193, 61, .25)));
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.filter-poprocket {
|
||||
-webkit-filter: sepia(.15) brightness(1.2);
|
||||
filter: sepia(.15) brightness(1.2);
|
||||
}
|
||||
|
||||
.filter-poprocket::before {
|
||||
background: radial-gradient(circle closest-corner, rgba(206, 39, 70, .75) 40%, black 80%);
|
||||
background: -o-radial-gradient(circle closest-corner, rgba(206, 39, 70, .75) 40%, black 80%);
|
||||
background: -moz-radial-gradient(circle closest-corner, rgba(206, 39, 70, .75) 40%, black 80%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, rgba(206, 39, 70, .75) 40%, black 80%);
|
||||
content: "";
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.filter-reyes {
|
||||
-webkit-filter: sepia(.75) contrast(.75) brightness(1.25) saturate(1.4);
|
||||
filter: sepia(.75) contrast(.75) brightness(1.25) saturate(1.4);
|
||||
}
|
||||
|
||||
.filter-rise {
|
||||
-webkit-filter: sepia(.25) contrast(1.25) brightness(1.2) saturate(.9);
|
||||
filter: sepia(.25) contrast(1.25) brightness(1.2) saturate(.9);
|
||||
}
|
||||
|
||||
.filter-rise::before {
|
||||
background: radial-gradient(circle closest-corner, transparent 0, rgba(230, 193, 61, .25) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, transparent 0, rgba(230, 193, 61, .25) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, transparent 0, rgba(230, 193, 61, .25) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, transparent 0, rgba(230, 193, 61, .25) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: lighten;
|
||||
}
|
||||
|
||||
.filter-sierra {
|
||||
-webkit-filter: sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg);
|
||||
filter: sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg);
|
||||
}
|
||||
|
||||
.filter-sierra::before {
|
||||
background: radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(0, 0, 0, .65) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(0, 0, 0, .65) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(0, 0, 0, .65) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, rgba(128, 78, 15, .5) 0, rgba(0, 0, 0, .65) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.filter-skyline {
|
||||
-webkit-filter: sepia(.15) contrast(1.25) brightness(1.25) saturate(1.2);
|
||||
filter: sepia(.15) contrast(1.25) brightness(1.25) saturate(1.2);
|
||||
}
|
||||
|
||||
.filter-slumber {
|
||||
-webkit-filter: sepia(.35) contrast(1.25) saturate(1.25);
|
||||
filter: sepia(.35) contrast(1.25) saturate(1.25);
|
||||
}
|
||||
|
||||
.filter-slumber::before {
|
||||
background: rgba(125, 105, 24, .2);
|
||||
content: "";
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
.filter-stinson {
|
||||
-webkit-filter: sepia(.35) contrast(1.25) brightness(1.1) saturate(1.25);
|
||||
filter: sepia(.35) contrast(1.25) brightness(1.1) saturate(1.25);
|
||||
}
|
||||
|
||||
.filter-stinson::before {
|
||||
background: rgba(125, 105, 24, .45);
|
||||
content: "";
|
||||
mix-blend-mode: lighten;
|
||||
}
|
||||
|
||||
.filter-sutro {
|
||||
-webkit-filter: sepia(.4) contrast(1.2) brightness(.9) saturate(1.4) hue-rotate(-10deg);
|
||||
filter: sepia(.4) contrast(1.2) brightness(.9) saturate(1.4) hue-rotate(-10deg);
|
||||
}
|
||||
|
||||
.filter-sutro::before {
|
||||
background: radial-gradient(circle closest-corner, transparent 50%, rgba(0, 0, 0, .5) 90%);
|
||||
background: -o-radial-gradient(circle closest-corner, transparent 50%, rgba(0, 0, 0, .5) 90%);
|
||||
background: -moz-radial-gradient(circle closest-corner, transparent 50%, rgba(0, 0, 0, .5) 90%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, transparent 50%, rgba(0, 0, 0, .5) 90%);
|
||||
content: "";
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
.filter-toaster {
|
||||
-webkit-filter: sepia(.25) contrast(1.5) brightness(.95) hue-rotate(-15deg);
|
||||
filter: sepia(.25) contrast(1.5) brightness(.95) hue-rotate(-15deg);
|
||||
}
|
||||
|
||||
.filter-toaster::before {
|
||||
background: radial-gradient(circle, #804e0f, rgba(0, 0, 0, .25));
|
||||
background: -o-radial-gradient(circle, #804e0f, rgba(0, 0, 0, .25));
|
||||
background: -moz-radial-gradient(circle, #804e0f, rgba(0, 0, 0, .25));
|
||||
background: -webkit-radial-gradient(circle, #804e0f, rgba(0, 0, 0, .25));
|
||||
content: "";
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.filter-valencia {
|
||||
-webkit-filter: sepia(.25) contrast(1.1) brightness(1.1);
|
||||
filter: sepia(.25) contrast(1.1) brightness(1.1);
|
||||
}
|
||||
|
||||
.filter-valencia::before {
|
||||
background: rgba(230, 193, 61, .1);
|
||||
content: "";
|
||||
mix-blend-mode: lighten;
|
||||
}
|
||||
|
||||
.filter-vesper {
|
||||
-webkit-filter: sepia(.35) contrast(1.15) brightness(1.2) saturate(1.3);
|
||||
filter: sepia(.35) contrast(1.15) brightness(1.2) saturate(1.3);
|
||||
}
|
||||
|
||||
.filter-vesper::before {
|
||||
background: rgba(125, 105, 24, .25);
|
||||
content: "";
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.filter-walden {
|
||||
-webkit-filter: sepia(.35) contrast(.8) brightness(1.25) saturate(1.4);
|
||||
filter: sepia(.35) contrast(.8) brightness(1.25) saturate(1.4);
|
||||
}
|
||||
|
||||
.filter-walden::before {
|
||||
background: rgba(229, 240, 128, .5);
|
||||
content: "";
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
.filter-willow {
|
||||
-webkit-filter: brightness(1.2) contrast(.85) saturate(.05) sepia(.2);
|
||||
filter: brightness(1.2) contrast(.85) saturate(.05) sepia(.2);
|
||||
}
|
||||
|
||||
.filter-xpro-ii {
|
||||
-webkit-filter: sepia(.45) contrast(1.25) brightness(1.75) saturate(1.3) hue-rotate(-5deg);
|
||||
filter: sepia(.45) contrast(1.25) brightness(1.75) saturate(1.3) hue-rotate(-5deg);
|
||||
}
|
||||
|
||||
.filter-xpro-ii::before {
|
||||
background: radial-gradient(circle closest-corner, rgba(0, 91, 154, .35) 0, rgba(0, 0, 0, .65) 100%);
|
||||
background: -o-radial-gradient(circle closest-corner, rgba(0, 91, 154, .35) 0, rgba(0, 0, 0, .65) 100%);
|
||||
background: -moz-radial-gradient(circle closest-corner, rgba(0, 91, 154, .35) 0, rgba(0, 0, 0, .65) 100%);
|
||||
background: -webkit-radial-gradient(circle closest-corner, rgba(0, 91, 154, .35) 0, rgba(0, 0, 0, .65) 100%);
|
||||
content: "";
|
||||
mix-blend-mode: multiply;
|
||||
}
|
29
resources/assets/sass/custom.scss
vendored
29
resources/assets/sass/custom.scss
vendored
|
@ -193,6 +193,13 @@ body, button, input, textarea {
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: map-get($grid-breakpoints, "sm")) {
|
||||
.card-md-rounded-0 {
|
||||
border-width: 1px 0;
|
||||
border-radius:0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading-bar {
|
||||
from { background-position: 0 0; }
|
||||
to { background-position: 100vw 0; }
|
||||
|
@ -234,3 +241,25 @@ body, button, input, textarea {
|
|||
height: 32px;
|
||||
background-position: 50%;
|
||||
}
|
||||
|
||||
|
||||
.status-photo img {
|
||||
object-fit: contain;
|
||||
max-height: calc(100vh - (6rem));
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-1.25em);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.details-animated[open] {
|
||||
animation-name: fadeInDown;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
|
|
13
resources/lang/de/navmenu.php
Normal file
13
resources/lang/de/navmenu.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'viewMyProfile' => 'Mein Profil anschauen',
|
||||
'myTimeline' => 'Meine Timeline',
|
||||
'publicTimeline' => 'Öffentliche Timeline',
|
||||
'remoteFollow' => 'Aus der Ferne folgen',
|
||||
'settings' => 'Einstellungen',
|
||||
'admin' => 'Administration',
|
||||
'logout' => 'Abmelden',
|
||||
|
||||
];
|
|
@ -5,5 +5,6 @@ return [
|
|||
'likedPhoto' => 'gefällt dein Foto.',
|
||||
'startedFollowingYou' => 'folgt dir nun.',
|
||||
'commented' => 'hat deinen Post kommentiert.',
|
||||
'mentionedYou' => 'hat dich erwähnt.'
|
||||
|
||||
];
|
|
@ -4,5 +4,5 @@ return [
|
|||
'emptyTimeline' => 'This user has no posts yet!',
|
||||
'emptyFollowers' => 'This user has no followers yet!',
|
||||
'emptyFollowing' => 'This user is not following anyone yet!',
|
||||
'savedWarning' => 'Only you can see what you\'ve saved',
|
||||
];
|
||||
'savedWarning' => 'Only you can see what you’ve saved',
|
||||
];
|
||||
|
|
13
resources/lang/he/navmenu.php
Normal file
13
resources/lang/he/navmenu.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'viewMyProfile' => 'צפה בפרופיל שלי',
|
||||
'myTimeline' => 'ציר הזמן שלי',
|
||||
'publicTimeline' => 'ציר הזמן הציבורי',
|
||||
'remoteFollow' => 'עקיבה מרחוק',
|
||||
'settings' => 'הגדרות',
|
||||
'admin' => 'מנהל',
|
||||
'logout' => 'התנתק',
|
||||
|
||||
];
|
|
@ -5,5 +5,6 @@ return [
|
|||
'likedPhoto' => 'אהבו את התמונה שלך.',
|
||||
'startedFollowingYou' => 'התחיל לעקוב אחריך.',
|
||||
'commented' => 'הגיב על הפוסט שלך.',
|
||||
'mentionedYou' => 'הזכיר אותך.'
|
||||
|
||||
];
|
||||
|
|
24
resources/views/account/verify_email.blade.php
Normal file
24
resources/views/account/verify_email.blade.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container mt-4">
|
||||
<div class="col-12 col-md-8 offset-md-2">
|
||||
@if (session('status'))
|
||||
<div class="alert alert-success">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
<div class="card">
|
||||
<div class="card-header font-weight-bold bg-white">Confirm Email Address</div>
|
||||
<div class="card-body">
|
||||
<p class="lead">You need to confirm your email address (<span class="font-weight-bold">{{Auth::user()->email}}</span>) before you can proceed.</p>
|
||||
<hr>
|
||||
<form method="post">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-primary btn-block py-1 font-weight-bold">Send Confirmation Email</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -26,16 +26,16 @@
|
|||
<link rel="self" type="application/atom+xml" href="{{$profile->permalink('.atom')}}"/>
|
||||
@foreach($items as $item)
|
||||
<entry>
|
||||
<title><![CDATA[{{ $item->caption }}]]></title>
|
||||
<title>{{ $item->caption }}</title>
|
||||
<link rel="alternate" href="{{ $item->url() }}" />
|
||||
<id>{{ url($item->id) }}</id>
|
||||
<author>
|
||||
<name> <![CDATA[{{ $item->profile->username }}]]></name>
|
||||
</author>
|
||||
<summary type="html">
|
||||
<![CDATA[{!! $item->caption !!}]]>
|
||||
{{ $item->caption }}
|
||||
</summary>
|
||||
<updated>{{ $item->updated_at->toAtomString() }}</updated>
|
||||
</entry>
|
||||
@endforeach
|
||||
</feed>
|
||||
</feed>
|
||||
|
|
12
resources/views/emails/confirm_email.blade.php
Normal file
12
resources/views/emails/confirm_email.blade.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
@component('mail::message')
|
||||
# Email Confirmation
|
||||
|
||||
Please confirm your email address.
|
||||
|
||||
@component('mail::button', ['url' => $verify->url()])
|
||||
Confirm Email
|
||||
@endcomponent
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
@endcomponent
|
|
@ -5,9 +5,9 @@
|
|||
<div class="error-page py-5 my-5">
|
||||
<div class="card mx-5">
|
||||
<div class="card-body p-5">
|
||||
<h1 class="text-center">403 - Forbidden</h1>
|
||||
<h1 class="text-center">403 – Forbidden</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@endsection
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
<div class="error-page py-5 my-5">
|
||||
<div class="card mx-5">
|
||||
<div class="card-body p-5">
|
||||
<h1 class="text-center">404 - Page Not Found</h1>
|
||||
<h1 class="text-center">404 – Page Not Found</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@endsection
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<div class="error-page py-5 my-5">
|
||||
<div class="card mx-5">
|
||||
<div class="card-body p-5 text-center">
|
||||
<h1>503 - Service Unavailable</h1>
|
||||
<h1>503 – Service Unavailable</h1>
|
||||
<p class="lead mb-0">Our services are overloaded at the moment, please try again later.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@endsection
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
<footer>
|
||||
<div class="container py-5">
|
||||
<p class="mb-0 text-uppercase font-weight-bold small">
|
||||
<a href="{{route('site.about')}}" class="text-primary pr-2">About Us</a>
|
||||
<a href="{{route('site.help')}}" class="text-primary pr-2">Support</a>
|
||||
<a href="{{route('site.opensource')}}" class="text-primary pr-2">Open Source</a>
|
||||
<a href="{{route('site.language')}}" class="text-primary pr-2">Language</a>
|
||||
<span class="px-2"></span>
|
||||
<a href="{{route('site.terms')}}" class="text-primary pr-2 pl-2">Terms</a>
|
||||
<a href="{{route('site.privacy')}}" class="text-primary pr-2">Privacy</a>
|
||||
<a href="{{route('site.platform')}}" class="text-primary pr-2">API</a>
|
||||
<span class="px-2"></span>
|
||||
<a href="#" class="text-primary pr-2 pl-2">Directory</a>
|
||||
<a href="#" class="text-primary pr-2">Profiles</a>
|
||||
<a href="#" class="text-primary">Hashtags</a>
|
||||
<p class="mb-0 text-uppercase font-weight-bold small text-justify">
|
||||
<a href="{{route('site.about')}}" class="text-primary pr-3">About Us</a>
|
||||
<a href="{{route('site.help')}}" class="text-primary pr-3">Support</a>
|
||||
<a href="{{route('site.opensource')}}" class="text-primary pr-3">Open Source</a>
|
||||
<a href="{{route('site.terms')}}" class="text-primary pr-3">Terms</a>
|
||||
<a href="{{route('site.privacy')}}" class="text-primary pr-3">Privacy</a>
|
||||
<a href="{{route('site.platform')}}" class="text-primary pr-3">API</a>
|
||||
<a href="#" class="text-primary pr-3">Directory</a>
|
||||
<a href="#" class="text-primary pr-3">Profiles</a>
|
||||
<a href="#" class="text-primary pr-3">Hashtags</a>
|
||||
<a href="{{route('site.language')}}" class="text-primary pr-3">Language</a>
|
||||
<a href="http://pixelfed.org" class="text-muted float-right" rel="noopener">Powered by PixelFed</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
<nav class="navbar navbar-expand navbar-light navbar-laravel sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="{{ url('/timeline') }}" title="Logo">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>
|
||||
<strong class="font-weight-bold">{{ config('app.name', 'Laravel') }}</strong>
|
||||
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2">
|
||||
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
|
||||
</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
@auth
|
||||
<ul class="navbar-nav ml-auto d-none d-md-block">
|
||||
<form class="form-inline search-form">
|
||||
<input class="form-control mr-sm-2 search-form-input" type="search" placeholder="Search" aria-label="Search">
|
||||
</form>
|
||||
</ul>
|
||||
@endauth
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
@guest
|
||||
|
@ -21,7 +23,9 @@
|
|||
<a class="nav-link" href="{{route('discover')}}" title="Discover"><i class="far fa-compass fa-lg"></i></a>
|
||||
</li>
|
||||
<li class="nav-item px-2">
|
||||
<a class="nav-link" href="{{route('notifications')}}" title="Notifications"><i class="far fa-heart fa-lg"></i></a>
|
||||
<a class="nav-link" href="{{route('notifications')}}" title="Notifications">
|
||||
<i class="far fa-heart fa-lg"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown px-2">
|
||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre title="User Menu">
|
||||
|
@ -76,8 +80,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@auth
|
||||
<nav class="breadcrumb d-md-none d-flex">
|
||||
<form class="form-inline search-form mx-auto">
|
||||
<input class="form-control mr-sm-2 search-form-input" type="search" placeholder="Search" aria-label="Search">
|
||||
</form>
|
||||
</nav>
|
||||
@endauth
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.app',['title' => $profile->username . "'s followers"])
|
||||
@extends('layouts.app',['title' => $profile->username . "’s followers"])
|
||||
|
||||
@section('content')
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.app',['title' => $profile->username . "'s follows"])
|
||||
@extends('layouts.app',['title' => $profile->username . "’s follows"])
|
||||
|
||||
@section('content')
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue