]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'development' into default-templates
authorDan Brown <redacted>
Mon, 11 Dec 2023 11:41:43 +0000 (11:41 +0000)
committerDan Brown <redacted>
Mon, 11 Dec 2023 11:41:43 +0000 (11:41 +0000)
14 files changed:
app/Entities/Controllers/BookController.php
app/Entities/Controllers/PageController.php
app/Entities/Models/Book.php
app/Entities/Repos/PageRepo.php
database/migrations/2022_12_02_104541_add_default_template_to_books.php [new file with mode: 0644]
lang/en/entities.php
resources/sass/_forms.scss
resources/views/books/create.blade.php
resources/views/books/edit.blade.php
resources/views/books/parts/form.blade.php
resources/views/entities/template-manager.blade.php [new file with mode: 0644]
resources/views/pages/delete.blade.php
resources/views/pages/guest-create.blade.php
resources/views/pages/parts/template-manager.blade.php

index 55d28c6847e32a1227cb4663902e3388fbb27524..9b938d89a41c7f23fb311d5eedd86c2a0770a36a 100644 (file)
@@ -7,6 +7,7 @@ use BookStack\Activity\ActivityType;
 use BookStack\Activity\Models\View;
 use BookStack\Activity\Tools\UserEntityWatchOptions;
 use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\BookRepo;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Tools\Cloner;
@@ -81,8 +82,14 @@ class BookController extends Controller
 
         $this->setPageTitle(trans('entities.books_create'));
 
+        $templates = Page::visible()
+            ->where('template', '=', true)
+            ->orderBy('name', 'asc')
+            ->get();
+
         return view('books.create', [
             'bookshelf' => $bookshelf,
+            'templates' => $templates,
         ]);
     }
 
@@ -100,6 +107,7 @@ class BookController extends Controller
             'description' => ['string', 'max:1000'],
             'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
             'tags'        => ['array'],
+            'default_template'  => ['nullable', 'exists:pages,id'], 
         ]);
 
         $bookshelf = null;
@@ -154,7 +162,12 @@ class BookController extends Controller
         $this->checkOwnablePermission('book-update', $book);
         $this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()]));
 
-        return view('books.edit', ['book' => $book, 'current' => $book]);
+        $templates = Page::visible()
+            ->where('template', '=', true)
+            ->orderBy('name', 'asc')
+            ->get();
+
+        return view('books.edit', ['book' => $book, 'current' => $book, 'templates' => $templates]);
     }
 
     /**
@@ -174,6 +187,7 @@ class BookController extends Controller
             'description' => ['string', 'max:1000'],
             'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
             'tags'        => ['array'],
+            'default_template'  => ['nullable', 'exists:pages,id'], 
         ]);
 
         if ($request->has('image_reset')) {
index 4d8c7e809f9b087a12d5dff8702ec0c5a5d0fbc8..ad75448b34a30fb3fc04a10e435d3607644432e7 100644 (file)
@@ -5,6 +5,7 @@ namespace BookStack\Entities\Controllers;
 use BookStack\Activity\Models\View;
 use BookStack\Activity\Tools\CommentTree;
 use BookStack\Activity\Tools\UserEntityWatchOptions;
+use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\PageRepo;
 use BookStack\Entities\Tools\BookContents;
@@ -71,7 +72,6 @@ class PageController extends Controller
         $page = $this->pageRepo->getNewDraftPage($parent);
         $this->pageRepo->publishDraft($page, [
             'name' => $request->get('name'),
-            'html' => '',
         ]);
 
         return redirect($page->getUrl('/edit'));
@@ -259,11 +259,13 @@ class PageController extends Controller
         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-delete', $page);
         $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()]));
+        $times_used_as_template = Book::where('default_template', '=', $page->id)->count();
 
         return view('pages.delete', [
             'book'    => $page->book,
             'page'    => $page,
             'current' => $page,
+            'times_used_as_template' => $times_used_as_template,
         ]);
     }
 
index f54a0bf2d6a464d859848ac1f97e674322562a5a..8584e755e60c38fe5d250235096e339935d2ceb2 100644 (file)
@@ -27,7 +27,7 @@ class Book extends Entity implements HasCoverImage
 
     public $searchFactor = 1.2;
 
-    protected $fillable = ['name', 'description'];
+    protected $fillable = ['name', 'description', 'default_template'];
     protected $hidden = ['pivot', 'image_id', 'deleted_at'];
 
     /**
@@ -71,6 +71,14 @@ class Book extends Entity implements HasCoverImage
         return 'cover_book';
     }
 
+    /**
+     * Get the Page that is used as default template for newly created pages within this Book.
+     */
+    public function defaultTemplate(): BelongsTo
+    {
+        return $this->belongsTo(Page::class, 'default_template');
+    }
+
     /**
      * Get all pages within this book.
      */
index dbd4a47d243e23f3b0f5870e9d14de42ec83a705..5269a0bccb613ed43e90128f6c286c277d066959 100644 (file)
@@ -136,6 +136,12 @@ class PageRepo
             $page->book_id = $parent->id;
         }
 
+        if ($page->book->defaultTemplate) {
+            $page->forceFill([
+                'html'  => $page->book->defaultTemplate->html,
+            ]);
+        }
+
         $page->save();
         $page->refresh()->rebuildPermissions();
 
diff --git a/database/migrations/2022_12_02_104541_add_default_template_to_books.php b/database/migrations/2022_12_02_104541_add_default_template_to_books.php
new file mode 100644 (file)
index 0000000..755f83b
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddDefaultTemplateToBooks extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->integer('default_template')->nullable();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->dropColumn('default_template');
+        });
+    }
+}
index cfb5aae1a78c4218f43f9ad12de8b75838099279..e4c67f5ca8114b539712cfd42945ceac88276c60 100644 (file)
@@ -204,6 +204,7 @@ return [
     'pages_delete_draft' => 'Delete Draft Page',
     'pages_delete_success' => 'Page deleted',
     'pages_delete_draft_success' => 'Draft page deleted',
+    'pages_delete_warning_template' => '{0}|{1}Be careful: this page is used as a template for :count book.|[2,*]Be careful: this page is used as a template for :count books.',
     'pages_delete_confirm' => 'Are you sure you want to delete this page?',
     'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?',
     'pages_editing_named' => 'Editing Page :pageName',
@@ -350,6 +351,8 @@ return [
     'templates_replace_content' => 'Replace page content',
     'templates_append_content' => 'Append to page content',
     'templates_prepend_content' => 'Prepend to page content',
+    'default_template' => 'Default Page Template',
+    'default_template_explain' => "Assign a default template that will be used for all new pages in this book.",
 
     // Profile View
     'profile_user_for_x' => 'User for :time',
index 2257e8000d0fc7c50fe95c1f1ed0dfa13a493881..cd5d929f4b57835e5e8da3629d2988645f2ed5e9 100644 (file)
@@ -434,7 +434,7 @@ input[type=color] {
   &.flexible input {
     width: 100%;
   }
-  .search-box-cancel {
+  button.search-box-cancel {
     left: auto;
     right: 0;
   }
index eead4191c345260382c36fc664c2bfc91eaaf5f1..6253a49bbb92bf136142707bc6ffe1525a60d048 100644 (file)
         <main class="content-wrap card">
             <h1 class="list-heading">{{ trans('entities.books_create') }}</h1>
             <form action="{{ isset($bookshelf) ? $bookshelf->getUrl('/create-book') : url('/books') }}" method="POST" enctype="multipart/form-data">
-                @include('books.parts.form', ['returnLocation' => isset($bookshelf) ? $bookshelf->getUrl() : url('/books')])
+                @include('books.parts.form', [
+                    'templates' => $templates, 
+                    'returnLocation' => isset($bookshelf) ? $bookshelf->getUrl() : url('/books')
+                ])
             </form>
         </main>
     </div>
index 180500e0a04ae737b1f86668d7f8a41c070919e9..9ec47293509f881bb8b29bae16e616209b7e9b8c 100644 (file)
             <h1 class="list-heading">{{ trans('entities.books_edit') }}</h1>
             <form action="{{ $book->getUrl() }}" method="POST" enctype="multipart/form-data">
                 <input type="hidden" name="_method" value="PUT">
-                @include('books.parts.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
+                @include('books.parts.form', [
+                    'model' => $book, 
+                    'templates' => $templates,
+                    'returnLocation' => $book->getUrl()
+                ])
             </form>
         </main>
 
index 56d385c9e2a936065eb27c28e677e32ea84b1dcf..9b66b8ac87a73b4d1a2e1d3f240665bd1bbfdb03 100644 (file)
     </div>
 </div>
 
+<div class="form-group collapsible" component="collapsible" id="template-control">
+    <button refs="collapsible@trigger" type="button" class="collapse-title text-primary" aria-expanded="false">
+        <label for="template-manager">{{ trans('entities.default_template') }}</label>
+    </button>
+    <div refs="collapsible@content" class="collapse-content">
+        @include('entities.template-manager', ['entity' => $book ?? null, 'templates' => $templates])
+    </div>
+</div>
+
 <div class="form-group text-right">
     <a href="{{ $returnLocation }}" class="button outline">{{ trans('common.cancel') }}</a>
     <button type="submit" class="button">{{ trans('entities.books_save') }}</button>
diff --git a/resources/views/entities/template-manager.blade.php b/resources/views/entities/template-manager.blade.php
new file mode 100644 (file)
index 0000000..fe04d93
--- /dev/null
@@ -0,0 +1,10 @@
+<p class="text-muted small">
+    {!! nl2br(e(trans('entities.default_template_explain'))) !!}
+</p>
+
+<select name="default_template" id="default_template">
+    <option value="">---</option>
+    @foreach ($templates as $template)
+        <option @if(isset($entity) && $entity->default_template === $template->id) selected @endif value="{{ $template->id }}">{{ $template->name }}</option>
+    @endforeach
+</select>
\ No newline at end of file
index 39cd07bbb1c227070008cce0f0df5b776b006177..9ce50d48b13fda72bb0cf1a95504e187d8c56e18 100644 (file)
@@ -19,6 +19,9 @@
         <div class="card content-wrap auto-height">
             <h1 class="list-heading">{{ $page->draft ? trans('entities.pages_delete_draft') : trans('entities.pages_delete') }}</h1>
 
+            @if ($times_used_as_template > 0)
+                <p>{{ trans_choice('entities.pages_delete_warning_template', $times_used_as_template) }}</p>
+            @endif
 
             <div class="grid half v-center">
                 <div>
index d6e1cae446aeb729228b537595067eca9c8fcb13..11970b8b6e38d6dacd5aa49e8feef071101488bb 100644 (file)
@@ -22,7 +22,7 @@
 
                 <div class="form-group title-input">
                     <label for="name">{{ trans('entities.pages_name') }}</label>
-                    @include('form.text', ['name' => 'name'])
+                    @include('form.text', ['name' => 'name', 'autofocus' => true])
                 </div>
 
                 <div class="form-group text-right">
index c209626cdbf24054e70c6ecb65620adf302b272b..ee4467552e4bf54c70c606b198372932e9edb36a 100644 (file)
@@ -14,7 +14,7 @@
     <div class="search-box flexible mb-m" style="display: {{ count($templates) > 0 ? 'block' : 'none' }}">
         <input refs="template-manager@searchInput" type="text" name="template-search" placeholder="{{ trans('common.search') }}">
         <button refs="template-manager@searchButton" tabindex="-1" type="button">@icon('search')</button>
-        <button refs="template-manager@searchCancel" class="search-box-cancel text-neg" type="button" style="display: none">@icon('close')</button>
+        <button refs="template-manager@searchCancel" class="search-box-cancel text-neg" tabindex="-1" type="button" style="display: none">@icon('close')</button>
     </div>
 
     <div refs="template-manager@list">