Forráskód Böngészése

Merge pull request #264 from dansup/frontend-ui-refactor

Frontend ui refactor
daniel 7 éve
szülő
commit
ff9ff70992

+ 15 - 0
app/EmailVerification.php

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

+ 43 - 2
app/Http/Controllers/AccountController.php

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

+ 4 - 0
app/Http/Controllers/StatusController.php

@@ -40,6 +40,10 @@ class StatusController extends Controller
         '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);

+ 1 - 0
app/Http/Kernel.php

@@ -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 - 0
app/Http/Middleware/EmailVerificationCheck.php

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

+ 8 - 0
app/Jobs/StatusPipeline/StatusDelete.php

@@ -58,6 +58,14 @@ 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')

+ 34 - 0
app/Mail/ConfirmEmail.php

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

+ 20 - 0
config/pixelfed.php

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

+ 35 - 0
database/migrations/2018_06_14_041422_create_email_verifications_table.php

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

BIN
public/css/app.css


BIN
public/js/app.js


BIN
public/mix-manifest.json


+ 1 - 0
resources/assets/js/bootstrap.js

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

+ 2 - 0
resources/assets/js/components/commentform.js

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

+ 110 - 0
resources/assets/js/components/statusform.js

@@ -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 - 0
resources/assets/sass/app.scss

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

+ 445 - 0
resources/assets/sass/components/filters.scss

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

+ 15 - 0
resources/assets/sass/custom.scss

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

+ 24 - 0
resources/views/account/verify_email.blade.php

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

+ 12 - 0
resources/views/emails/confirm_email.blade.php

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

+ 4 - 0
resources/views/layouts/partial/nav.blade.php

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

+ 1 - 1
resources/views/profile/show.blade.php

@@ -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">

+ 54 - 12
resources/views/timeline/partial/new-form.blade.php

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

+ 4 - 1
routes/web.php

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