diff --git a/app/BookShelf.php b/app/Bookshelf.php similarity index 88% rename from app/BookShelf.php rename to app/Bookshelf.php index 47f873bcd..1e33e31f6 100644 --- a/app/BookShelf.php +++ b/app/Bookshelf.php @@ -1,7 +1,7 @@ belongsToMany(Book::class, 'bookshelves_books', 'bookshelf_id', 'book_id'); + } + /** * Get the url for this bookshelf. * @param string|bool $path diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php new file mode 100644 index 000000000..a1c56f29a --- /dev/null +++ b/app/Http/Controllers/BookshelfController.php @@ -0,0 +1,341 @@ +entityRepo = $entityRepo; + $this->userRepo = $userRepo; + $this->exportService = $exportService; + parent::__construct(); + } + + /** + * Display a listing of the book. + * @return Response + */ + public function index() + { + $shelves = $this->entityRepo->getAllPaginated('bookshelf', 18); + $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false; + $popular = $this->entityRepo->getPopular('bookshelf', 4, 0); + $new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0); + $shelvesViewType = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid')); + $this->setPageTitle(trans('entities.shelves')); + return view('shelves/index', [ + 'shelves' => $shelves, + 'recents' => $recents, + 'popular' => $popular, + 'new' => $new, + 'shelvesViewType' => $shelvesViewType + ]); + } + + /** + * Show the form for creating a new bookshelf. + * @return Response + */ + public function create() + { + $this->checkPermission('bookshelf-create-all'); + $this->setPageTitle(trans('entities.shelves_create')); + $books = $this->entityRepo->getAll('book', false, 'update'); + return view('shelves/create', ['books' => $books]); + } + + /** + * Store a newly created book in storage. + * @param Request $request + * @return Response + */ + public function store(Request $request) + { + $this->checkPermission('bookshelf-create-all'); + $this->validate($request, [ + 'name' => 'required|string|max:255', + 'description' => 'string|max:1000', + ]); + + $bookshelf = $this->entityRepo->createFromInput('bookshelf', $request->all()); + $this->entityRepo->updateShelfBooks($bookshelf, $request->get('books', '')); + Activity::add($bookshelf, 'bookshelf_create'); + + return redirect($bookshelf->getUrl()); + } + +// +// /** +// * Display the specified book. +// * @param $slug +// * @return Response +// */ +// public function show($slug) +// { +// $book = $this->entityRepo->getBySlug('book', $slug); +// $this->checkOwnablePermission('book-view', $book); +// $bookChildren = $this->entityRepo->getBookChildren($book); +// Views::add($book); +// $this->setPageTitle($book->getShortName()); +// return view('books/show', [ +// 'book' => $book, +// 'current' => $book, +// 'bookChildren' => $bookChildren, +// 'activity' => Activity::entityActivity($book, 20, 0) +// ]); +// } +// +// /** +// * Show the form for editing the specified book. +// * @param $slug +// * @return Response +// */ +// public function edit($slug) +// { +// $book = $this->entityRepo->getBySlug('book', $slug); +// $this->checkOwnablePermission('book-update', $book); +// $this->setPageTitle(trans('entities.books_edit_named', ['bookName'=>$book->getShortName()])); +// return view('books/edit', ['book' => $book, 'current' => $book]); +// } +// +// /** +// * Update the specified book in storage. +// * @param Request $request +// * @param $slug +// * @return Response +// */ +// public function update(Request $request, $slug) +// { +// $book = $this->entityRepo->getBySlug('book', $slug); +// $this->checkOwnablePermission('book-update', $book); +// $this->validate($request, [ +// '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()); +// } +// +// /** +// * Shows the page to confirm deletion +// * @param $bookSlug +// * @return \Illuminate\View\View +// */ +// public function showDelete($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $this->checkOwnablePermission('book-delete', $book); +// $this->setPageTitle(trans('entities.books_delete_named', ['bookName'=>$book->getShortName()])); +// return view('books/delete', ['book' => $book, 'current' => $book]); +// } +// +// /** +// * Shows the view which allows pages to be re-ordered and sorted. +// * @param string $bookSlug +// * @return \Illuminate\View\View +// */ +// public function sort($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $this->checkOwnablePermission('book-update', $book); +// $bookChildren = $this->entityRepo->getBookChildren($book, true); +// $books = $this->entityRepo->getAll('book', false, 'update'); +// $this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()])); +// return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]); +// } +// +// /** +// * Shows the sort box for a single book. +// * Used via AJAX when loading in extra books to a sort. +// * @param $bookSlug +// * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View +// */ +// public function getSortItem($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $bookChildren = $this->entityRepo->getBookChildren($book); +// return view('books/sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); +// } +// +// /** +// * Saves an array of sort mapping to pages and chapters. +// * @param string $bookSlug +// * @param Request $request +// * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector +// */ +// public function saveSort($bookSlug, Request $request) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $this->checkOwnablePermission('book-update', $book); +// +// // Return if no map sent +// if (!$request->filled('sort-tree')) { +// return redirect($book->getUrl()); +// } +// +// // Sort pages and chapters +// $sortMap = collect(json_decode($request->get('sort-tree'))); +// $bookIdsInvolved = collect([$book->id]); +// +// // Load models into map +// $sortMap->each(function ($mapItem) use ($bookIdsInvolved) { +// $mapItem->type = ($mapItem->type === 'page' ? 'page' : 'chapter'); +// $mapItem->model = $this->entityRepo->getById($mapItem->type, $mapItem->id); +// // Store source and target books +// $bookIdsInvolved->push(intval($mapItem->model->book_id)); +// $bookIdsInvolved->push(intval($mapItem->book)); +// }); +// +// // Get the books involved in the sort +// $bookIdsInvolved = $bookIdsInvolved->unique()->toArray(); +// $booksInvolved = $this->entityRepo->book->newQuery()->whereIn('id', $bookIdsInvolved)->get(); +// // Throw permission error if invalid ids or inaccessible books given. +// if (count($bookIdsInvolved) !== count($booksInvolved)) { +// $this->showPermissionError(); +// } +// // Check permissions of involved books +// $booksInvolved->each(function (Book $book) { +// $this->checkOwnablePermission('book-update', $book); +// }); +// +// // Perform the sort +// $sortMap->each(function ($mapItem) { +// $model = $mapItem->model; +// +// $priorityChanged = intval($model->priority) !== intval($mapItem->sort); +// $bookChanged = intval($model->book_id) !== intval($mapItem->book); +// $chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter; +// +// if ($bookChanged) { +// $this->entityRepo->changeBook($mapItem->type, $mapItem->book, $model); +// } +// if ($chapterChanged) { +// $model->chapter_id = intval($mapItem->parentChapter); +// $model->save(); +// } +// if ($priorityChanged) { +// $model->priority = intval($mapItem->sort); +// $model->save(); +// } +// }); +// +// // Rebuild permissions and add activity for involved books. +// $booksInvolved->each(function (Book $book) { +// $this->entityRepo->buildJointPermissionsForBook($book); +// Activity::add($book, 'book_sort', $book->id); +// }); +// +// return redirect($book->getUrl()); +// } +// +// /** +// * Remove the specified book from storage. +// * @param $bookSlug +// * @return Response +// */ +// public function destroy($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $this->checkOwnablePermission('book-delete', $book); +// Activity::addMessage('book_delete', 0, $book->name); +// $this->entityRepo->destroyBook($book); +// return redirect('/books'); +// } +// +// /** +// * Show the Restrictions view. +// * @param $bookSlug +// * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View +// */ +// public function showRestrict($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $this->checkOwnablePermission('restrictions-manage', $book); +// $roles = $this->userRepo->getRestrictableRoles(); +// return view('books/restrictions', [ +// 'book' => $book, +// 'roles' => $roles +// ]); +// } +// +// /** +// * Set the restrictions for this book. +// * @param $bookSlug +// * @param $bookSlug +// * @param Request $request +// * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector +// */ +// public function restrict($bookSlug, Request $request) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $this->checkOwnablePermission('restrictions-manage', $book); +// $this->entityRepo->updateEntityPermissionsFromRequest($request, $book); +// session()->flash('success', trans('entities.books_permissions_updated')); +// return redirect($book->getUrl()); +// } +// +// /** +// * Export a book as a PDF file. +// * @param string $bookSlug +// * @return mixed +// */ +// public function exportPdf($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $pdfContent = $this->exportService->bookToPdf($book); +// return response()->make($pdfContent, 200, [ +// 'Content-Type' => 'application/octet-stream', +// 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.pdf' +// ]); +// } +// +// /** +// * Export a book as a contained HTML file. +// * @param string $bookSlug +// * @return mixed +// */ +// public function exportHtml($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $htmlContent = $this->exportService->bookToContainedHtml($book); +// return response()->make($htmlContent, 200, [ +// 'Content-Type' => 'application/octet-stream', +// 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.html' +// ]); +// } +// +// /** +// * Export a book as a plain text file. +// * @param $bookSlug +// * @return mixed +// */ +// public function exportPlainText($bookSlug) +// { +// $book = $this->entityRepo->getBySlug('book', $bookSlug); +// $htmlContent = $this->exportService->bookToPlainText($book); +// return response()->make($htmlContent, 200, [ +// 'Content-Type' => 'application/octet-stream', +// 'Content-Disposition' => 'attachment; filename="' . $bookSlug . '.txt' +// ]); +// } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index d50baa86f..f6bd13e6f 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -252,7 +252,7 @@ class UserController extends Controller return $this->currentUser->id == $id; }); - $viewType = $request->get('book_view_type'); + $viewType = $request->get('view_type'); if (!in_array($viewType, ['grid', 'list'])) { $viewType = 'list'; } @@ -262,4 +262,27 @@ class UserController extends Controller return redirect()->back(302, [], "/settings/users/$id"); } + + /** + * Update the user's preferred shelf-list display setting. + * @param $id + * @param Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function switchShelfView($id, Request $request) + { + $this->checkPermissionOr('users-manage', function () use ($id) { + return $this->currentUser->id == $id; + }); + + $viewType = $request->get('view_type'); + if (!in_array($viewType, ['grid', 'list'])) { + $viewType = 'list'; + } + + $user = $this->user->findOrFail($id); + setting()->putUser($user, 'bookshelves_view_type', $viewType); + + return redirect()->back(302, [], "/settings/users/$id"); + } } diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index bdd1e37b1..ea7fc4882 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -1,6 +1,7 @@ bookshelf = $bookshelf; $this->book = $book; $this->chapter = $chapter; $this->page = $page; $this->pageRevision = $pageRevision; $this->entities = [ + 'bookshelf' => $this->bookshelf, 'page' => $this->page, 'chapter' => $this->chapter, 'book' => $this->book @@ -533,6 +542,23 @@ class EntityRepo return $entityModel; } + /** + * Sync the books assigned to a shelf from a comma-separated list + * of book IDs. + * @param Bookshelf $shelf + * @param string $books + */ + public function updateShelfBooks(Bookshelf $shelf, string $books) + { + $ids = explode(',', $books); + if (count($ids) === 0) { + return; + } + + $bookIds = $this->entityQuery('book')->whereIn('id', $ids)->get(['id'])->pluck('id'); + $shelf->books()->sync($bookIds); + } + /** * Change the book that an entity belongs to. * @param string $type @@ -1157,6 +1183,8 @@ class EntityRepo /** * Destroy the provided book and all its child entities. * @param Book $book + * @throws NotifyException + * @throws \Throwable */ public function destroyBook(Book $book) { @@ -1177,6 +1205,7 @@ class EntityRepo /** * Destroy a chapter and its relations. * @param Chapter $chapter + * @throws \Throwable */ public function destroyChapter(Chapter $chapter) { @@ -1198,6 +1227,7 @@ class EntityRepo * Destroy a given page along with its dependencies. * @param Page $page * @throws NotifyException + * @throws \Throwable */ public function destroyPage(Page $page) { diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php index 0dd316b34..428cb895f 100644 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@ -204,6 +204,7 @@ class PermissionService /** * Rebuild the entity jointPermissions for a particular entity. * @param Entity $entity + * @throws \Throwable */ public function buildJointPermissionsForEntity(Entity $entity) { @@ -214,7 +215,9 @@ class PermissionService return; } - $entities[] = $entity->book; + if ($entity->book) { + $entities[] = $entity->book; + } if ($entity->isA('page') && $entity->chapter_id) { $entities[] = $entity->chapter; @@ -226,13 +229,13 @@ class PermissionService } } - $this->deleteManyJointPermissionsForEntities($entities); $this->buildJointPermissionsForEntities(collect($entities)); } /** * Rebuild the entity jointPermissions for a collection of entities. * @param Collection $entities + * @throws \Throwable */ public function buildJointPermissionsForEntities(Collection $entities) { @@ -412,7 +415,7 @@ class PermissionService return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); } - if ($entity->isA('book')) { + if ($entity->isA('book') || $entity->isA('bookshelf')) { return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn); } diff --git a/database/migrations/2018_08_04_115700_create_bookshelves_table.php b/database/migrations/2018_08_04_115700_create_bookshelves_table.php index f32a1cdfb..c7840e1a1 100644 --- a/database/migrations/2018_08_04_115700_create_bookshelves_table.php +++ b/database/migrations/2018_08_04_115700_create_bookshelves_table.php @@ -30,6 +30,18 @@ class CreateBookshelvesTable extends Migration $table->index('restricted'); }); + Schema::create('bookshelves_books', function (Blueprint $table) { + $table->integer('bookshelf_id')->unsigned(); + $table->integer('book_id')->unsigned(); + + $table->foreign('bookshelf_id')->references('id')->on('bookshelves') + ->onUpdate('cascade')->onDelete('cascade'); + $table->foreign('book_id')->references('id')->on('books') + ->onUpdate('cascade')->onDelete('cascade'); + + $table->primary(['bookshelf_id', 'book_id']); + }); + // Copy existing role permissions from Books $ops = ['View All', 'View Own', 'Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; foreach ($ops as $op) { @@ -75,5 +87,6 @@ class CreateBookshelvesTable extends Migration // Drop shelves table Schema::dropIfExists('bookshelves'); + Schema::dropIfExists('bookshelves_books'); } } diff --git a/package-lock.json b/package-lock.json index ec4da5ce2..f8c43993b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5857,6 +5857,21 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" }, + "jquery-sortable": { + "version": "0.9.13", + "resolved": "https://registry.npmjs.org/jquery-sortable/-/jquery-sortable-0.9.13.tgz", + "integrity": "sha1-HL+2VQE6B0c3BXHwbiL1JKAP+6I=", + "requires": { + "jquery": "^2.1.2" + }, + "dependencies": { + "jquery": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", + "integrity": "sha1-LInWiJterFIqfuoywUUhVZxsvwI=" + } + } + }, "js-base64": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", diff --git a/package.json b/package.json index 12d972cf9..58f2dad5e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "codemirror": "^5.26.0", "dropzone": "^5.4.0", "jquery": "^3.3.1", + "jquery-sortable": "^0.9.13", "markdown-it": "^8.3.1", "markdown-it-task-lists": "^2.0.0", "vue": "^2.2.6", diff --git a/resources/assets/js/components/index.js b/resources/assets/js/components/index.js index aa69f3265..e1aef032c 100644 --- a/resources/assets/js/components/index.js +++ b/resources/assets/js/components/index.js @@ -18,6 +18,7 @@ let componentMapping = { 'collapsible': require('./collapsible'), 'toggle-switch': require('./toggle-switch'), 'page-display': require('./page-display'), + 'shelf-sort': require('./shelf-sort'), }; window.components = {}; diff --git a/resources/assets/js/components/shelf-sort.js b/resources/assets/js/components/shelf-sort.js new file mode 100644 index 000000000..91713ab41 --- /dev/null +++ b/resources/assets/js/components/shelf-sort.js @@ -0,0 +1,41 @@ + +class ShelfSort { + + constructor(elem) { + this.elem = elem; + this.sortGroup = this.initSortable(); + this.input = document.getElementById('books-input'); + } + + initSortable() { + const sortable = require('jquery-sortable'); + const placeHolderContent = this.getPlaceholderHTML(); + + return $('.scroll-box').sortable({ + group: 'shelf-books', + exclude: '.instruction,.scroll-box-placeholder', + containerSelector: 'div.scroll-box', + itemSelector: '.scroll-box-item', + placeholder: placeHolderContent, + onDrop: this.onDrop.bind(this) + }); + } + + onDrop($item, container, _super) { + const data = this.sortGroup.sortable('serialize').get(); + this.input.value = data[0].map(item => item.id).join(','); + _super($item, container); + } + + getPlaceholderHTML() { + const placeHolder = document.querySelector('.scroll-box-placeholder'); + placeHolder.style.display = 'block'; + const placeHolderContent = placeHolder.outerHTML; + placeHolder.style.display = 'none'; + return placeHolderContent; + } + + +} + +module.exports = ShelfSort; \ No newline at end of file diff --git a/resources/assets/sass/_grid.scss b/resources/assets/sass/_grid.scss index 8f15153b5..0e1f85ce6 100644 --- a/resources/assets/sass/_grid.scss +++ b/resources/assets/sass/_grid.scss @@ -192,8 +192,26 @@ div[class^="col-"] img { flex-direction: column; border: 1px solid #ddd; min-width: 100px; + h2 { + width: 100%; + font-size: 1.5em; + margin: 0 0 10px; + } + h2 a { + display: block; + width: 100%; + line-height: 1.2; + text-decoration: none; + } + p { + font-size: .85em; + margin: 0; + line-height: 1.6em; + } .grid-card-content { flex: 1; + border-top: 0; + border-bottom-width: 2px; } .grid-card-content, .grid-card-footer { padding: $-l; @@ -203,6 +221,23 @@ div[class^="col-"] img { } } +.book-grid-item .grid-card-content h2 a { + color: $color-book; + fill: $color-book; +} + +.bookshelf-grid-item .grid-card-content h2 a { + color: $color-bookshelf; + fill: $color-bookshelf; +} + +.book-grid-item .grid-card-footer { + p.small { + font-size: .8em; + margin: 0; + } +} + @include smaller-than($m) { .grid.third { grid-template-columns: 1fr 1fr; diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index 3338b3938..0afed4b36 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -408,32 +408,3 @@ ul.pagination { } } -.book-grid-item .grid-card-content { - border-top: 0; - border-bottom-width: 2px; - h2 { - width: 100%; - font-size: 1.5em; - margin: 0 0 10px; - } - h2 a { - display: block; - width: 100%; - line-height: 1.2; - color: #009688;; - fill: #009688;; - text-decoration: none; - } - p { - font-size: .85em; - margin: 0; - line-height: 1.6em; - } -} - -.book-grid-item .grid-card-footer { - p.small { - font-size: .8em; - margin: 0; - } -} \ No newline at end of file diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index da11846d8..63a91c968 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -281,6 +281,14 @@ p.secondary, p .secondary, span.secondary, .text-secondary { } } +.text-bookshelf { + color: $color-bookshelf; + fill: $color-bookshelf; + &:hover { + color: $color-bookshelf; + fill: $color-bookshelf; + } +} .text-book { color: $color-book; fill: $color-book; diff --git a/resources/assets/sass/_variables.scss b/resources/assets/sass/_variables.scss index e62d37efe..006d1b3f0 100644 --- a/resources/assets/sass/_variables.scss +++ b/resources/assets/sass/_variables.scss @@ -47,6 +47,7 @@ $warning: $secondary; $primary-faded: rgba(21, 101, 192, 0.15); // Item Colors +$color-bookshelf: #af5a5a; $color-book: #009688; $color-chapter: #ef7c3c; $color-page: $primary; diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index 0b2dfbf75..ab5972cbd 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -206,6 +206,12 @@ $btt-size: 40px; transition: all ease-in-out 120ms; cursor: pointer; } + &.compact { + font-size: 10px; + .entity-item-snippet { + display: none; + } + } } .entity-list-item.selected { @@ -214,6 +220,20 @@ $btt-size: 40px; } } +.scroll-box { + max-height: 250px; + overflow-y: scroll; + border: 1px solid #DDD; + border-radius: 3px; + .scroll-box-item { + padding: $-xs $-m; + border-bottom: 1px solid #DDD; + &:last-child { + border-bottom: 0; + } + } +} + .center-box { margin: $-xxl auto 0 auto; width: 420px; diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 834b977e7..c744c5557 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -69,6 +69,16 @@ return [ */ 'shelves' => 'Shelves', 'shelves_long' => 'BookShelves', + 'shelves_empty' => 'No shelves have been created', + 'shelves_create' => 'Create New Shelf', + 'shelves_popular' => 'Popular Shelves', + 'shelves_new' => 'New Shelves', + 'shelves_popular_empty' => 'The most popular shelves will appear here.', + 'shelves_new_empty' => 'The most recently created shelves will appear here.', + 'shelves_save' => 'Save Shelf', + 'shelves_books' => 'Books on this shelf', + 'shelves_add_books' => 'Add books to this shelf', + 'shelves_drag_books' => 'Drag books here to add them to this shelf', /** * Books @@ -212,6 +222,7 @@ return [ 'page_tags' => 'Page Tags', 'chapter_tags' => 'Chapter Tags', 'book_tags' => 'Book Tags', + 'shelf_tags' => 'Shelf Tags', 'tag' => 'Tag', 'tags' => 'Tags', 'tag_value' => 'Tag Value (Optional)', diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index 93517ef6f..93ee6cdc6 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -33,7 +33,7 @@