diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index f21741e..bc71626 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -49,16 +49,14 @@ class RegisterController extends Controller */ protected function validator(array $data) { - return Validator::make($data, [ + return $this->makeValidator($data, [ 'name' => [ - 'regex:/^[a-zA-Z0-9_.-]+$/', 'required', - 'string', - 'max:255', + VALI_NAME, 'unique:users' ], - 'email' => 'required|string|email|max:255|unique:users', - 'password' => 'required|string|min:6|max:1000|confirmed', // max len to foil DOS attempts + 'email' => ['required', 'unique:users', VALI_EMAIL], + 'password' => ['required', 'confirmed', VALI_PASSWORD], ]); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 83b4f91..5b2cec2 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -3,13 +3,39 @@ namespace App\Http\Controllers; use Illuminate\Foundation\Bus\DispatchesJobs; +use Illuminate\Http\Request; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class Controller extends BaseController { - use AuthorizesRequests, DispatchesJobs, ValidatesRequests; + use AuthorizesRequests, + DispatchesJobs, + ValidatesRequests { + ValidatesRequests::validate as validate_orig; + ValidatesRequests::validateWithBag as validateWithBag_orig; + } + + // Hacks to allow recursive nesting of validations in string and array format + + public function makeValidator($data, $rules, $messages = array(), $customAttributes = array()) + { + return \Validator::make($data, vali($rules), $messages, $customAttributes); + } + + public function validate(Request $request, array $rules, + array $messages = [], array $customAttributes = []) + { + return objBag($this->validate_orig($request, vali($rules), $messages, $customAttributes)); + } + + public function validateWithBag($errorBag, Request $request, array $rules, + array $messages = [], array $customAttributes = []) + { + return objBag($this->validateWithBag_orig($errorBag, $request, vali($rules), + $messages, $customAttributes)); + } protected function backWithErrors($errors) { diff --git a/app/Http/Controllers/TableController.php b/app/Http/Controllers/TableController.php index 2066139..7e9696d 100644 --- a/app/Http/Controllers/TableController.php +++ b/app/Http/Controllers/TableController.php @@ -54,21 +54,24 @@ class TableController extends Controller /** @var User $u */ $u = \Auth::user(); - $this->validate($request, [ - 'name' => 'required|string|max:255', - 'title' => 'string|string|max:255', - 'description' => 'string|nullable|max:4000', - 'license' => 'string|nullable|max:4000', - 'origin' => 'string|nullable|max:4000', + $input = $this->validate($request, [ + 'name' => [ + 'required', + VALI_NAME, + Rule::unique('tables'), + ], + 'title' => ['required', VALI_LINE], + 'description' => ['nullable', VALI_TEXT], + 'license' => ['nullable', VALI_TEXT], + 'origin' => ['nullable', VALI_TEXT], 'columns' => 'required|string', 'data' => 'string|nullable', ]); // Check if table name is unique for user - $tabName = $request->get('name'); - if ($u->tables()->where('name', $tabName)->exists()) { + if ($u->tables()->where('name', $input->name)->exists()) { return $this->backWithErrors([ - 'name' => "A table called \"$tabName\" already exists in your account.", + 'name' => "A table called \"$input->name\" already exists in your account.", ]); } @@ -76,7 +79,7 @@ class TableController extends Controller /** @var Column[] $columns */ $columns = []; $column_keys = []; // for checking duplicates - $colTable = array_map('str_getcsv', explode("\n", $request->get('columns'))); + $colTable = array_map('str_getcsv', explode("\n", $input->columns)); // prevent griefing via long list of columns if (count($colTable) > 100) return $this->backWithErrors(['columns' => "Too many columns"]); @@ -104,7 +107,7 @@ class TableController extends Controller } if (count($columns) == 0) return $this->backWithErrors(['columns' => "Define at least one column"]); - $rowTable = array_map('str_getcsv', explode("\n", $request->get('data'))); + $rowTable = array_map('str_getcsv', explode("\n", $input->data)); // Preparing data to insert into the Rows table $rowsData = null; @@ -116,6 +119,10 @@ class TableController extends Controller $parsed = []; foreach ($row as $i => $val) { $key = $columns[$i]->name; + if (strlen($val) > 255) { + // try to stop people inserting unstructured crap / malformed CSV + throw new NotApplicableException("Value for column $key too long."); + } $parsed[$key] = $columns[$i]->cast($val); } return [ @@ -127,7 +134,7 @@ class TableController extends Controller } $revisionFields = [ - 'note' => "Initial revision of table $u->name/$tabName", + 'note' => "Initial revision of table $u->name/$input->name", 'columns' => json_encode($columns), 'row_count' => count($rowsData), ]; @@ -135,11 +142,11 @@ class TableController extends Controller $tableFields = [ 'owner_id' => $u->id, 'revision_id' => 0, - 'name' => $tabName, - 'title' => $request->get('title'), - 'description' => $request->get('description'), - 'license' => $request->get('license'), - 'origin' => $request->get('origin'), + 'name' => $input->name, + 'title' => $input->title, + 'description' => $input->description, + 'license' => $input->license, + 'origin' => $input->origin, ]; \DB::transaction(function () use ($revisionFields, $tableFields, $rowsData) { @@ -156,6 +163,6 @@ class TableController extends Controller $revision->rows()->createMany($rowsData); }); - return redirect(route('table.view', ['user' => $u, 'table' => $tabName])); + return redirect(route('table.view', ['user' => $u, 'table' => $input->name])); } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 0fa0d3d..c1ba545 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -3,7 +3,12 @@ namespace App\Http\Controllers; +use App\Models\EmailConfirmation; use App\Models\User; +use Hash; +use Illuminate\Http\Request; +use Illuminate\Validation\Rule; +use MightyPork\Utils\Str; class UserController extends Controller { @@ -22,18 +27,66 @@ class UserController extends Controller } /** - * Edit user profile + * Edit own profile * * @param User $user * @return \Illuminate\View\View */ public function edit() { - return view('user.edit')->with('user', \Auth::user()); + return view('user.edit')->with('user', \user()); } - public function store() + /** + * Store changed profile + */ + public function store(Request $request) { - echo "Not impl"; + $input = $this->validate($request, [ + 'name' => [ + 'required', + VALI_NAME, + Rule::unique('users')->ignoreModel(\user()), + ], + 'email' => [ + 'required', + VALI_EMAIL, + Rule::unique('users')->ignoreModel(\user()), + ], + 'bio' => ['nullable', VALI_TEXT], + 'title' => ['required', VALI_LINE], + 'website' => ['required', VALI_LINE], + 'new_password' => ['nullable', 'confirmed', VALI_PASSWORD], + ]); + + $user = user(); + + if ($input->email != $user->email) { + $confirmation = EmailConfirmation::create([ + 'user_id' => $user->id, + 'email' => $input->email, + 'token' => Str::random(60), + ]); + + flash()->warning("New e-mail confirmation sent to $input->email.")->important(); + + // TODO send the e-mail + + unset($input->email); + } + + $user->fill($input->all()); + + if ($input->has('new_password')) { + $user->password = Hash::make($input->new_password); + + flash()->warning('Password changed'); + } + + $user->save(); + + flash()->success('Settings saved'); + + return back(); } } diff --git a/app/Models/EmailConfirmation.php b/app/Models/EmailConfirmation.php index 31ba99e..3393ffe 100644 --- a/app/Models/EmailConfirmation.php +++ b/app/Models/EmailConfirmation.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Model; * @property \Carbon\Carbon $created_at * @property int $user_id * @property string $token + * @property string $email * @property-read User $user */ class EmailConfirmation extends Model diff --git a/app/Models/User.php b/app/Models/User.php index 67c85b7..8988e78 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -21,6 +21,7 @@ use MightyPork\Exceptions\NotExistException; * @property string $name - unique, for vanity URL * @property string $title - for display * @property string $bio - user bio + * @property string $website - custom url * @property string $email - unique, for login and social auth chaining * @property string $password - hashed pw * @property bool $confirmed - user e-mail is confirmed @@ -48,7 +49,7 @@ class User extends Authenticatable * @var array */ protected $fillable = [ - 'name', 'title', 'email', 'password', + 'name', 'title', 'email', 'password', 'bio', 'website' ]; /** diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php new file mode 100644 index 0000000..7e68a23 --- /dev/null +++ b/app/Providers/ValidationServiceProvider.php @@ -0,0 +1,29 @@ + $rules) { + // top level + if (is_array($rules)) { + $ar = []; + foreach ($rules as $rule) { + if (is_string($rule) && strpos($rule, '|') !== false) { + foreach (explode('|', $rule) as $rr) { + $ar[] = $rr; + } + } else if (is_array($rule)) { + // nested array, assume no further recursion + foreach ($rule as $rr) { + $ar[] = $rr; + } + } else { + // Rule + $ar[] = $rule; + } + } + $result[$key] = $ar; + } else { + // string or Rule + $result[$key] = $rules; + } + } + return $result; +} diff --git a/composer.json b/composer.json index 28d4ff5..2a16dbc 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "doctrine/dbal": "^2.7", "fideloper/proxy": "^4.0", "guzzlehttp/guzzle": "^6.0", + "laracasts/flash": "^3.0", "laravel/framework": "5.6.*", "laravel/socialite": "^3.0", "laravel/tinker": "^1.0", diff --git a/composer.lock b/composer.lock index 5d68f45..25b4447 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1bb552d1b383427434286ed0ced8293b", + "content-hash": "4a71a6ea4e0158136c11e0a93170b0c3", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -1051,6 +1051,60 @@ ], "time": "2015-04-20T18:58:01+00:00" }, + { + "name": "laracasts/flash", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/laracasts/flash.git", + "reference": "10cd420ab63fd0796bf5e1e5b99f87636d2f4333" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laracasts/flash/zipball/10cd420ab63fd0796bf5e1e5b99f87636d2f4333", + "reference": "10cd420ab63fd0796bf5e1e5b99f87636d2f4333", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laracasts\\Flash\\FlashServiceProvider" + ], + "aliases": { + "Flash": "Laracasts\\Flash\\Flash" + } + } + }, + "autoload": { + "psr-0": { + "Laracasts\\Flash": "src/" + }, + "files": [ + "src/Laracasts/Flash/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeffrey Way", + "email": "jeffrey@laracasts.com" + } + ], + "description": "Easy flash notifications", + "time": "2017-06-22T19:01:19+00:00" + }, { "name": "laravel/framework", "version": "v5.6.27", diff --git a/config/app.php b/config/app.php index 199b8ff..d154da3 100644 --- a/config/app.php +++ b/config/app.php @@ -159,6 +159,7 @@ return [ // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\ValidationServiceProvider::class, MightyPork\Providers\BladeExtensionsProvider::class, MightyPork\Providers\MacroServiceProvider::class, diff --git a/database/migrations/2018_07_22_083900_create_email_confirmations_table.php b/database/migrations/2018_07_22_083900_create_email_confirmations_table.php index 76d1c5b..4d4bf6e 100644 --- a/database/migrations/2018_07_22_083900_create_email_confirmations_table.php +++ b/database/migrations/2018_07_22_083900_create_email_confirmations_table.php @@ -14,6 +14,7 @@ class CreateEmailConfirmationsTable extends Migration public function up() { Schema::create('email_confirmations', function (Blueprint $table) { + $table->increments('id'); $table->unsignedInteger('user_id')->index(); $table->timestamp('created_at')->nullable(); $table->string('email'); diff --git a/porklib/helpers.php b/porklib/helpers.php index 2bb2f40..a88be7c 100644 --- a/porklib/helpers.php +++ b/porklib/helpers.php @@ -65,7 +65,8 @@ function unless($cond, $then, $else = '') * - Undefined keys are returned as null. * - array and object values are wrapped in objBag when returned. */ -class objBag implements JsonSerializable { +class objBag implements JsonSerializable, ArrayAccess { + /** @var object */ private $wrapped; public function __construct($wrapped) @@ -86,7 +87,21 @@ class objBag implements JsonSerializable { return null; } - public function __isset($name) + public function __set($name, $value) + { + if ($this->wrapped) { + $this->wrapped->$name = $value; + } + } + + public function __unset($name) + { + if ($this->wrapped) { + unset($this->wrapped->$name); + } + } + + public function __isset($name) { return isset($this->wrapped->$name); } @@ -99,7 +114,7 @@ class objBag implements JsonSerializable { public function has($name) { - return isset($this->$name); + return isset($this->$name) && $this->$name !== null; } public function unpack() @@ -107,6 +122,16 @@ class objBag implements JsonSerializable { return $this->wrapped; } + public function toArray() + { + return(array)$this->wrapped; + } + + public function all() + { + return $this->toArray(); + } + /** * Specify data which should be serialized to JSON * @@ -119,6 +144,26 @@ class objBag implements JsonSerializable { { return $this->wrapped; } + + public function offsetExists($offset) + { + return isset($this->$offset); + } + + public function offsetGet($offset) + { + return $this->$offset; + } + + public function offsetSet($offset, $value) + { + $this->$offset = $value; + } + + public function offsetUnset($offset) + { + unset($this->$offset); + } } function objBag($obj) { diff --git a/public/fonts/fa-dtbl-1-preview.html b/public/fonts/fa-dtbl-1-preview.html index 2540925..9faf934 100644 --- a/public/fonts/fa-dtbl-1-preview.html +++ b/public/fonts/fa-dtbl-1-preview.html @@ -161,7 +161,8 @@ [data-icon]:before { content: attr(data-icon); } [data-icon]:before, - .fa-bell:before, + .fa-at:before, +.fa-bell:before, .fa-bell-o:before, .fa-calendar:before, .fa-check:before, @@ -177,8 +178,10 @@ .fa-floppy-o:before, .fa-gavel:before, .fa-github:before, +.fa-globe:before, .fa-google:before, .fa-history:before, +.fa-key-modern:before, .fa-link:before, .fa-pencil-square-o:before, .fa-question-circle:before, @@ -216,47 +219,50 @@ font-smoothing: antialiased; } - .fa-bell:before { content: "\f100"; } -.fa-bell-o:before { content: "\f101"; } -.fa-calendar:before { content: "\f102"; } -.fa-check:before { content: "\f103"; } -.fa-clock-o:before { content: "\f104"; } -.fa-cloud-upload:before { content: "\f105"; } -.fa-code-fork:before { content: "\f106"; } -.fa-download:before { content: "\f107"; } -.fa-eye:before { content: "\f108"; } -.fa-eye-slash:before { content: "\f109"; } -.fa-facebook-square:before { content: "\f10a"; } -.fa-filter:before { content: "\f10b"; } -.fa-flag:before { content: "\f10c"; } -.fa-floppy-o:before { content: "\f10d"; } -.fa-gavel:before { content: "\f10e"; } -.fa-github:before { content: "\f10f"; } -.fa-google:before { content: "\f110"; } -.fa-history:before { content: "\f111"; } -.fa-link:before { content: "\f112"; } -.fa-pencil-square-o:before { content: "\f113"; } -.fa-question-circle:before { content: "\f114"; } -.fa-quote-left:before { content: "\f115"; } -.fa-reply:before { content: "\f116"; } -.fa-rss:before { content: "\f117"; } -.fa-search:before { content: "\f118"; } -.fa-share-alt:before { content: "\f119"; } -.fa-sign-in:before { content: "\f11a"; } -.fa-sign-out:before { content: "\f11b"; } -.fa-sliders:before { content: "\f11c"; } -.fa-sort:before { content: "\f11d"; } -.fa-sort-asc:before { content: "\f11e"; } -.fa-sort-desc:before { content: "\f11f"; } -.fa-star:before { content: "\f120"; } -.fa-star-o:before { content: "\f121"; } -.fa-table:before { content: "\f122"; } -.fa-times:before { content: "\f123"; } -.fa-trash:before { content: "\f124"; } -.fa-trash-o:before { content: "\f125"; } -.fa-user-circle-o:before { content: "\f126"; } -.fa-user-plus:before { content: "\f127"; } -.fa-wrench:before { content: "\f128"; } + .fa-at:before { content: "\f100"; } +.fa-bell:before { content: "\f101"; } +.fa-bell-o:before { content: "\f102"; } +.fa-calendar:before { content: "\f103"; } +.fa-check:before { content: "\f104"; } +.fa-clock-o:before { content: "\f105"; } +.fa-cloud-upload:before { content: "\f106"; } +.fa-code-fork:before { content: "\f107"; } +.fa-download:before { content: "\f108"; } +.fa-eye:before { content: "\f109"; } +.fa-eye-slash:before { content: "\f10a"; } +.fa-facebook-square:before { content: "\f10b"; } +.fa-filter:before { content: "\f10c"; } +.fa-flag:before { content: "\f10d"; } +.fa-floppy-o:before { content: "\f10e"; } +.fa-gavel:before { content: "\f10f"; } +.fa-github:before { content: "\f110"; } +.fa-globe:before { content: "\f111"; } +.fa-google:before { content: "\f112"; } +.fa-history:before { content: "\f113"; } +.fa-key-modern:before { content: "\f114"; } +.fa-link:before { content: "\f115"; } +.fa-pencil-square-o:before { content: "\f116"; } +.fa-question-circle:before { content: "\f117"; } +.fa-quote-left:before { content: "\f118"; } +.fa-reply:before { content: "\f119"; } +.fa-rss:before { content: "\f11a"; } +.fa-search:before { content: "\f11b"; } +.fa-share-alt:before { content: "\f11c"; } +.fa-sign-in:before { content: "\f11d"; } +.fa-sign-out:before { content: "\f11e"; } +.fa-sliders:before { content: "\f11f"; } +.fa-sort:before { content: "\f120"; } +.fa-sort-asc:before { content: "\f121"; } +.fa-sort-desc:before { content: "\f122"; } +.fa-star:before { content: "\f123"; } +.fa-star-o:before { content: "\f124"; } +.fa-table:before { content: "\f125"; } +.fa-times:before { content: "\f126"; } +.fa-trash:before { content: "\f127"; } +.fa-trash-o:before { content: "\f128"; } +.fa-user-circle-o:before { content: "\f129"; } +.fa-user-plus:before { content: "\f12a"; } +.fa-wrench:before { content: "\f12b"; } @@ -272,11 +278,24 @@
-

fa-dtbl-1 contains 41 glyphs:

+

fa-dtbl-1 contains 44 glyphs:

Toggle Preview Characters
+
+
+ PpPpPpPpPpPpPpPpPpPp +
+
+ 12141618212436486072 +
+
+ + +
+
+
PpPpPpPpPpPpPpPpPpPp @@ -286,7 +305,7 @@
- +
@@ -299,7 +318,7 @@
- +
@@ -312,7 +331,7 @@
- +
@@ -325,7 +344,7 @@
- +
@@ -338,7 +357,7 @@
- +
@@ -351,7 +370,7 @@
- +
@@ -364,7 +383,7 @@
- +
@@ -377,7 +396,7 @@
- +
@@ -390,7 +409,7 @@
- +
@@ -403,7 +422,7 @@
- +
@@ -416,7 +435,7 @@
- +
@@ -429,7 +448,7 @@
- +
@@ -442,7 +461,7 @@
- +
@@ -456,7 +475,7 @@
- +
@@ -470,7 +489,7 @@
- +
@@ -483,7 +502,20 @@
- + +
+ + +
+
+ PpPpPpPpPpPpPpPpPpPp +
+
+ 12141618212436486072 +
+
+ +
@@ -496,7 +528,7 @@
- +
@@ -509,7 +541,20 @@
- + +
+ + +
+
+ PpPpPpPpPpPpPpPpPpPp +
+
+ 12141618212436486072 +
+
+ +
@@ -522,7 +567,7 @@
- +
@@ -536,7 +581,7 @@
- +
@@ -549,7 +594,7 @@
- +
@@ -562,7 +607,7 @@
- +
@@ -575,7 +620,7 @@
- +
@@ -588,7 +633,7 @@
- +
@@ -601,7 +646,7 @@
- +
@@ -614,7 +659,7 @@
- +
@@ -627,7 +672,7 @@
- +
@@ -640,7 +685,7 @@
- +
@@ -653,7 +698,7 @@
- +
@@ -666,7 +711,7 @@
- +
@@ -679,7 +724,7 @@
- +
@@ -692,7 +737,7 @@
- +
@@ -705,7 +750,7 @@
- +
@@ -718,7 +763,7 @@
- +
@@ -731,7 +776,7 @@
- +
@@ -745,7 +790,7 @@
- +
@@ -758,7 +803,7 @@
- +
@@ -771,7 +816,7 @@
- +
@@ -784,7 +829,7 @@
- +
@@ -797,7 +842,7 @@
- +
@@ -810,7 +855,7 @@
- +
diff --git a/public/fonts/fa-dtbl-1.css b/public/fonts/fa-dtbl-1.css index dba08d5..25c5282 100644 --- a/public/fonts/fa-dtbl-1.css +++ b/public/fonts/fa-dtbl-1.css @@ -38,44 +38,47 @@ font-smoothing: antialiased; } -.fa-bell::before { content: "\f100"; } -.fa-bell-o::before { content: "\f101"; } -.fa-calendar::before { content: "\f102"; } -.fa-check::before { content: "\f103"; } -.fa-clock-o::before { content: "\f104"; } -.fa-cloud-upload::before { content: "\f105"; } -.fa-code-fork::before { content: "\f106"; } -.fa-download::before { content: "\f107"; } -.fa-eye::before { content: "\f108"; } -.fa-eye-slash::before { content: "\f109"; } -.fa-facebook-square::before { content: "\f10a"; } -.fa-filter::before { content: "\f10b"; } -.fa-flag::before { content: "\f10c"; } -.fa-floppy-o::before, .fa-save::before { content: "\f10d"; } -.fa-gavel::before, .fa-legal::before { content: "\f10e"; } -.fa-github::before { content: "\f10f"; } -.fa-google::before { content: "\f110"; } -.fa-history::before { content: "\f111"; } -.fa-link::before { content: "\f112"; } -.fa-pencil-square-o::before, .fa-edit::before { content: "\f113"; } -.fa-question-circle::before { content: "\f114"; } -.fa-quote-left::before { content: "\f115"; } -.fa-reply::before { content: "\f116"; } -.fa-rss::before { content: "\f117"; } -.fa-search::before { content: "\f118"; } -.fa-share-alt::before { content: "\f119"; } -.fa-sign-in::before { content: "\f11a"; } -.fa-sign-out::before { content: "\f11b"; } -.fa-sliders::before { content: "\f11c"; } -.fa-sort::before { content: "\f11d"; } -.fa-sort-asc::before { content: "\f11e"; } -.fa-sort-desc::before { content: "\f11f"; } -.fa-star::before { content: "\f120"; } -.fa-star-o::before { content: "\f121"; } -.fa-table::before { content: "\f122"; } -.fa-times::before, .fa-close::before { content: "\f123"; } -.fa-trash::before { content: "\f124"; } -.fa-trash-o::before { content: "\f125"; } -.fa-user-circle-o::before { content: "\f126"; } -.fa-user-plus::before { content: "\f127"; } -.fa-wrench::before { content: "\f128"; } +.fa-at::before { content: "\f100"; } +.fa-bell::before { content: "\f101"; } +.fa-bell-o::before { content: "\f102"; } +.fa-calendar::before { content: "\f103"; } +.fa-check::before { content: "\f104"; } +.fa-clock-o::before { content: "\f105"; } +.fa-cloud-upload::before { content: "\f106"; } +.fa-code-fork::before { content: "\f107"; } +.fa-download::before { content: "\f108"; } +.fa-eye::before { content: "\f109"; } +.fa-eye-slash::before { content: "\f10a"; } +.fa-facebook-square::before { content: "\f10b"; } +.fa-filter::before { content: "\f10c"; } +.fa-flag::before { content: "\f10d"; } +.fa-floppy-o::before, .fa-save::before { content: "\f10e"; } +.fa-gavel::before, .fa-legal::before { content: "\f10f"; } +.fa-github::before { content: "\f110"; } +.fa-globe::before { content: "\f111"; } +.fa-google::before { content: "\f112"; } +.fa-history::before { content: "\f113"; } +.fa-key-modern::before { content: "\f114"; } +.fa-link::before { content: "\f115"; } +.fa-pencil-square-o::before, .fa-edit::before { content: "\f116"; } +.fa-question-circle::before { content: "\f117"; } +.fa-quote-left::before { content: "\f118"; } +.fa-reply::before { content: "\f119"; } +.fa-rss::before { content: "\f11a"; } +.fa-search::before { content: "\f11b"; } +.fa-share-alt::before { content: "\f11c"; } +.fa-sign-in::before { content: "\f11d"; } +.fa-sign-out::before { content: "\f11e"; } +.fa-sliders::before { content: "\f11f"; } +.fa-sort::before { content: "\f120"; } +.fa-sort-asc::before { content: "\f121"; } +.fa-sort-desc::before { content: "\f122"; } +.fa-star::before { content: "\f123"; } +.fa-star-o::before { content: "\f124"; } +.fa-table::before { content: "\f125"; } +.fa-times::before, .fa-close::before { content: "\f126"; } +.fa-trash::before { content: "\f127"; } +.fa-trash-o::before { content: "\f128"; } +.fa-user-circle-o::before { content: "\f129"; } +.fa-user-plus::before { content: "\f12a"; } +.fa-wrench::before { content: "\f12b"; } diff --git a/public/fonts/fa-dtbl-1.eot b/public/fonts/fa-dtbl-1.eot index ba2b8b5..c8b5a09 100644 Binary files a/public/fonts/fa-dtbl-1.eot and b/public/fonts/fa-dtbl-1.eot differ diff --git a/public/fonts/fa-dtbl-1.svg b/public/fonts/fa-dtbl-1.svg index 1f6fdde..2a2f089 100644 --- a/public/fonts/fa-dtbl-1.svg +++ b/public/fonts/fa-dtbl-1.svg @@ -5,7 +5,7 @@ --> -Created by FontForge 20170805 at Sun Jul 22 15:14:07 2018 +Created by FontForge 20170805 at Sun Jul 22 23:07:59 2018 By ondra The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/OFL). Fork Awesome is a fork based of off Font Awesome 4.7.0 by Dave Gandy. More info on licenses at https://forkawesome.github.io @@ -19,154 +19,168 @@ The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/ panose-1="2 0 5 3 0 0 0 0 0 0" ascent="1536" descent="-256" - bbox="-0.0376684 -256 2048 1536" + bbox="-0.0376684 -256 2048 1536.01" underline-thickness="89.6" underline-position="-179.2" - unicode-range="U+0020-F128" + unicode-range="U+0020-F12B" /> - + - - - - - - - - - - - - - - - - + - - + - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/fonts/fa-dtbl-1.ttf b/public/fonts/fa-dtbl-1.ttf index 5b07fa0..1e110b4 100644 Binary files a/public/fonts/fa-dtbl-1.ttf and b/public/fonts/fa-dtbl-1.ttf differ diff --git a/public/fonts/fa-dtbl-1.woff2 b/public/fonts/fa-dtbl-1.woff2 index 3a52f45..30263db 100644 Binary files a/public/fonts/fa-dtbl-1.woff2 and b/public/fonts/fa-dtbl-1.woff2 differ diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 03a1357..d05eb66 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -1,19 +1,28 @@ - /** * First we will load all of this project's JavaScript dependencies which * includes Vue and other libraries. It is a great starting point when * building robust, powerful web applications using Vue and Laravel. */ -require('./bootstrap'); +require('./bootstrap') let url_slug = require('./url-slug') $(function () { - $('[data-toggle="tooltip"]').tooltip({ - container: 'body' - }) + $('[data-toggle="tooltip"]').tooltip({ + container: 'body' + }) + + // auto hide flash alerts + let $notifs = $('div.alert').not('.alert-important').addClass('fadeout') + setTimeout(() => { + $notifs.addClass('fade') + setTimeout(() => { + $notifs.addClass('hidden') + }, 500) + }, 2500) }) +// auto-alias $(document).on('input keypress paste keyup', 'input[data-autoalias]', function () { const $this = $(this) const target_name = $this.data('autoalias') @@ -31,7 +40,6 @@ $(document).on('input keypress paste keyup', 'input[data-autoalias]', function ( } }) - // // window.Vue = require('vue'); // diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index 619446f..88e04e6 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -159,3 +159,12 @@ html { @extend .btn-sm; @extend .btn-outline-light; } + +.fadeout { + transition: opacity .5s ease-in-out; + opacity: 1; + + &.fade { + opacity: 0; + } +} diff --git a/resources/views/form/input.blade.php b/resources/views/form/input.blade.php index 600aa66..175a395 100644 --- a/resources/views/form/input.blade.php +++ b/resources/views/form/input.blade.php @@ -11,26 +11,26 @@
@if($w->prepend)
- {{$w->prepend}} + {!! $w->prepend !!}
@endif attributes !!}> @if($w->append)
- {{$w->append}} + {!! $w->append !!}
@endif @if ($errors->has($w->name)) - {{ $errors->first($w->name) }} - + {{ $errors->first($w->name) }} + @endif
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 1b5b46e..77b52d3 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -22,7 +22,10 @@ @include('layouts.main-nav')
- @yield('content') +
+ @include('flash::message') + @yield('content') +
@include('layouts.footer') diff --git a/resources/views/table/create.blade.php b/resources/views/table/create.blade.php index f6ee4db..55b6fc2 100644 --- a/resources/views/table/create.blade.php +++ b/resources/views/table/create.blade.php @@ -3,58 +3,56 @@ @extends('layouts.app') @section('content') -
-
- - @csrf -
- @php(Widget::setLayout(3, 7)) - - {!! Widget::header(1, 'New Table') !!} - - {!! Widget::text('title', 'Title')->autoAlias('name', '-') - ->help('Unique among your tables') !!} - - {!! Widget::text('name', 'Name')->value('')->prepend(user()->handle.' /') - ->help('Unique among your tables, and part of the URL; only letters, digits and - some symbols are allowed.') !!} - - {!! Widget::textarea('description', 'Description')->height('8em') - ->help('Description of the table. URLs in a full format will be clickable.') !!} - - {!! Widget::text('license', 'License') - ->help('License applicable to the table\'s data, if any. By default, all - tables are CC0 or Public Domain.') !!} - - {!! Widget::text('origin', 'Adapted from') - ->help('If you took the data from some external site, a book, etc., write it here. - URLs in a full format will be clickable.') !!} - - {!! Widget::textarea('columns', 'Columns')->value($exampleColumns)->height('8em') - ->help(' -
- Column parameters in CSV format: -
    -
  • column identifier
    letters, numbers, underscore -
  • column data type
    int, string, float, bool -
  • column title
    used for display (optional) -
-
') !!} - - {!! Widget::textarea('data', 'Initial data')->value($exampleData)->height('12em') - ->help(' - Initial table data in CSV format, columns corresponding to the - specification you entered above.') !!} - -
-
- -
+ + + @csrf +
+ @php(Widget::setLayout(3, 7)) + + {!! Widget::header(1, 'New Table') !!} + + {!! Widget::text('title', 'Title')->autoAlias('name', '-') + ->help('Unique among your tables') !!} + + {!! Widget::text('name', 'Name')->value('')->prepend(user()->handle.' /') + ->help('Unique among your tables, and part of the URL; only letters, digits and + some symbols are allowed.') !!} + + {!! Widget::textarea('description', 'Description')->height('8em') + ->help('Description of the table. URLs in a full format will be clickable.') !!} + + {!! Widget::text('license', 'License') + ->help('License applicable to the table\'s data, if any. By default, all + tables are CC0 or Public Domain.') !!} + + {!! Widget::text('origin', 'Adapted from') + ->help('If you took the data from some external site, a book, etc., write it here. + URLs in a full format will be clickable.') !!} + + {!! Widget::textarea('columns', 'Columns')->value($exampleColumns)->height('8em') + ->help(' +
+ Column parameters in CSV format: +
    +
  • column identifier
    letters, numbers, underscore +
  • column data type
    int, string, float, bool +
  • column title
    used for display (optional) +
+
') !!} + + {!! Widget::textarea('data', 'Initial data')->value($exampleData)->height('12em') + ->help(' + Initial table data in CSV format, columns corresponding to the + specification you entered above.') !!} + +
+
+
- -
+
+ @endsection diff --git a/resources/views/table/view.blade.php b/resources/views/table/view.blade.php index 4b8f90d..6ae8903 100644 --- a/resources/views/table/view.blade.php +++ b/resources/views/table/view.blade.php @@ -7,35 +7,33 @@ @endphp @section('content') -
-
-

{{ $table->title }}

-
-
+
+

{{ $table->title }}

+
+
-
- - - - - @foreach($columns as $col) - - @endforeach - - - - @foreach($rows as $row) - - - @php($rdata = json_decode($row['data'], true)) - @foreach($columns as $col) - - @endforeach - - @endforeach - -
ID{{ $col->title }}
#{{ $row->id }}{{ $rdata[$col->name] }}
-
+
+ + + + + @foreach($columns as $col) + + @endforeach + + + + @foreach($rows as $row) + + + @php($rdata = json_decode($row['data'], true)) + @foreach($columns as $col) + + @endforeach + + @endforeach + +
ID{{ $col->title }}
#{{ $row->id }}{{ $rdata[$col->name] }}
@endsection diff --git a/resources/views/user/edit.blade.php b/resources/views/user/edit.blade.php index 014210f..2d2cdc4 100644 --- a/resources/views/user/edit.blade.php +++ b/resources/views/user/edit.blade.php @@ -3,46 +3,47 @@ @extends('layouts.app') @section('content') -
-
- @csrf + + @csrf -
- @php(Widget::setLayout(3, 7)) +
+ @php(Widget::setLayout(3, 7)) - {!! Widget::header(1, 'Settings') !!} + {!! Widget::header(1, 'Settings') !!} - {!! Widget::text('title', 'Display Name')->value($user->title)->required()->autofocus() - ->help('Shown on your profile page, tables, comments, etc.') !!} + {!! Widget::text('title', 'Display Name')->value($user->title)->required()->autofocus() + ->help('Shown on your profile page, tables, comments, etc.') !!} - {!! Widget::textarea('bio', 'About Me')->value($user->bio)->required()->height('8em') - ->help('This is shown in your profile box') !!} + {!! Widget::text('name', 'Username')->value($user->name)->required() + ->prepend('@') + ->help('Part of your vanity URL. Caution: changing this will alter URLs of your tables.') !!} - {!! Widget::text('name', 'Username')->value($user->name)->required() - ->prepend('@') - ->help('Part of your vanity URL. Caution: changing this will alter URLs of your tables.') !!} + {!! Widget::textarea('bio', 'About Me')->value($user->bio)->height('8em') + ->help('This is shown in your profile box') !!} - {!! Widget::email('email', 'E-Mail Address')->value($user->email)->required() - ->help('Used to login and for password resets. - This field is protected; a change will be applied only after you confirm - the new e-mail address via a confirmation link we\'ll send you to it.') !!} + {!! Widget::text('website', 'Website')->value($user->website)->prepend('') + ->help('Custom clickable link shown on your profile page.') !!} - {!! Widget::header(3, 'Password Change') !!} - {!! Widget::par('Leave empty to keep your current password (if any).') !!} + {!! Widget::email('email', 'E-Mail Address')->value($user->email)->required() + ->help('Used to login and for password resets. + This field is protected; a change will be applied only after you confirm + the new e-mail address via a confirmation link we\'ll send you to it.') !!} - {!! Widget::password('new_password', 'New Password') !!} + {!! Widget::header(3, 'Password Change') !!} + {!! Widget::par('Leave empty to keep your current password (if any).') !!} - {!! Widget::password('new_password_confirmation', 'Confirm New Password') !!} + {!! Widget::password('new_password', 'New Password') !!} -
-
- -
+ {!! Widget::password('new_password_confirmation', 'Confirm New Password') !!} + +
+
+
- -
+
+ @endsection diff --git a/resources/views/user/view.blade.php b/resources/views/user/view.blade.php index ebe2d1a..2f37df0 100644 --- a/resources/views/user/view.blade.php +++ b/resources/views/user/view.blade.php @@ -7,89 +7,86 @@ @endphp @section('content') -
+
-
+ {{-- Dash card --}} +
+
+
+ {{ $user->title }} - {{-- Dash card --}} -
-
-
- {{ $user->title }} - - @if(authed() && user()->is($user)) - Edit - @endif -
- -
- @if (session('status')) - - @endif + @if(authed() && user()->is($user)) + Edit + @endif +
- @if($user->bio) - @if(mb_strlen($user->bio) > 250) - - - @else -

- {{ $user->bio }} -

- @endif - @endif +
+ @if (session('status')) + + @endif - @if($user->website) + @if($user->bio) + @if(mb_strlen($user->bio) > 250) + + + @else

- {{-- - --}}{{ $user->website }} + {{ $user->bio }}

@endif + @endif -

- {{-- - --}}Joined {{ $user->created_at->diffForHumans() }} + @if($user->website) +

+ {{-- + --}}{{ $user->website }}

-
+ @endif + +

+ {{-- + --}}Joined {{ $user->created_at->diffForHumans() }} +

+
- {{-- Table list card --}} -
-
-
- - @if(authed() && user()->is($user)) - Your Tables - @else - User's Tables - @endif - - - - + {{-- Table list card --}} +
+
+
+ @if(authed() && user()->is($user)) - New + Your Tables + @else + User's Tables @endif -
+ - @include('user._table-list') + + + @if(authed() && user()->is($user)) + New + @endif
-
+ @include('user._table-list') +
+
@endsection diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index bfeeab7..fa58d6c 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -3,15 +3,13 @@ @extends('layouts.app') @section('content') -
-
-
-
-
Dashboard
+
+
+
+
Dashboard
-
-

Welcome to the public landing page.

-
+
+

Welcome to the public landing page.