From 1629f712bc2b0294bccc4128e375c460a6ad9bca Mon Sep 17 00:00:00 2001 From: Kovah <mail@kovah.de> Date: Thu, 10 Jan 2019 22:39:01 +0100 Subject: [PATCH] Implement bookmarklet feature (#5) --- app/Helper/LinkAce.php | 15 ++ .../Controllers/App/BookmarkletController.php | 72 +++++++++ .../App/UserSettingsController.php | 8 +- app/Http/Controllers/Auth/LoginController.php | 13 +- .../Controllers/Models/LinkController.php | 11 ++ resources/assets/sass/custom/_layout.scss | 2 +- resources/lang/en/linkace.php | 3 + resources/lang/en/settings.php | 4 + .../actions/bookmarklet/complete.blade.php | 26 ++++ .../actions/bookmarklet/create.blade.php | 7 + .../views/actions/bookmarklet/login.blade.php | 78 ++++++++++ .../views/actions/settings/user.blade.php | 19 ++- resources/views/layouts/bookmarklet.blade.php | 32 ++++ .../views/models/links/_create-form.blade.php | 140 ++++++++++++++++++ resources/views/models/links/create.blade.php | 138 +---------------- routes/web.php | 24 +-- 16 files changed, 441 insertions(+), 151 deletions(-) create mode 100644 app/Http/Controllers/App/BookmarkletController.php create mode 100644 resources/views/actions/bookmarklet/complete.blade.php create mode 100644 resources/views/actions/bookmarklet/create.blade.php create mode 100644 resources/views/actions/bookmarklet/login.blade.php create mode 100644 resources/views/layouts/bookmarklet.blade.php create mode 100644 resources/views/models/links/_create-form.blade.php diff --git a/app/Helper/LinkAce.php b/app/Helper/LinkAce.php index 4ed52797..3f156ceb 100644 --- a/app/Helper/LinkAce.php +++ b/app/Helper/LinkAce.php @@ -11,6 +11,7 @@ class LinkAce { /** * Get the title of an HTML page b + * * @param string $url * @return string|string[] */ @@ -40,4 +41,18 @@ class LinkAce return $title; } + + /** + * Generate the code for the bookmarklet + * + * @return mixed|string + */ + public static function generateBookmarkletCode() + { + $bm_code = "javascript:javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;window.open('##URL##?u='%20+%20encodeURIComponent(url)+'&t='%20+%20encodeURIComponent(title),'_blank','menubar=no,height=720,width=600,toolbar=no,scrollbars=yes,status=no,dialog=1');})();"; + + $bm_code = str_replace('##URL##', route('bookmarklet-add'), $bm_code); + + return $bm_code; + } } diff --git a/app/Http/Controllers/App/BookmarkletController.php b/app/Http/Controllers/App/BookmarkletController.php new file mode 100644 index 00000000..f5dc2af3 --- /dev/null +++ b/app/Http/Controllers/App/BookmarkletController.php @@ -0,0 +1,72 @@ +<?php + +namespace App\Http\Controllers\App; + +use App\Http\Controllers\Controller; +use App\Models\Category; +use Illuminate\Http\Request; + +class BookmarkletController extends Controller +{ + /** + * Show the application dashboard. + * + * @param Request $request + * @return \Illuminate\Http\Response + */ + public function getLinkAddForm(Request $request) + { + $new_url = $request->get('u'); + $new_title = $request->get('t'); + + // Rredirect to the login if the user is not logged in + if (!auth()->check()) { + // Save details for the link in the session + session(['bookmarklet.new_url' => $new_url]); + session(['bookmarklet.new_title' => $new_title]); + session(['bookmarklet.login_redirect' => true]); + + return redirect()->route('bookmarklet-login'); + } + + if ($new_url === null) { + // Receive the link details from the session + $new_url = session('bookmarklet.new_url'); + $new_title = session('bookmarklet.new_title'); + + session()->remove('bookmarklet.new_url'); + session()->remove('bookmarklet.new_title'); + } + + session(['bookmarklet.create' => true]); + + return view('actions.bookmarklet.create') + ->with('bookmark_url', $new_url) + ->with('bookmark_title', $new_title) + ->with('categories', Category::parentOnly() + ->byUser(auth()->user()->id) + ->orderBy('name', 'asc') + ->get() + ); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Http\Response + */ + public function getCompleteView() + { + return view('actions.bookmarklet.complete'); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Http\Response + */ + public function getLoginForm() + { + return view('actions.bookmarklet.login'); + } +} diff --git a/app/Http/Controllers/App/UserSettingsController.php b/app/Http/Controllers/App/UserSettingsController.php index 36b7858a..a78b61ec 100644 --- a/app/Http/Controllers/App/UserSettingsController.php +++ b/app/Http/Controllers/App/UserSettingsController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\App; +use App\Helper\LinkAce; use App\Http\Controllers\Controller; use App\Http\Requests\UserSettingsUpdateRequest; use App\Models\Setting; @@ -22,7 +23,12 @@ class UserSettingsController extends Controller */ public function getUserSettings() { - return view('actions.settings.user', ['user' => auth()->user()]); + $bookmarklet_code = LinkAce::generateBookmarkletCode(); + + return view('actions.settings.user', [ + 'user' => auth()->user(), + 'bookmarklet_code' => $bookmarklet_code, + ]); } /** diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 3a1f70b8..5a2f345a 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -23,9 +23,18 @@ class LoginController extends Controller /** * Where to redirect users after login. * - * @var string + * @return string */ - protected $redirectTo = '/dashboard'; + protected function redirectTo() + { + // Redirect to the bookmarklet form after login from the bookmarklet + if (session('bookmarklet.login_redirect')) { + session()->remove('bookmarklet.login_redirect'); + return route('bookmarklet-add'); + } + + return '/dashboard'; + } /** * Create a new controller instance. diff --git a/app/Http/Controllers/Models/LinkController.php b/app/Http/Controllers/Models/LinkController.php index 2312b35f..a59b429b 100644 --- a/app/Http/Controllers/Models/LinkController.php +++ b/app/Http/Controllers/Models/LinkController.php @@ -79,11 +79,22 @@ class LinkController extends Controller alert(trans('link.added_successfully'), 'success'); + $is_bookmarklet = session('bookmarklet.create'); + if ($request->get('reload_view')) { session()->flash('reload_view', true); + + if ($is_bookmarklet) { + return redirect()->route('bookmarklet-add'); + } + return redirect()->route('links.create'); } + if ($is_bookmarklet) { + return redirect()->route('bookmarklet-complete'); + } + return redirect()->route('links.show', [$link->id]); } diff --git a/resources/assets/sass/custom/_layout.scss b/resources/assets/sass/custom/_layout.scss index 7a89d955..f8410678 100644 --- a/resources/assets/sass/custom/_layout.scss +++ b/resources/assets/sass/custom/_layout.scss @@ -1,5 +1,5 @@ // Layout styles -body { +body:not(.bookmarklet) { padding-top: $nav-link-height + ($navbar-padding-y * 2); } diff --git a/resources/lang/en/linkace.php b/resources/lang/en/linkace.php index a3b7a70c..5b6be7cf 100644 --- a/resources/lang/en/linkace.php +++ b/resources/lang/en/linkace.php @@ -36,4 +36,7 @@ return [ 'no' => 'No', 'no_results_found' => 'No :model found.', + + 'bookmarklet_close' => 'This bookmarklet window automatically closes in <span class="bm-timer">5</span> seconds.', + 'open_linkace' => 'Open LinkAce', ]; diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 09d56233..fbf1f0d0 100644 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -13,6 +13,10 @@ return [ 'save_settings' => 'Save Settings', 'settings_saved' => 'Settings successfully updated!', + 'bookmarklet' => 'Bookmarklet', + 'bookmarklet_button' => 'Drag this to your Bookmarks or right-click and save it as a bookmark', + 'bookmarklet_help' => 'Add this Bookmarklet to your browser to quickly add links from the sites you visit without having to open LinkAce manually.', + 'change_password' => 'Change Password', 'old_password' => 'Old Password', 'new_password' => 'New Password', diff --git a/resources/views/actions/bookmarklet/complete.blade.php b/resources/views/actions/bookmarklet/complete.blade.php new file mode 100644 index 00000000..59935357 --- /dev/null +++ b/resources/views/actions/bookmarklet/complete.blade.php @@ -0,0 +1,26 @@ +@extends('layouts.bookmarklet') + +@section('content') + + <div class="mt-3"> + <p>@lang('linkace.bookmarklet_close')</p> + + <a href="{{ route('front') }}" target="_blank" class="btn btn-primary"> + <i class="fa fa-arrow-left fa-mr"></i> + @lang('linkace.open_linkace') + </a> + </div> + +@endsection + +@push('scripts') + <script> + var timer = $('.bm-timer'); + window.setInterval(function () { + timer.text(parseInt(timer.text()) - 1); + }, 1000); + window.setTimeout(function () { + window.close(); + }, 5000); + </script> +@endpush diff --git a/resources/views/actions/bookmarklet/create.blade.php b/resources/views/actions/bookmarklet/create.blade.php new file mode 100644 index 00000000..70f7035a --- /dev/null +++ b/resources/views/actions/bookmarklet/create.blade.php @@ -0,0 +1,7 @@ +@extends('layouts.bookmarklet') + +@section('content') + + @include('models.links._create-form') + +@endsection diff --git a/resources/views/actions/bookmarklet/login.blade.php b/resources/views/actions/bookmarklet/login.blade.php new file mode 100644 index 00000000..30cc60e3 --- /dev/null +++ b/resources/views/actions/bookmarklet/login.blade.php @@ -0,0 +1,78 @@ +@extends('layouts.bookmarklet') + +@section('content') + + <div class="card"> + <div class="card-header"> + @lang('linkace.login') + </div> + <div class="card-body"> + <form method="POST" action="{{ route('login') }}" aria-label="@lang('linkace.login')"> + @csrf + + <div class="form-group"> + <div class="input-group mb-3"> + <div class="input-group-prepend"> + <div class="input-group-text"> + <i class="fa fa-fw fa-envelope"></i> + </div> + </div> + <input type="email" name="email" id="email" class="form-control" + value="{{ old('email') }}" + placeholder="@lang('linkace.email')" aria-label="@lang('linkace.email')" required + autofocus> + </div> + + @if ($errors->has('email')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('email') }} + </p> + @endif + </div> + + <div class="form-group"> + <div class="input-group mb-3"> + <div class="input-group-prepend"> + <div class="input-group-text"> + <i class="fa fa-fw fa-lock"></i> + </div> + </div> + <input type="password" name="password" id="password" class="form-control" + placeholder="@lang('linkace.password')" aria-label="@lang('linkace.password')"> + </div> + @if ($errors->has('password')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('password') }} + </p> + @endif + </div> + + <div class="row mt-4"> + <div class="col-8"> + + <div class="custom-control custom-checkbox pt-1"> + <input type="hidden" name="remember_me" value="0"> + <input type="checkbox" class="custom-control-input" id="remember_me" + @if(old('remember_me')) checked @endif> + + <label class="custom-control-label" for="remember_me"> + @lang('linkace.remember_me') + </label> + </div> + + </div> + <div class="col-4"> + + <button type="submit" class="btn btn-primary btn-block"> + @lang('linkace.login') + </button> + + </div> + </div> + + </form> + </div> + </div> + + +@endsection diff --git a/resources/views/actions/settings/user.blade.php b/resources/views/actions/settings/user.blade.php index 424ea63d..d1bb6112 100644 --- a/resources/views/actions/settings/user.blade.php +++ b/resources/views/actions/settings/user.blade.php @@ -3,6 +3,23 @@ @section('content') <div class="card"> + <div class="card-header"> + @lang('settings.bookmarklet') + </div> + <div class="card-body"> + + <p>@lang('settings.bookmarklet_help')</p> + + <a href="{{ $bookmarklet_code }}" class="btn btn-primary"> + @lang('settings.bookmarklet') + </a> + + <p class="small">@lang('settings.bookmarklet_button')</p> + + </div> + </div> + + <div class="card mt-4"> <div class="card-header"> @lang('settings.user_settings') </div> @@ -164,7 +181,7 @@ </div> </div> - <div class="card mt-5"> + <div class="card mt-4"> <div class="card-header"> @lang('settings.change_password') </div> diff --git a/resources/views/layouts/bookmarklet.blade.php b/resources/views/layouts/bookmarklet.blade.php new file mode 100644 index 00000000..c8594577 --- /dev/null +++ b/resources/views/layouts/bookmarklet.blade.php @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <meta name="csrf-token" content="{{ csrf_token() }}"> + + <title>{{ config('app.name', 'LinkAce') }}</title> + + <link href="{{ asset('assets/app.css') }}" rel="stylesheet"> +</head> +<body class="bookmarklet"> +<div id="app"> + + <main class="main container"> + <div class="mb-3 text-center"> + <img src="{{ asset('assets/img/logo_linkace.svg') }}" alt="@lang('linkace.linkace')" + width="81" height="30"> + </div> + + @include('partials.alerts') + @yield('content') + </main> + + <script src="{{ asset('assets/dependencies.js') }}"></script> + @stack('scripts') + +</div> +</body> +</html> diff --git a/resources/views/models/links/_create-form.blade.php b/resources/views/models/links/_create-form.blade.php new file mode 100644 index 00000000..b24a590f --- /dev/null +++ b/resources/views/models/links/_create-form.blade.php @@ -0,0 +1,140 @@ +<div class="card"> + <header class="card-header"> + @lang('link.add') + </header> + <div class="card-body"> + + <form action="{{ route('links.store') }}" method="POST"> + @csrf + + <div class="form-group"> + <label class="label" for="url">@lang('link.url')</label> + <input name="url" id="url" type="url" + class="form-control form-control-lg{{ $errors->has('url') ? ' is-invalid' : '' }}" + placeholder="@lang('link.url')" value="{{ old('url') ?: $bookmark_url ?? '' }}" + required autofocus> + @if ($errors->has('url')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('url') }} + </p> + @endif + </div> + + <div class="row"> + <div class="col"> + + <div class="form-group"> + <label class="label" for="title">@lang('link.title')</label> + <input name="title" id="title" + class="form-control{{ $errors->has('title') ? ' is-invalid' : '' }}" + type="text" placeholder="@lang('link.title')" + value="{{ old('title') ?: $bookmark_title ?? '' }}"> + @if ($errors->has('title')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('title') }} + </p> + @endif + </div> + + <div class="form-group"> + <label for="description">@lang('link.description')</label> + <textarea name="description" id="description" rows="4" class="form-control" + placeholder="@lang('link.description')">{{ old('description') }}</textarea> + + @if ($errors->has('description')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('description') }} + </p> + @endif + </div> + + </div> + <div class="col"> + + <div class="form-group"> + <label for="category_id">@lang('category.category')</label> + <select id="category_id" name="category_id" + class="custom-select{{ $errors->has('category_id') ? ' is-invalid' : '' }}"> + <option value="0">@lang('category.select_category')</option> + @foreach($categories as $category) + <option value="{{ $category->id }}"> + {{ $category->name }} + </option> + @if($category->childCategories) + @foreach($category->childCategories as $child_category) + <option value="{{ $child_category->id }}"> + → {{ $child_category->name }} + </option> + @endforeach + @endif + @endforeach + </select> + + + @if ($errors->has('category_id')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('category_id') }} + </p> + @endif + </div> + + <div class="form-group"> + <label for="tags">@lang('tag.tags')</label> + <input name="tags" id="tags" type="text" placeholder="@lang('tag.tags')" + value="{{ old('tags') }}"> + + @if ($errors->has('url')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('tags') }} + </p> + @endif + </div> + + <div class="form-group"> + <label for="is_private">@lang('linkace.is_private')</label> + <select id="is_private" name="is_private" + class="custom-select{{ $errors->has('is_private') ? ' is-invalid' : '' }}"> + <option value="0"> + @lang('linkace.no') + </option> + <option value="1" @if(usersettings('private_default') === '1') selected @endif> + @lang('linkace.yes') + </option> + </select> + + @if ($errors->has('is_private')) + <p class="invalid-feedback" role="alert"> + {{ $errors->first('is_private') }} + </p> + @endif + </div> + + </div> + </div> + + <div class="mt-3 d-flex align-items-center"> + + @if(!isset($bookmark_url)) + <div class="custom-control custom-checkbox ml-auto mr-4"> + <input class="custom-control-input" type="checkbox" id="reload_view" name="reload_view" + @if(session('reload_view')) checked @endif> + <label class="custom-control-label" for="reload_view"> + @lang('linkace.continue_adding') + </label> + </div> + @endif + + <button type="submit" class="btn btn-primary"> + <i class="fa fa-save fa-mr"></i> @lang('link.add') + </button> + + </div> + + </form> + + </div> +</div> + +@push('scripts') + @include('models.links._tags-js') +@endpush diff --git a/resources/views/models/links/create.blade.php b/resources/views/models/links/create.blade.php index 86065e62..489731f8 100644 --- a/resources/views/models/links/create.blade.php +++ b/resources/views/models/links/create.blade.php @@ -2,142 +2,6 @@ @section('content') - <div class="card"> - <header class="card-header"> - @lang('link.add') - </header> - <div class="card-body"> - - <form action="{{ route('links.store') }}" method="POST"> - @csrf - - <div class="form-group"> - <label class="label" for="url">@lang('link.url')</label> - <input name="url" id="url" type="url" - class="form-control form-control-lg{{ $errors->has('url') ? ' is-invalid' : '' }}" - placeholder="@lang('link.url')" value="{{ old('url') }}" - required autofocus> - @if ($errors->has('url')) - <p class="invalid-feedback" role="alert"> - {{ $errors->first('url') }} - </p> - @endif - </div> - - <div class="row"> - <div class="col"> - - <div class="form-group"> - <label class="label" for="title">@lang('link.title')</label> - <input name="title" id="title" - class="form-control{{ $errors->has('title') ? ' is-invalid' : '' }}" - type="text" placeholder="@lang('link.title')" value="{{ old('title') }}"> - @if ($errors->has('title')) - <p class="invalid-feedback" role="alert"> - {{ $errors->first('title') }} - </p> - @endif - </div> - - <div class="form-group"> - <label for="description">@lang('link.description')</label> - <textarea name="description" id="description" rows="4" class="form-control" - placeholder="@lang('link.description')">{{ old('description') }}</textarea> - - @if ($errors->has('description')) - <p class="invalid-feedback" role="alert"> - {{ $errors->first('description') }} - </p> - @endif - </div> - - </div> - <div class="col"> - - <div class="form-group"> - <label for="category_id">@lang('category.category')</label> - <select id="category_id" name="category_id" - class="custom-select{{ $errors->has('category_id') ? ' is-invalid' : '' }}"> - <option value="0">@lang('category.select_category')</option> - @foreach($categories as $category) - <option value="{{ $category->id }}"> - {{ $category->name }} - </option> - @if($category->childCategories) - @foreach($category->childCategories as $child_category) - <option value="{{ $child_category->id }}"> - → {{ $child_category->name }} - </option> - @endforeach - @endif - @endforeach - </select> - - - @if ($errors->has('category_id')) - <p class="invalid-feedback" role="alert"> - {{ $errors->first('category_id') }} - </p> - @endif - </div> - - <div class="form-group"> - <label for="tags">@lang('tag.tags')</label> - <input name="tags" id="tags" type="text" placeholder="@lang('tag.tags')" - value="{{ old('tags') }}"> - - @if ($errors->has('url')) - <p class="invalid-feedback" role="alert"> - {{ $errors->first('tags') }} - </p> - @endif - </div> - - <div class="form-group"> - <label for="is_private">@lang('linkace.is_private')</label> - <select id="is_private" name="is_private" - class="custom-select{{ $errors->has('is_private') ? ' is-invalid' : '' }}"> - <option value="0"> - @lang('linkace.no') - </option> - <option value="1" @if(usersettings('private_default') === '1') selected @endif> - @lang('linkace.yes') - </option> - </select> - - @if ($errors->has('is_private')) - <p class="invalid-feedback" role="alert"> - {{ $errors->first('is_private') }} - </p> - @endif - </div> - - </div> - </div> - - <div class="mt-3 d-flex align-items-center"> - - <div class="custom-control custom-checkbox ml-auto mr-4"> - <input class="custom-control-input" type="checkbox" id="reload_view" name="reload_view" - @if(session('reload_view')) checked @endif> - <label class="custom-control-label" for="reload_view"> - @lang('linkace.continue_adding') - </label> - </div> - - <button type="submit" class="btn btn-primary"> - <i class="fa fa-save fa-mr"></i> @lang('link.add') - </button> - - </div> - - </form> - - </div> - </div> - - @push('scripts') - @include('models.links._tags-js') - @endpush + @include('models.links._create-form') @endsection diff --git a/routes/web.php b/routes/web.php index 8dccc039..e56aef81 100644 --- a/routes/web.php +++ b/routes/web.php @@ -20,19 +20,25 @@ Route::get('/', function () { })->name('front'); // Authentication Routes -$this->get('login', 'Auth\LoginController@showLoginForm')->name('login'); -$this->post('login', 'Auth\LoginController@login'); -$this->post('logout', 'Auth\LoginController@logout')->name('logout'); +Route::get('login', 'Auth\LoginController@showLoginForm')->name('login'); +Route::post('login', 'Auth\LoginController@login'); +Route::post('logout', 'Auth\LoginController@logout')->name('logout'); // Registration Routes (disabled, use the `artisan registeruser` command) -//$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); -//$this->post('register', 'Auth\RegisterController@register'); +//Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); +//Route::post('register', 'Auth\RegisterController@register'); // Password Reset Routes -$this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); -$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); -$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); -$this->post('password/reset', 'Auth\ResetPasswordController@reset'); +Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); +Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); +Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); +Route::post('password/reset', 'Auth\ResetPasswordController@reset'); + +Route::prefix('bookmarklet')->group(function () { + Route::get('add', 'App\BookmarkletController@getLinkAddForm')->name('bookmarklet-add'); + Route::get('show', 'App\BookmarkletController@getCompleteView')->name('bookmarklet-complete'); + Route::get('login', 'App\BookmarkletController@getLoginForm')->name('bookmarklet-login'); +}); // Model routes Route::group(['middleware' => ['auth']], function () {