]> BookStack Code Mirror - bookstack/commitdiff
Books grid view
authorBharadwaja G <redacted>
Mon, 4 Sep 2017 14:57:52 +0000 (20:27 +0530)
committerBharadwaja G <redacted>
Mon, 4 Sep 2017 14:57:52 +0000 (20:27 +0530)
21 files changed:
app/Book.php
app/Http/Controllers/BookController.php
app/User.php
bootstrap/autoload.php
config/app.php
public/book_default_cover.png [new file with mode: 0644]
public/default.png [deleted file]
resources/assets/js/global.js
resources/assets/sass/styles.scss
resources/lang/de/settings.php
resources/lang/en/settings.php
resources/lang/es/settings.php
resources/lang/fr/settings.php
resources/lang/nl/settings.php
resources/lang/pt_BR/settings.php
resources/lang/sk/settings.php
resources/views/books/form.blade.php
resources/views/books/grid-item.blade.php
resources/views/books/index.blade.php
resources/views/users/edit.blade.php
tests/UserProfileTest.php

index 538505266530272fd08a4f4d33fa2e8adc59be62..fed602332bb18c41489f0008793bf3b1228eb24d 100644 (file)
@@ -3,7 +3,7 @@
 class Book extends Entity
 {
 
-    protected $fillable = ['name', 'description', 'image'];
+    protected $fillable = ['name', 'description', 'image_id'];
 
     /**
      * Get the url for this book.
@@ -17,30 +17,45 @@ class Book extends Entity
         }
         return baseUrl('/books/' . urlencode($this->slug));
     }
-
-    public function getBookCover()
+    
+    /**
+     * Returns book cover image, if book cover not exists return default cover image.
+     * @param int $height - Height of the image
+     * @param type $width - Width of the image
+     * @return type string
+     */
+    public function getBookCover($height = 170, $width = 300)
     {
-        $default = baseUrl('/default.png');
-        $image = $this->image;
+        $default = baseUrl('/book_default_cover.png');
+        $image = $this->image_id;
         if ($image === 0 || $image === '0' || $image === null) 
             return $default;
         try {
-            $cover = $this->cover ? baseUrl($this->cover->getThumb(120, 192, false)) : $default;
+            $cover = $this->cover ? baseUrl($this->cover->getThumb($width, $height, false)) : $default;
         } catch (\Exception $err) {
             $cover = $default;
         }
         return $cover;
     }
-
+    
+    /**
+     * Get an excerpt of this book's name to the specified length or less.
+     * @param int $length
+     * @return string
+     */
     public function getHeadingExcerpt($length = 35)
     {
         $bookHeading = $this->name;
         return strlen($bookHeading) > $length ? substr($bookHeading, 0, $length-3) . '...' : $bookHeading;
     }
     
+    /**
+     * Get the cover image of the book
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     */
     public function cover()
     {
-        return $this->belongsTo(Image::class, 'image');
+        return $this->belongsTo(Image::class, 'image_id');
     }
     /*
      * Get the edit url for this book.
@@ -88,5 +103,14 @@ class Book extends Entity
     {
         return "'BookStack\\\\Book' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
     }
+    
+    /**
+     * Get the user that created the page revision
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     */
+    public function createdBy()
+    {   
+        return $this->belongsTo(User::class, 'created_by');
+    }
 
 }
index d515d83a3ac921a19fa98e6752aa1e61222eabe5..b6175d8591e5ec6ee1a586c21b2e2517f9c19755 100644 (file)
@@ -40,12 +40,14 @@ class BookController extends Controller
         $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
         $popular = $this->entityRepo->getPopular('book', 4, 0);
         $new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
+        $booksViewType = $this->currentUser->books_view_type;
         $this->setPageTitle('Books');
         return view('books/index', [
             'books' => $books,
             'recents' => $recents,
             'popular' => $popular,
-            'new' => $new
+            'new' => $new, 
+            'booksViewType' => $booksViewType
         ]);
     }
 
index 703322cbd269c5dfb80fcf164e5900f10be60d6d..264723be9c615a8477ea47fd1be670c3756cf405 100644 (file)
@@ -22,7 +22,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
      * The attributes that are mass assignable.
      * @var array
      */
-    protected $fillable = ['name', 'email', 'image_id', 'books_display' ];
+    protected $fillable = ['name', 'email', 'image_id', 'books_view_type' ];
 
     /**
      * The attributes excluded from the model's JSON form.
index 29f66ace473825fd1be89f60468ce572eca9ac46..90a8f4e23c2b1a96baa8b9c03924726be0462cfc 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 define('LARAVEL_START', microtime(true));
+ini_set('xdebug.max_nesting_level', 120);
 
 /*
 |--------------------------------------------------------------------------
index a390eaf83a16eb20360f47e30be0d6090ad73eea..23aad7d487811ab7d0861b9a26de4373da0455fb 100644 (file)
@@ -18,7 +18,7 @@ return [
     |
     */
 
-    'debug' => env('APP_DEBUG', false),
+    'debug' => env('APP_DEBUG', true),
 
     /*
     |--------------------------------------------------------------------------
diff --git a/public/book_default_cover.png b/public/book_default_cover.png
new file mode 100644 (file)
index 0000000..7b6c995
Binary files /dev/null and b/public/book_default_cover.png differ
diff --git a/public/default.png b/public/default.png
deleted file mode 100644 (file)
index 1147e23..0000000
Binary files a/public/default.png and /dev/null differ
index 176aff7330014f77e655e57c5375e29189606be5..26dbf9a5eaab58859b777b1ddc892a55963ceb6a 100644 (file)
@@ -102,64 +102,6 @@ jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) {
     };
 });
 
-// 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);
@@ -172,14 +114,6 @@ $(document).ready(function(){
    });
 });
 
-// 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
index b0b41afc77a4eb88e5a47d3bb94a204413afba6e..04aa0d968886b6e81ba048f9890673fd1d47c0a4 100644 (file)
@@ -228,39 +228,73 @@ $btt-size: 40px;
   }
 }
 
+// styles for Books grid view
+.cover {
+    width: 290px;
+    border-radius: 3px;
+  }
 
+.featured-image-container {
+    position: relative;
+    overflow: hidden;
+    background: #F2F2F2;
+    border: 1px solid #ddd;
+    border-bottom: 0px;
+}
 
-.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;
-  }
+.featured-image-container img {
+    display: block;
+    max-width: 100%;
+    height: auto;
+    -webkit-transition: all .5s ease;
+    -moz-transition: all .5s ease;
+    -ms-transition: all .5s ease;
+    -o-transition: all .5s ease;
+    transition: all .5s ease;
 }
 
-.gallery-image {
-  margin-top: 5%;
-  text-align: center;
-  img {
-    border-radius: 3px;
-  }
+.book-content {
+    padding: 30px;
+    border: 1px solid #ddd;
+    border-top: 0px;
+    border-bottom-width: 2px;
+}
+.book-content h2 {
+    font-size: 1.5em;
+    line-height: 1.2;
+    margin: 0 0 10px;
 }
 
-.cover {
-    height: 192px;
-    width: 120px;
-    border-radius: 3px;
-  }
\ No newline at end of file
+.book-content  h2 a {
+    display: block;
+    color: #009688;;
+    text-decoration: none;
+}
+
+.book-content p {
+    font-size: .85em;
+    margin: 0 0 10px;
+    line-height: 1.6em;
+}
+
+.featured-image-container img:hover {
+    -webkit-transform: scale(1.15);
+    -moz-transform: scale(1.15);
+    -ms-transform: scale(1.15);
+    -o-transform: scale(1.15);
+    transform: scale(1.15);
+    opacity: .5;
+}
+.books-grid-div {
+    margin-bottom : 20px;
+}
+
+@media (min-width:992px){
+    .row.auto-clear .col-md-4:nth-child(3n+1){clear:left;}
+}
+@media (min-width:992px){
+    .row.auto-clear .col-md-4:nth-child(3n+1){clear:left;}
+}
+@media (max-width:991px){
+    .row.auto-clear .col-xs-6:nth-child(2n+1){clear:left;}
+}
\ No newline at end of file
index 598f9f6631f08f406eadbfdfc9078dafe5cc1811..2da517292289452e706a5330b3b955adecda90df 100644 (file)
@@ -96,7 +96,7 @@ return [
     '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_books_view_type' => 'Bevorzugtes Display-Layout für Bücher',
     'users_edit' => 'Benutzer bearbeiten',
     'users_edit_profile' => 'Profil bearbeiten',
     'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
index 14b5371ffa4d46ec0d4e6598bbfc11e80d1b35a7..644ca3a31497491fd20e4906a96ceaddb6221fda 100644 (file)
@@ -94,7 +94,7 @@ return [
     '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_books_view_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.',
index 4651247b9df46c3f188bb0112944e584d3b51e2e..9535d3f455146d520e5b060685857217ef912a0b 100644 (file)
@@ -91,7 +91,7 @@ return [
     '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_books_view_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.',
index 5516e66a4609942afd5c82564f85d898b3b4eb94..399afdc9ab3b907bab6f464ccf3c5650aff1dfae 100644 (file)
@@ -91,7 +91,7 @@ return [
     '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_books_view_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.',
index 0323d87d4c6f82fc3306007ff23d8d37dd17ed04..7b8adf602e7ad0c00176ff9028a882ad45fcf506 100644 (file)
@@ -91,7 +91,7 @@ return [
     '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_books_view_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.',
index 6c8ff3039ec9fc5aba8e9f714cdc4be35ac27977..8ebd2b728e6a91c88b85e49d895618e42e11a758 100644 (file)
@@ -91,7 +91,7 @@ return [
     '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_books_view_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.',
index 8a8d93e59883af71b8f782e5b8e73d995afd6c69..4438f8038750fd9b7895eb62eae81db3c054279f 100644 (file)
@@ -91,7 +91,7 @@ return [
     '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_books_view_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.',
index fc30093d2e05fc6e5d0b553a5ba74fb38c2be264..6356da72b1f79d625bae3ea1e6a7853822496e7c 100644 (file)
         <p class="small">{{ trans('common.cover_image_description') }}</p>
 
         @include('components.image-picker', [
-            'resizeHeight' => '192',
-            'resizeWidth' => '120',
+            'resizeHeight' => '512',
+            'resizeWidth' => '512',
             'showRemove' => true,
             'defaultImage' => baseUrl('/default.png'),
-            'currentImage' => @isset($model) ? $model->getBookCover(80) : baseUrl('/default.png') ,
-            'currentId' => @isset($model) ? $model->image : 0,
-            'name' => 'image',
+            'currentImage' => @isset($model) ? $model->getBookCover() : baseUrl('/default.png') ,
+            'currentId' => @isset($model) ? $model->image_id : 0,
+            'name' => 'image_id',
             'imageClass' => 'cover'
         ])
 </div>
index 3c0ec1ef750f1a7746d25b216b898930f2ebcaba..74801ec845a83f5c9f34d1c9564a1711d702ffe4 100644 (file)
@@ -1,19 +1,18 @@
-<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}}">
+<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4 books-grid-div"  data-entity-type="book" data-entity-id="{{$book->id}}">
+    <div class="featured-image-container">
+        <a href="{{$book->getUrl()}}" title="{{$book->name}}">
+            <img width="1600" height="900" 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 class="book-content">
+        <h2><a href="{{$book->getUrl()}}" title="{{$book->name}}" > {{$book->getHeadingExcerpt()}} </a></h2>
+        @if(isset($book->searchSnippet))
+            <p >{{!! $book->searchSnippet !!}}</p>
+        @else
+            <p >{{ $book->getExcerpt(130) }}</p>
+        @endif
+        <div >
+            <span>@include('partials.entity-meta', ['entity' => $book])</span>
+        </div>
+    </div>
 </div>
\ No newline at end of file
index c22b4591f01c43f3efc126cf30dc40a8acc4e1d7..d392af045aab36ee3a99e03a21a8a2a9200517d5 100644 (file)
 @stop
 
 @section('body')
-
-    <div class="container small" ng-non-bindable>
+    @if($booksViewType === 'list')
+        <div class="container small" ng-non-bindable>
+    @else
+        <div class="container" ng-non-bindable>
+    @endif
         <h1>{{ trans('entities.books') }}</h1>
         @if(count($books) > 0)
-            @foreach($books as $book)
-                @include('books/list-item', ['book' => $book])
-                <hr>
-            @endforeach
-            {!! $books->render() !!}
+            @if($booksViewType === 'list')
+                @foreach($books as $book)
+                    @include('books/list-item', ['book' => $book])
+                    <hr>
+                @endforeach
+                {!! $books->render() !!}
+            @else 
+             <div class="row auto-clear">
+                    @foreach($books as $key => $book)
+                            @include('books/grid-item', ['book' => $book])
+                    @endforeach
+                <div class="col-xs-12">
+                    {!! $books->render() !!}
+                </div>
+             </div>
+            @endif
         @else
             <p class="text-muted">{{ trans('entities.books_empty') }}</p>
             @if(userCan('books-create-all'))
@@ -55,5 +69,4 @@
             @endif
         @endif
     </div>
-
 @stop
\ No newline at end of file
index fc75593b824b7221e869437727c59768a1d42d46..00db029070f32657f626b4679e0778db83b70070 100644 (file)
                                     @endforeach
                                 </select>
                             </div>
+                            <div class="form-group">
+                                <label for="books-view-type">{{ trans('settings.users_books_view_type') }}</label>
+                                <select name="books_view_type" id="books-view-type">
+                                    <option @if($user->books_view_type === 'grid') selected @endif value="grid">Grid</option>
+                                    <option @if($user->books_view_type === 'list') selected @endif value="list">List</option>
+                                </select>
+                            </div>
                         </div>
                     </div>
                     <div class="form-group text-right">
index e0c87d992abdb6f6a301d55dabd69f55ab0226cb..eabfce00409ea5c569c44634a5989d8fa94e395d 100644 (file)
@@ -95,26 +95,26 @@ class UserProfileTest extends BrowserKitTest
             ->see('cannot delete the guest user');
     }
 
-    public function test_books_display_is_list()
+    public function test_books_view_is_list()
     {
         $editor = $this->getEditor([
-          'books_display' => 'list'
+          'books_view_type' => 'list'
         ]);
 
         $this->actingAs($editor)
             ->visit('/books')
-            ->pageNotHasElement('.gallery-item')
+            ->pageNotHasElement('.featured-image-container')
             ->pageHasElement('.entity-list-item');
     }
 
-    public function test_books_display_is_grid()
+    public function test_books_view_is_grid()
     {
         $editor = $this->getEditor([
-          'books_display' => 'grid'
+          'books_view_type' => 'grid'
         ]);
 
         $this->actingAs($editor)
             ->visit('/books')
-            ->pageHasElement('.gallery-item');
+            ->pageHasElement('.featured-image-container');
     }
 }