This commit is contained in:
Pierre Jaury 2018-06-16 08:52:27 +02:00
commit 58cc272c88
41 changed files with 1213 additions and 98 deletions

View File

@ -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
View 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}";
}
}

View File

@ -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
{
@ -30,6 +31,46 @@ class AccountController extends Controller
return view('account.activity', compact('profile', 'notifications'));
}
public function verifyEmail(Request $request)
{
return view('account.verify_email');
}
public function sendVerifyEmail(Request $request)
{
if(EmailVerification::whereUserId(Auth::id())->count() !== 0) {
return redirect()->back()->with('status', 'A verification email has already been sent! Please check your email.');
}
$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(DB::raw('BINARY user_token'), $userToken)
->where(DB::raw('BINARY 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('/timeline');
}
}
public function fetchNotifications($id)
{
$key = config('cache.prefix') . ":user.{$id}.notifications";

View File

@ -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(strip_tags($comment));
$reply->rendered = $comment;
$reply->in_reply_to_id = $status->id;
$reply->in_reply_to_profile_id = $status->profile_id;
$reply->save();

View File

@ -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());

View File

@ -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,
];
}

View 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('login') &&
!$request->is('i/confirm-email/*')
) {
return redirect('/i/verify-email');
}
return $next($request);
}
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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();

View File

@ -6,21 +6,28 @@ use 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,22 +46,40 @@ 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()
{
$status = $this->status;
$text = e($status->caption);
$tags = HashtagLexer::getHashtags($text);
$rendered = $text;
if(count($tags) > 0) {
$rendered = HashtagLexer::replaceHashtagsWithLinks($text);
}
$status->rendered = $rendered;
$this->storeHashtags();
$this->storeMentions();
$status->rendered = $this->autolink;
$status->entities = json_encode($this->entities);
$status->save();
Cache::forever('post.' . $status->id, $status);
}
public function storeHashtags()
{
$tags = array_unique($this->entities['hashtags']);
$status = $this->status;
foreach($tags as $tag) {
$slug = str_slug($tag);
@ -64,11 +89,32 @@ class StatusEntityLexer implements ShouldQueue
['slug' => $slug]
);
$stag = new StatusHashtag;
$stag->status_id = $status->id;
$stag->hashtag_id = $htag->id;
$stag->save();
StatusHashtag::firstOrCreate(
['status_id' => $status->id],
['hashtag_id' => $htag->id]
);
}
}
public function storeMentions()
{
$mentions = array_unique($this->entities['mentions']);
$status = $this->status;
foreach($mentions as $mention) {
$mentioned = Profile::whereUsername($mention)->first();
if(empty($mentioned) || !isset($mentioned->id)) {
continue;
}
$m = new Mention;
$m->status_id = $status->id;
$m->profile_id = $mentioned->id;
$m->save();
MentionPipeline::dispatch($status, $m);
}
}
}

View File

@ -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
View 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]);
}
}

View File

@ -2,11 +2,21 @@
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;

View File

@ -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()
{

View File

@ -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');
}
}

View File

@ -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',
];

View File

@ -2,12 +2,21 @@
namespace App;
use Illuminate\Database\Eloquent\Model;
use Storage;
use Vinkla\Hashids\Facades\Hashids;
use Illuminate\Database\Eloquent\Model;
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();

View File

@ -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'];
/**
* The attributes that are mass assignable.

View File

@ -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();

View File

@ -96,5 +96,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),
];

View File

@ -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');
});
}
}

View File

@ -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()
{
//
}
}

View File

@ -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');
}
}

4
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -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=10b2b118e1aa4607622d",
"/css/app.css": "/css/app.css?id=d8339100d1c73fdb7957",
"/js/timeline.js": "/js/timeline.js?id=d9a3145c0cd21ca09172",
"/js/activity.js": "/js/activity.js?id=723dfb98bbbc96a9d39f"
}

View File

@ -22,6 +22,7 @@ try {
require('./components/commentform');
require('./components/searchform');
require('./components/bookmarkform');
require('./components/statusform');
} catch (e) {}
/**

View File

@ -14,6 +14,7 @@ $(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) {
@ -33,6 +34,7 @@ $(document).ready(function() {
commentform.val('');
commentform.blur();
commentform.prop('disabled', false);
})
.catch(function (res) {

View 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);
});
});

View File

@ -11,6 +11,8 @@
@import "custom";
@import "components/filters";
@import "components/typeahead";
@import "components/notifications";

View 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;
}

View File

@ -234,3 +234,18 @@ body, button, input, textarea {
height: 32px;
background-position: 50%;
}
@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;
}

View 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

View 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

View File

@ -6,11 +6,13 @@
</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
@ -76,8 +78,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

View File

@ -27,7 +27,7 @@
@foreach($timeline as $status)
<div class="col-12 col-md-4 mb-4">
<a class="card info-overlay" href="{{$status->url()}}">
<div class="square">
<div class="square {{$status->firstMedia()->filter_class}}">
<div class="square-content" style="background-image: url('{{$status->thumb()}}')"></div>
<div class="info-overlay-text">
<h5 class="text-white m-auto font-weight-bold">

View File

@ -16,7 +16,47 @@
</div>
</div>
<div class="col-12 col-md-8 status-photo px-0">
<img src="{{$status->mediaUrl()}}" width="100%">
@if($status->is_nsfw && $status->media_count == 1)
<details class="details-animated">
<p>
<summary>NSFW / Hidden Image</summary>
<a class="max-hide-overflow {{$status->firstMedia()->filter_class}}" href="{{$status->url()}}">
<img class="card-img-top" src="{{$status->mediaUrl()}}">
</a>
</p>
</details>
@elseif(!$status->is_nsfw && $status->media_count == 1)
<div class="{{$status->firstMedia()->filter_class}}">
<img src="{{$status->mediaUrl()}}" width="100%">
</div>
@elseif($status->is_nsfw && $status->media_count > 1)
@elseif(!$status->is_nsfw && $status->media_count > 1)
<div id="photoCarousel" class="carousel slide carousel-fade" data-ride="carousel">
<ol class="carousel-indicators">
@for($i = 0; $i < $status->media_count; $i++)
<li data-target="#photoCarousel" data-slide-to="{{$i}}" class="{{$i == 0 ? 'active' : ''}}"></li>
@endfor
</ol>
<div class="carousel-inner">
@foreach($status->media()->orderBy('order')->get() as $media)
<div class="carousel-item {{$loop->iteration == 1 ? 'active' : ''}}">
<figure class="{{$media->filter_class}}">
<img class="d-block w-100" src="{{$media->url()}}" alt="{{$status->caption}}">
</figure>
</div>
@endforeach
</div>
<a class="carousel-control-prev" href="#photoCarousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#photoCarousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
@endif
</div>
<div class="col-12 col-md-4 px-0 d-flex flex-column border-left border-md-left-0">
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
@ -40,7 +80,7 @@
@foreach($status->comments->reverse()->take(10) as $item)
<p class="mb-0">
<span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$item->profile->url()}}">{{$item->profile->username}}</a></bdi></span>
<span class="comment-text">{!!$item->rendered!!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
<span class="comment-text">{!! $item->rendered ?? e($item->caption) !!} <a href="{{$item->url()}}" class="text-dark small font-weight-bold float-right">{{$item->created_at->diffForHumans(null, true, true ,true)}}</a></span>
</p>
@endforeach
</div>

View File

@ -15,6 +15,7 @@
<a class="dropdown-item" href="#">Embed</a>
@if(Auth::check())
@if(Auth::user()->profile->id === $item->profile->id || Auth::user()->is_admin == true)
<a class="dropdown-item" href="{{$item->editUrl()}}">Edit</a>
<form method="post" action="/i/delete">
@csrf
<input type="hidden" name="type" value="post">
@ -29,16 +30,16 @@
</div>
</div>
@if($item->is_nsfw)
<details>
<details class="details-animated">
<p>
<summary>NSFW / Hidden Image</summary>
<a class="max-hide-overflow" href="{{$item->url()}}">
<a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
<img class="card-img-top" src="{{$item->mediaUrl()}}">
</a>
</p>
</details>
@else
<a class="max-hide-overflow" href="{{$item->url()}}">
<a class="max-hide-overflow {{$item->firstMedia()->filter_class}}" href="{{$item->url()}}">
<img class="card-img-top" src="{{$item->mediaUrl()}}">
</a>
@endif
@ -73,7 +74,7 @@
</div>
@if($item->comments()->count() > 3)
<div class="more-comments">
<a class="text-muted" href="#">Load more comments</a>
<a class="text-muted" href="{{$item->url()}}">Load more comments</a>
</div>
@endif
<div class="comments">
@ -84,7 +85,7 @@
<a class="text-dark" href="{{$status->profile->url()}}">{{$status->profile->username}}</a>
</bdi>
</span>
<span class="comment-text">{!!$status->rendered!!}</span>
<span class="comment-text">{!! $item->rendered ?? e($item->caption) !!}</span>
<span class="float-right">
<a href="{{$status->url()}}" class="text-dark small font-weight-bold">
{{$status->created_at->diffForHumans(null, true, true, true)}}
@ -92,12 +93,6 @@
</span>
</p>
@else
@foreach($item->comments->reverse()->take(3) as $comment)
<p class="mb-0">
<span class="font-weight-bold pr-1"><bdi><a class="text-dark" href="{{$comment->profile->url()}}">{{$comment->profile->username}}</a></bdi></span>
<span class="comment-text">{{ str_limit($comment->caption, 125) }}</span>
</p>
@endforeach
@endif
</div>
<div class="timestamp pt-1">

View File

@ -1,13 +1,20 @@
<div class="card">
<div class="card-header font-weight-bold">New Post</div>
<div class="card-body" id="statusForm">
@if (session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<form method="post" action="/timeline" enctype="multipart/form-data">
@csrf
<input type="hidden" name="filter_name" value="">
<input type="hidden" name="filter_class" value="">
<div class="form-group">
<label class="font-weight-bold text-muted small">Upload Image</label>
<input type="file" class="form-control-file" name="photo" accept="image/*">
<input type="file" class="form-control-file" id="fileInput" name="photo[]" accept="image/*" multiple="">
<small class="form-text text-muted">
Max Size: @maxFileSize(). Supported formats: jpeg, png, gif, bmp.
Max Size: @maxFileSize(). Supported formats: jpeg, png, gif, bmp. Limited to {{config('pixelfed.max_album_length')}} photos per post.
</small>
</div>
<div class="form-group">
@ -17,17 +24,52 @@
Max length: {{config('pixelfed.max_caption_length')}} characters.
</small>
</div>
{{-- <div class="form-group">
<label class="font-weight-bold text-muted small">CW/NSFW</label>
<div class="switch switch-sm">
<input type="checkbox" class="switch" id="cw-switch" name="cw">
<label for="cw-switch" class="small font-weight-bold">(Default off)</label>
<div class="form-group">
<button class="btn btn-primary btn-sm px-3 py-1 font-weight-bold" type="button" data-toggle="collapse" data-target="#collapsePreview" aria-expanded="false" aria-controls="collapsePreview">
Options
</button>
<div class="collapse" id="collapsePreview">
<div class="form-group pt-3">
<label class="font-weight-bold text-muted small">CW/NSFW</label>
<div class="switch switch-sm">
<input type="checkbox" class="switch" id="cw-switch" name="cw">
<label for="cw-switch" class="small font-weight-bold">(Default off)</label>
</div>
<small class="form-text text-muted">
Please mark all NSFW and controversial content, as per our content policy.
</small>
</div>
{{-- <div class="form-group">
<label class="font-weight-bold text-muted small">Visibility</label>
<div class="switch switch-sm">
<input type="checkbox" class="switch" id="visibility-switch" name="visibility">
<label for="visibility-switch" class="small font-weight-bold">Public | Followers-only</label>
</div>
<small class="form-text text-muted">
Toggle this to limit this post to your followers only.
</small>
</div> --}}
<div class="form-group d-none form-preview">
<label class="font-weight-bold text-muted small">Photo Preview</label>
<figure class="filterContainer">
<img class="filterPreview img-fluid">
</figure>
<small class="form-text text-muted font-weight-bold">
No filter selected.
</small>
</div>
<div class="form-group d-none form-filters">
<label for="filterSelectDropdown" class="font-weight-bold text-muted small">Select Filter</label>
<select class="form-control" id="filterSelectDropdown">
<option value="none" selected="">No Filter</option>
</select>
</div>
</div>
<small class="form-text text-muted">
Please mark all NSFW and controversial content, as per our content policy.
</small>
</div> --}}
</div>
<button type="submit" class="btn btn-outline-primary btn-block">Post</button>
</form>
</div>
</div>
</div>

View File

@ -25,7 +25,7 @@ Route::domain(config('pixelfed.domain.admin'))->group(function() {
Route::get('media/list', 'AdminController@media')->name('admin.media');
});
Route::domain(config('pixelfed.domain.app'))->group(function() {
Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(function() {
Route::view('/', 'welcome');
@ -62,6 +62,9 @@ Route::domain(config('pixelfed.domain.app'))->group(function() {
Route::post('follow', 'FollowerController@store');
Route::post('bookmark', 'BookmarkController@store');
Route::get('lang/{locale}', 'SiteController@changeLocale');
Route::get('verify-email', 'AccountController@verifyEmail');
Route::post('verify-email', 'AccountController@sendVerifyEmail');
Route::get('confirm-email/{userToken}/{randomToken}', 'AccountController@confirmVerifyEmail');
Route::group(['prefix' => 'report'], function() {
Route::get('/', 'ReportController@showForm')->name('report.form');