{{$book->name}}
{!! nl2br(e($book->description)) !!}
+{!! $book->descriptionHtml() !!}
@if(count($bookChildren) > 0)diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index faa578893..481c621e6 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -93,7 +93,7 @@ class BookController extends Controller $this->checkPermission('book-create-all'); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], + 'description_html' => ['string', 'max:2000'], 'image' => array_merge(['nullable'], $this->getImageValidationRules()), 'tags' => ['array'], 'default_template_id' => ['nullable', 'integer'], @@ -168,7 +168,7 @@ class BookController extends Controller $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], + 'description_html' => ['string', 'max:2000'], 'image' => array_merge(['nullable'], $this->getImageValidationRules()), 'tags' => ['array'], 'default_template_id' => ['nullable', 'integer'], diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index fcfd37538..acc972348 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -18,15 +18,11 @@ use Illuminate\Validation\ValidationException; class BookshelfController extends Controller { - protected BookshelfRepo $shelfRepo; - protected ShelfContext $shelfContext; - protected ReferenceFetcher $referenceFetcher; - - public function __construct(BookshelfRepo $shelfRepo, ShelfContext $shelfContext, ReferenceFetcher $referenceFetcher) - { - $this->shelfRepo = $shelfRepo; - $this->shelfContext = $shelfContext; - $this->referenceFetcher = $referenceFetcher; + public function __construct( + protected BookshelfRepo $shelfRepo, + protected ShelfContext $shelfContext, + protected ReferenceFetcher $referenceFetcher + ) { } /** @@ -81,10 +77,10 @@ class BookshelfController extends Controller { $this->checkPermission('bookshelf-create-all'); $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'tags' => ['array'], ]); $bookIds = explode(',', $request->get('books', '')); @@ -164,10 +160,10 @@ class BookshelfController extends Controller $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'tags' => ['array'], ]); if ($request->has('image_reset')) { diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 40a537303..73f314ab6 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -22,13 +22,10 @@ use Throwable; class ChapterController extends Controller { - protected ChapterRepo $chapterRepo; - protected ReferenceFetcher $referenceFetcher; - - public function __construct(ChapterRepo $chapterRepo, ReferenceFetcher $referenceFetcher) - { - $this->chapterRepo = $chapterRepo; - $this->referenceFetcher = $referenceFetcher; + public function __construct( + protected ChapterRepo $chapterRepo, + protected ReferenceFetcher $referenceFetcher + ) { } /** @@ -51,14 +48,16 @@ class ChapterController extends Controller */ public function store(Request $request, string $bookSlug) { - $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], + $validated = $this->validate($request, [ + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], ]); $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); $this->checkOwnablePermission('chapter-create', $book); - $chapter = $this->chapterRepo->create($request->all(), $book); + $chapter = $this->chapterRepo->create($validated, $book); return redirect($chapter->getUrl()); } @@ -111,10 +110,16 @@ class ChapterController extends Controller */ public function update(Request $request, string $bookSlug, string $chapterSlug) { + $validated = $this->validate($request, [ + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + ]); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); - $this->chapterRepo->update($chapter, $request->all()); + $this->chapterRepo->update($chapter, $validated); return redirect($chapter->getUrl()); } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index ee9a7f447..7bbe2d8a4 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -26,10 +26,11 @@ use Illuminate\Support\Collection; class Book extends Entity implements HasCoverImage { use HasFactory; + use HasHtmlDescription; public $searchFactor = 1.2; - protected $fillable = ['name', 'description']; + protected $fillable = ['name']; protected $hidden = ['pivot', 'image_id', 'deleted_at']; /** diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index 4b44025a4..cf22195f7 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Bookshelf extends Entity implements HasCoverImage { use HasFactory; + use HasHtmlDescription; protected $table = 'bookshelves'; diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 98889ce3f..17fccfd6c 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -15,6 +15,7 @@ use Illuminate\Support\Collection; class Chapter extends BookChild { use HasFactory; + use HasHtmlDescription; public $searchFactor = 1.2; diff --git a/app/Entities/Models/HasHtmlDescription.php b/app/Entities/Models/HasHtmlDescription.php new file mode 100644 index 000000000..cc431f7fc --- /dev/null +++ b/app/Entities/Models/HasHtmlDescription.php @@ -0,0 +1,21 @@ +description_html ?: '
' . e($this->description) . '
'; + return HtmlContentFilter::removeScriptsFromHtmlString($html); + } +} diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 2894a04e3..f6b9ff578 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\TagRepo; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; +use BookStack\Entities\Models\HasHtmlDescription; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceUpdater; use BookStack\Uploads\ImageRepo; @@ -12,15 +13,11 @@ use Illuminate\Http\UploadedFile; class BaseRepo { - protected TagRepo $tagRepo; - protected ImageRepo $imageRepo; - protected ReferenceUpdater $referenceUpdater; - - public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo, ReferenceUpdater $referenceUpdater) - { - $this->tagRepo = $tagRepo; - $this->imageRepo = $imageRepo; - $this->referenceUpdater = $referenceUpdater; + public function __construct( + protected TagRepo $tagRepo, + protected ImageRepo $imageRepo, + protected ReferenceUpdater $referenceUpdater + ) { } /** @@ -29,6 +26,7 @@ class BaseRepo public function create(Entity $entity, array $input) { $entity->fill($input); + $this->updateDescription($entity, $input); $entity->forceFill([ 'created_by' => user()->id, 'updated_by' => user()->id, @@ -54,6 +52,7 @@ class BaseRepo $oldUrl = $entity->getUrl(); $entity->fill($input); + $this->updateDescription($entity, $input); $entity->updated_by = user()->id; if ($entity->isDirty('name') || empty($entity->slug)) { @@ -99,4 +98,20 @@ class BaseRepo $entity->save(); } } + + protected function updateDescription(Entity $entity, array $input): void + { + if (!in_array(HasHtmlDescription::class, class_uses($entity))) { + return; + } + + /** @var HasHtmlDescription $entity */ + if (isset($input['description_html'])) { + $entity->description_html = $input['description_html']; + $entity->description = html_entity_decode(strip_tags($input['description_html'])); + } else if (isset($input['description'])) { + $entity->description = $input['description']; + $entity->description_html = $entity->descriptionHtml(); + } + } } diff --git a/database/migrations/2023_12_17_140913_add_description_html_to_entities.php b/database/migrations/2023_12_17_140913_add_description_html_to_entities.php new file mode 100644 index 000000000..68c52e81b --- /dev/null +++ b/database/migrations/2023_12_17_140913_add_description_html_to_entities.php @@ -0,0 +1,36 @@ + $table->text('description_html'); + + Schema::table('books', $addColumn); + Schema::table('chapters', $addColumn); + Schema::table('bookshelves', $addColumn); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $removeColumn = fn(Blueprint $table) => $table->removeColumn('description_html'); + + Schema::table('books', $removeColumn); + Schema::table('chapters', $removeColumn); + Schema::table('bookshelves', $removeColumn); + } +}; diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index d7c6bba72..f0a2dbe1c 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -304,7 +304,7 @@ export function buildForInput(options) { // Return config object return { width: '100%', - height: '300px', + height: '185px', target: options.containerElement, cache_suffix: `?version=${version}`, content_css: [ @@ -312,7 +312,7 @@ export function buildForInput(options) { ], branding: false, skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5', - body_class: 'page-content', + body_class: 'wysiwyg-input', browser_spellcheck: true, relative_urls: false, language: options.language, @@ -323,11 +323,13 @@ export function buildForInput(options) { remove_trailing_brs: false, statusbar: false, menubar: false, - plugins: 'link autolink', + plugins: 'link autolink lists', contextmenu: false, - toolbar: 'bold italic underline link', + toolbar: 'bold italic underline link bullist numlist', content_style: getContentStyle(options), color_map: colorMap, + file_picker_types: 'file', + file_picker_callback: filePickerCallback, init_instance_callback(editor) { const head = editor.getDoc().querySelector('head'); head.innerHTML += fetchCustomHeadContent(); diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index cd5d929f4..b63f9cdd5 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -406,6 +406,14 @@ input[type=color] { height: auto; } +.description-input > .tox-tinymce { + border: 1px solid #DDD !important; + border-radius: 3px; + .tox-toolbar__primary { + justify-content: end; + } +} + .search-box { max-width: 100%; position: relative; diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 8e036fc46..c4336da7c 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -23,6 +23,13 @@ display: block; } +.wysiwyg-input.mce-content-body { + padding-block-start: 1rem; + padding-block-end: 1rem; + outline: 0; + display: block; +} + // Default styles for our custom root nodes .page-content.mce-content-body doc-root { display: block; diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index 3a2e30da6..d380c5871 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -9,17 +9,8 @@{!! nl2br(e($book->description)) !!}
+{!! $book->descriptionHtml() !!}
@if(count($bookChildren) > 0)