class Book extends Entity
{
- protected $fillable = ['name', 'description'];
+ protected $fillable = ['name', 'description', 'image'];
/**
* Get the url for this book.
return baseUrl('/books/' . urlencode($this->slug));
}
+ public function getBookCover()
+ {
+ $default = baseUrl('/default.png');
+ $image = $this->image;
+ if ($image === 0 || $image === '0' || $image === null)
+ return $default;
+ try {
+ $cover = $this->cover ? baseUrl($this->cover->getThumb(120, 192, false)) : $default;
+ } catch (\Exception $err) {
+ $cover = $default;
+ }
+ return $cover;
+ }
+
+ public function getHeadingExcerpt($length = 35)
+ {
+ $bookHeading = $this->name;
+ return strlen($bookHeading) > $length ? substr($bookHeading, 0, $length-3) . '...' : $bookHeading;
+ }
+
+ public function cover()
+ {
+ return $this->belongsTo(Image::class, 'image');
+ }
/*
* Get the edit url for this book.
* @return string
*/
public function index()
{
- $books = $this->entityRepo->getAllPaginated('book', 10);
+ $books = $this->entityRepo->getAllPaginated('book', 16);
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
- $popular = $this->entityRepo->getPopular('book', 4, 0);
+ $popular = $this->entityRepo->getPopular('book', 3, 0);
+ $books_display = $this->currentUser->books_display;
$this->setPageTitle('Books');
- return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular]);
+ return view('books/index', ['books' => $books, 'recents' => $recents, 'popular' => $popular, 'books_display' => $books_display] );
}
/**
'name' => 'required|string|max:255',
'description' => 'string|max:1000'
]);
- $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
- Activity::add($book, 'book_update', $book->id);
- return redirect($book->getUrl());
+ $book = $this->entityRepo->updateFromInput('book', $book, $request->all());
+ Activity::add($book, 'book_update', $book->id);
+ return redirect($book->getUrl());
}
/**
* The attributes that are mass assignable.
* @var array
*/
- protected $fillable = ['name', 'email', 'image_id'];
+ protected $fillable = ['name', 'email', 'image_id', 'books_display' ];
/**
* The attributes excluded from the model's JSON form.
--- /dev/null
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddCoverImageDisplay extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->string('books_display',10)->default('grid');
+ });
+
+ Schema::table('books', function (Blueprint $table) {
+ $table->integer('image');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn('books_display');
+ });
+
+ Schema::table('books', function (Blueprint $table) {
+ $table->dropColumn('image');
+ });
+ }
+}
};
});
+// Global jQuery Elements
+let notifications = $('.notification');
+let successNotification = notifications.filter('.pos');
+let errorNotification = notifications.filter('.neg');
+let warningNotification = notifications.filter('.warning');
+// Notification Events
+window.Events.listen('success', function (text) {
+ successNotification.hide();
+ successNotification.find('span').text(text);
+ setTimeout(() => {
+ successNotification.show();
+ }, 1);
+});
+window.Events.listen('warning', function (text) {
+ warningNotification.find('span').text(text);
+ warningNotification.show();
+});
+window.Events.listen('error', function (text) {
+ errorNotification.find('span').text(text);
+ errorNotification.show();
+});
+
+// Notification hiding
+notifications.click(function () {
+ $(this).fadeOut(100);
+});
+
+// Chapter page list toggles
+$('.chapter-toggle').click(function (e) {
+ e.preventDefault();
+ $(this).toggleClass('open');
+ $(this).closest('.chapter').find('.inset-list').slideToggle(180);
+});
+
+// Back to top button
+$('#back-to-top').click(function() {
+ $('#header').smoothScrollTo();
+});
+let scrollTopShowing = false;
+let scrollTop = document.getElementById('back-to-top');
+let scrollTopBreakpoint = 1200;
+window.addEventListener('scroll', function() {
+ let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
+ if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
+ scrollTop.style.display = 'block';
+ scrollTopShowing = true;
+ setTimeout(() => {
+ scrollTop.style.opacity = 0.4;
+ }, 1);
+ } else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) {
+ scrollTop.style.opacity = 0;
+ scrollTopShowing = false;
+ setTimeout(() => {
+ scrollTop.style.display = 'none';
+ }, 500);
+ }
+});
+
+// Common jQuery actions
+$('[data-action="expand-entity-list-details"]').click(function() {
+ $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
+});
+
+// Toggle thumbnail::hide image and reduce grid size
+$(document).ready(function(){
+ $('[data-action="expand-thumbnail"]').click(function(){
+ $('.gallery-item').toggleClass("collapse").find('img').slideToggle(50);
+ });
+});
+
+// Popup close
+$('.popup-close').click(function() {
+ $(this).closest('.overlay').fadeOut(240);
+});
+$('.overlay').click(function(event) {
+ if (!$(event.target).hasClass('overlay')) return;
+ $(this).fadeOut(240);
+});
+
// Detect IE for css
if(navigator.userAgent.indexOf('MSIE')!==-1
|| navigator.appVersion.indexOf('Trident/') > 0
+.gallery-item {
+ margin-bottom: 32px;
+ height: 350px;
+ overflow: hidden;
+ border: 1px solid #ccc;
+ h4 {
+ font-size: 1.2em;
+ text-align: center;
+ height: 55px;
+ padding: 0px 12px;
+ }
+ p {
+ font-size: 0.8em;
+ text-align: center;
+ padding: 0px 12px;
+ }
+ &.collapse {
+ height: 150px;
+ }
+}
+
+.gallery-image {
+ margin-top: 5%;
+ text-align: center;
+ img {
+ border-radius: 3px;
+ }
+}
+
+.cover {
+ height: 192px;
+ width: 120px;
+ border-radius: 3px;
+ }
\ No newline at end of file
'name' => 'Name',
'description' => 'Beschreibung',
'role' => 'Rolle',
+ 'cover_image' => 'Titelbild',
+ 'cover_image_description' => 'Das Bild sollte in einem Verhältnis von Höhe zu Breite von 1.6: 1 sein.',
/**
* Actions
'no_items' => 'Keine Einträge gefunden.',
'back_to_top' => 'nach oben',
'toggle_details' => 'Details zeigen/verstecken',
-
+ 'toggle_thumbnails' => 'Thumbnails zeigen/verstecken',
/**
* Header
*/
'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.',
'users_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?',
'users_delete_success' => 'Benutzer erfolgreich gelöscht.',
+ 'users_books_display_type' => 'Bevorzugtes Display-Layout für Bücher',
'users_edit' => 'Benutzer bearbeiten',
'users_edit_profile' => 'Profil bearbeiten',
'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
'name' => 'Name',
'description' => 'Description',
'role' => 'Role',
-
+ 'cover_image' => 'Cover image',
+ 'cover_image_description' => 'The image should be in a height/width ratio of 1.6:1.',
/**
* Actions
*/
'no_items' => 'No items available',
'back_to_top' => 'Back to top',
'toggle_details' => 'Toggle Details',
+ 'toggle_thumbnails' => 'Toggle Thumbnails',
/**
* Header
'users_external_auth_id' => 'External Authentication ID',
'users_password_warning' => 'Only fill the below if you would like to change your password:',
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
+ 'users_books_display_type' => 'Preferred layout for books viewing',
'users_delete' => 'Delete User',
'users_delete_named' => 'Delete user :userName',
'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.',
'name' => 'Nombre',
'description' => 'Descripción',
'role' => 'Rol',
-
+ 'cover_image' => 'Imagen de portada',
+ 'cover_image_description' => 'La imagen debe estar en una relación altura / anchura de 1.6: 1.',
/**
* Actions
*/
'no_items' => 'No hay items disponibles',
'back_to_top' => 'Volver arriba',
'toggle_details' => 'Alternar detalles',
+ 'toggle_thumbnails' => 'Alternar miniaturas',
/**
* Header
'users_external_auth_id' => 'ID externo de autenticación',
'users_password_warning' => 'Solo rellene a continuación si desea cambiar su password:',
'users_system_public' => 'Este usuario representa cualquier usuario invitado que visita la aplicación. No puede utilizarse para hacer login sio que es asignado automáticamente.',
+ 'users_books_display_type' => 'Diseño de pantalla preferido para libros',
'users_delete' => 'Borrar usuario',
'users_delete_named' => 'Borrar usuario :userName',
'users_delete_warning' => 'Se borrará completamente el usuario con el nombre \':userName\' del sistema.',
'name' => 'Nom',
'description' => 'Description',
'role' => 'Rôle',
-
+ 'cover_image' => 'Image de couverture',
+ 'cover_image_description' => 'L\'image devrait avoir un rapport d\'aspect 1.6: 1',
/**
* Actions
*/
'no_items' => 'Aucun élément',
'back_to_top' => 'Retour en haut',
'toggle_details' => 'Afficher les détails',
+ 'toggle_thumbnails' => 'Afficher les vignettes',
/**
* Header
'users_external_auth_id' => 'Identifiant d\'authentification externe',
'users_password_warning' => 'Remplissez ce fomulaire uniquement si vous souhaitez changer de mot de passe:',
'users_system_public' => 'Cet utilisateur représente les invités visitant votre instance. Il est assigné automatiquement aux invités.',
+ 'users_books_display_type' => 'Disposition d\'affichage préférée pour les livres',
'users_delete' => 'Supprimer un utilisateur',
'users_delete_named' => 'Supprimer l\'utilisateur :userName',
'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
'name' => 'Naam',
'description' => 'Beschrijving',
'role' => 'Rol',
-
+ 'cover_image' => 'Omslagfoto',
+ 'cover_image_description' => 'De afbeelding moet in een hoogte / breedte verhouding van 1.6: 1 zijn.',
/**
* Actions
*/
'no_items' => 'Geen items beschikbaar',
'back_to_top' => 'Terug naar boven',
'toggle_details' => 'Details Weergeven',
+ 'toggle_thumbnails' => 'Thumbnails Weergeven',
/**
* Header
'users_external_auth_id' => 'External Authentication ID',
'users_password_warning' => 'Vul onderstaande formulier alleen in als je het wachtwoord wilt aanpassen:',
'users_system_public' => 'De eigenschappen van deze gebruiker worden voor elke gastbezoeker gebruikt. Er kan niet mee ingelogd worden en wordt automatisch toegewezen.',
+ 'users_books_display_type' => 'Voorkeursuitleg voor het weergeven van boeken',
'users_delete' => 'Verwijder gebruiker',
'users_delete_named' => 'Verwijder gebruiker :userName',
'users_delete_warning' => 'Dit zal de gebruiker \':userName\' volledig uit het systeem verwijderen.',
'name' => 'Nome',
'description' => 'Descrição',
'role' => 'Regra',
-
+ 'cover_image' => 'Imagem de capa',
+ 'cover_image_description' => 'A imagem deve estar em uma relação de aspecto 1.6: 1.',
/**
* Actions
*/
'no_items' => 'Nenhum item disponível',
'back_to_top' => 'Voltar ao topo',
'toggle_details' => 'Alternar Detalhes',
+ 'toggle_thumbnails' => 'Alternar Miniaturas',
/**
* Header
'users_external_auth_id' => 'ID de Autenticação Externa',
'users_password_warning' => 'Preencha os dados abaixo caso queira modificar a sua senha:',
'users_system_public' => 'Esse usuário representa quaisquer convidados que visitam o aplicativo. Ele não pode ser usado para login.',
+ 'users_books_display_type' => 'Layout preferido para mostrar livros',
'users_delete' => 'Excluir Usuário',
'users_delete_named' => 'Excluir :userName',
'users_delete_warning' => 'A ação vai excluir completamente o usuário de nome \':userName\' do sistema.',
'name' => 'Meno',
'description' => 'Popis',
'role' => 'Rola',
-
+ 'cover_image' => 'Obal knihy',
+ 'cover_image_description' => 'Obraz by mal mať pomer strán 1.6: 1.',
/**
* Actions
*/
'no_items' => 'Žiadne položky nie sú dostupné',
'back_to_top' => 'Späť nahor',
'toggle_details' => 'Prepnúť detaily',
+ 'toggle_thumbnails' => 'Prepnúť náhľady',
/**
* Header
'users_external_auth_id' => 'Externé autentifikačné ID',
'users_password_warning' => 'Pole nižšie vyplňte iba ak chcete zmeniť heslo:',
'users_system_public' => 'Tento účet reprezentuje každého hosťovského používateľa, ktorý navštívi Vašu inštanciu. Nedá sa pomocou neho prihlásiť a je priradený automaticky.',
+ 'users_books_display_type' => 'Preferované rozloženie pre prezeranie kníh',
'users_delete' => 'Zmazať používateľa',
'users_delete_named' => 'Zmazať používateľa :userName',
'users_delete_warning' => ' Toto úplne odstráni používateľa menom \':userName\' zo systému.',
<div class="container small" ng-non-bindable>
<h1>{{ trans('entities.books_create') }}</h1>
- <form action="{{ baseUrl("/books") }}" method="POST">
+ <form action="{{ baseUrl("/books") }}" method="POST" enctype="multipart/form-data">
@include('books/form')
</form>
</div>
-
+<p class="margin-top large"><br></p>
+ @include('components.image-manager', ['imageType' => 'cover'])
@stop
\ No newline at end of file
@include('books/form', ['model' => $book])
</form>
</div>
-
+@include('components.image-manager', ['imageType' => 'cover'])
@stop
\ No newline at end of file
<label for="description">{{ trans('common.description') }}</label>
@include('form/textarea', ['name' => 'description'])
</div>
+<div class="form-group" id="logo-control">
+ <label for="user-avatar">{{ trans('common.cover_image') }}</label>
+ <p class="small">{{ trans('common.cover_image_description') }}</p>
+ @include('components.image-picker', [
+ 'resizeHeight' => '192',
+ 'resizeWidth' => '120',
+ 'showRemove' => true,
+ 'defaultImage' => baseUrl('/default.png'),
+ 'currentImage' => @isset($model) ? $model->getBookCover(80) : baseUrl('/default.png') ,
+ 'currentId' => @isset($model) ? $model->image : 0,
+ 'name' => 'image',
+ 'imageClass' => 'cover'
+ ])
+</div>
<div class="form-group">
<a href="{{ isset($book) ? $book->getUrl() : baseUrl('/books') }}" class="button muted">{{ trans('common.cancel') }}</a>
<button type="submit" class="button pos">{{ trans('entities.books_save') }}</button>
--- /dev/null
+<div class="col-xs-6 col-sm-4 col-md-4 col-lg-3" data-entity-type="book" data-entity-id="{{$book->id}}">
+ <div class="gallery-item">
+ <h4>
+ <a class="text-book entity-list-item-link" href="{{$book->getUrl()}}" title="{{$book->name}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->getHeadingExcerpt()}}</span>
+ <br>
+ </a>
+ </h4>
+ <div class="gallery-image">
+ <a class="text-book entity-list-item-link" href="{{$book->getUrl()}}">
+ <img src="{{$book->getBookCover()}}" alt="{{$book->name}}">
+ </a>
+ </div>
+ @if(isset($book->searchSnippet))
+ <p class="text-muted">{!! $book->searchSnippet !!}</p>
+ @else
+ <p class="text-muted">{{ $book->getExcerpt(80) }}</p>
+ @endif
+</div>
+</div>
\ No newline at end of file
<div class="faded-small toolbar">
<div class="container">
<div class="row">
- <div class="col-xs-1"></div>
- <div class="col-xs-11 faded">
- <div class="action-buttons">
+ <div class="col-xs-12 faded">
+ <div class="action-buttons text-left">
+ <a data-action="expand-thumbnail" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>{{ trans('common.toggle_thumbnails') }}</a>
@if($currentUser->can('book-create-all'))
<a href="{{ baseUrl("/books/create") }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.books_create') }}</a>
@endif
- </div>
+ </div>
</div>
</div>
</div>
<div class="container" ng-non-bindable>
<div class="row">
- <div class="col-sm-7">
+ <div class="col-xs-12 col-sm-12 col-md-9">
<h1>{{ trans('entities.books') }}</h1>
@if(count($books) > 0)
- @foreach($books as $book)
- @include('books/list-item', ['book' => $book])
- <hr>
- @endforeach
- {!! $books->render() !!}
+ @if($books_display=='grid')
+ <div class="row">
+ @foreach($books as $book)
+ @include('books/grid-item', ['book' => $book])
+ @endforeach
+ <div class="col-xs-12">
+ {!! $books->render() !!}
+ </div>
+ </div>
+ @else
+ @foreach($books as $book)
+ @include('books/list-item', ['book' => $book])
+ @endforeach
+ {!! $books->render() !!}
+ @endif
@else
<p class="text-muted">{{ trans('entities.books_empty') }}</p>
@if(userCan('books-create-all'))
@endif
@endif
</div>
- <div class="col-sm-4 col-sm-offset-1">
+ <div class="col-xs-12 col-sm-12 col-md-3">
<div id="recents">
@if($recents)
<div class="margin-top"> </div>
<button class="text-button neg" data-action="remove-image" type="button">{{ trans('common.remove') }}</button>
@endif
- <input type="hidden" name="{{$name}}" id="{{$name}}" value="{{ isset($currentId) && ($currentId !== '' && $currentId !== false) ? $currentId : $currentImage}}">
+ <input type="hidden" name="{{$name}}" id="{{$name}}" value="{{ isset($currentId) && ($currentId !== 0 && $currentId !== false) ? $currentId : $currentImage}}">
</div>
<script>
@endforeach
</select>
</div>
+ <div class="form-group">
+ <label for="books_display">{{ trans('settings.users_books_display_type') }}</label>
+ <select name="books_display" id="books_display">
+ <option @if($user->books_display === 'grid') selected @endif value="grid">Grid</option>
+ <option @if($user->books_display === 'list') selected @endif value="list">List</option>
+ </select>
+ </div>
</div>
</div>
<div class="form-group">
->visit('/')
->seeInElement('#recently-updated-pages', $page->name);
}
-
- public function test_recently_created_pages_on_home()
- {
- $entityChain = $this->createEntityChainBelongingToUser($this->getEditor());
- $this->asAdmin()->visit('/')
- ->seeInElement('#recently-created-pages', $entityChain['page']->name);
- }
-
}
->seePageIs('/settings/users/' . $guestUser->id)
->see('cannot delete the guest user');
}
-
+
+ public function test_books_display_is_list()
+ {
+ $editor = $this->getEditor([
+ 'books_display' => 'list'
+ ]);
+
+ $this->actingAs($editor)
+ ->visit('/books')
+ ->pageNotHasElement('.gallery-item')
+ ->pageHasElement('.entity-list-item');
+ }
+
+ public function test_books_display_is_grid()
+ {
+ $editor = $this->getEditor([
+ 'books_display' => 'grid'
+ ]);
+
+ $this->actingAs($editor)
+ ->visit('/books')
+ ->pageHasElement('.gallery-item');
+ }
}